Update status checks and add optgroup support to form modals

- modal.js: add optgroup support for select options in formModal
- dhcp.js: use zone-based interface selector for DHCP ranges
- dashboard.js, wireguard.js: use status.up for state checks
- proxy.js: use inner.querySelector for modal field lookup
This commit is contained in:
2026-06-28 12:47:59 +00:00
parent d8d8425340
commit 391466664e
5 changed files with 72 additions and 38 deletions
+3 -3
View File
@@ -26,7 +26,7 @@ export default definePage({
meta=${Object.keys(fwZones).join(', ') || 'None'} />
<${StatCard} label="Interfaces Up" value=${upC + '/' + nCount}
meta=${upI.map(i => i.name).join(', ') || 'None up'} />
<${StatCard} label="WireGuard" value=${String(d.wg?.state || 'unknown')}
<${StatCard} label="WireGuard" value=${String((d.wg?.status?.up) ? 'up' : 'down')}
meta=${wP.length + ' peers'} />
<${StatCard} label="Certificates" value=${certs.length}
meta=${certW.length + ' expiring/expired'} />
@@ -37,8 +37,8 @@ export default definePage({
<div class="card-header">Services</div>
<div class="card-body">
<ul class="service-list">
<li><${ServiceStatus} state=${dmsk.state || 'down'} label="Dnsmasq" /></li>
<li><${ServiceStatus} state=${d.wg?.state || 'down'} label="WireGuard" /></li>
<li><${ServiceStatus} state=${dmsk.service_active ? 'up' : 'down'} label="Dnsmasq" /></li>
<li><${ServiceStatus} state=${(d.wg?.status?.up) ? 'up' : 'down'} label="WireGuard" /></li>
</ul>
</div>
</div>
+40 -25
View File
@@ -1,26 +1,40 @@
import { html, PageHeader, Badge, ConfirmDelete, Tabs, Table, ServiceStatus, renderGuard, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, ActionButton, ActionGroup, QuickModal } from '/static/hoover/index.js?v=7';
import { html, PageHeader, ConfirmDelete, Tabs, Table, ServiceStatus, renderGuardMulti, esc, enc, $val, apiFetch, toast, definePage, getModel, ActionButton, ActionGroup, QuickModal } from '/static/hoover/index.js?v=7';
const addRange = QuickModal({
title: 'Add DHCP Range',
fields: [
{ label: 'Interface (optional)', id: 'r-iface', placeholder: 'Leave empty for global' },
{ label: 'Start IP', id: 'r-start', placeholder: '192.168.1.100' },
{ label: 'End IP', id: 'r-end', placeholder: '192.168.1.200' },
{ label: 'Lease Time', id: 'r-lease', placeholder: '12h' },
],
submit: {
url: '/api/dhcp/ranges',
body: () => ({
interface: ($val('r-iface') || '').trim() || undefined,
start: ($val('r-start') || '').trim(),
end: ($val('r-end') || '').trim(),
lease_time: ($val('r-lease') || '').trim() || '12h',
}),
validate: (b) => !b.start || !b.end ? 'Start and end are required' : null,
successMsg: 'Range added',
},
refresh: 'dnsmasq',
});
function makeAddRange(activeZones) {
const opts = [
['', '(global)'],
...Object.entries(activeZones || {})
.filter(([, ifaces]) => Array.isArray(ifaces) && ifaces.length > 0)
.flatMap(([zone, ifaces]) => {
const label = zone + ' \u2192 ';
if (ifaces.length === 1)
return [[ifaces[0], label + ifaces[0]]];
return [{ group: zone, options: ifaces.map(i => [i, label + i]) }];
}),
];
return QuickModal({
title: 'Add DHCP Range',
fields: [
{ label: 'Interface (optional)', id: 'r-iface', tag: 'select', options: opts },
{ label: 'Start IP', id: 'r-start', placeholder: '192.168.1.100' },
{ label: 'End IP', id: 'r-end', placeholder: '192.168.1.200' },
{ label: 'Lease Time', id: 'r-lease', placeholder: '12h' },
],
submit: {
url: '/api/dhcp/ranges',
body: () => ({
interface: ($val('r-iface') || '').trim() || undefined,
start: ($val('r-start') || '').trim(),
end: ($val('r-end') || '').trim(),
lease_time: ($val('r-lease') || '').trim() || '12h',
}),
validate: (b) => !b.start || !b.end ? 'Start and end are required' : null,
successMsg: 'Range added',
},
refresh: 'dnsmasq',
});
}
const addLease = QuickModal({
title: 'Add Static Lease',
@@ -61,11 +75,12 @@ export default definePage({
init() {
return {
dnsmasq: getModel('dnsmasq'),
firewall: getModel('firewall'),
activeTab: 'ranges',
};
},
render(state) {
const guard = renderGuard(state.dnsmasq, 'DHCP & DNS', 'Dnsmasq management', state.dnsmasq.data);
const guard = renderGuardMulti('DHCP & DNS', 'Dnsmasq management', state.dnsmasq, state.firewall);
if (guard) return guard;
const cfg = state.dnsmasq.data?.config || {};
@@ -116,7 +131,7 @@ export default definePage({
const tabNames = ['ranges', 'leases', 'dns', 'active'];
const actions = ActionGroup(
html`<button class="btn btn-primary" onClick=${() => addRange(state)}>Add Range</button>`,
html`<button class="btn btn-primary" onClick=${() => makeAddRange(state.firewall.data?.zones?.active)(state)}>Add Range</button>`,
html`<button class="btn btn-outline" onClick=${() => addLease(state)}>Static Lease</button>`,
html`<button class="btn btn-outline" onClick=${() => addDns(state)}>DNS Record</button>`,
ActionButton({
@@ -141,7 +156,7 @@ export default definePage({
return [
PageHeader({ title: 'DHCP & DNS', subtitle: 'Dnsmasq management', actions }),
ServiceStatus({ state: status.state || 'down', label: 'Dnsmasq' }),
ServiceStatus({ state: status.service_active ? 'up' : 'down', label: 'Dnsmasq' }),
Tabs({ state, tabs: tabNames }),
state.activeTab === 'ranges'
? Table({ columns: ['Interface', 'Start', 'End', 'Lease', 'Action'], rows: rangesRows, emptyText: 'No DHCP ranges' }) : null,
+2 -2
View File
@@ -98,8 +98,8 @@ function addDomain(state) {
},
]);
const certSelect = document.getElementById('p-cert');
const domainInput = document.getElementById('p-domain');
const certSelect = inner.querySelector('#p-cert');
const domainInput = inner.querySelector('#p-domain');
if (certSelect && domainInput) {
certSelect.addEventListener('change', () => {
const val = certSelect.value;
+3 -3
View File
@@ -61,7 +61,7 @@ export default definePage({
if (guard) return guard;
const st = state.wireguard.data?.status || {};
const isUp = st.state === 'up';
const isUp = st.up || false;
const listenPort = (state.wireguard.data?.config?.interface || {}).listen_port || '-';
const peerRows = (state.wireguard.data?.peers || []).map(p => {
@@ -107,10 +107,10 @@ export default definePage({
return [
PageHeader({
title: 'WireGuard',
subtitle: 'Tunnel: ' + (st.state || 'unknown') + ', Listen: ' + listenPort,
subtitle: 'Tunnel: ' + (st.up ? 'up' : 'down') + ', Listen: ' + listenPort,
actions,
}),
ServiceStatus({ state: st.state || 'down' }),
ServiceStatus({ state: st.up ? 'up' : 'down' }),
peerRows.length
? Table({
columns: ['Peer', 'Public Key', 'Allowed IPs', 'Endpoint', 'Handshake', 'Transfer', 'Actions'],