summaryrefslogtreecommitdiffstats
path: root/routers/web/admin/admin.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
commitdd136858f1ea40ad3c94191d647487fa4f31926c (patch)
tree58fec94a7b2a12510c9664b21793f1ed560c6518 /routers/web/admin/admin.go
parentInitial commit. (diff)
downloadforgejo-debian.tar.xz
forgejo-debian.zip
Adding upstream version 9.0.0.upstream/9.0.0upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'routers/web/admin/admin.go')
-rw-r--r--routers/web/admin/admin.go254
1 files changed, 254 insertions, 0 deletions
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
new file mode 100644
index 0000000..067203b
--- /dev/null
+++ b/routers/web/admin/admin.go
@@ -0,0 +1,254 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package admin
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "runtime"
+ "time"
+
+ activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/updatechecker"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/cron"
+ "code.gitea.io/gitea/services/forms"
+ release_service "code.gitea.io/gitea/services/release"
+ repo_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplDashboard base.TplName = "admin/dashboard"
+ tplSystemStatus base.TplName = "admin/system_status"
+ tplSelfCheck base.TplName = "admin/self_check"
+ tplCron base.TplName = "admin/cron"
+ tplQueue base.TplName = "admin/queue"
+ tplStacktrace base.TplName = "admin/stacktrace"
+ tplQueueManage base.TplName = "admin/queue_manage"
+ tplStats base.TplName = "admin/stats"
+)
+
+var sysStatus struct {
+ StartTime string
+ NumGoroutine int
+
+ // General statistics.
+ MemAllocated string // bytes allocated and still in use
+ MemTotal string // bytes allocated (even if freed)
+ MemSys string // bytes obtained from system (sum of XxxSys below)
+ Lookups uint64 // number of pointer lookups
+ MemMallocs uint64 // number of mallocs
+ MemFrees uint64 // number of frees
+
+ // Main allocation heap statistics.
+ HeapAlloc string // bytes allocated and still in use
+ HeapSys string // bytes obtained from system
+ HeapIdle string // bytes in idle spans
+ HeapInuse string // bytes in non-idle span
+ HeapReleased string // bytes released to the OS
+ HeapObjects uint64 // total number of allocated objects
+
+ // Low-level fixed-size structure allocator statistics.
+ // Inuse is bytes used now.
+ // Sys is bytes obtained from system.
+ StackInuse string // bootstrap stacks
+ StackSys string
+ MSpanInuse string // mspan structures
+ MSpanSys string
+ MCacheInuse string // mcache structures
+ MCacheSys string
+ BuckHashSys string // profiling bucket hash table
+ GCSys string // GC metadata
+ OtherSys string // other system allocations
+
+ // Garbage collector statistics.
+ NextGC string // next run in HeapAlloc time (bytes)
+ LastGCTime string // last run time
+ PauseTotalNs string
+ PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
+ NumGC uint32
+}
+
+func updateSystemStatus() {
+ sysStatus.StartTime = setting.AppStartTime.Format(time.RFC3339)
+
+ m := new(runtime.MemStats)
+ runtime.ReadMemStats(m)
+ sysStatus.NumGoroutine = runtime.NumGoroutine()
+
+ sysStatus.MemAllocated = base.FileSize(int64(m.Alloc))
+ sysStatus.MemTotal = base.FileSize(int64(m.TotalAlloc))
+ sysStatus.MemSys = base.FileSize(int64(m.Sys))
+ sysStatus.Lookups = m.Lookups
+ sysStatus.MemMallocs = m.Mallocs
+ sysStatus.MemFrees = m.Frees
+
+ sysStatus.HeapAlloc = base.FileSize(int64(m.HeapAlloc))
+ sysStatus.HeapSys = base.FileSize(int64(m.HeapSys))
+ sysStatus.HeapIdle = base.FileSize(int64(m.HeapIdle))
+ sysStatus.HeapInuse = base.FileSize(int64(m.HeapInuse))
+ sysStatus.HeapReleased = base.FileSize(int64(m.HeapReleased))
+ sysStatus.HeapObjects = m.HeapObjects
+
+ sysStatus.StackInuse = base.FileSize(int64(m.StackInuse))
+ sysStatus.StackSys = base.FileSize(int64(m.StackSys))
+ sysStatus.MSpanInuse = base.FileSize(int64(m.MSpanInuse))
+ sysStatus.MSpanSys = base.FileSize(int64(m.MSpanSys))
+ sysStatus.MCacheInuse = base.FileSize(int64(m.MCacheInuse))
+ sysStatus.MCacheSys = base.FileSize(int64(m.MCacheSys))
+ sysStatus.BuckHashSys = base.FileSize(int64(m.BuckHashSys))
+ sysStatus.GCSys = base.FileSize(int64(m.GCSys))
+ sysStatus.OtherSys = base.FileSize(int64(m.OtherSys))
+
+ sysStatus.NextGC = base.FileSize(int64(m.NextGC))
+ sysStatus.LastGCTime = time.Unix(0, int64(m.LastGC)).Format(time.RFC3339)
+ sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
+ sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
+ sysStatus.NumGC = m.NumGC
+}
+
+func prepareDeprecatedWarningsAlert(ctx *context.Context) {
+ if len(setting.DeprecatedWarnings) > 0 {
+ content := setting.DeprecatedWarnings[0]
+ if len(setting.DeprecatedWarnings) > 1 {
+ content += fmt.Sprintf(" (and %d more)", len(setting.DeprecatedWarnings)-1)
+ }
+ ctx.Flash.Error(content, true)
+ }
+}
+
+// Dashboard show admin panel dashboard
+func Dashboard(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.dashboard")
+ ctx.Data["PageIsAdminDashboard"] = true
+ ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx)
+ ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx)
+ updateSystemStatus()
+ ctx.Data["SysStatus"] = sysStatus
+ ctx.Data["SSH"] = setting.SSH
+ prepareDeprecatedWarningsAlert(ctx)
+ ctx.HTML(http.StatusOK, tplDashboard)
+}
+
+func SystemStatus(ctx *context.Context) {
+ updateSystemStatus()
+ ctx.Data["SysStatus"] = sysStatus
+ ctx.HTML(http.StatusOK, tplSystemStatus)
+}
+
+// DashboardPost run an admin operation
+func DashboardPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.AdminDashboardForm)
+ ctx.Data["Title"] = ctx.Tr("admin.dashboard")
+ ctx.Data["PageIsAdminDashboard"] = true
+ updateSystemStatus()
+ ctx.Data["SysStatus"] = sysStatus
+
+ // Run operation.
+ if form.Op != "" {
+ switch form.Op {
+ case "sync_repo_branches":
+ go func() {
+ if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext()); err != nil {
+ log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
+ }
+ }()
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started"))
+ case "sync_repo_tags":
+ go func() {
+ if err := release_service.AddAllRepoTagsToSyncQueue(graceful.GetManager().ShutdownContext()); err != nil {
+ log.Error("AddAllRepoTagsToSyncQueue: %v: %v", ctx.Doer.ID, err)
+ }
+ }()
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_tag.started"))
+ default:
+ task := cron.GetTask(form.Op)
+ if task != nil {
+ go task.RunWithUser(ctx.Doer, nil)
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
+ } else {
+ ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
+ }
+ }
+ }
+ if form.From == "monitor" {
+ ctx.Redirect(setting.AppSubURL + "/admin/monitor/cron")
+ } else {
+ ctx.Redirect(setting.AppSubURL + "/admin")
+ }
+}
+
+func SelfCheck(ctx *context.Context) {
+ ctx.Data["PageIsAdminSelfCheck"] = true
+ r, err := db.CheckCollationsDefaultEngine()
+ if err != nil {
+ ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true)
+ }
+
+ if r != nil {
+ ctx.Data["DatabaseType"] = setting.Database.Type
+ ctx.Data["DatabaseCheckResult"] = r
+ hasProblem := false
+ if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) {
+ ctx.Data["DatabaseCheckCollationMismatch"] = true
+ hasProblem = true
+ }
+ if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
+ ctx.Data["DatabaseCheckCollationCaseInsensitive"] = true
+ hasProblem = true
+ }
+ ctx.Data["DatabaseCheckInconsistentCollationColumns"] = r.InconsistentCollationColumns
+ hasProblem = hasProblem || len(r.InconsistentCollationColumns) > 0
+
+ ctx.Data["DatabaseCheckHasProblems"] = hasProblem
+ }
+
+ elapsed, err := cache.Test()
+ if err != nil {
+ ctx.Data["CacheError"] = err
+ } else if elapsed > cache.SlowCacheThreshold {
+ ctx.Data["CacheSlow"] = fmt.Sprint(elapsed)
+ }
+
+ ctx.HTML(http.StatusOK, tplSelfCheck)
+}
+
+func CronTasks(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
+ ctx.Data["PageIsAdminMonitorCron"] = true
+ ctx.Data["Entries"] = cron.ListTasks()
+ ctx.HTML(http.StatusOK, tplCron)
+}
+
+func MonitorStats(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.monitor.stats")
+ ctx.Data["PageIsAdminMonitorStats"] = true
+ modelStats := activities_model.GetStatistic(ctx).Counter
+ stats := map[string]any{}
+
+ // To avoid manually converting the values of the stats struct to an map,
+ // and to avoid using JSON to do this for us (JSON encoder converts numbers to
+ // scientific notation). Use reflect to convert the struct to an map.
+ rv := reflect.ValueOf(modelStats)
+ for i := 0; i < rv.NumField(); i++ {
+ field := rv.Field(i)
+ // Preserve old behavior, do not show arrays that are empty.
+ if field.Kind() == reflect.Slice && field.Len() == 0 {
+ continue
+ }
+ stats[rv.Type().Field(i).Name] = field.Interface()
+ }
+
+ ctx.Data["Stats"] = stats
+ ctx.HTML(http.StatusOK, tplStats)
+}