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
+105
View File
@@ -16,6 +16,16 @@ import (
// Notifier sends structured events to the hub via /api/v1/event.
// Non-blocking: fires requests in goroutines, logs errors but doesn't retry aggressively.
// Cooldown logic is handled by the Hub — the controller sends all events unconditionally.
// EventHistoryEntry records a sent event for the debug page.
type EventHistoryEntry struct {
Timestamp time.Time `json:"timestamp"`
EventType string `json:"event_type"`
Severity string `json:"severity"`
Message string `json:"message"`
HubStatus int `json:"hub_status"`
HubError string `json:"hub_error,omitempty"`
}
type Notifier struct {
hubURL string
apiKey string
@@ -28,6 +38,12 @@ type Notifier struct {
mu sync.Mutex
prevHealthStatus string // tracks previous health check status for change detection
// Event history ring buffer (debug page)
historyMu sync.RWMutex
history [50]EventHistoryEntry
histPos int
histFull bool
}
// New creates a new Notifier. Returns a no-op notifier if hub is not enabled.
@@ -454,6 +470,95 @@ func (n *Notifier) SendTest() error {
return nil
}
// ── Debug event testing ───────────────────────────────────────────────
// PushTestEventSync sends a test event synchronously and returns the Hub HTTP status code.
// Used by the debug page for event testing with configurable type/severity.
func (n *Notifier) PushTestEventSync(eventType, severity, message string) (statusCode int, err error) {
if !n.enabled {
return 0, fmt.Errorf("hub nem konfigurált")
}
payload := eventRequest{
CustomerID: n.customerID,
EventType: eventType,
Severity: severity,
Message: message,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return 0, fmt.Errorf("marshal: %w", err)
}
url := n.hubURL + "/api/v1/event"
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData))
if err != nil {
return 0, fmt.Errorf("request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+n.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := n.httpClient.Do(req)
if err != nil {
n.recordHistory(eventType, severity, message, 0, err.Error())
return 0, fmt.Errorf("send: %w", err)
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode >= 400 {
n.recordHistory(eventType, severity, message, resp.StatusCode, fmt.Sprintf("HTTP %d", resp.StatusCode))
return resp.StatusCode, fmt.Errorf("hub returned %d", resp.StatusCode)
}
n.recordHistory(eventType, severity, message, resp.StatusCode, "")
return resp.StatusCode, nil
}
// GetEventHistory returns the last N event history entries (newest first).
func (n *Notifier) GetEventHistory(limit int) []EventHistoryEntry {
n.historyMu.RLock()
defer n.historyMu.RUnlock()
total := n.histPos
if n.histFull {
total = len(n.history)
}
if limit <= 0 || limit > total {
limit = total
}
result := make([]EventHistoryEntry, 0, limit)
for i := 0; i < limit; i++ {
idx := n.histPos - 1 - i
if idx < 0 {
idx += len(n.history)
}
result = append(result, n.history[idx])
}
return result
}
// recordHistory appends an entry to the event history ring buffer.
func (n *Notifier) recordHistory(eventType, severity, message string, hubStatus int, hubError string) {
n.historyMu.Lock()
defer n.historyMu.Unlock()
n.history[n.histPos] = EventHistoryEntry{
Timestamp: time.Now(),
EventType: eventType,
Severity: severity,
Message: message,
HubStatus: hubStatus,
HubError: hubError,
}
n.histPos++
if n.histPos >= len(n.history) {
n.histPos = 0
n.histFull = true
}
}
// ── Backward compatibility ───────────────────────────────────────────
// notifyRequest is the JSON payload for the legacy /api/v1/notify endpoint.