mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Use a separate admin page to show global stats, remove actions stat (#25062)
				
					
				
			Before, Gitea shows the database table stats on the `admin dashboard` page. It has some problems: * `count(*)` is quite heavy. If tables have many records, this blocks loading the admin page blocks for a long time * Some users had even reported issues that they can't visit their admin page because this page causes blocking or `50x error (reverse proxy timeout)` * The `actions` stat is not useful. The table is simply too large. Does it really matter if it contains 1,000,000 rows or 9,999,999 rows? * The translation `admin.dashboard.statistic_info` is difficult to maintain. So, this PR uses a separate page to show the stats and removes the `actions` stat.  ## ⚠️ BREAKING The `actions` Prometheus metrics collector has been removed for the reasons mentioned beforehand. Please do not rely on its output anymore.
This commit is contained in:
		@@ -21,7 +21,7 @@ import (
 | 
				
			|||||||
type Statistic struct {
 | 
					type Statistic struct {
 | 
				
			||||||
	Counter struct {
 | 
						Counter struct {
 | 
				
			||||||
		User, Org, PublicKey,
 | 
							User, Org, PublicKey,
 | 
				
			||||||
		Repo, Watch, Star, Action, Access,
 | 
							Repo, Watch, Star, Access,
 | 
				
			||||||
		Issue, IssueClosed, IssueOpen,
 | 
							Issue, IssueClosed, IssueOpen,
 | 
				
			||||||
		Comment, Oauth, Follow,
 | 
							Comment, Oauth, Follow,
 | 
				
			||||||
		Mirror, Release, AuthSource, Webhook,
 | 
							Mirror, Release, AuthSource, Webhook,
 | 
				
			||||||
@@ -55,7 +55,6 @@ func GetStatistic() (stats Statistic) {
 | 
				
			|||||||
	stats.Counter.Repo, _ = repo_model.CountRepositories(db.DefaultContext, repo_model.CountRepositoryOptions{})
 | 
						stats.Counter.Repo, _ = repo_model.CountRepositories(db.DefaultContext, repo_model.CountRepositoryOptions{})
 | 
				
			||||||
	stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
 | 
						stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
 | 
				
			||||||
	stats.Counter.Star, _ = e.Count(new(repo_model.Star))
 | 
						stats.Counter.Star, _ = e.Count(new(repo_model.Star))
 | 
				
			||||||
	stats.Counter.Action, _ = db.EstimateCount(db.DefaultContext, new(Action))
 | 
					 | 
				
			||||||
	stats.Counter.Access, _ = e.Count(new(access_model.Access))
 | 
						stats.Counter.Access, _ = e.Count(new(access_model.Access))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	type IssueCount struct {
 | 
						type IssueCount struct {
 | 
				
			||||||
@@ -83,7 +82,7 @@ func GetStatistic() (stats Statistic) {
 | 
				
			|||||||
			Find(&stats.Counter.IssueByRepository)
 | 
								Find(&stats.Counter.IssueByRepository)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	issueCounts := []IssueCount{}
 | 
						var issueCounts []IssueCount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_ = e.Select("COUNT(*) AS count, is_closed").Table("issue").GroupBy("is_closed").Find(&issueCounts)
 | 
						_ = e.Select("COUNT(*) AS count, is_closed").Table("issue").GroupBy("is_closed").Find(&issueCounts)
 | 
				
			||||||
	for _, c := range issueCounts {
 | 
						for _, c := range issueCounts {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"xorm.io/builder"
 | 
						"xorm.io/builder"
 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
	"xorm.io/xorm/schemas"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DefaultContext is the default context to run xorm queries in
 | 
					// DefaultContext is the default context to run xorm queries in
 | 
				
			||||||
@@ -241,30 +240,6 @@ func TableName(bean interface{}) string {
 | 
				
			|||||||
	return x.TableName(bean)
 | 
						return x.TableName(bean)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EstimateCount returns an estimate of total number of rows in table
 | 
					 | 
				
			||||||
func EstimateCount(ctx context.Context, bean interface{}) (int64, error) {
 | 
					 | 
				
			||||||
	e := GetEngine(ctx)
 | 
					 | 
				
			||||||
	e.Context(ctx)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var rows int64
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	tablename := TableName(bean)
 | 
					 | 
				
			||||||
	switch x.Dialect().URI().DBType {
 | 
					 | 
				
			||||||
	case schemas.MYSQL:
 | 
					 | 
				
			||||||
		_, err = e.Context(ctx).SQL("SELECT table_rows FROM information_schema.tables WHERE tables.table_name = ? AND tables.table_schema = ?;", tablename, x.Dialect().URI().DBName).Get(&rows)
 | 
					 | 
				
			||||||
	case schemas.POSTGRES:
 | 
					 | 
				
			||||||
		// the table can live in multiple schemas of a postgres database
 | 
					 | 
				
			||||||
		// See https://wiki.postgresql.org/wiki/Count_estimate
 | 
					 | 
				
			||||||
		tablename = x.TableName(bean, true)
 | 
					 | 
				
			||||||
		_, err = e.Context(ctx).SQL("SELECT reltuples::bigint AS estimate FROM pg_class WHERE oid = ?::regclass;", tablename).Get(&rows)
 | 
					 | 
				
			||||||
	case schemas.MSSQL:
 | 
					 | 
				
			||||||
		_, err = e.Context(ctx).SQL("sp_spaceused ?;", tablename).Get(&rows)
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		return e.Context(ctx).Count(tablename)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return rows, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// InTransaction returns true if the engine is in a transaction otherwise return false
 | 
					// InTransaction returns true if the engine is in a transaction otherwise return false
 | 
				
			||||||
func InTransaction(ctx context.Context) bool {
 | 
					func InTransaction(ctx context.Context) bool {
 | 
				
			||||||
	_, ok := inTransaction(ctx)
 | 
						_, ok := inTransaction(ctx)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,6 @@ const namespace = "gitea_"
 | 
				
			|||||||
// exposes gitea metrics for prometheus
 | 
					// exposes gitea metrics for prometheus
 | 
				
			||||||
type Collector struct {
 | 
					type Collector struct {
 | 
				
			||||||
	Accesses           *prometheus.Desc
 | 
						Accesses           *prometheus.Desc
 | 
				
			||||||
	Actions            *prometheus.Desc
 | 
					 | 
				
			||||||
	Attachments        *prometheus.Desc
 | 
						Attachments        *prometheus.Desc
 | 
				
			||||||
	BuildInfo          *prometheus.Desc
 | 
						BuildInfo          *prometheus.Desc
 | 
				
			||||||
	Comments           *prometheus.Desc
 | 
						Comments           *prometheus.Desc
 | 
				
			||||||
@@ -56,11 +55,6 @@ func NewCollector() Collector {
 | 
				
			|||||||
			"Number of Accesses",
 | 
								"Number of Accesses",
 | 
				
			||||||
			nil, nil,
 | 
								nil, nil,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
		Actions: prometheus.NewDesc(
 | 
					 | 
				
			||||||
			namespace+"actions",
 | 
					 | 
				
			||||||
			"Number of Actions",
 | 
					 | 
				
			||||||
			nil, nil,
 | 
					 | 
				
			||||||
		),
 | 
					 | 
				
			||||||
		Attachments: prometheus.NewDesc(
 | 
							Attachments: prometheus.NewDesc(
 | 
				
			||||||
			namespace+"attachments",
 | 
								namespace+"attachments",
 | 
				
			||||||
			"Number of Attachments",
 | 
								"Number of Attachments",
 | 
				
			||||||
@@ -207,7 +201,6 @@ func NewCollector() Collector {
 | 
				
			|||||||
// Describe returns all possible prometheus.Desc
 | 
					// Describe returns all possible prometheus.Desc
 | 
				
			||||||
func (c Collector) Describe(ch chan<- *prometheus.Desc) {
 | 
					func (c Collector) Describe(ch chan<- *prometheus.Desc) {
 | 
				
			||||||
	ch <- c.Accesses
 | 
						ch <- c.Accesses
 | 
				
			||||||
	ch <- c.Actions
 | 
					 | 
				
			||||||
	ch <- c.Attachments
 | 
						ch <- c.Attachments
 | 
				
			||||||
	ch <- c.BuildInfo
 | 
						ch <- c.BuildInfo
 | 
				
			||||||
	ch <- c.Comments
 | 
						ch <- c.Comments
 | 
				
			||||||
@@ -246,11 +239,6 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
 | 
				
			|||||||
		prometheus.GaugeValue,
 | 
							prometheus.GaugeValue,
 | 
				
			||||||
		float64(stats.Counter.Access),
 | 
							float64(stats.Counter.Access),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	ch <- prometheus.MustNewConstMetric(
 | 
					 | 
				
			||||||
		c.Actions,
 | 
					 | 
				
			||||||
		prometheus.GaugeValue,
 | 
					 | 
				
			||||||
		float64(stats.Counter.Action),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	ch <- prometheus.MustNewConstMetric(
 | 
						ch <- prometheus.MustNewConstMetric(
 | 
				
			||||||
		c.Attachments,
 | 
							c.Attachments,
 | 
				
			||||||
		prometheus.GaugeValue,
 | 
							prometheus.GaugeValue,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2619,7 +2619,6 @@ dashboard.new_version_hint = Gitea %s is now available, you are running %s. Chec
 | 
				
			|||||||
dashboard.statistic = Summary
 | 
					dashboard.statistic = Summary
 | 
				
			||||||
dashboard.operations = Maintenance Operations
 | 
					dashboard.operations = Maintenance Operations
 | 
				
			||||||
dashboard.system_status = System Status
 | 
					dashboard.system_status = System Status
 | 
				
			||||||
dashboard.statistic_info = The Gitea database holds <b>%d</b> users, <b>%d</b> organizations, <b>%d</b> public keys, <b>%d</b> repositories, <b>%d</b> watches, <b>%d</b> stars, ~<b>%d</b> actions, <b>%d</b> accesses, <b>%d</b> issues, <b>%d</b> comments, <b>%d</b> social accounts, <b>%d</b> follows, <b>%d</b> mirrors, <b>%d</b> releases, <b>%d</b> authentication sources, <b>%d</b> webhooks, <b>%d</b> milestones, <b>%d</b> labels, <b>%d</b> hook tasks, <b>%d</b> teams, <b>%d</b> update tasks, <b>%d</b> attachments.
 | 
					 | 
				
			||||||
dashboard.operation_name = Operation Name
 | 
					dashboard.operation_name = Operation Name
 | 
				
			||||||
dashboard.operation_switch = Switch
 | 
					dashboard.operation_switch = Switch
 | 
				
			||||||
dashboard.operation_run = Run
 | 
					dashboard.operation_run = Run
 | 
				
			||||||
@@ -3060,6 +3059,8 @@ config.xorm_log_sql = Log SQL
 | 
				
			|||||||
config.get_setting_failed = Get setting %s failed
 | 
					config.get_setting_failed = Get setting %s failed
 | 
				
			||||||
config.set_setting_failed = Set setting %s failed
 | 
					config.set_setting_failed = Set setting %s failed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					monitor.stats = Stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
monitor.cron = Cron Tasks
 | 
					monitor.cron = Cron Tasks
 | 
				
			||||||
monitor.name = Name
 | 
					monitor.name = Name
 | 
				
			||||||
monitor.schedule = Schedule
 | 
					monitor.schedule = Schedule
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,11 +8,13 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	activities_model "code.gitea.io/gitea/models/activities"
 | 
						activities_model "code.gitea.io/gitea/models/activities"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/updatechecker"
 | 
						"code.gitea.io/gitea/modules/updatechecker"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
@@ -26,6 +28,7 @@ const (
 | 
				
			|||||||
	tplQueue       base.TplName = "admin/queue"
 | 
						tplQueue       base.TplName = "admin/queue"
 | 
				
			||||||
	tplStacktrace  base.TplName = "admin/stacktrace"
 | 
						tplStacktrace  base.TplName = "admin/stacktrace"
 | 
				
			||||||
	tplQueueManage base.TplName = "admin/queue_manage"
 | 
						tplQueueManage base.TplName = "admin/queue_manage"
 | 
				
			||||||
 | 
						tplStats       base.TplName = "admin/stats"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var sysStatus struct {
 | 
					var sysStatus struct {
 | 
				
			||||||
@@ -111,7 +114,6 @@ func updateSystemStatus() {
 | 
				
			|||||||
func Dashboard(ctx *context.Context) {
 | 
					func Dashboard(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
						ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
				
			||||||
	ctx.Data["PageIsAdminDashboard"] = true
 | 
						ctx.Data["PageIsAdminDashboard"] = true
 | 
				
			||||||
	ctx.Data["Stats"] = activities_model.GetStatistic()
 | 
					 | 
				
			||||||
	ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate()
 | 
						ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate()
 | 
				
			||||||
	ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion()
 | 
						ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion()
 | 
				
			||||||
	// FIXME: update periodically
 | 
						// FIXME: update periodically
 | 
				
			||||||
@@ -126,7 +128,6 @@ func DashboardPost(ctx *context.Context) {
 | 
				
			|||||||
	form := web.GetForm(ctx).(*forms.AdminDashboardForm)
 | 
						form := web.GetForm(ctx).(*forms.AdminDashboardForm)
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
						ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
				
			||||||
	ctx.Data["PageIsAdminDashboard"] = true
 | 
						ctx.Data["PageIsAdminDashboard"] = true
 | 
				
			||||||
	ctx.Data["Stats"] = activities_model.GetStatistic()
 | 
					 | 
				
			||||||
	updateSystemStatus()
 | 
						updateSystemStatus()
 | 
				
			||||||
	ctx.Data["SysStatus"] = sysStatus
 | 
						ctx.Data["SysStatus"] = sysStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -153,3 +154,30 @@ func CronTasks(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["Entries"] = cron.ListTasks()
 | 
						ctx.Data["Entries"] = cron.ListTasks()
 | 
				
			||||||
	ctx.HTML(http.StatusOK, tplCron)
 | 
						ctx.HTML(http.StatusOK, tplCron)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MonitorStats(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("admin.monitor.stats")
 | 
				
			||||||
 | 
						ctx.Data["PageIsAdminMonitorStats"] = true
 | 
				
			||||||
 | 
						bs, err := json.Marshal(activities_model.GetStatistic().Counter)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("MonitorStats", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						statsCounter := map[string]any{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(bs, &statsCounter)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("MonitorStats", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						statsKeys := make([]string, 0, len(statsCounter))
 | 
				
			||||||
 | 
						for k := range statsCounter {
 | 
				
			||||||
 | 
							if statsCounter[k] == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							statsKeys = append(statsKeys, k)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Strings(statsKeys)
 | 
				
			||||||
 | 
						ctx.Data["StatsKeys"] = statsKeys
 | 
				
			||||||
 | 
						ctx.Data["StatsCounter"] = statsCounter
 | 
				
			||||||
 | 
						ctx.HTML(http.StatusOK, tplStats)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -538,8 +538,8 @@ func registerRoutes(m *web.Route) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// ***** START: Admin *****
 | 
						// ***** START: Admin *****
 | 
				
			||||||
	m.Group("/admin", func() {
 | 
						m.Group("/admin", func() {
 | 
				
			||||||
		m.Get("", adminReq, admin.Dashboard)
 | 
							m.Get("", admin.Dashboard)
 | 
				
			||||||
		m.Post("", adminReq, web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
 | 
							m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		m.Group("/config", func() {
 | 
							m.Group("/config", func() {
 | 
				
			||||||
			m.Get("", admin.Config)
 | 
								m.Get("", admin.Config)
 | 
				
			||||||
@@ -548,6 +548,7 @@ func registerRoutes(m *web.Route) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		m.Group("/monitor", func() {
 | 
							m.Group("/monitor", func() {
 | 
				
			||||||
 | 
								m.Get("/stats", admin.MonitorStats)
 | 
				
			||||||
			m.Get("/cron", admin.CronTasks)
 | 
								m.Get("/cron", admin.CronTasks)
 | 
				
			||||||
			m.Get("/stacktrace", admin.Stacktrace)
 | 
								m.Get("/stacktrace", admin.Stacktrace)
 | 
				
			||||||
			m.Post("/stacktrace/cancel/{pid}", admin.StacktraceCancel)
 | 
								m.Post("/stacktrace/cancel/{pid}", admin.StacktraceCancel)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,14 +5,6 @@
 | 
				
			|||||||
				<p>{{(.locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer) | Str2html}}</p>
 | 
									<p>{{(.locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer) | Str2html}}</p>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		{{end}}
 | 
							{{end}}
 | 
				
			||||||
		<h4 class="ui top attached header">
 | 
					 | 
				
			||||||
			{{.locale.Tr "admin.dashboard.statistic"}}
 | 
					 | 
				
			||||||
		</h4>
 | 
					 | 
				
			||||||
		<div class="ui attached segment">
 | 
					 | 
				
			||||||
			<p>
 | 
					 | 
				
			||||||
				{{.locale.Tr "admin.dashboard.statistic_info" .Stats.Counter.User .Stats.Counter.Org .Stats.Counter.PublicKey .Stats.Counter.Repo .Stats.Counter.Watch .Stats.Counter.Star .Stats.Counter.Action .Stats.Counter.Access .Stats.Counter.Issue .Stats.Counter.Comment .Stats.Counter.Oauth .Stats.Counter.Follow .Stats.Counter.Mirror .Stats.Counter.Release .Stats.Counter.AuthSource .Stats.Counter.Webhook .Stats.Counter.Milestone .Stats.Counter.Label .Stats.Counter.HookTask .Stats.Counter.Team .Stats.Counter.UpdateTask .Stats.Counter.Attachment | Str2html}}
 | 
					 | 
				
			||||||
			</p>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
		<h4 class="ui top attached header">
 | 
							<h4 class="ui top attached header">
 | 
				
			||||||
			{{.locale.Tr "admin.dashboard.operations"}}
 | 
								{{.locale.Tr "admin.dashboard.operations"}}
 | 
				
			||||||
		</h4>
 | 
							</h4>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,9 @@
 | 
				
			|||||||
		<div class="item">
 | 
							<div class="item">
 | 
				
			||||||
			{{.locale.Tr "admin.monitor"}}
 | 
								{{.locale.Tr "admin.monitor"}}
 | 
				
			||||||
			<div class="menu">
 | 
								<div class="menu">
 | 
				
			||||||
 | 
									<a class="{{if .PageIsAdminMonitorStats}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/stats">
 | 
				
			||||||
 | 
										{{.locale.Tr "admin.monitor.stats"}}
 | 
				
			||||||
 | 
									</a>
 | 
				
			||||||
				<a class="{{if .PageIsAdminMonitorCron}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/cron">
 | 
									<a class="{{if .PageIsAdminMonitorCron}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/cron">
 | 
				
			||||||
					{{.locale.Tr "admin.monitor.cron"}}
 | 
										{{.locale.Tr "admin.monitor.cron"}}
 | 
				
			||||||
				</a>
 | 
									</a>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								templates/admin/stats.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								templates/admin/stats.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin monitor")}}
 | 
				
			||||||
 | 
					<div class="admin-setting-content">
 | 
				
			||||||
 | 
						<h4 class="ui top attached header">
 | 
				
			||||||
 | 
							{{.locale.Tr "admin.dashboard.statistic"}}
 | 
				
			||||||
 | 
						</h4>
 | 
				
			||||||
 | 
						<div class="ui attached table segment">
 | 
				
			||||||
 | 
							<table class="ui very basic striped table unstackable">
 | 
				
			||||||
 | 
								{{range $statsKey := .StatsKeys}}
 | 
				
			||||||
 | 
								<tr>
 | 
				
			||||||
 | 
									<td width="200">{{$statsKey}}</td>
 | 
				
			||||||
 | 
									<td>{{index $.StatsCounter $statsKey}}</td>
 | 
				
			||||||
 | 
								</tr>
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
 | 
							</table>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "admin/layout_footer" .}}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user