fix: daemon /run spawn hardening, auth guard before first paint, WS refresh cap, interfaces runtime state

systemd: pre-create volatile /run paths so vacuum-walld's ProtectSystem=strict namespace setup cannot fail with 226/NAMESPACE — RuntimeDirectory=vacuum-wall nginx plus a tmpfiles.d spec (installed to /etc/tmpfiles.d/) covering /run/firewalld and /run/nginx.pid. Drop /run/sudo from ReadWritePaths: NOPASSWD children never need it, and its absence crash-looped restarts after sudo removed /run/sudo.

webui: run the auth session check before mounting the shell so logged-out visitors never flash the sidebar or a protected page; router guard and sidebar now react to auth state, and the login page renders full-bleed.

ws: cap refresh->reconnect episodes at 2 consecutive failures; if the WS path stays dead after a token refresh, abandon reconnection instead of looping refreshAuth forever (UI keeps working via REST until reload).

api: GET /api/network/interfaces now includes loopback and returns per-interface {config, runtime}; dashboard reads runtime.state (carrier counts as up) and the interfaces page filters lo client-side.

daemon: re-collect nginx state after lazy config migration (cached list went stale when the on-disk format changed under it), skip system_import.nginx when config.json already exists (re-parsing vacuum-wall's own generated sites is lossy), and poll nginx (60s) / acme (300s) state so file drift self-heals.
This commit is contained in:
2026-08-19 15:32:36 +00:00
parent 4bd4c374fd
commit 9c9f92ad04
16 changed files with 244 additions and 32 deletions
+67
View File
@@ -95,6 +95,11 @@ body {
min-width: 0;
}
/* Full-bleed main when the sidebar renders nothing (logged-out / login view) */
#sidebar:empty ~ .main {
margin-left: 0;
}
/* Cards */
.card {
background: var(--bg-secondary);
@@ -255,6 +260,68 @@ body {
min-height: 80px;
}
/* Login */
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
width: 100%;
max-width: 380px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
padding: 2.5rem 2rem;
}
.login-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent);
}
.login-subtitle {
color: var(--text-muted);
margin-bottom: 1.75rem;
}
.login-form .form-group {
margin-bottom: 1rem;
}
.login-error {
min-height: 1.25rem;
margin-bottom: 0.75rem;
color: var(--danger);
font-size: 0.85rem;
}
.login-divider {
display: flex;
align-items: center;
gap: 0.75rem;
margin: 1.5rem 0;
color: var(--text-muted);
font-size: 0.8rem;
}
.login-divider::before,
.login-divider::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.btn-login,
.btn-passkey {
width: 100%;
}
/* Badges */
.badge {
display: inline-block;