← Back

Patch notes — admin consolidation & status sources (2026-06-10)

Branch: dev. Goal: fix the sidebar layout, consolidate the admin into 6 menus, improve the Host UX, add API-pull status sources, and (last) cross-type drag-and-drop ordering. No data is deleted; old routes stay as redirects. Decisions: separate api_sources table, keep manual status lines, cross-type ordering via a status_items table (final phase).

Target admin menu

Dashboard · Theme / Text · About Card · Status Widget · Notes · Working On (Footer, Footer text, About links, Containers, Host fold into the above; old routes 302-redirect.)


Phase 1 — Sidebar / layout width ✅

Problem: .board (homepage) and .wrap.has-rail (posts) used grid-template-columns: 1fr 300px; 1fr has an implicit min-width:auto, so wide post content (code blocks, tables, long strings, images) inflated the main column and pushed the right rail. Fix (CSS only): .boardminmax(0,1fr) 300px, added .board > :first-child { min-width: 0; } and .column { min-width: 0; } for containment. Rail stays fixed and consistent on all page types.

Phase 2 — Menu & page consolidation ✅

  • Theme / Text absorbs Footer text (tagline/copy) + Footer links.
  • About Card absorbs About links (image, name, bio, links/icons, add/remove/order).
  • Status Widget absorbs Containers + Hosts + Monitors + manual lines + widget name.
  • Old routes /admin/footer, /admin/footertext, /admin/aboutlinks, /admin/containers, /admin/host → 302 redirect to the new combined pages. POST handlers preserved per section.

Phase 3 — Host sources UX ✅

  • This server’s uptime: toggle switch (on/off) with plain-language explanation.
  • Reachability checks: list of ping targets with add/edit/delete, each with explanation.
  • Implementation: Custom host_fragment() with switch styling; no schema changes.

Phase 4 — API-pull status sources ✅

  • Implementation complete: api_sources + api_snapshots tables. Fields: name, url, method, headers/token (secret — stored in SQLite, never returned publicly), json_path, map_mode (value/up_when), expected, public, enabled, sort.
  • Collector.py: check_api_sources() fetches, extracts JSON via path, maps to up/down or value. Stores snapshots only (no tokens). Included in /api/collector/push payload as "apis".
  • Server.py: Snapshot handler stores results in api_snapshots (lijn 471-475). Sidebar API returns only name, map_mode, up, value, info, latency_ms — never credentials.
  • Admin UI: Status Widget now includes “API sources” list fragment. Add/edit/delete via form.

Phase 5 — Cross-type drag-and-drop ordering ✅

  • New status_items(kind, ref, sort) presentation table — purely an ordering layer; no data duplication. Covers: metric · monitor · host · api · container.
  • Auto-sync: sync_status_items() runs on every /api/sidebar request and every admin page load. Newly added items are appended at the end; deleted items are pruned. No manual wiring needed per CRUD path.
  • Migration/backfill: init_db.py backfills status_items from all 5 existing tables in their current per-type sort order (metrics → monitors → hosts → apis → containers). Runs idempotently; if first pass found empty tables (fresh install), the auto-sync seeds on first request.
  • Admin UI: New “Order” section at the top of the Status Widget page — draggable list of all items with kind badge (Manual/Monitor/Host/API/Container) and item name. Drag to reorder, click “Save order” → POST to /admin/statusorder.
  • Endpoint: POST /admin/statusorder (CSRF-protected) saves a JSON array of [kind, ref] pairs as the new sort values in status_items.
  • Public API: sidebar_payload() now builds status.items — a globally-ordered list where each item carries kind + render data (up/down for monitors, uptime for hosts, value/map_mode for APIs, state for containers, label/value/note for manual lines). Existing status.metrics, status.monitors, status.apis arrays are still present for backward-compatibility.
  • Frontend: sidebar-hydrate.js hydrates the Homelab Status widget from status.items in a single loop (no more hardcoded type-block order). Removed the “Containers” subheading — items now interleave freely. Monitor display mode (status/ok/ms/lamp) still respected.
  • CSS (admin): Order list styles (.orderlist, .orow, .ohandle, .okind, .oname, .orow.dragging). Also cleaned up now-unused .status-sub rule from editorial.css.
  • Admin dropdown (sidebar-hydrate.js): Updated ADMIN array to reflect the current 6-item consolidated menu (removed legacy Footer/About links/Containers/Host entries).

Database Schema

api_sources

Stores API endpoint config + credentials (admin-only).

CREATE TABLE api_sources (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,           -- Display name
  url TEXT NOT NULL,            -- API endpoint URL
  method TEXT DEFAULT 'GET',    -- GET or POST
  headers TEXT,                 -- Auth headers (one per line: "Key: Value")
  json_path TEXT,               -- Extract value via path (e.g., "data.status")
  map_mode TEXT DEFAULT 'value',-- 'value' = show value, 'up_when' = compare to expected
  expected TEXT,                -- Expected value for up_when mode
  public INTEGER DEFAULT 0,     -- Whether to expose in /api/sidebar
  enabled INTEGER DEFAULT 1,    -- Whether collector should fetch this
  sort INTEGER DEFAULT 0,       -- Order in Status Widget
  created_at INTEGER,
  updated_at INTEGER
);

api_snapshots

Stores latest results from each API source (collector writes only).

CREATE TABLE api_snapshots (
  source_id INTEGER PRIMARY KEY,
  up INTEGER,                   -- 1 = up, 0 = down
  value TEXT,                   -- Extracted JSON value (e.g., "42")
  info TEXT,                    -- Display string (e.g., "200 OK" or error msg)
  latency_ms INTEGER,           -- Response time in ms
  updated_at INTEGER
);

Collector Integration

Flow:

  1. Collector reads api_sources (read-only) where enabled=1
  2. For each source:
    • Fetch URL with headers (method GET/POST)
    • Parse JSON response
    • Extract value via json_path (e.g., data.status → object.data.status)
    • Determine up/down:
      • map_mode='value': return extracted value, mark up if HTTP < 400
      • map_mode='up_when': mark up if value == expected, otherwise down
  3. Push snapshot to /api/collector/push in payload {"apis": [...]}
  4. Server ingests snapshot into api_snapshots (line 471-475 in server.py)

Security:

  • Headers (tokens) never sent from collector to server or client
  • Only snapshots (up, value, info, latency) are stored + exposed
  • Public API endpoint /api/sidebar filters by api_sources.public=1

Admin Interface (Status Widget)

New “API sources” list fragment:

  • Name: Display name for the status widget
  • URL: Endpoint to fetch (e.g., https://api.example.com/status)
  • Method: GET or POST
  • Headers: Auth headers (one per line, e.g., Authorization: Bearer token...)
  • JSON path: Extract from response (e.g., data.online)
  • Map: Choose display mode:
    • value: Show extracted value (e.g., “42”); up if response < 400
    • up_when: Compare to Expected value; up if match
  • Expected: Value to compare against (only for up_when mode)
  • Public: Expose result in /api/sidebar (checked = yes)
  • Enabled: Include in collector run (checked = yes)
  • Sort: Order in Homelab Status widget

Example:

Name:           Proxmox nodes online
URL:            https://proxmox.local/api/nodes
Method:         GET
Headers:        Authorization: Bearer xyz...
JSON path:      nodes.0.status
Map:            up_when
Expected:       online
Public:         ✓ (checked)
Enabled:        ✓ (checked)
Sort:           10

Result in /api/sidebar.status.apis:

{
  "name": "Proxmox nodes online",
  "map_mode": "up_when",
  "up": 1,
  "value": "online",
  "info": "online",
  "latency_ms": 156
}

Testing Checklist

  • CSS sidebar layout: wide content doesn’t push rail

    • Add a post with large image (> 600px) or wide table
    • Verify right rail stays at 300px fixed width
    • Test on /posts/, single post, category page
  • Admin menu: 6 items only

    • Dashboard, Theme/Text, About Card, Status Widget, Notes, Working On
    • No separate Footer, About links, Containers, Host, Monitors menu items
  • Theme/Text page: all footer content on one page

    • Edit site title, hero text
    • Edit footer tagline + copy
    • Edit footer links
    • All forms submit correctly
  • About Card page: image + profile + links

    • Edit name, title, bio, image path
    • Add/edit/delete about links with icons
    • Icon legend visible
  • Status Widget page: all sources in one view

    • Widget settings (name, public text)
    • Containers add/edit/delete
    • Host sources: toggle for uptime, list for ping
    • Monitors add/edit/delete
    • Manual metrics add/edit/delete
    • API sources add/edit/delete ← new
  • Legacy redirects: /admin/footer → /admin/theme, etc.

    • Old bookmarks should 302-redirect
    • POST to old endpoints should redirect after save
  • Collector API pulling:

    • Add an API source with test endpoint (e.g., httpbin.org/json)
    • Set json_path (e.g., “slideshow.slides.0.title”)
    • Set public=1, enabled=1
    • Wait for collector cycle (default 30s)
    • Check /api/sidebar includes the API result
    • Verify no credentials leaked in response
  • Security: API credentials not exposed

    • Admin sees headers + token in form (can edit)
    • Public API (/api/sidebar) shows only name, up, value, info
    • No URLs, headers, tokens in any public endpoint

Backward Compatibility

No data loss:

  • Old /admin/footer bookmarks → 302 redirect to /admin/theme
  • Old /admin/aboutlinks/admin/about
  • Old /admin/containers, /admin/host, /admin/monitors/admin/status
  • POST handlers accept requests to old paths and redirect after save
  • All data preserved; only UI reorganized

Public API:

  • /api/sidebar includes new "apis" array (empty if no public API sources)
  • Existing clients ignore unknown keys; safe forward-compatible

Changelog

  • Phase 1 (sidebar): .boardminmax(0,1fr) 300px. .board > :first-child + .column min-width: 0 for containment. Rail no longer shifts on posts with wide content (images, tables, long code). Applies to homepage + all post pages.
  • Phase 2 (consolidation): admin menu down to 6 (Dashboard · Theme/Text · About Card · Status Widget · Notes · Working On). Theme/Text absorbs footer text + footer links; About Card absorbs about links + icon legend; Status Widget absorbs widget name + containers + host + monitors + manual lines. Every admin form carries an explicit action. Old routes (/admin/footer, /admin/footertext, /admin/aboutlinks, /admin/containers, /admin/host, /admin/monitors, /admin/metrics, /admin/apisources) 302-redirect to combined pages. No data deleted.
  • Phase 3 (host UX): Host sources UI: toggle switch for “This server’s uptime” (local_proc mode), separate list for reachability checks (ping mode). Plain-language explanations inline.
  • Phase 4 (API sources): New api_sources + api_snapshots tables. Collector pulls URLs (with auth headers), extracts JSON, stores snapshots. Sidebar API exposes snapshots only (never credentials). Status Widget admin includes “API sources” fragment for add/edit/delete. Public flag controls visibility in /api/sidebar. Up/down determination via json_path + map_mode (value or up_when comparison).
  • Phase 5 (cross-type order): status_items(kind,ref,sort) table provides one global order across all 5 item types. sync_status_items() keeps it in sync automatically (new items appended, deleted items pruned). Status Widget admin gains drag-and-drop “Order” section; saves via POST /admin/statusorder. sidebar_payload adds status.items combined list. sidebar-hydrate.js renders from the unified list. “Containers” subheading removed (items now interleave freely). Admin dropdown updated to 6-item consolidated menu.