- Move all inline <script> blocks (layout head/theme, topbar dark toggle, foot, paste, new, history, mine, settings, admin, unlock) to external files under internal/web/static/. Page data reaches scripts via data-* attributes (data-paste-id, data-default-dark) instead of template vars. - Replace inline onclick handlers (copy, delete, stats toggle) with addEventListener wiring. - Convert inline style="" attributes to CSS utility classes; swatch colors are now set via CSSOM/DOM APIs instead of innerHTML strings. - script-src/style-src are now plain 'self'; img-src data: stays for the SVG data-URI backgrounds. Verified with headless chromium: zero CSP violations on all pages in dark and light presets, theme swatches, admin lock, tables and paste view render correctly.
75 lines
3.1 KiB
JavaScript
75 lines
3.1 KiB
JavaScript
// #99/#139: admin lock + settings panel. Key lives in sessionStorage for this visit only.
|
|
(function () {
|
|
var KEY = 'palette_admin_key';
|
|
var keyInput = document.getElementById('admin-key');
|
|
var status = document.getElementById('admin-key-status');
|
|
var panel = document.getElementById('admin-panel');
|
|
|
|
function key() { return sessionStorage.getItem(KEY) || ''; }
|
|
|
|
function api(path, opts) {
|
|
opts = opts || {};
|
|
// allow callers to override the key header (e.g. validating a typed key, #99)
|
|
opts.headers = Object.assign({ 'X-Admin-Key': key() }, opts.headers || {});
|
|
if (opts.body) opts.headers['Content-Type'] = 'application/json';
|
|
return fetch(path, opts);
|
|
}
|
|
|
|
function loadSettings() {
|
|
api('/admin/api/settings').then(function (r) {
|
|
if (r.status !== 200) { showLock(); return; }
|
|
return r.json();
|
|
}).then(function (s) {
|
|
if (!s) return;
|
|
document.getElementById('rl-burst').value = s.rate_limit_burst;
|
|
document.getElementById('rl-refill').value = s.rate_limit_per_minute;
|
|
document.getElementById('max-content').value = s.max_content_bytes;
|
|
document.getElementById('default-expiry').value = s.default_expiry;
|
|
document.getElementById('slug-days').value = s.custom_slug_reservation_days;
|
|
document.getElementById('burn-window').value = s.burn_viewer_window_minutes;
|
|
panel.classList.remove('hidden');
|
|
});
|
|
}
|
|
|
|
function showLock() {
|
|
panel.classList.add('hidden');
|
|
sessionStorage.removeItem(KEY);
|
|
}
|
|
|
|
document.getElementById('admin-key-form').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
// #99: don't persist the key until the server accepts it
|
|
api('/admin/api/settings', { headers: { 'X-Admin-Key': keyInput.value } }).then(function (r) {
|
|
if (r.status === 200) {
|
|
sessionStorage.setItem(KEY, keyInput.value);
|
|
status.textContent = '✓';
|
|
keyInput.value = '';
|
|
loadSettings();
|
|
} else {
|
|
status.textContent = 'invalid key';
|
|
showLock();
|
|
}
|
|
});
|
|
});
|
|
|
|
document.getElementById('admin-settings-form').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var body = {
|
|
rate_limit_burst: parseFloat(document.getElementById('rl-burst').value),
|
|
rate_limit_per_minute: parseFloat(document.getElementById('rl-refill').value),
|
|
max_content_bytes: parseInt(document.getElementById('max-content').value, 10),
|
|
default_expiry: document.getElementById('default-expiry').value,
|
|
custom_slug_reservation_days: parseInt(document.getElementById('slug-days').value, 10),
|
|
burn_viewer_window_minutes: parseInt(document.getElementById('burn-window').value, 10)
|
|
};
|
|
api('/admin/api/settings', { method: 'POST', body: JSON.stringify(body) }).then(function (r) {
|
|
document.getElementById('admin-save-status').textContent = r.status === 200 ? 'saved' : 'error';
|
|
if (r.status !== 200) showLock();
|
|
});
|
|
});
|
|
|
|
// #112: always show the lock on fresh load — do not auto-restore the
|
|
// panel from a stale sessionStorage key. The key is only written after a
|
|
// successful unlock (above) so in-page actions still work within this visit.
|
|
})();
|