Files
palette/internal/web/templates/admin.html
T
fen 8dde83ff68
CI / test (pull_request) Successful in 28s
CI / docker (pull_request) Skipped
Admin lock: replace explanation paragraph with centered key input
2026-09-09 21:28:11 -05:00

109 lines
4.5 KiB
HTML

{{template "head" .}}
{{template "topbar" .}}
<div class="center">
<div class="float unlock-card">
<div class="inner">
<h1>Admin</h1>
<form id="admin-key-form">
<div class="pw-field">
<input type="password" id="admin-key" placeholder="Admin key" autocomplete="off" autofocus>
</div>
<button class="btn" type="submit">Unlock</button>
<span id="admin-key-status"></span>
</form>
<div id="admin-panel" style="display:none">
<h2>Settings</h2>
<form id="admin-settings-form">
<table>
<tr><td>Rate-limit burst</td><td><input type="number" id="rl-burst" min="1" step="1"></td></tr>
<tr><td>Rate-limit refill per minute</td><td><input type="number" id="rl-refill" min="0.1" step="0.1"></td></tr>
<tr><td>Max content bytes</td><td><input type="number" id="max-content" min="1" step="1"></td></tr>
<tr><td>Default expiry</td><td><input type="text" id="default-expiry" placeholder="e.g. 168h, 30m, 0 = never"></td></tr>
<tr><td>Custom URL reservation days</td><td><input type="number" id="slug-days" min="1" step="1"></td></tr>
<tr><td>Burn viewer window (minutes)</td><td><input type="number" id="burn-window" min="1" step="1"></td></tr>
</table>
<button type="submit">Save</button>
<span id="admin-save-status"></span>
</form>
</div>
</div>
</div>
</div>
<script>
(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.style.display = '';
});
}
function showLock() {
panel.style.display = 'none';
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.
})();
</script>
{{template "foot" .}}