mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-08 17:05:45 +02:00
feat: support cancel button
This commit is contained in:
parent
f3a9a25682
commit
736275f0b1
@ -430,8 +430,10 @@ func UpdateTaskByState(state *runnerv1.TaskState) (*Task, error) {
|
|||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
task := &Task{}
|
task := &Task{}
|
||||||
if _, err := e.ID(state.Id).Get(task); err != nil {
|
if has, err := e.ID(state.Id).Get(task); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, util.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||||
@ -483,54 +485,55 @@ func UpdateTaskByState(state *runnerv1.TaskState) (*Task, error) {
|
|||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StopTask(ctx context.Context, task *Task, result runnerv1.Result) (*Task, error) {
|
func StopTask(ctx context.Context, taskID int64, status Status) error {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
if !status.IsDone() {
|
||||||
if err != nil {
|
return fmt.Errorf("cannot stop task with status %v", status)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
task := &Task{}
|
||||||
|
if has, err := e.ID(taskID).Get(task); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return util.ErrNotExist
|
||||||
|
}
|
||||||
|
if task.Status.IsDone() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
now := timeutil.TimeStampNow()
|
now := timeutil.TimeStampNow()
|
||||||
if result != runnerv1.Result_RESULT_UNSPECIFIED {
|
task.Status = status
|
||||||
task.Status = Status(result)
|
task.Stopped = now
|
||||||
task.Stopped = now
|
if _, err := UpdateRunJob(ctx, &RunJob{
|
||||||
if _, err := UpdateRunJob(ctx, &RunJob{
|
ID: task.JobID,
|
||||||
ID: task.JobID,
|
Status: task.Status,
|
||||||
Status: task.Status,
|
Stopped: task.Stopped,
|
||||||
Stopped: task.Stopped,
|
}, nil); err != nil {
|
||||||
}, nil); err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := e.ID(task.ID).Update(task); err != nil {
|
if _, err := e.ID(task.ID).Update(task); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := task.LoadAttributes(ctx); err != nil {
|
if err := task.LoadAttributes(ctx); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, step := range task.Steps {
|
for _, step := range task.Steps {
|
||||||
if !step.Status.IsDone() {
|
if !step.Status.IsDone() {
|
||||||
step.Status = Status(result)
|
step.Status = status
|
||||||
if step.Started == 0 {
|
if step.Started == 0 {
|
||||||
step.Started = now
|
step.Started = now
|
||||||
}
|
}
|
||||||
step.Stopped = now
|
step.Stopped = now
|
||||||
}
|
}
|
||||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commiter.Commit(); err != nil {
|
return nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSubset(set, subset []string) bool {
|
func isSubset(set, subset []string) bool {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/bots"
|
"code.gitea.io/gitea/modules/bots"
|
||||||
context_module "code.gitea.io/gitea/modules/context"
|
context_module "code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
|
||||||
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
|
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
|
||||||
@ -45,8 +46,9 @@ type ViewRequest struct {
|
|||||||
type ViewResponse struct {
|
type ViewResponse struct {
|
||||||
StateData struct {
|
StateData struct {
|
||||||
BuildInfo struct {
|
BuildInfo struct {
|
||||||
HTMLURL string `json:"htmlurl"`
|
HTMLURL string `json:"htmlurl"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
Cancelable bool `json:"cancelable"`
|
||||||
} `json:"buildInfo"`
|
} `json:"buildInfo"`
|
||||||
AllJobGroups []ViewGroup `json:"allJobGroups"`
|
AllJobGroups []ViewGroup `json:"allJobGroups"`
|
||||||
CurrentJobInfo struct {
|
CurrentJobInfo struct {
|
||||||
@ -103,6 +105,7 @@ func ViewPost(ctx *context_module.Context) {
|
|||||||
resp := &ViewResponse{}
|
resp := &ViewResponse{}
|
||||||
resp.StateData.BuildInfo.Title = run.Title
|
resp.StateData.BuildInfo.Title = run.Title
|
||||||
resp.StateData.BuildInfo.HTMLURL = run.HTMLURL()
|
resp.StateData.BuildInfo.HTMLURL = run.HTMLURL()
|
||||||
|
resp.StateData.BuildInfo.Cancelable = !run.Status.IsDone()
|
||||||
|
|
||||||
respJobs := make([]*ViewJob, len(jobs))
|
respJobs := make([]*ViewJob, len(jobs))
|
||||||
for i, v := range jobs {
|
for i, v := range jobs {
|
||||||
@ -115,8 +118,8 @@ func ViewPost(ctx *context_module.Context) {
|
|||||||
|
|
||||||
resp.StateData.AllJobGroups = []ViewGroup{
|
resp.StateData.AllJobGroups = []ViewGroup{
|
||||||
{
|
{
|
||||||
Summary: "Only One Group", // TODO: maybe we don't need job group
|
// TODO: maybe we don't need job group
|
||||||
Jobs: respJobs,
|
Jobs: respJobs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,6 +224,45 @@ func Rerun(ctx *context_module.Context) {
|
|||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Cancel(ctx *context_module.Context) {
|
||||||
|
runIndex := ctx.ParamsInt64("run")
|
||||||
|
|
||||||
|
_, jobs := getRunJobs(ctx, runIndex, -1)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
for _, job := range jobs {
|
||||||
|
status := job.Status
|
||||||
|
if status.IsDone() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if job.TaskID == 0 {
|
||||||
|
job.Status = bots_model.StatusCancelled
|
||||||
|
job.Stopped = timeutil.TimeStampNow()
|
||||||
|
n, err := bots_model.UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return fmt.Errorf("job has changed, try again")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := bots_model.StopTask(ctx, job.TaskID, bots_model.StatusCancelled); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); 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) {
|
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)
|
run, err := bots_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -242,12 +284,12 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (current
|
|||||||
v.Run = run
|
v.Run = run
|
||||||
}
|
}
|
||||||
|
|
||||||
if jobIndex < 0 || jobIndex >= int64(len(jobs)) {
|
if jobIndex >= 0 && jobIndex < int64(len(jobs)) {
|
||||||
if len(jobs) == 0 {
|
if len(jobs) == 0 {
|
||||||
ctx.Error(http.StatusNotFound, fmt.Sprintf("run %v has no job %v", runIndex, jobIndex))
|
ctx.Error(http.StatusNotFound, fmt.Sprintf("run %v has no job %v", runIndex, jobIndex))
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
current = jobs[jobIndex]
|
||||||
}
|
}
|
||||||
current = jobs[jobIndex]
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1225,7 +1225,7 @@ func RegisterRoutes(m *web.Route) {
|
|||||||
Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
|
Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
|
||||||
m.Post("/rerun", builds.Rerun)
|
m.Post("/rerun", builds.Rerun)
|
||||||
})
|
})
|
||||||
|
m.Post("/cancel", builds.Cancel)
|
||||||
})
|
})
|
||||||
}, reqRepoBuildsReader, builds.MustEnableBuilds)
|
}, reqRepoBuildsReader, builds.MustEnableBuilds)
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -34,7 +32,9 @@ func StopZombieTasks(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
if _, err := bots_model.StopTask(ctx, task, runnerv1.Result_RESULT_FAILURE); err != nil {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
return bots_model.StopTask(ctx, task.ID, bots_model.StatusFailure)
|
||||||
|
}); err != nil {
|
||||||
log.Warn("stop zombie task %v: %v", task.ID, err)
|
log.Warn("stop zombie task %v: %v", task.ID, err)
|
||||||
// go on
|
// go on
|
||||||
}
|
}
|
||||||
@ -54,7 +54,9 @@ func StopEndlessTasks(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
if _, err := bots_model.StopTask(ctx, task, runnerv1.Result_RESULT_FAILURE); err != nil {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
return bots_model.StopTask(ctx, task.ID, bots_model.StatusFailure)
|
||||||
|
}); err != nil {
|
||||||
log.Warn("stop endless task %v: %v", task.ID, err)
|
log.Warn("stop endless task %v: %v", task.ID, err)
|
||||||
// go on
|
// go on
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="job-group-section" v-for="(jobGroup, i) in allJobGroups" :key="i">
|
<div class="job-group-section" v-for="(jobGroup, i) in allJobGroups" :key="i">
|
||||||
<div class="job-group-summary">
|
<!-- <div class="job-group-summary">-->
|
||||||
{{ jobGroup.summary }}
|
<!-- {{ jobGroup.summary }}-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div class="job-brief-list">
|
<div class="job-brief-list">
|
||||||
<a class="job-brief-item" v-for="(job, index) in jobGroup.jobs" :key="job.id" v-bind:href="buildInfo.htmlurl+'/jobs/'+index">
|
<a class="job-brief-item" v-for="(job, index) in jobGroup.jobs" :key="job.id" v-bind:href="buildInfo.htmlurl+'/jobs/'+index">
|
||||||
<SvgIcon name="octicon-check-circle-fill" class="green" v-if="job.status === 'success'"/>
|
<SvgIcon name="octicon-check-circle-fill" class="green" v-if="job.status === 'success'"/>
|
||||||
@ -23,6 +23,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="ui fluid tiny basic red button" @click="cancelRun()" v-if="buildInfo.cancelable">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -160,6 +161,17 @@ const sfc = {
|
|||||||
body: {},
|
body: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// cancel a run
|
||||||
|
cancelRun() {
|
||||||
|
fetch(this.buildInfo.htmlurl+'/cancel', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Csrf-Token': csrfToken,
|
||||||
|
},
|
||||||
|
body: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
formatDuration(d) {
|
formatDuration(d) {
|
||||||
d = Math.round(d);
|
d = Math.round(d);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user