Show last cron messages on monitor page ()

As discussed on  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
options/locale
services/cron
templates/admin

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>