diff --git a/.gitignore b/.gitignore index 9477fa0..aea62e0 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,15 @@ Plugin/dist/ *.swp *~ .vscode/ + +# ── Local-only plugin surfaces (per-installation, never pushed) ─────────────── +# Varaverk.page discovers pages/local/*.php and registers each as a tab; api/local/ holds their +# endpoints. Both are symlinks into a store outside this repo, so what they contain belongs to +# one installation and is not part of the project. The tracked loader is deliberately generic — +# it names no page — so the public mirror never learns what a given server runs here. +# +# No trailing slash on either pattern. These paths are symlinks, not directories, and git treats +# a symlink as a blob — a "dir/" pattern does not match one, so the entries sat untracked rather +# than ignored, which is the same near-miss the *.bak rule above documents. +Plugin/unraid/pages/local +Plugin/unraid/api/local diff --git a/Plugin/unraid/Varaverk.page b/Plugin/unraid/Varaverk.page index 812c8e7..6ae5bab 100644 --- a/Plugin/unraid/Varaverk.page +++ b/Plugin/unraid/Varaverk.page @@ -477,8 +477,42 @@ $validTabs = ['monitor', 'scheduler', 'watchdog', 'partnership', 'fallback', 'ar $_vv_ai = vv_ai_owner_ui_on(); if ($_vv_ai) $validTabs[] = 'ai'; +// ── Local pages ─────────────────────────────────────────────────────────────────────────────── +// pages/local/ is gitignored, so whatever is in it belongs to this installation alone and never +// reaches the public mirror. This loader is the tracked half: a generic extension point that +// knows nothing about what it is loading. +// +// It exists because the alternative — a tracked `if (file_exists(pages/thing.php))` per private +// page — puts the name and purpose of every private page into the public repo, which defeats the +// point of keeping the page out of it. +// +// Discovered rather than configured: a conf key listing local pages would itself be a tracked +// file naming them, and an untracked one would be a second thing to keep in sync with the +// directory. The directory is the declaration. +// +// The label comes from a `// vv-local-page: Name` line in the first 2KB of the file, falling back +// to the capitalised id. Reading it out of the file keeps the page self-describing — nothing +// outside it has to be edited to add one. +$localPages = []; +foreach (glob("$pluginDir/pages/local/*.php") ?: [] as $_lp) { + $_id = basename($_lp, '.php'); + // Ids are restricted and collisions rejected: $tab is user input that becomes an include + // path below, and a local page must never be able to shadow a real tab. + if (!preg_match('/^[a-z0-9][a-z0-9_-]{0,31}$/', $_id)) continue; + if (in_array($_id, $validTabs, true)) continue; + $_lbl = ucfirst($_id); + if (preg_match('/^\s*(?:\/\/|#)\s*vv-local-page:\s*(.+)$/m', (string)@file_get_contents($_lp, false, null, 0, 2048), $_m)) { + $_lbl = trim($_m[1]); + } + $localPages[$_id] = ['path' => $_lp, 'label' => $_lbl]; + $validTabs[] = $_id; +} +unset($_lp, $_id, $_lbl, $_m); + if (!in_array($tab, $validTabs)) $tab = 'monitor'; $tabLabels = ['monitor' => 'Monitor', 'scheduler' => 'Scheduler', 'watchdog' => 'Watchdog', 'partnership' => 'Partnership', 'fallback' => 'FallBack', 'arrs' => 'Media Stack', 'rsync' => 'Rsync', 'auth' => 'Auth Stack', 'settings' => 'Settings', 'ai' => 'AI']; +foreach ($localPages as $_id => $_lp) $tabLabels[$_id] = $_lp['label']; +unset($_id, $_lp); // Cache stamp for the stylesheet and script below. Both are served straight off the plugin // directory at a path that never changes, so a browser holding an old copy keeps using it after @@ -521,9 +555,12 @@ foreach (['css/varaverk.css', 'js/varaverk.js'] as $_vv_a) {
Page not found: $tab

"; + else echo "

Page not found: " . htmlspecialchars($tab, ENT_QUOTES) . "

"; ?>