Files
FabledSteward/steward/migrations/versions/0032_retire_duplicate_widgets.py
bvandeusenandClaude Opus 5.5 5492336f1e
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 50s
CI / unit (push) Failing after 1m16s
CI / publish (push) Skipped
feat(dashboard): a curated default layout, merged duplicate widgets, a labelled Overview (#5213)
- New installs start from widgets.RECOMMENDED, packed row by row for the
  enabled plugins; the edit drawer can reset a dashboard to it.
- Host Agent — Resources folds into Hosts — Overview (which gains load 1m),
  UniFi — Devices into UniFi — Overview; migration 0032 moves existing
  dashboards onto the survivors.
- The Overview labels what it counts (hosts, monitors up/down/pending,
  alerts firing) and lists firing alerts and down monitors with since-times.
- Uptime polls every 5 minutes (poll_seconds) instead of every cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:42:04 -04:00

53 lines
2.2 KiB
Python

"""Merge duplicate dashboard widgets into the one that covers both (#5213)
Revision ID: 0032_retire_duplicate_widgets
Revises: 0031_ansible_run_origin
Create Date: 2026-10-07
Host Agent — Resources (host_resources) folds into Hosts — Overview
(hosts_overview), and UniFi — Devices (unifi_devices) into UniFi — Overview
(unifi_overview); widgets.RETIRED names the pairs. A dashboard that already
shows the surviving widget loses the retired placement; any other keeps its
cell, now showing the survivor. The old widget's own options don't apply to
the new one, so they are cleared.
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0032_retire_duplicate_widgets"
down_revision: Union[str, None] = "0031_ansible_run_origin"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
# Frozen here rather than imported: a migration keeps meaning what it meant.
_PAIRS = (("host_resources", "hosts_overview"), ("unifi_devices", "unifi_overview"))
def upgrade() -> None:
for old, new in _PAIRS:
op.execute(sa.text("""
DELETE FROM dashboard_widgets d
WHERE d.widget_key = :old
AND EXISTS (SELECT 1 FROM dashboard_widgets k
WHERE k.dashboard_id = d.dashboard_id AND k.widget_key = :new)
""").bindparams(old=old, new=new))
# Two placements of the old widget on one dashboard: keep the first.
op.execute(sa.text("""
DELETE FROM dashboard_widgets d
WHERE d.widget_key = :old
AND EXISTS (SELECT 1 FROM dashboard_widgets k
WHERE k.dashboard_id = d.dashboard_id AND k.widget_key = :old
AND k.id < d.id)
""").bindparams(old=old))
op.execute(sa.text("""
UPDATE dashboard_widgets SET widget_key = :new, config_json = NULL
WHERE widget_key = :old
""").bindparams(old=old, new=new))
def downgrade() -> None:
# The merge is one-way: which survivor cells were once the retired widget
# isn't recorded, and the retired widgets no longer exist to render.
pass