style(webui): unify UI across all pages to solid modern system

tokens instead of hardcoded, v9 everywhere, dialog guarded, confirm->dialog, empty unified, health out of nav, swagger icons/topbar, gutters, th uppercase, content 1280 centered.
This commit is contained in:
2026-09-08 12:30:50 +02:00
parent 2a75537fb9
commit ef62b8e94a
9 changed files with 1587 additions and 1668 deletions
+44 -6
View File
@@ -38,6 +38,42 @@ function showToast(message, type = 'info') {
}, 3600);
}
function confirmAction(message) {
return new Promise((resolve) => {
let settled = false;
const done = (value) => {
if (settled) return;
settled = true;
if (dialog.open) dialog.close();
resolve(value);
};
let dialog = document.getElementById('confirm-dialog');
if (!dialog) {
dialog = document.createElement('dialog');
dialog.id = 'confirm-dialog';
dialog.className = 'confirm-dialog';
dialog.innerHTML =
'<div class="confirm-dialog-body"><div class="eyebrow">Confirm</div>' +
'<p class="confirm-dialog-message muted"></p>' +
'<div class="confirm-dialog-actions">' +
'<button class="button" data-confirm-cancel type="button">Cancel</button>' +
'<button class="button primary" data-confirm-ok type="button">Confirm</button>' +
'</div></div>';
document.body.appendChild(dialog);
}
dialog.querySelector('.confirm-dialog-message').textContent = message;
dialog.querySelector('[data-confirm-ok]').onclick = () => done(true);
dialog.querySelector('[data-confirm-cancel]').onclick = () => done(false);
dialog.onclose = () => done(false);
if (!dialog.open) dialog.showModal();
});
}
function setHealthState(el, ok, warnOnly = false) {
el.classList.remove('is-ok', 'is-warn', 'is-dim');
el.classList.add(ok ? 'is-ok' : warnOnly ? 'is-dim' : 'is-warn');
}
function downloadJson(filename, payload) {
const blob = new Blob([JSON.stringify(payload, null, 2) + '\n'], {
type: 'application/json',
@@ -105,7 +141,7 @@ function renderAccountList() {
root.innerHTML = '';
if (!settingsState.accounts.length) {
root.innerHTML = '<div class="empty-state">No accounts yet.</div>';
root.innerHTML = '<div class="empty-state"><p class="muted">No accounts yet.</p></div>';
return;
}
@@ -168,17 +204,19 @@ function renderAccountData() {
: 'No account selected.';
document.getElementById('health-credentials').textContent = health.api_credentials ? 'Saved' : 'Missing';
document.getElementById('health-credentials').style.color = health.api_credentials ? 'var(--ok)' : 'var(--warn)';
setHealthState(document.getElementById('health-credentials'), Boolean(health.api_credentials));
document.getElementById('health-session').textContent =
health.session_ready || isAccountAuthorized(auth) ? 'Ready' : 'Missing';
document.getElementById('health-session').style.color =
health.session_ready || isAccountAuthorized(auth) ? 'var(--ok)' : 'var(--warn)';
setHealthState(
document.getElementById('health-session'),
Boolean(health.session_ready || isAccountAuthorized(auth)),
);
document.getElementById('health-continuous').textContent = continuousStatus.running
? 'Running'
: continuousConfig.enabled
? 'Enabled'
: 'Stopped';
document.getElementById('health-continuous').style.color = continuousStatus.running ? 'var(--ok)' : 'var(--dim)';
setHealthState(document.getElementById('health-continuous'), Boolean(continuousStatus.running), true);
document.getElementById('health-last-scrape').textContent = displayTime(
health.last_scrape || continuousStatus.last_iteration_at,
);
@@ -342,7 +380,7 @@ async function exportAccount() {
async function deleteAccount() {
if (!settingsState.activeAccount) return;
const account = settingsState.accounts.find((item) => item.id === settingsState.activeAccount);
if (!window.confirm(`Remove account "${accountLabel(account)}"? All its account data will be deleted.`)) return;
if (!(await confirmAction(`Remove account "${accountLabel(account)}"? All its account data will be deleted.`))) return;
await api(`/api/accounts/${encodeURIComponent(settingsState.activeAccount)}`, { method: 'DELETE' });
setActiveAccount(null);
await loadAccounts();