import { h, PageHeader, Badge, Empty, ConfirmDelete, renderGuard, enc, esc, $val, definePage, getModel, MultiSelectModal, QuickModal } from '/static/hoover/index.js?v=7'; const addZone = QuickModal({ title: 'Add Zone', fields: [ { label: 'Zone name', id: 'zone-name', placeholder: 'e.g. internal' }, { label: 'Target', id: 'zone-target', placeholder: 'default (usually leave as default)' }, ], submit: { url: '/api/firewall/zones', body: () => ({ name: ($val('zone-name') || '').trim(), target: ($val('zone-target') || '').trim() || 'default' }), validate: (b) => !b.name ? 'Zone name required' : null, successMsg: 'Zone created', }, refresh: 'firewall', }); export default definePage({ init() { return { firewall: getModel('firewall'), }; }, render(state) { const guard = renderGuard(state.firewall, 'Zones', 'Firewall zones', state.firewall.data?.zones); if (guard) return guard; const zones = state.firewall.data?.zones?.available || []; const activeZones = state.firewall.data?.zones?.active || {}; const zoneDetails = {}; for (const name of zones) { const activeIfaces = activeZones[name]; zoneDetails[name] = { interfaces: Array.isArray(activeIfaces) ? activeIfaces : [] }; } const zoneCards = Object.entries(zoneDetails).map(([name, zdata]) => { const z = typeof zdata === 'object' ? zdata : {}; const ifacesArr = Array.isArray(z.interfaces) ? z.interfaces : []; const svcsArr = Array.isArray(z.services) ? z.services : []; return h('div', { class: 'card', key: name, style: 'position:relative;' }, h('div', { style: 'display:flex;justify-content:space-between;align-items:flex-start;' }, h('div', null, h('h3', { style: 'font-size:16px;color:var(--accent);' }, name), h('div', { class: 'text-muted text-sm', style: 'margin-bottom:10px;' }, z.target ? 'Target: ' + esc(z.target) : '', ), ), ), h('div', { class: 'text-sm mb-4' }, h('div', { class: 'text-muted', style: 'margin-bottom:4px;' }, 'Interfaces'), ifacesArr.length ? ifacesArr.map(i => Badge({ text: esc(i) })) : h('span', { class: 'text-muted' }, 'None'), ), h('div', { class: 'text-sm mb-4' }, h('div', { class: 'text-muted', style: 'margin-bottom:4px;' }, 'Services'), svcsArr.length ? svcsArr.map(s => Badge({ text: esc(s), variant: 'success' })) : h('span', { class: 'text-muted' }, 'None'), ), h('div', { style: 'display:flex;gap:6px;' }, h('button', { class: 'btn btn-sm btn-outline', 'on:click': () => MultiSelectModal({ title: 'Interfaces: ' + name, url: '/api/firewall/zones/' + enc(name) + '/interfaces', options: state.firewall.data?.interfaces || [], selected: ifacesArr, fieldKey: 'interfaces', successMsg: 'Interfaces updated', refresh: 'firewall', })(), }, 'Interfaces'), h('button', { class: 'btn btn-sm btn-outline', 'on:click': () => MultiSelectModal({ title: 'Services: ' + name, url: '/api/firewall/zones/' + enc(name) + '/services', options: state.firewall.data?.services || [], selected: svcsArr, fieldKey: 'services', successMsg: 'Services updated', refresh: 'firewall', })(), }, 'Services'), ConfirmDelete({ url: '/api/firewall/zones/' + enc(name), message: 'Delete zone ' + name + '?', success: 'Zone ' + name + ' deleted', refresh: 'firewall', label: 'Delete', }), ), ); }); return [ PageHeader({ title: 'Zones', subtitle: 'Firewall zones', actions: h('button', { class: 'btn btn-primary', 'on:click': () => addZone(), }, 'Add Zone'), }), zoneCards.length ? h('div', { class: 'card-grid' }, ...zoneCards) : Empty({ text: 'No zones configured. Add a zone to get started.' }), ]; }, });