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): .board → minmax(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_snapshotstables. 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/pushpayload 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/sidebarrequest 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.pybackfillsstatus_itemsfrom 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 newsortvalues instatus_items. - Public API:
sidebar_payload()now buildsstatus.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). Existingstatus.metrics,status.monitors,status.apisarrays are still present for backward-compatibility. - Frontend:
sidebar-hydrate.jshydrates the Homelab Status widget fromstatus.itemsin 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-subrule fromeditorial.css. - Admin dropdown (sidebar-hydrate.js): Updated
ADMINarray 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:
- Collector reads
api_sources(read-only) whereenabled=1 - 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 < 400map_mode='up_when': mark up if value == expected, otherwise down
- Push snapshot to
/api/collector/pushin payload{"apis": [...]} - 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/sidebarfilters byapi_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 < 400up_when: Compare to Expected value; up if match
- Expected: Value to compare against (only for
up_whenmode) - 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/sidebarincludes 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/footerbookmarks → 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/sidebarincludes new"apis"array (empty if no public API sources)- Existing clients ignore unknown keys; safe forward-compatible
Changelog
- Phase 1 (sidebar):
.board→minmax(0,1fr) 300px..board > :first-child+.columnmin-width: 0for 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_snapshotstables. 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 viaPOST /admin/statusorder.sidebar_payloadaddsstatus.itemscombined list.sidebar-hydrate.jsrenders from the unified list. “Containers” subheading removed (items now interleave freely). Admin dropdown updated to 6-item consolidated menu.