v0.15.4: Hub disabled notification, PushOnce, ReportingDisabled field
This commit is contained in:
@@ -1,5 +1,15 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### What was just completed (2026-02-19 session 54)
|
||||||
|
- **v0.15.4 (controller) + hub v0.1.6 — Hub reporting improvements:**
|
||||||
|
|
||||||
|
**Controller:** When `hub.enabled: false` but URL+API key are configured, the controller now creates the `Pusher` and sends a one-time "disabled" notification on startup (`health.status = "disabled"`, `reporting_disabled: true`). This replaces the old behavior where a disabled controller was indistinguishable from a crashed node. Added `PushOnce()` method to `Pusher` (bypasses the `enabled` flag). Added `ReportingDisabled` field to the `Report` struct.
|
||||||
|
|
||||||
|
**Hub:** Added "disabled" status handling — when the latest report has `health_status = "disabled"`, the overall status is "disabled" (checked BEFORE the stale-time logic, so it stays "PAUSED" even after 30min+). Dashboard shows gray "PAUSED" badge. Customer detail shows "Reporting has been disabled on this node" with a hint to re-enable. Storage labels now shown (`label` field with fallback to `mount`). Report history timestamps now show date + time ("Feb 19 09:46" instead of "09:46:54"). New `.status-badge-disabled` CSS (neutral gray `#475569`).
|
||||||
|
|
||||||
|
**Files modified (controller):** `internal/report/types.go`, `internal/report/pusher.go`, `cmd/controller/main.go`
|
||||||
|
**Files modified (hub):** `hub/internal/web/server.go`, `hub/internal/web/templates/dashboard.html`, `hub/internal/web/templates/customer.html`, `hub/internal/web/templates/style.css`
|
||||||
|
|
||||||
### What was just completed (2026-02-19 session 53)
|
### What was just completed (2026-02-19 session 53)
|
||||||
- **v0.15.3 — Show all storage paths on dashboard + fix hub report:**
|
- **v0.15.3 — Show all storage paths on dashboard + fix hub report:**
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
|
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
|
||||||
|
|
||||||
Last updated: 2026-02-17 (session 35)
|
Last updated: 2026-02-19 (session 54)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ Last updated: 2026-02-17 (session 35)
|
|||||||
- Customer deployments use Docker Compose (not Kubernetes) for simplicity
|
- Customer deployments use Docker Compose (not Kubernetes) for simplicity
|
||||||
|
|
||||||
### felhom-controller (this repo)
|
### felhom-controller (this repo)
|
||||||
- **Version:** v0.11.9
|
- **Version:** v0.15.4
|
||||||
- **Phase 1:** ✅ COMPLETE — Stack Manager + Deploy Flow
|
- **Phase 1:** ✅ COMPLETE — Stack Manager + Deploy Flow
|
||||||
- **Phase 2:** ✅ COMPLETE — Monitoring & Health (scheduler, CPU/temp, healthchecks.io pings)
|
- **Phase 2:** ✅ COMPLETE — Monitoring & Health (scheduler, CPU/temp, healthchecks.io pings)
|
||||||
- **Phase 3:** ✅ COMPLETE — Backups (DB dumps, restic integration, manual trigger, **dedicated backup page**)
|
- **Phase 3:** ✅ COMPLETE — Backups (DB dumps, restic integration, manual trigger, **dedicated backup page**)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware.
|
A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware.
|
||||||
|
|
||||||
**Current version: v0.14.1**
|
**Current version: v0.15.4**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -247,17 +247,21 @@ func main() {
|
|||||||
|
|
||||||
// --- Central hub reporting ---
|
// --- Central hub reporting ---
|
||||||
var hubPusher *report.Pusher
|
var hubPusher *report.Pusher
|
||||||
if cfg.Hub.Enabled && cfg.Hub.URL != "" {
|
if cfg.Hub.URL != "" && cfg.Hub.APIKey != "" {
|
||||||
pushInterval, err := time.ParseDuration(cfg.Hub.PushInterval)
|
|
||||||
if err != nil {
|
|
||||||
pushInterval = 15 * time.Minute
|
|
||||||
}
|
|
||||||
hubPusher = report.NewPusher(&cfg.Hub, logger)
|
hubPusher = report.NewPusher(&cfg.Hub, logger)
|
||||||
sched.Every("hub-report", pushInterval, func(ctx context.Context) error {
|
if cfg.Hub.Enabled {
|
||||||
r := report.BuildReport(cfg, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths())
|
pushInterval, err := time.ParseDuration(cfg.Hub.PushInterval)
|
||||||
return hubPusher.Push(r)
|
if err != nil {
|
||||||
})
|
pushInterval = 15 * time.Minute
|
||||||
logger.Printf("[INFO] Hub reporting enabled (every %s to %s)", pushInterval, cfg.Hub.URL)
|
}
|
||||||
|
sched.Every("hub-report", pushInterval, func(ctx context.Context) error {
|
||||||
|
r := report.BuildReport(cfg, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths())
|
||||||
|
return hubPusher.Push(r)
|
||||||
|
})
|
||||||
|
logger.Printf("[INFO] Hub reporting enabled (every %s to %s)", pushInterval, cfg.Hub.URL)
|
||||||
|
} else {
|
||||||
|
logger.Printf("[INFO] Hub reporting disabled — will send disabled notification to %s", cfg.Hub.URL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sched.Start(ctx)
|
sched.Start(ctx)
|
||||||
@@ -284,11 +288,27 @@ func main() {
|
|||||||
|
|
||||||
// Hub report
|
// Hub report
|
||||||
if hubPusher != nil {
|
if hubPusher != nil {
|
||||||
r := report.BuildReport(cfg, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths())
|
if cfg.Hub.Enabled {
|
||||||
if err := hubPusher.Push(r); err != nil {
|
r := report.BuildReport(cfg, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths())
|
||||||
logger.Printf("[WARN] Startup hub report failed: %v", err)
|
if err := hubPusher.Push(r); err != nil {
|
||||||
|
logger.Printf("[WARN] Startup hub report failed: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Println("[INFO] Startup hub report sent")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Println("[INFO] Startup hub report sent")
|
// Send a minimal "disabled" notification so hub knows reporting is intentionally off
|
||||||
|
r := &report.Report{
|
||||||
|
Version: 1,
|
||||||
|
CustomerID: cfg.Customer.ID,
|
||||||
|
CustomerName: cfg.Customer.Name,
|
||||||
|
ControllerVersion: Version,
|
||||||
|
Timestamp: time.Now().UTC(),
|
||||||
|
ReportingDisabled: true,
|
||||||
|
Health: report.HealthReport{Status: "disabled", Issues: []string{}, Warnings: []string{}},
|
||||||
|
Stacks: report.StacksReport{Deployed: []string{}, Available: []string{}},
|
||||||
|
Containers: report.ContainerReport{List: []report.ContainerDetailReport{}},
|
||||||
|
}
|
||||||
|
hubPusher.PushOnce(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -84,3 +84,39 @@ func (p *Pusher) Push(report *Report) error {
|
|||||||
p.logger.Printf("[WARN] Hub report push failed after 3 attempts: %v", lastErr)
|
p.logger.Printf("[WARN] Hub report push failed after 3 attempts: %v", lastErr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PushOnce sends a single report regardless of the enabled flag.
|
||||||
|
// Used for one-time notifications (e.g., reporting-disabled on startup).
|
||||||
|
func (p *Pusher) PushOnce(report *Report) error {
|
||||||
|
if p.hubURL == "" || p.apiKey == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(report)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Printf("[WARN] Hub report marshal failed: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url := p.hubURL + "/api/v1/report"
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+p.apiKey)
|
||||||
|
|
||||||
|
resp, err := p.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Printf("[WARN] Hub disabled-notification failed: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
p.logger.Printf("[INFO] Hub disabled-notification sent (%d bytes)", len(data))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type Report struct {
|
|||||||
CustomerName string `json:"customer_name"`
|
CustomerName string `json:"customer_name"`
|
||||||
ControllerVersion string `json:"controller_version"`
|
ControllerVersion string `json:"controller_version"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
ReportingDisabled bool `json:"reporting_disabled,omitempty"`
|
||||||
System SystemReport `json:"system"`
|
System SystemReport `json:"system"`
|
||||||
Storage []StorageReport `json:"storage"`
|
Storage []StorageReport `json:"storage"`
|
||||||
Containers ContainerReport `json:"containers"`
|
Containers ContainerReport `json:"containers"`
|
||||||
|
|||||||
Reference in New Issue
Block a user