diff --git a/routers/web/repo/builds/view.go b/routers/web/repo/builds/view.go
index ad53db9420..f67c079df6 100644
--- a/routers/web/repo/builds/view.go
+++ b/routers/web/repo/builds/view.go
@@ -1,18 +1,21 @@
package builds
import (
+ "context"
"fmt"
"net/http"
"time"
bots_model "code.gitea.io/gitea/models/bots"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/bots"
- "code.gitea.io/gitea/modules/context"
+ context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web"
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
+ "xorm.io/builder"
)
-func View(ctx *context.Context) {
+func View(ctx *context_module.Context) {
ctx.Data["PageIsBuilds"] = true
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
@@ -85,7 +88,7 @@ type ViewStepLogLine struct {
T float64 `json:"t"`
}
-func ViewPost(ctx *context.Context) {
+func ViewPost(ctx *context_module.Context) {
req := web.GetForm(ctx).(*ViewRequest)
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
@@ -187,7 +190,37 @@ func ViewPost(ctx *context.Context) {
ctx.JSON(http.StatusOK, resp)
}
-func getRunJobs(ctx *context.Context, runIndex, jobIndex int64) (current *bots_model.RunJob, jobs []*bots_model.RunJob) {
+func Rerun(ctx *context_module.Context) {
+ runIndex := ctx.ParamsInt64("run")
+ jobIndex := ctx.ParamsInt64("job")
+
+ job, _ := getRunJobs(ctx, runIndex, jobIndex)
+ if ctx.Written() {
+ return
+ }
+ status := job.Status
+ if !status.IsDone() {
+ ctx.JSON(http.StatusOK, struct{}{})
+ return
+ }
+
+ job.TaskID = 0
+ job.Status = bots_model.StatusWaiting
+ job.Started = 0
+ job.Stopped = 0
+
+ if err := db.WithTx(func(ctx context.Context) error {
+ _, err := bots_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped")
+ return err
+ }, ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ ctx.JSON(http.StatusOK, struct{}{})
+}
+
+func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (current *bots_model.RunJob, jobs []*bots_model.RunJob) {
run, err := bots_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if _, ok := err.(bots_model.ErrRunNotExist); ok {
diff --git a/routers/web/web.go b/routers/web/web.go
index dc29528b52..ab6cb1256c 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1217,9 +1217,13 @@ func RegisterRoutes(m *web.Route) {
m.Combo("").
Get(builds.View).
Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
- m.Combo("/jobs/{job}").
- Get(builds.View).
- Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
+ m.Group("/jobs/{job}", func() {
+ m.Combo("").
+ Get(builds.View).
+ Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
+ m.Post("/rerun", builds.Rerun)
+ })
+
})
}, reqRepoBuildsReader, builds.MustEnableBuilds)
diff --git a/web_src/js/components/RepoBuildView.vue b/web_src/js/components/RepoBuildView.vue
index 801beb6123..805e3f7347 100644
--- a/web_src/js/components/RepoBuildView.vue
+++ b/web_src/js/components/RepoBuildView.vue
@@ -18,6 +18,9 @@
{{ job.name }}
+
@@ -146,6 +149,17 @@ const sfc = {
this.loadJobData(); // try to load the data immediately instead of waiting for next timer interval
}
},
+ // rerun a job
+ rerunJob(idx) {
+ fetch(this.buildInfo.htmlurl+'/jobs/'+idx+'/rerun', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Csrf-Token': csrfToken,
+ },
+ body: {},
+ });
+ },
formatDuration(d) {
d = Math.round(d);
@@ -359,6 +373,12 @@ export function initRepositoryBuildView() {
background: #f8f8f8;
border-radius: 5px;
text-decoration: none;
+ button.job-brief-rerun {
+ float: right;
+ border: none;
+ background-color: transparent;
+ outline: none
+ };
}
}
}
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 0a7a226fc9..b8c5009c50 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -29,6 +29,7 @@ import octiconXCircleFill from '../../public/img/svg/octicon-x-circle-fill.svg';
import octiconSkip from '../../public/img/svg/octicon-skip.svg';
import octiconMeter from '../../public/img/svg/octicon-meter.svg';
import octiconBlocked from '../../public/img/svg/octicon-blocked.svg';
+import octiconSync from '../../public/img/svg/octicon-sync.svg';
export const svgs = {
@@ -63,6 +64,7 @@ export const svgs = {
'octicon-skip': octiconSkip,
'octicon-meter': octiconMeter,
'octicon-blocked': octiconBlocked,
+ 'octicon-sync': octiconSync,
};