mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-26 09:09:36 -05:00
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:
parent
e69b7a92ed
commit
90e0a402c1
6 changed files with 109 additions and 67 deletions
|
@ -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>?
|
||||||
|
|
|
@ -51,7 +51,19 @@ type TaskTableRow struct {
|
||||||
Spec string
|
Spec string
|
||||||
Next time.Time
|
Next time.Time
|
||||||
Prev time.Time
|
Prev time.Time
|
||||||
|
Status string
|
||||||
|
LastMessage string
|
||||||
|
LastDoer string
|
||||||
ExecTimes int64
|
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
|
||||||
|
@ -85,6 +97,10 @@ func ListTasks() TaskTable {
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,9 @@ type Task struct {
|
||||||
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
|
||||||
|
Status string
|
||||||
|
LastMessage string
|
||||||
|
LastDoer string
|
||||||
ExecTimes int64
|
ExecTimes int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
35
templates/admin/cron.tmpl
Normal 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>
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue