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, Table, renderGuardMulti, enc, $val, apiFetch, toast, definePage, getModel, StatusText, QuickModal, ZoneSelect } from '/static/hoover/index.js';
|
||||
import { html, PageHeader, Table, renderGuardMulti, enc, $val, apiFetch, toast, definePage, getModel, StatusText, QuickModal, ZoneSelect, PendingDot, dirtySet, dirtyInfo } from '/static/hoover/index.js';
|
||||
|
||||
async function changeZone(name, zone, state) {
|
||||
const r = await apiFetch('/api/firewall/zones/' + enc(zone) + '/interfaces', {
|
||||
@@ -45,6 +45,7 @@ export default definePage({
|
||||
const guard = renderGuardMulti('Interfaces', 'Network interface management', state.firewall, state.network);
|
||||
if (guard) return guard;
|
||||
|
||||
const set = dirtySet(state.network.data?.status);
|
||||
const fwZones = state.firewall.data?.zones || {};
|
||||
const netData = state.network.data?.interfaces || {};
|
||||
const zones = Object.keys(fwZones);
|
||||
@@ -72,9 +73,10 @@ export default definePage({
|
||||
};
|
||||
});
|
||||
|
||||
const rows = ifaces.map(iface =>
|
||||
html`<tr key=${iface.name}>
|
||||
<td><strong>${iface.name}</strong></td>
|
||||
const rows = ifaces.map(iface => {
|
||||
const info = dirtyInfo(set, 'interfaces.' + iface.name);
|
||||
return html`<tr key=${iface.name} class=${info.class || undefined} title=${info.title || undefined}>
|
||||
<td>${info.dirty ? PendingDot({}) : ''}<strong>${iface.name}</strong></td>
|
||||
<td class="text-muted">${String(iface.mac || 'N/A')}</td>
|
||||
<td>${(iface.ips || []).join(', ') || 'N/A'}</td>
|
||||
<td><${StatusText} status=${iface.state} /></td>
|
||||
@@ -84,8 +86,8 @@ export default definePage({
|
||||
<button class="btn btn-sm btn-outline" style="margin-left:8px"
|
||||
onClick=${() => cfgModalFn(iface)}>Config</button>
|
||||
</td>
|
||||
</tr>`
|
||||
);
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
return [
|
||||
PageHeader({ title: 'Interfaces', subtitle: 'Network interface management' }),
|
||||
|
||||
Reference in New Issue
Block a user