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, };