All checks were successful
Deploy / deploy (push) Successful in 23s
The palette lived in a :root block duplicated across all three templates, so every reshade meant three edits — and drifted twice in practice (the favicon hardcoded its own hex; stale danger-border rgba literals survived two palette changes). Move tokens to a single static/css/base.css, derive every dim/border tint from the four source hues with color-mix, and point the favicon at one static/favicon.svg. Also splits the 1558-line dashboard.html into template + static assets: 662 lines of CSS and 396 of JS were re-sent uninlined on every load with no browser caching, which costs real time on the remote/cellular access this app is built for. Templates drop to 501/42/107 lines. Server-rendered config (timezone, script_root) now arrives via <body data-*>; wiring URL_BASE to request.script_root also fixes /api/* fetches for a future subpath mount, which the hardcoded "" would break. Header service pills now wrap instead of scrolling — they were clipped at narrow widths, and a hidden pill is a service whose status you cannot see. Verified 0 clipped and no page overflow at 320/375/700/1280. Footer gains a version and date so the running build is identifiable without trusting the Unraid Docker tab's cached container IDs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
2.3 KiB
JavaScript
61 lines
2.3 KiB
JavaScript
/* Reverse-proxy prefix, so /api/* still resolves on a subpath mount. */
|
|
const URL_BASE = document.body.dataset.urlBase || "";
|
|
|
|
function showToast(msg, isError) {
|
|
const t = document.getElementById('toast');
|
|
t.textContent = msg;
|
|
t.classList.toggle('error', !!isError);
|
|
t.classList.add('show');
|
|
setTimeout(() => t.classList.remove('show'), 2800);
|
|
}
|
|
|
|
async function post(url, payload) {
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
async function saveGeneral() {
|
|
try {
|
|
const resp = await post(URL_BASE + '/api/settings', {
|
|
timezone: document.getElementById('timezone').value,
|
|
arr_host: document.getElementById('arr_host').value.trim()
|
|
});
|
|
showToast(resp.ok ? 'General settings saved — reload the dashboard to apply.'
|
|
: 'Save failed.', !resp.ok);
|
|
} catch (e) { showToast('Network error — not saved.', true); }
|
|
}
|
|
|
|
async function saveService(name) {
|
|
try {
|
|
const key = document.getElementById(name + '_api_key').value.trim();
|
|
const resp = await post(URL_BASE + '/api/settings', { [name + '_api_key']: key });
|
|
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
showToast(resp.ok ? label + ' key saved — restart the container to apply.'
|
|
: 'Save failed.', !resp.ok);
|
|
} catch (e) { showToast('Network error — not saved.', true); }
|
|
}
|
|
|
|
async function changePassword() {
|
|
const errEl = document.getElementById('pw-error');
|
|
errEl.style.display = 'none';
|
|
try {
|
|
const resp = await post(URL_BASE + '/api/change-password', {
|
|
current: document.getElementById('pw_current').value,
|
|
new: document.getElementById('pw_new').value,
|
|
confirm: document.getElementById('pw_confirm').value
|
|
});
|
|
const data = await resp.json();
|
|
if (resp.ok) {
|
|
['pw_current', 'pw_new', 'pw_confirm'].forEach(id => document.getElementById(id).value = '');
|
|
showToast('Password updated.');
|
|
} else {
|
|
errEl.textContent = data.error || 'Failed to update password.';
|
|
errEl.style.display = 'block';
|
|
showToast(data.error || 'Failed to update password.', true);
|
|
}
|
|
} catch (e) { showToast('Network error — password not changed.', true); }
|
|
}
|