arr-summary/static/js/settings.js
Barely Removable 18c7719cd7
All checks were successful
Deploy / deploy (push) Successful in 23s
Extract CSS/JS to static files, wrap header pills, add version footer
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>
2026-08-26 14:27:52 -07:00

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); }
}