Add AUTH_STACK so the Auth tab follows the stack in force
Authentik is the likely destination and the page had Authelia and lldap wired in at every level, so the seam goes in now: the panels and every endpoint action route off one conf value, and a stack that cannot be driven yet says so rather than drawing controls with nothing behind.
This commit is contained in:
@@ -37,6 +37,12 @@
|
||||
// LLDAP user and group management
|
||||
// Authelia access-control rules and default policy
|
||||
//
|
||||
// STACK SWITCHING
|
||||
// AUTH_STACK in master.conf decides which panels exist. Proxies and Certs belong to Nginx
|
||||
// Proxy Manager and are drawn for every stack; Users and Access Control are the identity
|
||||
// stack's and are drawn only for one that Varaverk can drive. api/auth.php applies the same
|
||||
// rule to every action, so a tab left open across a switch cannot write to the old stack.
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/auth.php required directly for initial render
|
||||
// api/auth.php mutations
|
||||
@@ -60,6 +66,17 @@ require_once dirname(__DIR__) . '/include/ai_chat.php';
|
||||
.vv-au-btn.green{ border-color:#1a3a1a;background:#0d1f0d;color:#4caf50; }
|
||||
.vv-au-btn:disabled { opacity:.4;cursor:default; }
|
||||
|
||||
/* ── Stack indicator ─────────────────────────────────────────────────────── */
|
||||
/* Which stack the tab is driving. Always visible, because every panel below it means something
|
||||
different depending on this, and it is a conf value nothing else on the page reveals. */
|
||||
.vv-au-stackchip{ font-size:10px;padding:2px 8px;border-radius:3px;background:#12181f;
|
||||
border:1px solid #1e2a38;color:#5c9fd4;white-space:nowrap; }
|
||||
.vv-au-stacknote{ font-size:11px;color:#997;background:#1a1400;border:1px solid #3a2d00;
|
||||
border-radius:4px;padding:8px 12px;margin-bottom:10px;line-height:1.55; }
|
||||
.vv-au-stacknote b { color:#ffb74d;display:block;margin-bottom:2px; }
|
||||
.vv-au-stacknote.bad { background:#1a0d0d;border-color:#3a1a1a;color:#a77; }
|
||||
.vv-au-stacknote.bad b { color:#ef5350; }
|
||||
|
||||
/* ── Panels ──────────────────────────────────────────────────────────────── */
|
||||
.vv-au-panel { display:none; }
|
||||
.vv-au-panel.active { display:block; }
|
||||
@@ -231,19 +248,45 @@ require_once dirname(__DIR__) . '/include/ai_chat.php';
|
||||
<?php
|
||||
require_once __DIR__ . '/../include/auth.php';
|
||||
$isOwner = vv_is_owner();
|
||||
// AUTH_STACK decides which panels exist at all. Read once here and used for both the tabs and the
|
||||
// panels, so a tab can never be drawn for a panel the stack does not carry.
|
||||
$stackDef= vv_auth_stack_def();
|
||||
$panels = $stackDef['panels'];
|
||||
$first = $panels[0] ?? 'proxies';
|
||||
$tabs = ['proxies' => 'Proxies', 'users' => 'Users & Groups',
|
||||
'acl' => 'Access Control', 'certs' => 'Certs'];
|
||||
?>
|
||||
|
||||
<div class="vv-au-toolbar">
|
||||
<span class="vv-au-title">Auth Stack</span>
|
||||
<button class="vv-au-tab active" data-tab="proxies">Proxies</button>
|
||||
<button class="vv-au-tab" data-tab="users">Users & Groups</button>
|
||||
<button class="vv-au-tab" data-tab="acl">Access Control</button>
|
||||
<button class="vv-au-tab" data-tab="certs">Certs</button>
|
||||
<?php foreach ($tabs as $id => $label): if (!in_array($id, $panels, true)) continue; ?>
|
||||
<button class="vv-au-tab<?= $id === $first ? ' active' : '' ?>" data-tab="<?= $id ?>"><?= $label ?></button>
|
||||
<?php endforeach; ?>
|
||||
<span class="vv-au-stackchip" title="AUTH_STACK in master.conf — Settings below"><?= htmlspecialchars($stackDef['label']) ?></span>
|
||||
<button class="vv-au-btn" id="vv-au-refresh" title="Refresh current tab">↻ Refresh</button>
|
||||
</div>
|
||||
|
||||
<?php if (!vv_auth_stack_valid()): ?>
|
||||
<!-- A value that is not one of the known stacks. Named rather than silently corrected, because
|
||||
the tab is now showing a different stack than the conf asks for and that is worth knowing. -->
|
||||
<div class="vv-au-stacknote bad">
|
||||
<b>AUTH_STACK is set to "<?= htmlspecialchars(trim(vv_conf_vars()['AUTH_STACK'] ?? '')) ?>", which is not a stack this page knows.</b>
|
||||
Showing <?= htmlspecialchars($stackDef['label']) ?> instead. Fix it in Auth settings, below.
|
||||
</div>
|
||||
<?php elseif (!$stackDef['ready']): ?>
|
||||
<!-- Selected but not implemented. The panels that do work are still drawn — proxy hosts and
|
||||
certificates belong to Nginx Proxy Manager, not to the identity stack — and the two that
|
||||
cannot are absent with the reason stated, rather than present and broken. -->
|
||||
<div class="vv-au-stacknote">
|
||||
<b><?= htmlspecialchars($stackDef['label']) ?> is selected, and Varaverk cannot manage it yet.</b>
|
||||
<?= htmlspecialchars($stackDef['summary']) ?>
|
||||
<span style="color:#4a4a4a">Needed: <?= htmlspecialchars($stackDef['needs'] ?? '') ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Proxies ─────────────────────────────────────────────────────────────── -->
|
||||
<div class="vv-au-panel active" id="vv-au-panel-proxies">
|
||||
<?php if (in_array("proxies",$panels,true)): ?>
|
||||
<div class="vv-au-panel<?= $first==="proxies" ? " active" : "" ?>" id="vv-au-panel-proxies">
|
||||
<div class="vv-au-sec-bar">
|
||||
<span class="vv-au-sec-title" id="vv-au-proxy-count"></span>
|
||||
<button class="vv-au-btn prim" id="vv-au-proxy-add">+ Add Proxy</button>
|
||||
@@ -259,9 +302,11 @@ $isOwner = vv_is_owner();
|
||||
<div class="vv-au-empty" id="vv-au-proxy-empty" style="display:none">No proxy hosts configured.</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Users & Groups ─────────────────────────────────────────────────────── -->
|
||||
<div class="vv-au-panel" id="vv-au-panel-users">
|
||||
<?php if (in_array("users",$panels,true)): ?>
|
||||
<div class="vv-au-panel<?= $first==="users" ? " active" : "" ?>" id="vv-au-panel-users">
|
||||
<div class="vv-au-ug-grid">
|
||||
|
||||
<div class="vv-au-card">
|
||||
@@ -286,9 +331,11 @@ $isOwner = vv_is_owner();
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Access Control ─────────────────────────────────────────────────────── -->
|
||||
<div class="vv-au-panel" id="vv-au-panel-acl">
|
||||
<?php if (in_array("acl",$panels,true)): ?>
|
||||
<div class="vv-au-panel<?= $first==="acl" ? " active" : "" ?>" id="vv-au-panel-acl">
|
||||
<div class="vv-au-ac-defpol" id="vv-au-ac-defpol-bar">
|
||||
<span>Default policy:</span>
|
||||
<select id="vv-au-ac-defpol" <?= $isOwner ? '' : 'disabled' ?>>
|
||||
@@ -315,9 +362,11 @@ $isOwner = vv_is_owner();
|
||||
<div class="vv-au-acl-grid" id="vv-au-acl-body" style="display:none"></div>
|
||||
<div class="vv-au-card"><div class="vv-au-empty" id="vv-au-acl-empty" style="display:none">No rules configured.</div></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Certs ───────────────────────────────────────────────────────────────── -->
|
||||
<div class="vv-au-panel" id="vv-au-panel-certs">
|
||||
<?php if (in_array("certs",$panels,true)): ?>
|
||||
<div class="vv-au-panel<?= $first==="certs" ? " active" : "" ?>" id="vv-au-panel-certs">
|
||||
<div class="vv-au-sec-bar">
|
||||
<span class="vv-au-sec-title" id="vv-au-cert-ts"></span>
|
||||
<button class="vv-au-btn prim" id="vv-au-cert-run">Refresh</button>
|
||||
@@ -327,6 +376,7 @@ $isOwner = vv_is_owner();
|
||||
</div>
|
||||
<div id="vv-au-cert-cfg" style="margin-top:10px;font-size:10px;color:#3a3a3a;"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Modal overlay ──────────────────────────────────────────────────────── -->
|
||||
<div class="vv-au-overlay" id="vv-au-overlay">
|
||||
@@ -368,7 +418,7 @@ $isOwner = vv_is_owner();
|
||||
//
|
||||
// Both passwords render masked and are logged by name only — vv_conf_key_is_secret() matches
|
||||
// PASS, so the same key cannot be redacted in the audit log and legible in the form.
|
||||
vv_conf_ui_card('vv-cf-auth', 'NginxProxyManager|lldap|Authelia|Certificate Monitor', 'Auth settings');
|
||||
vv_conf_ui_card('vv-cf-auth', 'Auth Stack|NginxProxyManager|lldap|Authelia|Certificate Monitor', 'Auth settings');
|
||||
?>
|
||||
|
||||
<script>
|
||||
@@ -386,7 +436,10 @@ let _rules = [], _defaultPolicy = 'deny';
|
||||
// The trailing comment on the default_policy line, carried so a save puts it back. The block is
|
||||
// rebuilt from this model, so anything not held here is deleted by the next save.
|
||||
let _defaultNote = '';
|
||||
let _activeTab = 'proxies';
|
||||
// Seeded from PHP rather than hardcoded to 'proxies'. Which panels exist is AUTH_STACK's decision,
|
||||
// and a stack whose first panel is not Proxies would boot with _activeTab naming a tab that is not
|
||||
// on the page — so Refresh would reload nothing and the first render would be empty.
|
||||
let _activeTab = <?= json_encode($first) ?>;
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
function _esc(s) {
|
||||
@@ -409,6 +462,16 @@ function _post(params, cb) {
|
||||
|
||||
// wide is for the one dialog that carries a photo beside a form. Reset on every call rather than
|
||||
// only set, or a narrow dialog opened after the editor would inherit its width.
|
||||
// Attach only if the element is there. AUTH_STACK decides which panels the page renders, so on a
|
||||
// stack that does not carry Users or Access Control those containers genuinely do not exist —
|
||||
// and a bare getElementById(...).addEventListener would throw on load and take every listener
|
||||
// after it down with it, including the ones for the panels that do exist.
|
||||
function _on(id, ev, fn) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.addEventListener(ev, fn);
|
||||
return !!el;
|
||||
}
|
||||
|
||||
function _modal(html, wide) {
|
||||
const m = document.getElementById('vv-au-modal');
|
||||
m.innerHTML = html;
|
||||
@@ -474,7 +537,7 @@ document.querySelectorAll('.vv-au-tab').forEach(btn => {
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('vv-au-refresh').addEventListener('click', () => _loadTab(_activeTab));
|
||||
_on('vv-au-refresh', 'click', () => _loadTab(_activeTab));
|
||||
|
||||
function _loadTab(tab) {
|
||||
if (tab === 'proxies') _loadProxies();
|
||||
@@ -641,7 +704,7 @@ function _showModalErr(id, msg) {
|
||||
}
|
||||
|
||||
// Proxy event delegation
|
||||
document.getElementById('vv-au-panel-proxies').addEventListener('click', async e => {
|
||||
_on('vv-au-panel-proxies', 'click', async e => {
|
||||
// Add button
|
||||
if (e.target.id === 'vv-au-proxy-add') { _proxyModal(null); return; }
|
||||
// Toggle
|
||||
@@ -1143,7 +1206,7 @@ function _addToGroupModal(uid) {
|
||||
}
|
||||
|
||||
// User event delegation
|
||||
document.getElementById('vv-au-panel-users').addEventListener('click', async e => {
|
||||
_on('vv-au-panel-users', 'click', async e => {
|
||||
if (e.target.id === 'vv-au-user-add') { _userModal(null); return; }
|
||||
|
||||
const editBtn = e.target.closest('[data-user-edit]');
|
||||
@@ -1215,7 +1278,7 @@ function _loadGroups(done) {
|
||||
}
|
||||
|
||||
// Group event delegation
|
||||
document.getElementById('vv-au-panel-users').addEventListener('click', async e => {
|
||||
_on('vv-au-panel-users', 'click', async e => {
|
||||
if (e.target.id === 'vv-au-group-add') {
|
||||
_modal(`<h3>Add Group</h3>
|
||||
<div class="vv-au-field">
|
||||
@@ -1519,7 +1582,7 @@ function _aclMarkDirty(on) {
|
||||
|
||||
// Writes straight into the model and deliberately does not re-render: the element being typed in
|
||||
// is inside the markup a render would replace, which would drop focus on the first keystroke.
|
||||
document.getElementById('vv-au-panel-acl').addEventListener('input', e => {
|
||||
_on('vv-au-panel-acl', 'input', e => {
|
||||
const inp = e.target.closest('.vv-au-dominput');
|
||||
if (!inp) return;
|
||||
const r = parseInt(inp.dataset.rule), d = parseInt(inp.dataset.dom);
|
||||
@@ -1532,7 +1595,7 @@ document.getElementById('vv-au-panel-acl').addEventListener('input', e => {
|
||||
|
||||
// Enter adds a row underneath and moves to it, so a list of fourteen can be typed straight
|
||||
// through instead of returning to the add button between each one.
|
||||
document.getElementById('vv-au-panel-acl').addEventListener('keydown', e => {
|
||||
_on('vv-au-panel-acl', 'keydown', e => {
|
||||
const inp = e.target.closest('.vv-au-dominput');
|
||||
if (!inp || e.key !== 'Enter') return;
|
||||
e.preventDefault();
|
||||
@@ -1550,7 +1613,7 @@ function _focusDomain(r, d) {
|
||||
}
|
||||
|
||||
// ACL event delegation
|
||||
document.getElementById('vv-au-panel-acl').addEventListener('click', async e => {
|
||||
_on('vv-au-panel-acl', 'click', async e => {
|
||||
if (e.target.id === 'vv-au-rule-add') { _ruleModal(null); return; }
|
||||
|
||||
const domAdd = e.target.closest('[data-dom-add]');
|
||||
@@ -1645,10 +1708,10 @@ document.getElementById('vv-au-panel-acl').addEventListener('click', async e =>
|
||||
|
||||
// The default policy is the one control here that is not a rule, and it is the most consequential
|
||||
// one on the tab — it decides what happens to every hostname no rule names.
|
||||
document.getElementById('vv-au-ac-defpol').addEventListener('change', () => _aclMarkDirty(true));
|
||||
_on('vv-au-ac-defpol', 'change', () => _aclMarkDirty(true));
|
||||
|
||||
// ── Close modal on overlay click ──────────────────────────────────────────────
|
||||
document.getElementById('vv-au-overlay').addEventListener('click', e => {
|
||||
_on('vv-au-overlay', 'click', e => {
|
||||
if (e.target === document.getElementById('vv-au-overlay')) _closeModal();
|
||||
});
|
||||
|
||||
@@ -1722,7 +1785,7 @@ function _loadCerts(onDone) {
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('vv-au-cert-run').addEventListener('click', function() {
|
||||
_on('vv-au-cert-run', 'click', function() {
|
||||
const btn = this;
|
||||
btn.disabled = true; btn.textContent = 'Loading…';
|
||||
_loadCerts(() => { btn.disabled = false; btn.textContent = 'Refresh'; });
|
||||
@@ -1750,7 +1813,9 @@ if (document.getElementById('vv-au-ai-chat')) {
|
||||
}
|
||||
|
||||
// ── Boot ──────────────────────────────────────────────────────────────────────
|
||||
_loadProxies();
|
||||
// Through _loadTab so the opening panel follows AUTH_STACK, rather than calling _loadProxies()
|
||||
// directly and reaching into elements a different stack does not render.
|
||||
_loadTab(_activeTab);
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -807,6 +807,7 @@ Saved into `master.conf`, which does not need to be opened by hand.
|
||||
|
||||
| Setting | Control | Where | What it does |
|
||||
|---|---|---|---|
|
||||
| `AI_OWNER_HOST` | a text box | in this section | Which node runs the model, the retrieval index and the bug store. Every other node borrows it over the mesh, so a box without a GPU still gets the assistant — it just does not get the AI tab. Defaults to host1 when unset or malformed: whoever builds the mesh is host1. |
|
||||
| `AI_ENABLED` | a switch | Settings tab | Fail-closed: anything other than the literal "true" means off. |
|
||||
| `AI_CONNECT_TIMEOUT` | a number box, in seconds | in this section | seconds — probe when resolving which node has Ollama |
|
||||
| `AI_REQUEST_TIMEOUT` | a number box, in seconds | in this section | seconds — must clear a cold model load |
|
||||
@@ -1139,6 +1140,19 @@ Saved into `master.conf`, which does not need to be opened by hand.
|
||||
|---|---|---|---|
|
||||
| `ARRAY_STOP_SCRIPTS` | a list, one entry per line | in this section | Scripts run by array_stopping.sh for a planned shutdown — stops everything cleanly in order. Run sequentially (foreground) — each must complete before the next starts. Order matters: user scripts first (prevents new ops), then data movement, then containers. |
|
||||
|
||||
## Auth Stack
|
||||
|
||||
Reachable from:
|
||||
|
||||
- Auth tab → Auth settings → *Auth Stack*
|
||||
- Settings tab → All settings → *Auth Stack*
|
||||
|
||||
Saved into `master.conf`, which does not need to be opened by hand.
|
||||
|
||||
| Setting | Control | Where | What it does |
|
||||
|---|---|---|---|
|
||||
| `AUTH_STACK` | a dropdown (authelia_lldap, authentik) | in this section | Mesh-wide rather than per host: auth is the one service the partnership treats as shared, owned by the mesh owner and consumed by everyone else, so two nodes disagreeing about which stack is in force would mean two different answers to "who is this person". "authelia_lldap" — Authelia for access rules, lldap for users and groups. Fully implemented. "authentik" — single stack for both. NOT IMPLE… |
|
||||
|
||||
## Backup Verify
|
||||
|
||||
Reachable from:
|
||||
@@ -1179,7 +1193,7 @@ Saved into `master.conf`, which does not need to be opened by hand.
|
||||
|
||||
| Setting | Control | Where | What it does |
|
||||
|---|---|---|---|
|
||||
| `BUG_REPORT_LOCAL_ENABLED` | a switch | in this section | LOCAL ON — reports go to your own Gitea (HOST1_BUG_REPORT_* in host1.conf) and stay there. They do NOT reach the Varaverk maintainer. LOCAL OFF — reports open a prefilled GitHub issue you submit under your own account. Nothing is ever transmitted automatically: the report is shown read-only and sending is a separate press. |
|
||||
| `BUG_REPORT_LOCAL_ENABLED` | a switch | in this section | LOCAL ON — reports go to your own Gitea (see HOSTN_BUG_REPORT_* in host*.conf) and stay there. They do NOT reach the Varaverk maintainer. Turn it on if you want your own backlog. LOCAL OFF — reports open a prefilled GitHub issue you submit under your own account. |
|
||||
| `BUG_REPORT_GITHUB_REPO` | a text box | in this section | — |
|
||||
|
||||
## Certificate Monitor
|
||||
@@ -1923,6 +1937,16 @@ Saved into `master.conf`, which does not need to be opened by hand.
|
||||
| `SYS_WATCHDOG_RUNAWAY_STRIKES` | a number box, in consecutive | in this section | consecutive cycles before warning |
|
||||
| `SYS_WATCHDOG_MDSTAT_ERROR_LIMIT` | a number box | in this section | new errors in one cycle before acting |
|
||||
|
||||
## Timed Mutes
|
||||
|
||||
Route: Settings tab → All settings → *Timed Mutes*
|
||||
|
||||
Saved into `master.conf`, which does not need to be opened by hand.
|
||||
|
||||
| Setting | Control | Where | What it does |
|
||||
|---|---|---|---|
|
||||
| `WATCHDOG_MUTE_MAX_HOURS` | a number box | in this section | A mute silences one container for a bounded time and then expires on its own. It exists because every other exemption here is permanent — SCAN_IGNORE is a conf edit, an intentional stop lasts until cleared — so a temporary problem gets a permanent workaround that nobody revisits. Capped so "temporary" is enforced rather than intended: a mute longer than this is refused. |
|
||||
|
||||
## Transcode Management
|
||||
|
||||
Reachable from:
|
||||
@@ -2090,5 +2114,5 @@ Do not describe a route — there is none, and that is the point.
|
||||
HOST1 and HOST2 are what detect_hosts() matches the local hostname against. Editing one here would change which machine this believes it is.
|
||||
- **SHARED HOST CONFIGURATION** (`master.conf`) — `DATA_DIR`, `DB_DIR`, `STATE_DIR`, `AI_DATA_DIR`, `CACHE_BACKUP_DIR`, `LOG_ARCHIVE_DIR`, `BACKUP_DIR`, `CONF_BACKUP_DIR`, `PERSISTENT_CONF_CACHE`, `ARR_CACHE_BACKUP_DIR`
|
||||
DATA_DIR is the single on-disk root — move it and the whole tree follows, including the state the running scripts are holding open.
|
||||
- **State Files** (`master.conf`) — `SYS_WATCHDOG_STATE_FILE`, `DOCKER_WATCHDOG_FAILED_FILE`, `DOCKER_WATCHDOG_INTENTIONAL_FILE`, `SYS_WATCHDOG_REBOOT_LOG`, `SYS_WATCHDOG_OOM_FILE`
|
||||
- **State Files** (`master.conf`) — `SYS_WATCHDOG_STATE_FILE`, `DOCKER_WATCHDOG_FAILED_FILE`, `DOCKER_WATCHDOG_INTENTIONAL_FILE`, `WATCHDOG_MUTE_FILE`, `SYS_WATCHDOG_REBOOT_LOG`, `SYS_WATCHDOG_OOM_FILE`
|
||||
Paths to live state databases, derived from STATE_DIR. Repointing one orphans the state a watchdog is mid-way through writing.
|
||||
|
||||
Reference in New Issue
Block a user