v0.25.0 — Debug page: operator testing & diagnostics dashboard

Debug-mode-only dashboard (/debug) with 8 collapsible sections:
system diagnostics, notification testing, backup triggers, storage
simulation, hub & connectivity, self-update dry-run, DR/setup wizard,
and in-memory log viewer. Migrates debug dump from API router to web
server. Adds ring buffer log capture, storage disconnect simulation,
event history tracking, and cross-drive/self-update test methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 20:18:57 +01:00
parent be7803c0ac
commit 7f48786312
16 changed files with 2283 additions and 233 deletions
+41
View File
@@ -65,6 +65,11 @@ type Server struct {
// Asset syncer for Hub-managed assets (optional)
assetsSyncer *assets.Syncer
// Debug mode support
logBuffer *LogBuffer
debugCallbacks *DebugCallbacks
startTime time.Time
}
func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, crossDrive *backup.CrossDriveRunner, sched *scheduler.Scheduler, sett *settings.Settings, alertMgr *AlertManager, notif *notify.Notifier, updater *selfupdate.Updater, logger *log.Logger, version string) *Server {
@@ -143,6 +148,36 @@ func (s *Server) SetAssetsSyncer(as *assets.Syncer) {
s.assetsSyncer = as
}
// SetLogBuffer sets the in-memory log ring buffer for the debug log viewer.
func (s *Server) SetLogBuffer(lb *LogBuffer) {
s.logBuffer = lb
}
// SetDebugCallbacks sets the callbacks for debug endpoints that need main.go wiring.
func (s *Server) SetDebugCallbacks(dc *DebugCallbacks) {
s.debugCallbacks = dc
}
// SetStartTime records the controller start time for uptime calculation.
func (s *Server) SetStartTime(t time.Time) {
s.startTime = t
}
// isDebug returns true if the controller is running in debug mode.
func (s *Server) isDebug() bool {
return s.cfg.Logging.Level == "debug"
}
// ServeDebugAPI handles /api/debug/* routes (JSON API for debug operations).
// Called from the mux carve-out; debug mode check is done here.
func (s *Server) ServeDebugAPI(w http.ResponseWriter, r *http.Request) {
if !s.isDebug() {
http.NotFound(w, r)
return
}
s.handleDebugAPI(w, r)
}
// InRestoreMode returns true if the server is in DR restore mode.
func (s *Server) InRestoreMode() bool {
s.restoreMu.RLock()
@@ -239,6 +274,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case strings.HasPrefix(path, "/apps/"):
slug := strings.TrimPrefix(path, "/apps/")
s.appDetailHandler(w, r, slug)
case path == "/debug":
if !s.isDebug() {
http.NotFound(w, r)
return
}
s.debugPageHandler(w, r)
default:
http.NotFound(w, r)
}