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:
@@ -82,6 +82,7 @@ export function Card(props = {}) {
|
||||
* @param {string} [props.label] - Button text (default: 'Remove')
|
||||
* @param {object} [props.body] - Optional JSON body to send with DELETE
|
||||
* @param {string} [props.deleteKey] - Unique ID for pending-delete row styling
|
||||
* @param {function} [props.onComplete] - Callback after successful deletion
|
||||
*/
|
||||
export function ConfirmDelete(props = {}) {
|
||||
const opts = { method: 'DELETE' };
|
||||
@@ -118,13 +119,16 @@ export function ConfirmDelete(props = {}) {
|
||||
if (props.deleteKey && promises.length) {
|
||||
Promise.all(promises).finally(() => {
|
||||
_deleting.delete(props.deleteKey);
|
||||
if (props.onComplete) props.onComplete();
|
||||
});
|
||||
}
|
||||
} else if (props.deleteKey) {
|
||||
// Cleanup dimming synchronously on DELETE completion rather than
|
||||
// relying on a heuristic timeout that breaks when tabs are throttled.
|
||||
_deleting.delete(props.deleteKey);
|
||||
}
|
||||
|
||||
if (props.onComplete && !props.refresh) {
|
||||
props.onComplete();
|
||||
}
|
||||
} else {
|
||||
toast(r.error || 'Failed', 'error');
|
||||
}
|
||||
|
||||
@@ -56,6 +56,28 @@ export function parseZones(data) {
|
||||
* @param {Blob} blob
|
||||
* @param {string} filename
|
||||
*/
|
||||
/**
|
||||
* Format bytes to human-readable string.
|
||||
* @param {number} bytes
|
||||
*/
|
||||
export function fmtBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return (bytes / Math.pow(k, i)).toFixed(i > 0 ? 1 : 0) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a comma-separated string into trimmed, non-empty values.
|
||||
* @param {string} [value]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function csvToArr(value) {
|
||||
if (!value || !value.trim()) return [];
|
||||
return value.split(',').map(s => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
export function downloadBlob(blob, filename) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
||||
@@ -35,7 +35,7 @@ export { scheduleTokenRefresh, cancelTokenRefresh, checkSession, logout, initAut
|
||||
export { modelRegister, getModel, modelFetch, collectLoadingModels } from './model.js';
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────────────── */
|
||||
export { esc, att_esc, enc, $val, parseZones, downloadBlob } from './helpers.js';
|
||||
export { esc, att_esc, enc, $val, parseZones, fmtBytes, csvToArr, downloadBlob } from './helpers.js';
|
||||
|
||||
/* ── UI Components: Layout ───────────────────────────────────── */
|
||||
export { PageHeader, renderGuard, renderGuardMulti, Tabs, SectionTitle, ActionGroup, DataTableSection } from './components/layout.js';
|
||||
|
||||
Reference in New Issue
Block a user