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:
@@ -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 ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user