Show last cron messages on monitor page (#19223)

As discussed on #19221 we should store the results of the last task message on the
crontask and show them on the monitor page.

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
zeripath 2022-03-29 02:31:07 +01:00 committed by GitHub
parent e69b7a92ed
commit 90e0a402c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 67 deletions

View file

@ -2816,6 +2816,7 @@ monitor.process = Running Processes
monitor.desc = Description monitor.desc = Description
monitor.start = Start Time monitor.start = Start Time
monitor.execute_time = Execution Time monitor.execute_time = Execution Time
monitor.last_execution_result = Result
monitor.process.cancel = Cancel process monitor.process.cancel = Cancel process
monitor.process.cancel_desc = Cancelling a process may cause data loss monitor.process.cancel_desc = Cancelling a process may cause data loss
monitor.process.cancel_notices = Cancel: <strong>%s</strong>? monitor.process.cancel_notices = Cancel: <strong>%s</strong>?

View file

@ -47,11 +47,23 @@ func NewContext() {
// TaskTableRow represents a task row in the tasks table // TaskTableRow represents a task row in the tasks table
type TaskTableRow struct { type TaskTableRow struct {
Name string Name string
Spec string Spec string
Next time.Time Next time.Time
Prev time.Time Prev time.Time
ExecTimes int64 Status string
LastMessage string
LastDoer string
ExecTimes int64
task *Task
}
func (t *TaskTableRow) FormatLastMessage(locale string) string {
if t.Status == "finished" {
return t.task.GetConfig().FormatMessage(locale, t.Name, t.Status, t.LastDoer)
}
return t.task.GetConfig().FormatMessage(locale, t.Name, t.Status, t.LastDoer, t.LastMessage)
} }
// TaskTable represents a table of tasks // TaskTable represents a table of tasks
@ -80,11 +92,15 @@ func ListTasks() TaskTable {
} }
task.lock.Lock() task.lock.Lock()
tTable = append(tTable, &TaskTableRow{ tTable = append(tTable, &TaskTableRow{
Name: task.Name, Name: task.Name,
Spec: spec, Spec: spec,
Next: next, Next: next,
Prev: prev, Prev: prev,
ExecTimes: task.ExecTimes, ExecTimes: task.ExecTimes,
LastMessage: task.LastMessage,
Status: task.Status,
LastDoer: task.LastDoer,
task: task,
}) })
task.lock.Unlock() task.lock.Unlock()
} }

View file

@ -7,8 +7,6 @@ package cron
import ( import (
"time" "time"
user_model "code.gitea.io/gitea/models/user"
"github.com/unknwon/i18n" "github.com/unknwon/i18n"
) )
@ -17,7 +15,7 @@ type Config interface {
IsEnabled() bool IsEnabled() bool
DoRunAtStart() bool DoRunAtStart() bool
GetSchedule() string GetSchedule() string
FormatMessage(name, status string, doer *user_model.User, args ...interface{}) string FormatMessage(locale, name, status, doer string, args ...interface{}) string
DoNoticeOnSuccess() bool DoNoticeOnSuccess() bool
} }
@ -70,19 +68,20 @@ func (b *BaseConfig) DoNoticeOnSuccess() bool {
} }
// FormatMessage returns a message for the task // FormatMessage returns a message for the task
func (b *BaseConfig) FormatMessage(name, status string, doer *user_model.User, args ...interface{}) string { // Please note the `status` string will be concatenated with `admin.dashboard.cron.` and `admin.dashboard.task.` to provide locale messages. Similarly `name` will be composed with `admin.dashboard.` to provide the locale name for the task.
func (b *BaseConfig) FormatMessage(locale, name, status, doer string, args ...interface{}) string {
realArgs := make([]interface{}, 0, len(args)+2) realArgs := make([]interface{}, 0, len(args)+2)
realArgs = append(realArgs, i18n.Tr("en-US", "admin.dashboard."+name)) realArgs = append(realArgs, i18n.Tr(locale, "admin.dashboard."+name))
if doer == nil { if doer == "" {
realArgs = append(realArgs, "(Cron)") realArgs = append(realArgs, "(Cron)")
} else { } else {
realArgs = append(realArgs, doer.Name) realArgs = append(realArgs, doer)
} }
if len(args) > 0 { if len(args) > 0 {
realArgs = append(realArgs, args...) realArgs = append(realArgs, args...)
} }
if doer == nil || (doer.ID == -1 && doer.Name == "(Cron)") { if doer == "" {
return i18n.Tr("en-US", "admin.dashboard.cron."+status, realArgs...) return i18n.Tr(locale, "admin.dashboard.cron."+status, realArgs...)
} }
return i18n.Tr("en-US", "admin.dashboard.task."+status, realArgs...) return i18n.Tr(locale, "admin.dashboard.task."+status, realArgs...)
} }

View file

@ -29,11 +29,14 @@ var (
// Task represents a Cron task // Task represents a Cron task
type Task struct { type Task struct {
lock sync.Mutex lock sync.Mutex
Name string Name string
config Config config Config
fun func(context.Context, *user_model.User, Config) error fun func(context.Context, *user_model.User, Config) error
ExecTimes int64 Status string
LastMessage string
LastDoer string
ExecTimes int64
} }
// DoRunAtStart returns if this task should run at the start // DoRunAtStart returns if this task should run at the start
@ -86,24 +89,45 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
}() }()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
pm := process.GetManager() pm := process.GetManager()
ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(t.Name, "process", doer)) doerName := ""
if doer != nil && doer.ID != -1 {
doerName = doer.Name
}
ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage("en-US", t.Name, "process", doerName))
defer finished() defer finished()
if err := t.fun(ctx, doer, config); err != nil { if err := t.fun(ctx, doer, config); err != nil {
var message string
var status string
if db.IsErrCancelled(err) { if db.IsErrCancelled(err) {
message := err.(db.ErrCancelled).Message status = "cancelled"
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil { message = err.(db.ErrCancelled).Message
log.Error("CreateNotice: %v", err) } else {
} status = "error"
return message = err.Error()
} }
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil {
t.lock.Lock()
t.LastMessage = message
t.Status = status
t.LastDoer = doerName
t.lock.Unlock()
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "cancelled", doerName, message)); err != nil {
log.Error("CreateNotice: %v", err) log.Error("CreateNotice: %v", err)
} }
return return
} }
t.lock.Lock()
t.Status = "finished"
t.LastMessage = ""
t.LastDoer = doerName
t.lock.Unlock()
if config.DoNoticeOnSuccess() { if config.DoNoticeOnSuccess() {
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil { if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "finished", doerName)); err != nil {
log.Error("CreateNotice: %v", err) log.Error("CreateNotice: %v", err)
} }
} }

35
templates/admin/cron.tmpl Normal file
View file

@ -0,0 +1,35 @@
<h4 class="ui top attached header">
{{.i18n.Tr "admin.monitor.cron"}}
</h4>
<div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin">
<table class="ui very basic striped table">
<thead>
<tr>
<th></th>
<th>{{.i18n.Tr "admin.monitor.name"}}</th>
<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
<th>{{.i18n.Tr "admin.monitor.next"}}</th>
<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
<th>{{.i18n.Tr "admin.monitor.last_execution_result"}}</th>
</tr>
</thead>
<tbody>
{{range .Entries}}
<tr>
<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.i18n.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td>
<td>{{$.i18n.Tr (printf "admin.dashboard.%s" .Name)}}</td>
<td>{{.Spec}}</td>
<td>{{DateFmtLong .Next}}</td>
<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
<td>{{.ExecTimes}}</td>
<td {{if ne .Status ""}}class="tooltip" data-content="{{.FormatLastMessage $.i18n.Language}}"{{end}} >{{if eq .Status "" }}{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
<input type="hidden" name="from" value="monitor"/>
{{.CsrfTokenHtml}}
</form>
</div>

View file

@ -3,40 +3,7 @@
{{template "admin/navbar" .}} {{template "admin/navbar" .}}
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
<h4 class="ui top attached header"> {{template "admin/cron" .}}
{{.i18n.Tr "admin.monitor.cron"}}
</h4>
<div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin">
<table class="ui very basic striped table">
<thead>
<tr>
<th></th>
<th>{{.i18n.Tr "admin.monitor.name"}}</th>
<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
<th>{{.i18n.Tr "admin.monitor.next"}}</th>
<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
</tr>
</thead>
<tbody>
{{range .Entries}}
<tr>
<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.i18n.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td>
<td>{{$.i18n.Tr (printf "admin.dashboard.%s" .Name)}}</td>
<td>{{.Spec}}</td>
<td>{{DateFmtLong .Next}}</td>
<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
<td>{{.ExecTimes}}</td>
</tr>
{{end}}
</tbody>
</table>
<input type="hidden" name="from" value="monitor"/>
{{.CsrfTokenHtml}}
</form>
</div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.monitor.queues"}} {{.i18n.Tr "admin.monitor.queues"}}
</h4> </h4>