Auth page: pure-PHP Authelia ACL parser, NPM-sourced certs tab, watchdog JS fixes, rsync settings layout

- Replace python3/PyYAML Authelia ACL parser with pure PHP (no deps available on Unraid)
- Cert tab now pulls live from NPM API instead of cert_monitor.sh — auto-discovers all managed certs sorted by urgency
- Watchdog page: add missing GB constant and _fmtBytes/_relTime functions that were causing silent render failure
- Rsync settings card: pin to far-right 3 columns (grid-column:6/-1), toggle grid narrowed to 2 columns
- Add CLAUDE.md project context file on /boot for session persistence across reboots
- claude_startup.sh: symlink CLAUDE.md into /root on array start
This commit is contained in:
Gmer4Lfe
2026-06-06 15:39:34 -04:00
parent 9c3ace95a7
commit bac0f7c535
7 changed files with 425 additions and 113 deletions
+27 -34
View File
@@ -214,14 +214,11 @@ $isOwner = vv_is_owner();
<div class="vv-au-panel" 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">Run now</button>
<button class="vv-au-btn prim" id="vv-au-cert-run">Refresh</button>
</div>
<div class="vv-au-cert-grid" id="vv-au-cert-grid">
<div class="vv-au-loading">Loading…</div>
</div>
<div id="vv-au-cert-log" style="display:none;margin-top:12px;background:#0d0d0d;border:1px solid #1e1e1e;
border-radius:4px;padding:10px 12px;font-size:10px;color:#555;font-family:monospace;
max-height:140px;overflow-y:auto;white-space:pre-wrap;"></div>
<div id="vv-au-cert-cfg" style="margin-top:10px;font-size:10px;color:#3a3a3a;"></div>
</div>
@@ -954,29 +951,31 @@ function _certRel(ts) {
return Math.floor(d/86400) + 'd ago';
}
function _renderCerts(data) {
function _renderNpmCerts(data) {
const grid = document.getElementById('vv-au-cert-grid');
const ts = document.getElementById('vv-au-cert-ts');
const cfg = document.getElementById('vv-au-cert-cfg');
const warn = data.warn_days || 30, crit = data.crit_days || 7;
const certs = data.certs || [];
ts.textContent = data.checked_at ? 'Last checked: ' + _certRel(data.checked_at) : 'Not yet checked';
ts.textContent = certs.length + ' certificate' + (certs.length !== 1 ? 's' : '') + ' · live from NPM';
cfg.textContent = `Warn: ${warn}d · Crit: ${crit}d`;
const domains = data.domains || [];
if (!domains.length) {
grid.innerHTML = '<div class="vv-au-empty">No domains configured — add HOST*_CERT_MONITOR_DOMAINS to host.conf</div>';
if (!certs.length) {
grid.innerHTML = '<div class="vv-au-empty">No certificates found in NPM</div>';
return;
}
grid.innerHTML = domains.map(d => {
const s = d.status || 'UNKN';
const days = d.days;
grid.innerHTML = certs.map(c => {
const s = c.status || 'UNKN';
const days = c.days;
const col = _certDayColor(days, warn, crit);
const barPct = days != null ? Math.min(Math.round(days/90*100), 100) : 0;
const expStr = d.expires ? 'Expires ' + d.expires : (s === 'UNKN' ? 'Not yet checked' : '');
const barPct = days != null ? Math.min(Math.round(days / 90 * 100), 100) : 0;
const expStr = c.expires ? 'Expires ' + c.expires : '';
const extra = (c.domain_names || []).filter(d => d !== c.nice_name).join(', ');
return `<div class="vv-au-card" style="padding:12px 14px;">
<div class="vv-au-domain">${_esc(d.domain)}</div>
<div class="vv-au-domain">${_esc(c.nice_name)}</div>
${extra ? `<div style="font-size:9px;color:#444;margin-bottom:2px;word-break:break-all;">${_esc(extra)}</div>` : ''}
<div class="vv-au-cert-days" style="color:${col}">${days != null ? days : '—'}</div>
<div style="font-size:9px;color:#444;margin-bottom:6px;">${days != null ? 'days remaining' : ''}</div>
<span class="vv-au-badge ${_certBadgeCls(s)}">${_certBadgeTxt(s)}</span>
@@ -986,32 +985,26 @@ function _renderCerts(data) {
}).join('');
}
function _loadCerts() {
function _loadCerts(onDone) {
const grid = document.getElementById('vv-au-cert-grid');
grid.innerHTML = '<div class="vv-au-loading">Loading…</div>';
fetch(CERT_API)
fetch(CERT_API + '?action=npm')
.then(r => r.json())
.then(d => { if (d.ok) _renderCerts(d); })
.catch(() => { grid.innerHTML = '<div class="vv-au-empty" style="color:#ef5350">Failed to load cert data</div>'; });
.then(d => {
if (d.ok) _renderNpmCerts(d);
else throw new Error(d.error || 'NPM error');
if (onDone) onDone();
})
.catch(e => {
grid.innerHTML = `<div class="vv-au-empty" style="color:#ef5350">${_esc(e.message || 'Failed to load')}</div>`;
if (onDone) onDone();
});
}
document.getElementById('vv-au-cert-run').addEventListener('click', function() {
const btn = this;
const log = document.getElementById('vv-au-cert-log');
btn.disabled = true; btn.textContent = 'Checking…';
log.style.display = 'none'; log.textContent = '';
const fd = new FormData(); fd.append('action', 'run');
fetch(CERT_API, { method: 'POST', body: fd })
.then(r => r.json())
.then(d => {
btn.disabled = false; btn.textContent = 'Run now';
if (d.data) _renderCerts(d.data);
if (d.output && d.output.length) {
log.style.display = 'block';
log.textContent = d.output.join('\n').replace(/\x1b\[[0-9;]*m/g, '');
}
})
.catch(() => { btn.disabled = false; btn.textContent = 'Run now'; });
btn.disabled = true; btn.textContent = 'Loading…';
_loadCerts(() => { btn.disabled = false; btn.textContent = 'Refresh'; });
});
// ── Boot ──────────────────────────────────────────────────────────────────────