ui: amber pending-edit markers for unapplied config changes
Add hoover/dirty.js: line-matching helpers that flag UI rows/cards edited (saved to config) but not yet applied, consuming the pending state the daemon already streams — status.pending_diff for hash subsystems, firewall pending zone+type for firewalld. Visual language is amber (.config-dirty + PendingDot), distinct from the red .pending-delete; orphanInfo surfaces removed entries (e.g. WireGuard peers) on their container table. Wired into the backends, dhcp, interfaces, nat, proxy, rules, wireguard, and zones pages; Card and Table gain cls/title props. Covered by 27 node tests (tests/test-dirty.js).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { html, PageHeader, Badge, Empty, ConfirmDelete, renderGuard, enc, esc, $val, definePage, getModel, MultiSelectModal, QuickModal } from '/static/hoover/index.js';
|
||||
import { html, PageHeader, Badge, Empty, ConfirmDelete, renderGuard, enc, esc, $val, definePage, getModel, MultiSelectModal, QuickModal, PendingDot, fwDirty, fwInfo, fwTitle } from '/static/hoover/index.js';
|
||||
|
||||
// Services shown by default in the service picker. Everything else is only
|
||||
// visible with the "Show all options" toggle (or while it is already
|
||||
@@ -33,6 +33,8 @@ export default definePage({
|
||||
const guard = renderGuard(state.firewall, 'Zones', 'Firewall zones', state.firewall.data?.zones);
|
||||
if (guard) return guard;
|
||||
|
||||
const fw = fwDirty(state.firewall.data?.pending);
|
||||
|
||||
// Live zone data (parsed `--list-all-zones`): carries interfaces,
|
||||
// services, target, and masquerade for every defined zone.
|
||||
const liveZones = state.firewall.data?.zones || {};
|
||||
@@ -45,22 +47,26 @@ export default definePage({
|
||||
const z = typeof zdata === 'object' ? zdata : {};
|
||||
const ifacesArr = Array.isArray(z.interfaces) ? z.interfaces : [];
|
||||
const svcsArr = Array.isArray(z.services) ? z.services : [];
|
||||
return html`<div class="card" key=${name} style="position:relative">
|
||||
const info = fwInfo(fw, name);
|
||||
const ifTitle = fwTitle(fw, name, 'interfaces');
|
||||
const svcTitle = fwTitle(fw, name, 'services');
|
||||
const tgtTitle = fwTitle(fw, name, 'target');
|
||||
return html`<div class="card ${info.class}" title=${info.title || undefined} key=${name} style="position:relative">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start">
|
||||
<div>
|
||||
<h3 style="font-size:16px;color:var(--accent)">${name}</h3>
|
||||
<div class="text-muted text-sm" style="margin-bottom:10px">
|
||||
<h3 style="font-size:16px;color:var(--accent)">${info.dirty ? PendingDot({}) : ''}${name}</h3>
|
||||
<div class="text-muted text-sm" title=${tgtTitle || undefined} style="margin-bottom:10px">
|
||||
${z.target ? 'Target: ' + esc(z.target) : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm mb-4">
|
||||
<div class="text-sm mb-4" title=${ifTitle || undefined}>
|
||||
<div class="text-muted" style="margin-bottom:4px">Interfaces</div>
|
||||
${ifacesArr.length
|
||||
? ifacesArr.map(i => html`<${Badge} text=${esc(i)} />`)
|
||||
: html`<span class="text-muted">None</span>`}
|
||||
</div>
|
||||
<div class="text-sm mb-4">
|
||||
<div class="text-sm mb-4" title=${svcTitle || undefined}>
|
||||
<div class="text-muted" style="margin-bottom:4px">Services</div>
|
||||
${svcsArr.length
|
||||
? svcsArr.map(s => html`<${Badge} text=${esc(s)} variant="success" />`)
|
||||
|
||||
Reference in New Issue
Block a user