code.gitea.io/gitea@v1.22.3/routers/web/admin/admin.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2019 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package admin 6 7 import ( 8 "fmt" 9 "net/http" 10 "runtime" 11 "sort" 12 "strings" 13 "time" 14 15 activities_model "code.gitea.io/gitea/models/activities" 16 "code.gitea.io/gitea/models/db" 17 "code.gitea.io/gitea/modules/base" 18 "code.gitea.io/gitea/modules/graceful" 19 "code.gitea.io/gitea/modules/httplib" 20 "code.gitea.io/gitea/modules/json" 21 "code.gitea.io/gitea/modules/log" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/updatechecker" 24 "code.gitea.io/gitea/modules/web" 25 "code.gitea.io/gitea/services/context" 26 "code.gitea.io/gitea/services/cron" 27 "code.gitea.io/gitea/services/forms" 28 release_service "code.gitea.io/gitea/services/release" 29 repo_service "code.gitea.io/gitea/services/repository" 30 ) 31 32 const ( 33 tplDashboard base.TplName = "admin/dashboard" 34 tplSystemStatus base.TplName = "admin/system_status" 35 tplSelfCheck base.TplName = "admin/self_check" 36 tplCron base.TplName = "admin/cron" 37 tplQueue base.TplName = "admin/queue" 38 tplStacktrace base.TplName = "admin/stacktrace" 39 tplQueueManage base.TplName = "admin/queue_manage" 40 tplStats base.TplName = "admin/stats" 41 ) 42 43 var sysStatus struct { 44 StartTime string 45 NumGoroutine int 46 47 // General statistics. 48 MemAllocated string // bytes allocated and still in use 49 MemTotal string // bytes allocated (even if freed) 50 MemSys string // bytes obtained from system (sum of XxxSys below) 51 Lookups uint64 // number of pointer lookups 52 MemMallocs uint64 // number of mallocs 53 MemFrees uint64 // number of frees 54 55 // Main allocation heap statistics. 56 HeapAlloc string // bytes allocated and still in use 57 HeapSys string // bytes obtained from system 58 HeapIdle string // bytes in idle spans 59 HeapInuse string // bytes in non-idle span 60 HeapReleased string // bytes released to the OS 61 HeapObjects uint64 // total number of allocated objects 62 63 // Low-level fixed-size structure allocator statistics. 64 // Inuse is bytes used now. 65 // Sys is bytes obtained from system. 66 StackInuse string // bootstrap stacks 67 StackSys string 68 MSpanInuse string // mspan structures 69 MSpanSys string 70 MCacheInuse string // mcache structures 71 MCacheSys string 72 BuckHashSys string // profiling bucket hash table 73 GCSys string // GC metadata 74 OtherSys string // other system allocations 75 76 // Garbage collector statistics. 77 NextGC string // next run in HeapAlloc time (bytes) 78 LastGCTime string // last run time 79 PauseTotalNs string 80 PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] 81 NumGC uint32 82 } 83 84 func updateSystemStatus() { 85 sysStatus.StartTime = setting.AppStartTime.Format(time.RFC3339) 86 87 m := new(runtime.MemStats) 88 runtime.ReadMemStats(m) 89 sysStatus.NumGoroutine = runtime.NumGoroutine() 90 91 sysStatus.MemAllocated = base.FileSize(int64(m.Alloc)) 92 sysStatus.MemTotal = base.FileSize(int64(m.TotalAlloc)) 93 sysStatus.MemSys = base.FileSize(int64(m.Sys)) 94 sysStatus.Lookups = m.Lookups 95 sysStatus.MemMallocs = m.Mallocs 96 sysStatus.MemFrees = m.Frees 97 98 sysStatus.HeapAlloc = base.FileSize(int64(m.HeapAlloc)) 99 sysStatus.HeapSys = base.FileSize(int64(m.HeapSys)) 100 sysStatus.HeapIdle = base.FileSize(int64(m.HeapIdle)) 101 sysStatus.HeapInuse = base.FileSize(int64(m.HeapInuse)) 102 sysStatus.HeapReleased = base.FileSize(int64(m.HeapReleased)) 103 sysStatus.HeapObjects = m.HeapObjects 104 105 sysStatus.StackInuse = base.FileSize(int64(m.StackInuse)) 106 sysStatus.StackSys = base.FileSize(int64(m.StackSys)) 107 sysStatus.MSpanInuse = base.FileSize(int64(m.MSpanInuse)) 108 sysStatus.MSpanSys = base.FileSize(int64(m.MSpanSys)) 109 sysStatus.MCacheInuse = base.FileSize(int64(m.MCacheInuse)) 110 sysStatus.MCacheSys = base.FileSize(int64(m.MCacheSys)) 111 sysStatus.BuckHashSys = base.FileSize(int64(m.BuckHashSys)) 112 sysStatus.GCSys = base.FileSize(int64(m.GCSys)) 113 sysStatus.OtherSys = base.FileSize(int64(m.OtherSys)) 114 115 sysStatus.NextGC = base.FileSize(int64(m.NextGC)) 116 sysStatus.LastGCTime = time.Unix(0, int64(m.LastGC)).Format(time.RFC3339) 117 sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) 118 sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) 119 sysStatus.NumGC = m.NumGC 120 } 121 122 func prepareStartupProblemsAlert(ctx *context.Context) { 123 if len(setting.StartupProblems) > 0 { 124 content := setting.StartupProblems[0] 125 if len(setting.StartupProblems) > 1 { 126 content += fmt.Sprintf(" (and %d more)", len(setting.StartupProblems)-1) 127 } 128 ctx.Flash.Error(content, true) 129 } 130 } 131 132 // Dashboard show admin panel dashboard 133 func Dashboard(ctx *context.Context) { 134 ctx.Data["Title"] = ctx.Tr("admin.dashboard") 135 ctx.Data["PageIsAdminDashboard"] = true 136 ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx) 137 ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx) 138 updateSystemStatus() 139 ctx.Data["SysStatus"] = sysStatus 140 ctx.Data["SSH"] = setting.SSH 141 prepareStartupProblemsAlert(ctx) 142 ctx.HTML(http.StatusOK, tplDashboard) 143 } 144 145 func SystemStatus(ctx *context.Context) { 146 updateSystemStatus() 147 ctx.Data["SysStatus"] = sysStatus 148 ctx.HTML(http.StatusOK, tplSystemStatus) 149 } 150 151 // DashboardPost run an admin operation 152 func DashboardPost(ctx *context.Context) { 153 form := web.GetForm(ctx).(*forms.AdminDashboardForm) 154 ctx.Data["Title"] = ctx.Tr("admin.dashboard") 155 ctx.Data["PageIsAdminDashboard"] = true 156 updateSystemStatus() 157 ctx.Data["SysStatus"] = sysStatus 158 159 // Run operation. 160 if form.Op != "" { 161 switch form.Op { 162 case "sync_repo_branches": 163 go func() { 164 if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil { 165 log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err) 166 } 167 }() 168 ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started")) 169 case "sync_repo_tags": 170 go func() { 171 if err := release_service.AddAllRepoTagsToSyncQueue(graceful.GetManager().ShutdownContext()); err != nil { 172 log.Error("AddAllRepoTagsToSyncQueue: %v: %v", ctx.Doer.ID, err) 173 } 174 }() 175 ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_tag.started")) 176 default: 177 task := cron.GetTask(form.Op) 178 if task != nil { 179 go task.RunWithUser(ctx.Doer, nil) 180 ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) 181 } else { 182 ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) 183 } 184 } 185 } 186 if form.From == "monitor" { 187 ctx.Redirect(setting.AppSubURL + "/admin/monitor/cron") 188 } else { 189 ctx.Redirect(setting.AppSubURL + "/admin") 190 } 191 } 192 193 func SelfCheck(ctx *context.Context) { 194 ctx.Data["PageIsAdminSelfCheck"] = true 195 196 ctx.Data["StartupProblems"] = setting.StartupProblems 197 if len(setting.StartupProblems) == 0 && !setting.IsProd { 198 if time.Now().Unix()%2 == 0 { 199 ctx.Data["StartupProblems"] = []string{"This is a test warning message in dev mode"} 200 } 201 } 202 203 r, err := db.CheckCollationsDefaultEngine() 204 if err != nil { 205 ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true) 206 } 207 208 if r != nil { 209 ctx.Data["DatabaseType"] = setting.Database.Type 210 ctx.Data["DatabaseCheckResult"] = r 211 hasProblem := false 212 if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) { 213 ctx.Data["DatabaseCheckCollationMismatch"] = true 214 hasProblem = true 215 } 216 if !r.IsCollationCaseSensitive(r.DatabaseCollation) { 217 ctx.Data["DatabaseCheckCollationCaseInsensitive"] = true 218 hasProblem = true 219 } 220 ctx.Data["DatabaseCheckInconsistentCollationColumns"] = r.InconsistentCollationColumns 221 hasProblem = hasProblem || len(r.InconsistentCollationColumns) > 0 222 223 ctx.Data["DatabaseCheckHasProblems"] = hasProblem 224 } 225 ctx.HTML(http.StatusOK, tplSelfCheck) 226 } 227 228 func SelfCheckPost(ctx *context.Context) { 229 var problems []string 230 frontendAppURL := ctx.FormString("location_origin") + setting.AppSubURL + "/" 231 ctxAppURL := httplib.GuessCurrentAppURL(ctx) 232 if !strings.HasPrefix(ctxAppURL, frontendAppURL) { 233 problems = append(problems, ctx.Locale.TrString("admin.self_check.location_origin_mismatch", frontendAppURL, ctxAppURL)) 234 } 235 ctx.JSON(http.StatusOK, map[string]any{"problems": problems}) 236 } 237 238 func CronTasks(ctx *context.Context) { 239 ctx.Data["Title"] = ctx.Tr("admin.monitor.cron") 240 ctx.Data["PageIsAdminMonitorCron"] = true 241 ctx.Data["Entries"] = cron.ListTasks() 242 ctx.HTML(http.StatusOK, tplCron) 243 } 244 245 func MonitorStats(ctx *context.Context) { 246 ctx.Data["Title"] = ctx.Tr("admin.monitor.stats") 247 ctx.Data["PageIsAdminMonitorStats"] = true 248 bs, err := json.Marshal(activities_model.GetStatistic(ctx).Counter) 249 if err != nil { 250 ctx.ServerError("MonitorStats", err) 251 return 252 } 253 statsCounter := map[string]any{} 254 err = json.Unmarshal(bs, &statsCounter) 255 if err != nil { 256 ctx.ServerError("MonitorStats", err) 257 return 258 } 259 statsKeys := make([]string, 0, len(statsCounter)) 260 for k := range statsCounter { 261 if statsCounter[k] == nil { 262 continue 263 } 264 statsKeys = append(statsKeys, k) 265 } 266 sort.Strings(statsKeys) 267 ctx.Data["StatsKeys"] = statsKeys 268 ctx.Data["StatsCounter"] = statsCounter 269 ctx.HTML(http.StatusOK, tplStats) 270 }