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:
@@ -37,6 +37,13 @@ export function StatusDot(props = {}) {
|
||||
return h('span', { class: `status-dot status-${v}` });
|
||||
}
|
||||
|
||||
/**
|
||||
* Small amber dot marking a pending (edited, not yet applied) element.
|
||||
*/
|
||||
export function PendingDot() {
|
||||
return h('span', { class: 'pending-dot' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty-state placeholder card.
|
||||
*
|
||||
@@ -55,16 +62,20 @@ export function Empty(props = {}) {
|
||||
* @param {object} props
|
||||
* @param {string} [props.header]
|
||||
* @param {VNode[]} [props.children]
|
||||
* @param {string} [props.cls] - Extra class appended to the outer `div.card`
|
||||
* @param {string} [props.title] - Tooltip on the outer `div.card`
|
||||
*/
|
||||
export function Card(props = {}) {
|
||||
const key = props.key !== undefined ? { key: props.key } : {};
|
||||
const cls = props.cls ? `card ${props.cls}` : 'card';
|
||||
const title = props.title ? { title: props.title } : {};
|
||||
if (props.header) {
|
||||
return h('div', { class: 'card', ...key },
|
||||
return h('div', { class: cls, ...title, ...key },
|
||||
h('div', { class: 'card-header' }, props.header),
|
||||
h('div', { class: 'card-body' }, props.children || []),
|
||||
);
|
||||
}
|
||||
return h('div', { class: 'card', ...key }, props.children || []);
|
||||
return h('div', { class: cls, ...title, ...key }, props.children || []);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,6 +220,8 @@ export function ActionButton(props = {}) {
|
||||
* @param {string} [props.emptyText] - Empty-state message
|
||||
* @param {boolean} [props.wrapCard] - Wrap in div.card (default: true)
|
||||
* @param {string} [props.key] - VNode key
|
||||
* @param {string} [props.cls] - Extra class appended to the wrapper (or div.card)
|
||||
* @param {string} [props.title] - Tooltip on the wrapper element
|
||||
*/
|
||||
export function Table(props = {}) {
|
||||
const cols = props.columns || [];
|
||||
@@ -225,10 +238,12 @@ export function Table(props = {}) {
|
||||
),
|
||||
);
|
||||
const key = props.key !== undefined ? { key: props.key } : {};
|
||||
const title = props.title ? { title: props.title } : {};
|
||||
const cls = props.cls ? `card ${props.cls}` : 'card';
|
||||
if (props.wrapCard !== false) {
|
||||
return h('div', { class: 'card', ...key }, table);
|
||||
return h('div', { class: cls, ...title, ...key }, table);
|
||||
}
|
||||
return h('div', key, table);
|
||||
return h('div', { ...title, ...key }, table);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user