mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 21:08:25 +00: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:
		@@ -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>?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
									
								
							
							
						
						
									
										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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user