package web import ( "fmt" "net/http" "gitea.dooplex.hu/admin/felhom-controller/internal/scheduler" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" "gitea.dooplex.hu/admin/felhom-controller/internal/system" ) func (s *Server) baseData(page, title string) map[string]interface{} { return map[string]interface{}{ "Page": page, "Title": title, "CustomerName": s.cfg.Customer.Name, "Domain": s.cfg.Customer.Domain, "Version": s.version, } } func (s *Server) dashboardHandler(w http.ResponseWriter, _ *http.Request) { stackList := s.stackMgr.GetStacks() // Filter to deployed + protected stacks first var deployedStacks []stacks.Stack for _, st := range stackList { if st.Deployed || st.Protected { deployedStacks = append(deployedStacks, st) } } // Count from the DISPLAYED set only running, stopped := 0, 0 for _, st := range deployedStacks { switch st.State { case stacks.StateRunning, stacks.StateStarting, stacks.StateUnhealthy, stacks.StateRestarting: running++ case stacks.StateStopped, stacks.StateExited: stopped++ } } sysInfo := system.GetInfo(s.cfg.Paths.HDDPath, s.cpuCollector) data := s.baseData("dashboard", "Vezérlőpult") data["Stacks"] = deployedStacks data["RunningCount"] = running data["StoppedCount"] = stopped data["TotalCount"] = len(stackList) data["SystemInfo"] = sysInfo // Backup status data["BackupEnabled"] = s.cfg.Backup.Enabled if s.backupMgr != nil { nextDBDump := scheduler.NextDailyRun(s.cfg.Backup.DBDumpSchedule) nextBackup := scheduler.NextDailyRun(s.cfg.Backup.ResticSchedule) fullStatus := s.backupMgr.GetFullStatus(nextDBDump, nextBackup) data["DBDumpStatus"] = fullStatus.LastDBDump data["BackupStatus"] = fullStatus.LastBackup data["BackupRunning"] = fullStatus.Running data["BackupMaxAgeHours"] = s.cfg.Monitoring.Thresholds.BackupMaxAgeHours } s.render(w, "dashboard", data) } func (s *Server) stacksHandler(w http.ResponseWriter, _ *http.Request) { data := s.baseData("stacks", "Alkalmazások") data["Stacks"] = s.stackMgr.GetStacks() s.render(w, "stacks", data) } func (s *Server) logsHandler(w http.ResponseWriter, r *http.Request, name string) { stack, ok := s.stackMgr.GetStack(name) if !ok { http.NotFound(w, r) return } logs, err := s.stackMgr.GetLogs(name, 200) if err != nil { logs = fmt.Sprintf("Hiba a naplók lekérésekor: %v", err) } // Raw mode: return plain text for AJAX polling if r.URL.Query().Get("raw") == "1" { w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprint(w, logs) return } data := s.baseData("logs", stack.Meta.DisplayName+" — Naplók") data["Stack"] = stack data["Logs"] = logs s.render(w, "logs", data) } func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name string) { meta, appCfg, err := s.stackMgr.GetDeployFields(name) if err != nil { http.NotFound(w, r) return } stack, _ := s.stackMgr.GetStack(name) alreadyDeployed := appCfg != nil && appCfg.Deployed data := s.baseData("deploy", meta.DisplayName+" — Telepítés") data["Stack"] = stack data["Meta"] = meta data["AppConfig"] = appCfg data["AlreadyDeployed"] = alreadyDeployed data["LogoURL"] = s.cfg.AppLogoURL(meta.Slug) data["LogoPNGURL"] = s.cfg.AppLogoPNGURL(meta.Slug) data["AppPageURL"] = s.cfg.AppPageURL(meta.Slug) data["UserFields"] = meta.UserFacingFields() data["AutoFields"] = meta.AutoGeneratedFields() // Memory info for deploy page (only for non-deployed apps) if !alreadyDeployed { memInfo := map[string]interface{}{"Available": false} totalMB, memErr := system.GetTotalMemoryMB() if memErr == nil { reservedMB := s.cfg.System.ReservedMemoryMB usableMB := totalMB - reservedMB committedReqMB, committedLimitMB := s.stackMgr.CommittedMemory() newReqMB := stacks.ParseMemoryMB(meta.Resources.MemRequest) newLimitMB := stacks.ParseMemoryMB(meta.Resources.MemLimit) afterReqMB := committedReqMB + newReqMB afterLimitMB := committedLimitMB + newLimitMB percent := 0 if usableMB > 0 { percent = afterReqMB * 100 / usableMB } committedPercent := 0 if usableMB > 0 { committedPercent = committedReqMB * 100 / usableMB } memInfo["Available"] = true memInfo["TotalMB"] = totalMB memInfo["ReservedMB"] = reservedMB memInfo["UsableMB"] = usableMB memInfo["CommittedMB"] = committedReqMB memInfo["NewRequestMB"] = newReqMB memInfo["AfterMB"] = afterReqMB memInfo["Percent"] = percent memInfo["CommittedPercent"] = committedPercent memInfo["Blocked"] = newReqMB > 0 && afterReqMB > usableMB memInfo["OvercommitWarn"] = newLimitMB > 0 && afterLimitMB > totalMB } data["MemoryInfo"] = memInfo } s.render(w, "deploy", data) } func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug string) { var found *stacks.Stack for _, stack := range s.stackMgr.GetStacks() { if stack.Meta.Slug == slug { found = &stack break } } if found == nil { http.NotFound(w, r) return } // Load current optional config values from app.yaml currentValues := make(map[string]string) if appCfg := s.stackMgr.LoadAppConfigByName(found.Name); appCfg != nil { for k, v := range appCfg.Env { currentValues[k] = v } } data := s.baseData("stacks", found.Meta.DisplayName) data["Stack"] = found data["Meta"] = found.Meta data["AppInfo"] = found.Meta.AppInfo data["OptionalConfig"] = found.Meta.OptionalConfig data["CurrentValues"] = currentValues data["HasAppInfo"] = found.Meta.HasAppInfo() data["HasOptionalConfig"] = found.Meta.HasOptionalConfig() s.render(w, "app_info", data) } func (s *Server) monitoringHandler(w http.ResponseWriter, _ *http.Request) { data := s.baseData("monitoring", "Rendszermonitor") data["SystemInfo"] = system.GetInfo(s.cfg.Paths.HDDPath, s.cpuCollector) s.render(w, "monitoring", data) } func (s *Server) backupsHandler(w http.ResponseWriter, _ *http.Request) { data := s.baseData("backups", "Biztonsági mentés") if s.backupMgr != nil { nextDBDump := scheduler.NextDailyRun(s.cfg.Backup.DBDumpSchedule) nextBackup := scheduler.NextDailyRun(s.cfg.Backup.ResticSchedule) fullStatus := s.backupMgr.GetFullStatus(nextDBDump, nextBackup) data["Backup"] = fullStatus } else { data["Backup"] = nil } s.render(w, "backups", data) }