feat: auto-generate self-signed certs, hoover modal processing guard, refactor backends/proxy pages
This commit is contained in:
+59
-21
@@ -3,10 +3,12 @@
|
||||
*
|
||||
* JSON-friendly fetch wrapper with automatic header management.
|
||||
* Toast notification system with auto-dismiss.
|
||||
* Modal processing guard for async form submissions.
|
||||
*/
|
||||
|
||||
import { modelFetch } from './model.js?v=8';
|
||||
import { requestUpdate } from './reactivity.js?v=8';
|
||||
import { modelFetch } from './model.js?v=9';
|
||||
import { requestUpdate } from './reactivity.js?v=9';
|
||||
import { isModalProcessing, setModalProcessing, refreshModals } from './components/modal.js?v=9';
|
||||
|
||||
/**
|
||||
* JSON-friendly fetch wrapper.
|
||||
@@ -178,6 +180,33 @@ export async function poll(opts) {
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized handler wrapper that encapsulates processing guard,
|
||||
* processing state, error handling, and modal re-render.
|
||||
*
|
||||
* Used by any handler not using `apiSubmit`. The async function receives
|
||||
* no arguments and should perform validation (via `throw`), API calls,
|
||||
* success/error toasting, modal closing, and data refreshing.
|
||||
*
|
||||
* @param {function} fn – Async handler function
|
||||
* @returns {function} Wrapped handler
|
||||
*/
|
||||
export function formAction(fn) {
|
||||
return async () => {
|
||||
if (isModalProcessing()) return;
|
||||
setModalProcessing(true);
|
||||
refreshModals();
|
||||
try {
|
||||
await fn();
|
||||
} catch (e) {
|
||||
toast(e.message || 'Failed', 'error');
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate action button descriptors for modal form submission.
|
||||
*
|
||||
@@ -211,28 +240,37 @@ export function apiSubmit(opts) {
|
||||
label: submitText,
|
||||
cls: 'btn-primary',
|
||||
action: 's',
|
||||
processing: true,
|
||||
handler: async () => {
|
||||
const b = body ? body() : {};
|
||||
if (validate) {
|
||||
const err = validate(b);
|
||||
if (err) { toast(err, 'error'); return; }
|
||||
}
|
||||
const res = await apiFetch(url, { method, body: b });
|
||||
if (res.ok) {
|
||||
const synced = res.data?.synced;
|
||||
let msg = successMsg;
|
||||
if (synced && synced.length) {
|
||||
msg += ' (auto-synced: ' + synced.join(', ') + ')';
|
||||
synced.forEach(s => modelFetch(s));
|
||||
if (isModalProcessing()) return;
|
||||
setModalProcessing(true);
|
||||
refreshModals();
|
||||
try {
|
||||
const b = body ? body() : {};
|
||||
if (validate) {
|
||||
const err = validate(b);
|
||||
if (err) { toast(err, 'error'); return; }
|
||||
}
|
||||
toast(msg, 'success');
|
||||
if (closeModal) closeModal();
|
||||
if (refresh) {
|
||||
const models = Array.isArray(refresh) ? refresh : [refresh];
|
||||
await Promise.all(models.map(m => modelFetch(m)));
|
||||
const res = await apiFetch(url, { method, body: b });
|
||||
if (res.ok) {
|
||||
const synced = res.data?.synced;
|
||||
let msg = successMsg;
|
||||
if (synced && synced.length) {
|
||||
msg += ' (auto-synced: ' + synced.join(', ') + ')';
|
||||
synced.forEach(s => modelFetch(s));
|
||||
}
|
||||
toast(msg, 'success');
|
||||
if (closeModal) closeModal();
|
||||
if (refresh) {
|
||||
const models = Array.isArray(refresh) ? refresh : [refresh];
|
||||
await Promise.all(models.map(m => modelFetch(m)));
|
||||
}
|
||||
} else {
|
||||
toast(res.error || 'Failed', 'error');
|
||||
}
|
||||
} else {
|
||||
toast(res.error || 'Failed', 'error');
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user