feat: auto-generate self-signed certs, hoover modal processing guard, refactor backends/proxy pages
This commit is contained in:
+67
-43
@@ -1,4 +1,5 @@
|
||||
import { html, PageHeader, Empty, Table, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, modalVNodes, refreshModals, definePage, getModel, modelFetch, ActionCell, certStatusBadge, poll } from '/static/hoover/index.js?v=8';
|
||||
import { html, PageHeader, Empty, Table, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, modalVNodes, refreshModals, definePage, getModel, modelFetch, ActionCell, certStatusBadge, poll, formAction } from '/static/hoover/index.js?v=9';
|
||||
import { isModalProcessing, setModalProcessing } from '/static/hoover/components/modal.js?v=9';
|
||||
|
||||
function _accountCard(account) {
|
||||
if (!account || !account.registered) {
|
||||
@@ -44,22 +45,19 @@ function registerAccountModal() {
|
||||
label: 'Register',
|
||||
cls: 'btn-primary',
|
||||
action: 'r',
|
||||
handler: async () => {
|
||||
handler: formAction(async () => {
|
||||
const email = ($val('reg-email') || '').trim();
|
||||
if (!email) { toast('Email is required', 'error'); return; }
|
||||
if (!email) throw 'Email is required';
|
||||
const server = document.getElementById('reg-server')?.value || 'letsencrypt';
|
||||
const resp = await apiFetch('/api/certs/account/register', {
|
||||
method: 'POST',
|
||||
body: { email, server },
|
||||
});
|
||||
if (resp.ok) {
|
||||
toast('ACME account registered', 'success');
|
||||
closeModal();
|
||||
modelFetch('acme');
|
||||
} else {
|
||||
toast(resp.error || 'Registration failed', 'error');
|
||||
}
|
||||
},
|
||||
if (!resp.ok) throw resp.error || 'Registration failed';
|
||||
toast('ACME account registered', 'success');
|
||||
closeModal();
|
||||
modelFetch('acme');
|
||||
}),
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -95,30 +93,44 @@ function settingsModal(account) {
|
||||
});
|
||||
|
||||
inner.querySelector('[data-action="set-save"]')?.addEventListener('click', async () => {
|
||||
const email = ($val('set-email') || '').trim();
|
||||
if (!email) { toast('Email is required', 'error'); return; }
|
||||
const resp = await apiFetch('/api/certs/email', {
|
||||
method: 'POST',
|
||||
body: { email },
|
||||
});
|
||||
if (resp.ok) {
|
||||
toast('Email updated', 'success');
|
||||
closeModal(idx);
|
||||
modelFetch('acme');
|
||||
} else {
|
||||
toast(resp.error || 'Failed', 'error');
|
||||
if (isModalProcessing()) return;
|
||||
setModalProcessing(true);
|
||||
try {
|
||||
const email = ($val('set-email') || '').trim();
|
||||
if (!email) { toast('Email is required', 'error'); return; }
|
||||
const resp = await apiFetch('/api/certs/email', {
|
||||
method: 'POST',
|
||||
body: { email },
|
||||
});
|
||||
if (resp.ok) {
|
||||
toast('Email updated', 'success');
|
||||
closeModal(idx);
|
||||
modelFetch('acme');
|
||||
} else {
|
||||
toast(resp.error || 'Failed', 'error');
|
||||
}
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
});
|
||||
|
||||
inner.querySelector('[data-action="set-deactivate"]')?.addEventListener('click', async () => {
|
||||
if (!confirm('Deactivate ACME account? You will need to register again to issue certificates.')) return;
|
||||
const resp = await apiFetch('/api/certs/account', { method: 'DELETE' });
|
||||
if (resp.ok) {
|
||||
toast('Account deactivated', 'success');
|
||||
closeModal(idx);
|
||||
modelFetch('acme');
|
||||
} else {
|
||||
toast(resp.error || 'Failed', 'error');
|
||||
if (isModalProcessing()) return;
|
||||
setModalProcessing(true);
|
||||
try {
|
||||
const resp = await apiFetch('/api/certs/account', { method: 'DELETE' });
|
||||
if (resp.ok) {
|
||||
toast('Account deactivated', 'success');
|
||||
closeModal(idx);
|
||||
modelFetch('acme');
|
||||
} else {
|
||||
toast(resp.error || 'Failed', 'error');
|
||||
}
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -215,9 +227,11 @@ function _bindIssueButtons(inner, modalIdx) {
|
||||
|
||||
inner.querySelector('[data-action="ic-validate"]')?.addEventListener('click', async () => {
|
||||
if (_currentIssueState.validating) return;
|
||||
if (isModalProcessing()) return;
|
||||
const s = _currentIssueState;
|
||||
const domain = ($val('ic-domain') || '').trim();
|
||||
if (!domain) { toast('Domain is required', 'error'); return; }
|
||||
setModalProcessing(true);
|
||||
s.validating = true;
|
||||
s.domain = domain;
|
||||
try {
|
||||
@@ -229,24 +243,33 @@ function _bindIssueButtons(inner, modalIdx) {
|
||||
refreshModals();
|
||||
} finally {
|
||||
s.validating = false;
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
});
|
||||
|
||||
inner.querySelector('[data-action="ic-issue"]')?.addEventListener('click', async () => {
|
||||
const body = { domain: _currentIssueState.domain };
|
||||
const issueResp = await apiFetch('/api/certs/issue/start', { method: 'POST', body });
|
||||
if (issueResp.ok) {
|
||||
const status = issueResp.data?.status;
|
||||
if (status === 'existing') {
|
||||
toast('Issuance already in progress for ' + _currentIssueState.domain, 'warning');
|
||||
if (isModalProcessing()) return;
|
||||
setModalProcessing(true);
|
||||
try {
|
||||
const body = { domain: _currentIssueState.domain };
|
||||
const issueResp = await apiFetch('/api/certs/issue/start', { method: 'POST', body });
|
||||
if (issueResp.ok) {
|
||||
const status = issueResp.data?.status;
|
||||
if (status === 'existing') {
|
||||
toast('Issuance already in progress for ' + _currentIssueState.domain, 'warning');
|
||||
} else {
|
||||
toast('Issuance started for ' + _currentIssueState.domain, 'success');
|
||||
}
|
||||
closeModal(modalIdx);
|
||||
const rid = issueResp.data?.request_id;
|
||||
if (rid) pollCertIssue(rid);
|
||||
} else {
|
||||
toast('Issuance started for ' + _currentIssueState.domain, 'success');
|
||||
toast(issueResp.error || 'Failed', 'error');
|
||||
}
|
||||
closeModal(modalIdx);
|
||||
const rid = issueResp.data?.request_id;
|
||||
if (rid) pollCertIssue(rid);
|
||||
} else {
|
||||
toast(issueResp.error || 'Failed', 'error');
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -295,7 +318,8 @@ export default definePage({
|
||||
removeUrl=${'/api/certs/' + enc(c.domain)}
|
||||
removeMessage=${'Remove certificate for ' + c.domain + '?'}
|
||||
removeSuccess="Certificate removed"
|
||||
removeRefresh="acme" />
|
||||
removeRefresh="acme"
|
||||
deleteKey=${c.domain} />
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user