refactor: extract shared utilities and standardize page patterns
- Add fmtBytes() and csvToArr() helpers to hoover/helpers.js - Replace inline async patterns with ActionButton/ConfirmDelete in wireguard.js - Convert addDomain/editDomain to QuickModal + apiSubmit in proxy.js - Convert settingsModal handlers to formAction in certs.js - Remove redundant synced handling from dhcp.js apply button - Add onComplete callback to ConfirmDelete (fixes users.js onRefresh bug) - Fix passkeys.js ActionCell/Table usage (invalid component API) - Remove duplicate fmtBytes from dashboard.js
This commit is contained in:
+64
-70
@@ -1,5 +1,4 @@
|
||||
import { html, PageHeader, Badge, Empty, Table, renderGuardMulti, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, ActionButton, ActionCell, ConfirmDelete, certStatusBadge, ActionGroup, formAction } from '/static/hoover/index.js';
|
||||
import { openModal, formModal, closeModal } from '/static/hoover/components/modal.js';
|
||||
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';
|
||||
import { openBackendModal } from '/static/pages/backends.js';
|
||||
|
||||
function certLookup(acmeData) {
|
||||
@@ -82,48 +81,48 @@ function addDomain(state, preselectedBackend) {
|
||||
const backends = state.backends ? (state.backends.data || {}) : {};
|
||||
const certOptions = buildCertOptions(state.acme ? (state.acme.data.certs || []) : []);
|
||||
const backendOptions = buildBackendOptions(backends);
|
||||
openModal((inner) => {
|
||||
formModal(inner, 'Add Proxy Domain', [
|
||||
const modal = QuickModal({
|
||||
title: 'Add Proxy Domain',
|
||||
fields: [
|
||||
{ label: 'Domain', id: 'p-domain', placeholder: 'example.com' },
|
||||
{ label: 'Backend', id: 'p-backend', tag: 'select', options: backendOptions },
|
||||
{ label: 'Cert', id: 'p-cert', tag: 'select', options: certOptions },
|
||||
], [
|
||||
{ label: 'Cancel', cls: 'btn-outline', action: 'c', handler: () => closeModal() },
|
||||
{
|
||||
label: 'Add',
|
||||
cls: 'btn-primary',
|
||||
action: 's',
|
||||
handler: formAction(async () => {
|
||||
const domain = ($val('p-domain') || '').trim();
|
||||
if (!domain) throw 'Domain is required';
|
||||
const backend = ($val('p-backend') || '').trim();
|
||||
if (!backend) throw 'Backend is required';
|
||||
const body = { domain, backend, force_ssl: true };
|
||||
const certVal = certValueFromSelect($val('p-cert'));
|
||||
if (certVal) body.cert = certVal;
|
||||
const res = await apiFetch('/api/proxy/domains', { method: 'POST', body });
|
||||
if (!res.ok) throw res.error || 'Failed';
|
||||
toast('Domain added', 'success');
|
||||
closeModal();
|
||||
await Promise.all(['nginx', 'acme'].map(m => modelFetch(m)));
|
||||
}),
|
||||
],
|
||||
submit: {
|
||||
url: '/api/proxy/domains',
|
||||
body: () => {
|
||||
const body = {
|
||||
domain: ($val('p-domain') || '').trim(),
|
||||
backend: ($val('p-backend') || '').trim(),
|
||||
force_ssl: true,
|
||||
};
|
||||
const certVal = certValueFromSelect($val('p-cert'));
|
||||
if (certVal) body.cert = certVal;
|
||||
return body;
|
||||
},
|
||||
]);
|
||||
if (preselectedBackend) {
|
||||
const backendSelect = inner.querySelector('#p-backend');
|
||||
if (backendSelect) backendSelect.value = preselectedBackend;
|
||||
}
|
||||
const certSelect = inner.querySelector('#p-cert');
|
||||
const domainInput = inner.querySelector('#p-domain');
|
||||
if (certSelect && domainInput) {
|
||||
certSelect.addEventListener('change', () => {
|
||||
const val = certSelect.value;
|
||||
if (val && val.startsWith('acme|')) {
|
||||
domainInput.value = val.slice(5);
|
||||
}
|
||||
});
|
||||
}
|
||||
validate: (b) => !b.domain ? 'Domain is required' :
|
||||
!b.backend ? 'Backend is required' : null,
|
||||
successMsg: 'Domain added',
|
||||
},
|
||||
refresh: ['nginx', 'acme'],
|
||||
postRender: (inner) => {
|
||||
if (preselectedBackend) {
|
||||
const backendSelect = inner.querySelector('#p-backend');
|
||||
if (backendSelect) backendSelect.value = preselectedBackend;
|
||||
}
|
||||
const certSelect = inner.querySelector('#p-cert');
|
||||
const domainInput = inner.querySelector('#p-domain');
|
||||
if (certSelect && domainInput) {
|
||||
certSelect.addEventListener('change', () => {
|
||||
const val = certSelect.value;
|
||||
if (val && val.startsWith('acme|')) {
|
||||
domainInput.value = val.slice(5);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
modal({});
|
||||
}
|
||||
|
||||
function editDomain(d, state) {
|
||||
@@ -139,45 +138,40 @@ function editDomain(d, state) {
|
||||
selectedCert = d.cert;
|
||||
}
|
||||
const paths = backend.paths || {};
|
||||
const pathKeys = Object.keys(paths);
|
||||
const pathSummary = pathKeys.map(p => {
|
||||
const pcfg = paths[p];
|
||||
const pathSummary = Object.entries(paths).map(([p, pcfg]) => {
|
||||
const be = pcfg.backend || {};
|
||||
return `${esc(p)} → ${esc(be.host || '-')}:${be.port || '-'}`;
|
||||
}).join('\n') || '—';
|
||||
openModal((inner) => {
|
||||
formModal(inner, 'Edit: ' + esc(d.domain), [
|
||||
const modal = QuickModal({
|
||||
title: 'Edit: ' + esc(d.domain),
|
||||
fields: [
|
||||
{ label: 'Domain', id: 'pe-domain', value: d.domain },
|
||||
{ label: 'Backend', id: 'pe-backend', value: (d.backend_name || '-') + ' (' + (backend.label || '—') + ')' },
|
||||
{ label: 'Paths', id: 'pe-paths', tag: 'textarea', value: pathSummary, readonly: true },
|
||||
{ label: 'Paths', id: 'pe-paths', tag: 'textarea', value: pathSummary },
|
||||
{ label: 'Cert', id: 'pe-cert', tag: 'select', options: certOptions },
|
||||
{ label: 'Force SSL', id: 'pe-force-ssl', tag: 'checkbox', checked: d.force_ssl },
|
||||
], [
|
||||
{ label: 'Cancel', cls: 'btn-outline', action: 'c', handler: () => closeModal() },
|
||||
{
|
||||
label: 'Save',
|
||||
cls: 'btn-primary',
|
||||
action: 's',
|
||||
handler: formAction(async () => {
|
||||
const rawCert = $val('pe-cert');
|
||||
if (!rawCert) throw 'Cert is required';
|
||||
const forceSsl = document.getElementById('pe-force-ssl')?.checked ?? true;
|
||||
const body = { cert: certValueFromSelect(rawCert), force_ssl: forceSsl };
|
||||
const res = await apiFetch('/api/proxy/domains/' + enc(d.domain), { method: 'PUT', body });
|
||||
if (!res.ok) throw res.error || 'Failed';
|
||||
toast('Domain updated', 'success');
|
||||
closeModal();
|
||||
await Promise.all(['nginx', 'acme'].map(m => modelFetch(m)));
|
||||
}),
|
||||
},
|
||||
]);
|
||||
const certSelect = inner.querySelector('#pe-cert');
|
||||
if (certSelect) certSelect.value = selectedCert;
|
||||
const domainInput = inner.querySelector('#pe-domain');
|
||||
if (domainInput) { domainInput.readOnly = true; domainInput.style.background = '#f5f5f5'; }
|
||||
const backendInput = inner.querySelector('#pe-backend');
|
||||
if (backendInput) { backendInput.readOnly = true; backendInput.style.background = '#f5f5f5'; }
|
||||
],
|
||||
submit: {
|
||||
url: '/api/proxy/domains/' + enc(d.domain),
|
||||
method: 'PUT',
|
||||
body: () => ({
|
||||
cert: certValueFromSelect($val('pe-cert')) || '',
|
||||
force_ssl: document.getElementById('pe-force-ssl')?.checked ?? true,
|
||||
}),
|
||||
validate: (b) => !b.cert ? 'Cert is required' : null,
|
||||
successMsg: 'Domain updated',
|
||||
},
|
||||
refresh: ['nginx', 'acme'],
|
||||
postRender: (inner) => {
|
||||
const certSelect = inner.querySelector('#pe-cert');
|
||||
if (certSelect) certSelect.value = selectedCert;
|
||||
for (const id of ['pe-domain', 'pe-backend']) {
|
||||
const el = inner.querySelector('#' + id);
|
||||
if (el) { el.readOnly = true; el.style.background = '#f5f5f5'; }
|
||||
}
|
||||
},
|
||||
});
|
||||
modal({});
|
||||
}
|
||||
|
||||
function domainRow(domainName, domainPaths, state) {
|
||||
|
||||
Reference in New Issue
Block a user