feat: add controller update trigger + version checker (v0.1.8)
Hub now tracks controller_url from reports, periodically checks the Gitea registry for the latest controller image version, and shows a "Trigger Update" button on the customer detail page that proxies to the controller's self-update API endpoint using the shared API key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
-10
@@ -29,6 +29,7 @@ type CustomerSummary struct {
|
||||
ContainerRunning int
|
||||
BackupLastSnapshot *time.Time
|
||||
ReportJSON string
|
||||
ControllerURL string
|
||||
|
||||
// Computed fields (not stored)
|
||||
TimeSinceReport time.Duration
|
||||
@@ -98,7 +99,14 @@ func (s *Store) migrate() error {
|
||||
updated_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// v0.1.8: add controller_url column (idempotent — ignore error if already exists)
|
||||
s.db.Exec("ALTER TABLE reports ADD COLUMN controller_url TEXT")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationPrefs holds per-customer notification preferences.
|
||||
@@ -201,6 +209,7 @@ func (s *Store) SaveReport(customerID string, reportJSON []byte) error {
|
||||
// Parse denormalized fields from the JSON
|
||||
var parsed struct {
|
||||
ControllerVersion string `json:"controller_version"`
|
||||
ControllerURL string `json:"controller_url"`
|
||||
System struct {
|
||||
CPUPercent float64 `json:"cpu_percent"`
|
||||
MemoryPercent float64 `json:"memory_percent"`
|
||||
@@ -229,13 +238,13 @@ func (s *Store) SaveReport(customerID string, reportJSON []byte) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO reports (customer_id, report_json, health_status, cpu_percent,
|
||||
memory_percent, container_total, container_running,
|
||||
backup_last_snapshot, controller_version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
backup_last_snapshot, controller_version, controller_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
customerID, string(reportJSON),
|
||||
parsed.Health.Status, parsed.System.CPUPercent,
|
||||
parsed.System.MemoryPercent, parsed.Containers.Total,
|
||||
parsed.Containers.Running, backupSnapshot,
|
||||
parsed.ControllerVersion,
|
||||
parsed.ControllerVersion, parsed.ControllerURL,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@@ -246,7 +255,7 @@ func (s *Store) GetCustomers() ([]CustomerSummary, error) {
|
||||
SELECT r.customer_id, r.received_at, r.report_json,
|
||||
r.health_status, r.cpu_percent, r.memory_percent,
|
||||
r.container_total, r.container_running,
|
||||
r.backup_last_snapshot, r.controller_version
|
||||
r.backup_last_snapshot, r.controller_version, r.controller_url
|
||||
FROM reports r
|
||||
INNER JOIN (
|
||||
SELECT customer_id, MAX(received_at) as max_time
|
||||
@@ -265,11 +274,12 @@ func (s *Store) GetCustomers() ([]CustomerSummary, error) {
|
||||
var c CustomerSummary
|
||||
var receivedAt string
|
||||
var backupSnapshot sql.NullString
|
||||
var controllerURL sql.NullString
|
||||
|
||||
if err := rows.Scan(&c.CustomerID, &receivedAt, &c.ReportJSON,
|
||||
&c.HealthStatus, &c.CPUPercent, &c.MemoryPercent,
|
||||
&c.ContainerTotal, &c.ContainerRunning,
|
||||
&backupSnapshot, &c.ControllerVersion); err != nil {
|
||||
&backupSnapshot, &c.ControllerVersion, &controllerURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -282,6 +292,9 @@ func (s *Store) GetCustomers() ([]CustomerSummary, error) {
|
||||
c.BackupLastSnapshot = &t
|
||||
}
|
||||
}
|
||||
if controllerURL.Valid {
|
||||
c.ControllerURL = controllerURL.String
|
||||
}
|
||||
|
||||
// Parse customer_name from JSON
|
||||
var report struct {
|
||||
@@ -306,7 +319,7 @@ func (s *Store) GetCustomer(customerID string) (*CustomerSummary, error) {
|
||||
SELECT customer_id, received_at, report_json,
|
||||
health_status, cpu_percent, memory_percent,
|
||||
container_total, container_running,
|
||||
backup_last_snapshot, controller_version
|
||||
backup_last_snapshot, controller_version, controller_url
|
||||
FROM reports
|
||||
WHERE customer_id = ?
|
||||
ORDER BY received_at DESC
|
||||
@@ -315,11 +328,12 @@ func (s *Store) GetCustomer(customerID string) (*CustomerSummary, error) {
|
||||
var c CustomerSummary
|
||||
var receivedAt string
|
||||
var backupSnapshot sql.NullString
|
||||
var controllerURL sql.NullString
|
||||
|
||||
if err := row.Scan(&c.CustomerID, &receivedAt, &c.ReportJSON,
|
||||
&c.HealthStatus, &c.CPUPercent, &c.MemoryPercent,
|
||||
&c.ContainerTotal, &c.ContainerRunning,
|
||||
&backupSnapshot, &c.ControllerVersion); err != nil {
|
||||
&backupSnapshot, &c.ControllerVersion, &controllerURL); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -335,6 +349,9 @@ func (s *Store) GetCustomer(customerID string) (*CustomerSummary, error) {
|
||||
c.BackupLastSnapshot = &t
|
||||
}
|
||||
}
|
||||
if controllerURL.Valid {
|
||||
c.ControllerURL = controllerURL.String
|
||||
}
|
||||
|
||||
var report struct {
|
||||
CustomerName string `json:"customer_name"`
|
||||
@@ -355,7 +372,7 @@ func (s *Store) GetCustomerHistory(customerID string, since time.Duration) ([]Cu
|
||||
SELECT customer_id, received_at, report_json,
|
||||
health_status, cpu_percent, memory_percent,
|
||||
container_total, container_running,
|
||||
backup_last_snapshot, controller_version
|
||||
backup_last_snapshot, controller_version, controller_url
|
||||
FROM reports
|
||||
WHERE customer_id = ? AND received_at >= ?
|
||||
ORDER BY received_at DESC`, customerID, cutoff)
|
||||
@@ -369,11 +386,12 @@ func (s *Store) GetCustomerHistory(customerID string, since time.Duration) ([]Cu
|
||||
var c CustomerSummary
|
||||
var receivedAt string
|
||||
var backupSnapshot sql.NullString
|
||||
var controllerURL sql.NullString
|
||||
|
||||
if err := rows.Scan(&c.CustomerID, &receivedAt, &c.ReportJSON,
|
||||
&c.HealthStatus, &c.CPUPercent, &c.MemoryPercent,
|
||||
&c.ContainerTotal, &c.ContainerRunning,
|
||||
&backupSnapshot, &c.ControllerVersion); err != nil {
|
||||
&backupSnapshot, &c.ControllerVersion, &controllerURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -386,6 +404,9 @@ func (s *Store) GetCustomerHistory(customerID string, since time.Duration) ([]Cu
|
||||
c.BackupLastSnapshot = &t
|
||||
}
|
||||
}
|
||||
if controllerURL.Valid {
|
||||
c.ControllerURL = controllerURL.String
|
||||
}
|
||||
|
||||
history = append(history, c)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user