import { html, PageHeader, Badge, Empty, Table, renderGuardMulti, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, ActionButton, ActionCell, ConfirmDelete, certStatusBadge, ActionGroup, QuickModal } from '/static/hoover/index.js?v=7'; // --------------------------------------------------------------------------- // Cert lookup map from ACME state keyed by domain name // --------------------------------------------------------------------------- function certLookup(acmeData) { const m = {}; if (acmeData && acmeData.certs) { for (const c of acmeData.certs) { m[c.domain] = c; } } return m; } // --------------------------------------------------------------------------- // Add Domain modal — paths-based body // --------------------------------------------------------------------------- const addDomain = QuickModal({ title: 'Add Proxy Domain', fields: [ { label: 'Domain', id: 'p-domain', placeholder: 'example.com' }, { label: 'Path', id: 'p-path', placeholder: '/' }, { label: 'Backend Host', id: 'p-host', placeholder: '192.168.1.10' }, { label: 'Backend Port', id: 'p-port', type: 'number', placeholder: '8080' }, { label: 'Protocol', id: 'p-proto', placeholder: 'http or https' }, { label: 'Cert (optional)', id: 'p-cert', placeholder: 'acme' }, ], submit: { url: '/api/proxy/domains', body: () => { const path = ($val('p-path') || '/').trim() || '/'; return { domain: ($val('p-domain') || '').trim(), paths: { [path]: { backend: { host: ($val('p-host') || '').trim(), port: parseInt($val('p-port')), proto: ($val('p-proto') || 'http').trim() || 'http', }, headers: {}, }, }, cert: ($val('p-cert') || '').trim() || undefined, }; }, validate: (b) => { if (!b.domain) return 'Domain is required'; const p = b.paths ? Object.values(b.paths)[0] : {}; const be = p && p.backend; if (!be || !be.host || !be.port) return 'Host and port are required'; return null; }, successMsg: 'Domain added', }, refresh: ['nginx', 'acme'], }); // --------------------------------------------------------------------------- // Edit Domain modal — updates backend for the root path // --------------------------------------------------------------------------- const editDomain = QuickModal({ title: (d) => 'Edit: ' + d.domain + ' ' + d.path, fields: (d) => { const be = d.backend || {}; return [ { label: 'Backend Host', id: 'pe-host', value: be.host || '' }, { label: 'Backend Port', id: 'pe-port', type: 'number', value: be.port || '' }, { label: 'Protocol', id: 'pe-proto', value: be.proto || 'http' }, { label: 'Cert (optional)', id: 'pe-cert', value: d._cert || d.cert || '' }, ]; }, submit: { url: (d) => '/api/proxy/domains/' + enc(d.domain), method: 'PUT', body: (d) => ({ backend: { host: ($val('pe-host') || '').trim(), port: parseInt($val('pe-port')), proto: ($val('pe-proto') || 'http').trim(), }, cert: ($val('pe-cert') || '').trim() || undefined, }), validate: (b) => !b.backend || !b.backend.host || !b.backend.port ? 'Host and port are required' : null, successMsg: 'Domain updated', }, refresh: ['nginx', 'acme'], }); // --------------------------------------------------------------------------- // Path detail row // --------------------------------------------------------------------------- function pathRow(d, certs, domainPaths) { const be = d.backend || {}; const cert = certs[d.domain]; const certBadge = cert ? certStatusBadge({ daysRemaining: cert.days_remaining, expired: cert.expired, }) : Badge({ text: '—', variant: 'info' }); const isWs = d.is_websocket; const isMgmt = d.is_management; const multiPath = (domainPaths || []).length > 1; let actions; if (isMgmt) { actions = Badge({ text: 'mgmt', variant: 'warning' }); } else if (isWs) { actions = ActionButton({ url: '/api/proxy/domains/' + enc(d.domain), method: 'PUT', body: () => ({ path: d.path }), label: 'Delete', cls: 'btn btn-sm btn-danger', successMsg: 'Path removed', refresh: ['nginx', 'acme'], }); } else { actions = ActionCell({ editLabel: 'Edit', editClick: () => editDomain(d), removeUrl: '/api/proxy/domains/' + enc(d.domain), removeMessage: 'Remove ' + enc(d.domain) + ' ' + enc(d.path) + '?', removeSuccess: 'Removed', removeRefresh: ['nginx', 'acme'], removeLabel: 'Delete', }); } const flagBadges = []; if (isWs) flagBadges.push(Badge({ text: 'ws', variant: 'secondary' })); if (isMgmt) flagBadges.push(Badge({ text: 'mgmt', variant: 'warning' })); return html`
${esc(d.path)}