Add structured headers to the PHP pages layer

Documents each tab's purpose, what it renders, and which endpoints it polls.
Pages that start with markup get the header in a <?php ?> block so it never
reaches the browser.

Also corrects the layer diagram in README-unraid.md: eight of eleven pages hold
no PHP logic and poll api/ for everything — only auth, monitor and scheduler
require an include/ file directly.
This commit is contained in:
Gmer4Lfe
2026-08-02 00:51:41 -04:00
parent 0b4ff27be4
commit ccc6c742ee
12 changed files with 414 additions and 5 deletions
+10 -5
View File
@@ -85,14 +85,19 @@ templates, and the WebGUI install path.
## ━━━ THE WEB UI — THREE LAYERS ━━━
```
pages/*.php what you look at ── requires ──► include/
pages/*.php what you look at ── polls ─────► api/
api/*.php what the page fetches ── requires ──► include/
include/*.php the actual logic — vv_*() functions, shared by both
include/*.php the actual logic — vv_*() functions
```
The split matters: a page and its API endpoint call **the same** `vv_*()` builders, so a
cached response and a live one cannot disagree in shape. Adding a metric means adding it in
`include/` once, and both the page and the endpoint get it.
**Most pages hold no logic at all.** Eight of the eleven are pure view — markup, CSS and a
poll loop, with every value arriving as JSON from `api/`. Only `auth.php`, `monitor.php` and
`scheduler.php` require an `include/` file directly, and then only to server-render their
initial state; their mutations still go through endpoints.
That is why the layer split holds: a page and its endpoint cannot disagree about what a value
means, because the page does not compute it. Adding a metric means adding it in `include/`
once, and the endpoint, the cache writer, and the page all pick it up together.
| Layer | Files | Role |
|-------|-------|------|
+28
View File
@@ -1,3 +1,31 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Arrs tab. Library counts, queue state, root folders and recent cleanup/discovery activity
// for every Sonarr / Radarr / Lidarr instance across all nodes.
//
// DESIGN PRINCIPLES
// Pure view — markup plus a poll against api/arrs.php. Instance discovery, live fetching
// and stats parsing all live in include/arrs.php.
//
// Local nodes are live, remote nodes are cached. The page surfaces cache age rather than
// hiding it, so a 2-hour-old partner count is never presented as current.
//
// OPERATIONAL SAFEGUARDS
// Read-only with respect to media. Nothing here triggers a scan, import, or deletion —
// the arr cleanups are scheduled scripts, not page buttons.
//
// A node with no cache yet renders as "not collected", distinct from a node that genuinely
// has an empty library.
//
// RENDERS
// Per-node arr cards: tracked counts, queue, root folders, cleanup and discovery stats
// Inline conf editing for arr-related settings
//
// DEPENDS ON
// api/arrs.php polled every 60s → include/arrs.php
// api/confform.php inline conf edits → include/confform.php
?>
<style>
/* ── Cards ───────────────────────────────────────────────── */
.vv-arr-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:12px 14px;min-width:0; }
+44
View File
@@ -1,3 +1,47 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Auth tab. Manages the stack sitting in front of every protected hostname — NPM proxy
// hosts and certificates, LLDAP users and groups, and Authelia access-control rules.
//
// OPERATIONAL MODEL
// The most consequential page in the UI. A wrong edit here does not render badly, it locks
// people out of every service or exposes one that should be protected.
//
// DESIGN PRINCIPLES
// Requires include/auth.php directly rather than going through an endpoint for everything.
// Server-rendered for the initial state, with api/auth.php handling mutations. The
// credentials involved never reach the browser either way.
//
// Only the owner host edits auth config.
// Changes are made here and reach the partner through Critical-Data sync, not by the
// browser writing to two hosts. One source of truth, one direction of travel.
//
// Authelia rules are edited as a block, preserving the rest of the YAML untouched.
//
// OPERATIONAL SAFEGUARDS
// Every destructive action is confirmed — deleting a user, removing a proxy host, or
// rewriting the rule set are all one click away from locking out a household.
//
// Rule writes are atomic and refuse a missing config. include/auth.php writes to a temp
// file and renames; a missing configuration.yml is an error, never a freshly created file
// with a permissive default policy.
//
// Certificate state is read-only here. Renewal is owned by the cert monitor.
//
// All values render escaped — usernames, domains and rule fields are attacker-adjacent
// strings by definition on this page.
//
// RENDERS
// NPM proxy host list and editor, certificate status
// LLDAP user and group management
// Authelia access-control rules and default policy
//
// DEPENDS ON
// include/auth.php required directly for initial render
// api/auth.php mutations
// api/cert.php certificate status
?>
<style>
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
.vv-au-toolbar { display:flex;align-items:center;gap:8px;margin-bottom:12px;flex-wrap:wrap; }
+28
View File
@@ -1,3 +1,31 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Docker tab. Container inventory grouped into Varaverk-owned folders, with start / stop /
// restart controls and WebUI links.
//
// DESIGN PRINCIPLES
// Pure view — inventory and folder state come from api/docker.php; actions go to
// api/docker_action.php. Folder storage and the conf mirror live in include/docker.php.
//
// Folders are Varaverk's own, not folder.view3's. The grouping survives that plugin being
// absent or reset; it is synced to only when installed.
//
// OPERATIONAL SAFEGUARDS
// Destructive actions are confirmed in the browser before the request is sent.
//
// Container actions are routed through a dedicated action endpoint that validates the name
// against the real inventory — the page never composes a docker command.
//
// Container names are escaped on render, so a name containing markup cannot inject.
//
// RENDERS
// Folder-grouped container grid, per-container state, WebUI links, action buttons
//
// DEPENDS ON
// api/docker.php polled every 60s → include/docker.php
// api/docker_action.php start/stop/restart
?>
<style>
.vv-dk-toolbar { display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:0 2px;flex-wrap:wrap; }
.vv-dk-title { font-size:13px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;flex:1; }
+32
View File
@@ -1,3 +1,35 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Fallback tab. Current state of every node (NORMAL / FALLBACK / NO_INTERNET / DARK),
// which tiers have activated, handback strike progress, and which covered containers are
// actually running.
//
// DESIGN PRINCIPLES
// Pure view — markup plus a poll against api/fallback.php. State parsing lives in
// include/fallback.php.
//
// Reports what each node believes about itself. Remote state is read from the partner's own
// state file rather than inferred from what this host can reach.
//
// Polls faster than most tabs (30s) because this is the page someone opens during an
// outage, when stale numbers are actively misleading.
//
// OPERATIONAL SAFEGUARDS
// Read-only. The page cannot trigger a failover, force a handback, or start a covered
// container. Fallback is driven by fallback.sh reacting to real reachability, and a manual
// override from a browser is exactly the wrong way to enter that state.
//
// A missing state file renders as unknown, never as NORMAL — claiming healthy for a
// fallback process that is not running would be the worst possible error on this page.
//
// RENDERS
// Per-node state, tier activation and delays, handback strikes, covered container status
//
// DEPENDS ON
// api/fallback.php polled every 30s → include/fallback.php
// api/confform.php inline conf edits → include/confform.php
?>
<style>
/* ── Existing status styles ── */
.vv-fb-active { background:#1a1200;border:1px solid #5a3800;border-radius:6px;padding:12px 14px; }
+46
View File
@@ -1,4 +1,50 @@
<?php require_once dirname(__DIR__) . '/include/monitor.php'; ?>
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Monitor tab. The main dashboard — CPU per core, memory, GPUs, disks and pools, network,
// UPS, VMs, containers, transcodes, media sessions, and the watchdog health roll-up, for
// this host and every partner.
//
// DESIGN PRINCIPLES
// Two poll rates, deliberately split.
// api/monitor_fast.php carries the cheap, fast-moving values (12s); api/monitor.php
// carries the full payload on a slower cycle. Everything refreshing at the fast rate
// would put real load on the WebGUI this page exists to watch.
//
// Served from the tmpfs cache, not live calls.
// api_cache_writer.sh refreshes the payload every minute and the endpoint serves that.
// ?live=1 bypasses it. A missing cache always falls back to a live call, so the cache
// can never be why the dashboard fails to load.
//
// Missing subsystems simply do not render.
// No GPU, no UPS, no VMs — the corresponding card is absent rather than showing zeros
// or an error. The page is built to be correct on hardware lacking any given part.
//
// OPERATIONAL SAFEGUARDS
// The health roll-up must not default to healthy.
// vv_watchdog_summary() is conjunctive across every strike set, and it reads state from
// STATE_DIR. Six of those paths once pointed at /tmp, every read returned empty, and
// the page reported healthy unconditionally (fixed 2026-08-02). If this panel looks
// suspiciously green, verify the paths before believing it.
//
// Container actions are confirmed and routed through the action endpoint, which validates
// against real inventory.
//
// All remote and container-supplied strings render escaped.
//
// RENDERS
// System header, CPU per core, memory breakdown, GPU cards, storage pools and array disks,
// network, UPS, VMs, containers, transcode sessions, media now-playing, watchdog summary,
// partner node cards
//
// DEPENDS ON
// include/monitor.php required directly for initial render
// api/monitor.php full payload, slower cycle
// api/monitor_fast.php fast-moving values, 12s
// api/system.php system info
// api/media.php now-playing sessions
// api/docker_action.php container actions
// api/flag_toggle.php toggles
<style>
@keyframes vvRsPulse {
0%,100% { opacity:.5; transform:scaleX(.9); }
+38
View File
@@ -1,3 +1,41 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Partnership tab. The mesh view — which nodes exist, whether they are reachable over
// Tailscale, their system summary, sync history, and the onboarding controls that deploy
// stacks to a partner.
//
// DESIGN PRINCIPLES
// Pure view — all node data comes from api/partnership.php; actions are separate endpoints.
// Reachability, SSH and system collection live in include/partnership.php.
//
// Reachability is probed on demand as well as polled, because "is my partner up right now"
// is the question this page exists to answer.
//
// OPERATIONAL SAFEGUARDS
// Every action that changes a partner is confirmed first — this page can deploy stacks and
// provision credentials on a machine 50 miles away, and there is no undo for that.
//
// Long-running operations go through api/run.php rather than blocking the page, so a deploy
// that takes minutes does not leave the browser hanging on a request.
//
// An unreachable partner degrades to its own card only; the rest of the mesh still renders.
//
// Values from remote nodes are escaped on render — this page displays strings originating
// on another machine.
//
// RENDERS
// Node cards with identity, reachability, system summary, sync history
// Onboarding and stack-deploy controls, API key provisioning, partnership settings
//
// DEPENDS ON
// api/partnership.php polled every 10s → include/partnership.php
// api/partnership_ping.php on-demand reachability probe
// api/partnership_settings.php settings writes
// api/create_api_key.php credential provisioning
// api/run.php long-running deploy operations
// api/confform.php inline conf edits
?>
<style>
.vv-pt-grid { display:grid; gap:12px; }
.vv-pt-node { background:#161616; border:1px solid #2a2a2a; border-radius:6px; padding:12px; min-width:0; }
+39
View File
@@ -1,3 +1,42 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Rsync tab. Sync profiles, share lists, transfer progress, per-run history, and manual
// sync triggers.
//
// DESIGN PRINCIPLES
// Pure view — profile and status data from api/rsync.php, profile edits through
// api/rsync_profiles.php, manual runs through api/manual_sync.php.
//
// Progress polls fast (2s) while a transfer is live and falls back to the slow interval
// otherwise, so an idle page is not hammering the endpoint.
//
// Profiles are edited as structured data, not as raw rsync flags typed by hand.
//
// OPERATIONAL SAFEGUARDS
// Manual sync is confirmed before it fires. A sync moves real data between two machines and
// is not something to trigger with a stray click.
//
// The page shows the global RSYNC_ENABLED gate alongside the per-tier toggles, so a run
// that will be skipped by the gate is visibly skipped rather than silently doing nothing.
//
// Heavy escaping throughout — share paths, profile names and rsync output all render as
// text, never as markup.
//
// The page does not compose rsync flags. Deletion semantics in particular are owned by
// Rsync/rsync.sh, where the pass-1-must-complete interlock lives.
//
// RENDERS
// Profile editor, share lists, live transfer progress, run history, manual sync controls
//
// DEPENDS ON
// api/rsync.php status, polled 2s live / 30s idle
// api/rsync_profiles.php profile read and write
// api/manual_sync.php manual run trigger
// api/rsync_win_arrays.php share array editing
// api/flag_toggle.php enable/disable toggles
// api/confform.php inline conf edits
?>
<style>
.vv-ry-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:10px 12px;min-width:0; }
.vv-ry-sec { font-size:10px;font-weight:bold;color:#444;letter-spacing:.07em;text-transform:uppercase;margin-bottom:6px; }
+59
View File
@@ -1,4 +1,63 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Scheduler tab. The largest page in the plugin and the main operational surface: the full
// script library, per-job schedules, live run status and logs, dry-run output, conf editing
// per script, and the custom-script editor.
//
// OPERATIONAL MODEL
// Writes state that changes what the machine does on a timer. Enabling, disabling or
// rescheduling a job here takes effect on the next cron rebuild — this page decides what
// runs unattended at 1am.
//
// DESIGN PRINCIPLES
// Script descriptions come from the scripts themselves.
// Requires include/scheduler.php, which parses the PURPOSE block out of each bash
// header. The library listing and the script's own documentation are the same text and
// cannot drift. This page is the reason the repo-wide header convention exists.
//
// The job tree is derived from conf, not maintained here.
// Orchestrator job lists come from master.conf arrays; custom scripts are whatever
// *.sh sits in CUSTOM_SCRIPTS_DIR. Nothing has to be registered in two places.
//
// Long operations never block the page.
// Runs, dry-runs and imports go through api/run.php and api/dryrun.php with status
// polled separately, so a script that takes ten minutes does not hold a request open.
//
// OPERATIONAL SAFEGUARDS
// Running a script from the UI is confirmed first. Several of these delete files.
//
// Dry-run is offered alongside run for anything destructive, and its output is shown in
// full rather than summarised — reading the list is the point.
//
// Stale locks are cleared explicitly through api/clearlock.php, never automatically. A lock
// that looks stale may belong to a job still running.
//
// Conf edits are surgical, preserving the comment blocks that document every threshold.
//
// Custom scripts live outside the git repo, so a user's own scripts are never touched by a
// pull and never committed by accident.
//
// Script content and log output render escaped throughout — this page displays arbitrary
// file contents.
//
// RENDERS
// Job tree by orchestrator tier, per-job schedule and toggle controls, live status and
// logs, dry-run output, per-script conf forms, custom script editor, run history
//
// DEPENDS ON
// include/scheduler.php required directly — schedule, cron rebuild, script library
// api/scheduler.php schedule read/write api/run.php execute
// api/dryrun.php dry-run execute api/stop.php terminate
// api/status.php live run status api/log.php log tail
// api/script.php script read/write api/readscript.php source
// api/scriptinfo.php header metadata api/import_script.php
// api/conf_toggle.php per-script enable api/flag_toggle.php
// api/confform.php conf forms api/rawconf.php raw conf edit
// api/board.php overview board api/recent.php recent runs
// api/snapshot.php state snapshot api/clearlock.php stale lock clear
// api/savefolders.php folder grouping api/reorderarray.php
// api/rsync_standalone.php
require_once dirname(__DIR__) . '/include/scheduler.php';
// Setup mode — auto-open a conf file and force the editing sequence
+29
View File
@@ -1,4 +1,33 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Settings tab. Varaverk's own configuration — storage mode, global toggles, and the conf
// fields that are not specific to any one subsystem.
//
// DESIGN PRINCIPLES
// Edits go through the same conf machinery the scheduler page uses (include/confform.php),
// so a value changed here is written exactly the way a value changed anywhere else is —
// surgically, preserving comments and ordering.
//
// Storage mode is presented as a migration, not a setting. Changing it moves the whole
// installation and regenerates the cron, which is why it routes through api/storage.php
// rather than a plain conf write.
//
// OPERATIONAL SAFEGUARDS
// The storage-mode change is confirmed before it runs. It is the most destructive operation
// reachable from the UI — rsync --delete, rm -rf, and a rewrite of every derived path.
//
// Conf writes are surgical. A settings page that regenerated master.conf would destroy the
// comment blocks that document every threshold in it.
//
// Values are escaped on render and on write-back.
//
// RENDERS
// Storage mode selector, global toggles, general conf fields
//
// DEPENDS ON
// api/confform.php conf read/write → include/confform.php
// api/storage.php storage-mode migration
$_myHost = vv_detect_host();
$_vars = vv_conf_vars();
$_myId = strtoupper($_myHost);
+33
View File
@@ -1,4 +1,37 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Setup tab. First-run wizard — walks a fresh install through host identity, credentials,
// API key creation, storage mode, and the readiness checklist.
//
// DESIGN PRINCIPLES
// The checklist is derived, never stored. api/checklist.php re-evaluates real state on each
// load, so a step cannot be marked complete while the thing it checks is actually missing.
//
// Setup state is pushed to partners. Once a node knows who it is, that identity is shared
// through vv_push_setup_state() rather than typed twice.
//
// Detection over interrogation. Where a value can be read from a running service, setup
// reads it instead of asking — the same principle conf_populate.sh follows.
//
// OPERATIONAL SAFEGUARDS
// Existing values are not overwritten by detection. A field already filled in stays as it
// is; the wizard fills gaps rather than resetting a working install.
//
// Ambiguous detection is refused rather than guessed — a container prefix matching two
// containers is reported for manual resolution, never picked arbitrarily.
//
// Credentials entered here are written to host*.conf, which is gitignored. They are never
// committed and never leave the node except through the explicit partner push.
//
// RENDERS
// Step-by-step wizard, readiness checklist, credential entry, API key creation
//
// DEPENDS ON
// api/setup.php wizard state and writes
// api/checklist.php live readiness evaluation
// api/create_api_key.php unraid-api key provisioning
// api/storage.php storage-mode selection
// First-run setup wizard — uniform flow for all hosts.
// Step 1: auto-detect environment + server identity form.
// Step 2: auto-populate + guide + checklist.
+28
View File
@@ -1,3 +1,31 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Watchdog tab. Shows what every watchdog has recorded across all nodes — strike counts,
// action levels, paused and stopped containers, restart history, OOM and reboot counts.
//
// DESIGN PRINCIPLES
// Pure view. No PHP logic and no include/ dependency — the whole page is markup plus a
// poll against api/watchdog.php. All parsing lives in include/watchdog.php behind that
// endpoint, so the page cannot hold a second opinion about what a strike means.
//
// Shows every node, not just this one. A watchdog page that only covered the host you
// happened to open it on would miss exactly the node in trouble.
//
// OPERATIONAL SAFEGUARDS
// Strictly read-only. There is no control on this page that clears a strike, restarts a
// container, or resets a counter — those belong to the watchdog that owns the state.
//
// A node that fails to report renders as unavailable rather than healthy. Absence of data
// is never drawn as an all-clear.
//
// RENDERS
// Per-node cards: resource / docker / system / storage / network watchdog state,
// strike pills, restart history, reboot and OOM counts
//
// DEPENDS ON
// api/watchdog.php polled every 30s → include/watchdog.php
?>
<style>
.vv-wd-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:10px;min-width:0; }
.vv-wd-sec { font-size:10px;font-weight:bold;color:#444;letter-spacing:.07em;text-transform:uppercase;margin-bottom:6px; }