diff --git a/README.md b/README.md index 7511508..88957a3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ During play the UI has no persistent location, task, inventory, economy, status, If the player asks to find or approach someone and no NPC is present, the direct dialogue scene may propose one person and an opening line. Rust creates that NPC only for the explicit request and commits the first exchange idempotently; a later turn continues the saved conversation. Clear requests about the current location are answered from its saved facts without asking the player to restate what they want to inspect. -The separate `/admin` route provides an authenticated, read-only operations view for player search/profile/trace, YAML prompt assets, generated Luau rules with validation results, and the append-only audit log. All privileged data comes from the Rust `/api/admin/...` API; the browser never connects directly to PostgreSQL. Backend endpoints require `accounts.is_admin=true`. +The separate `/admin` route provides an authenticated operations view for player search/profile/trace, YAML prompt assets, generated Luau rules with validation results, runtime debug logging, and the append-only audit log. All privileged data comes from the Rust `/api/admin/...` API; the browser never connects directly to PostgreSQL. Backend endpoints require `accounts.is_admin=true`. The Driftloggar view lets an administrator switch `debug=true` on or off without restarting pods; the setting is stored in PostgreSQL and propagated to running replicas. Verbose pod logs include request, AI-call, validation, and commit metadata but never raw player text, prompts, model responses, sessions, or secrets. Full protected prompt/provider exchanges remain in the audited player trace. The game is text- and dialogue-based and uses no images. Place descriptions include sound, smell, air/temperature, touch/surface, and spatial-orientation cues. Visible details may also be described in words, but are never required to understand the scene or play. The transcript is screen-reader announced, and Enter submits the input while Shift+Enter inserts a newline; no separate send button is required. diff --git a/index.html b/index.html index c17fd93..2795523 100644 --- a/index.html +++ b/index.html @@ -63,6 +63,7 @@ + @@ -392,6 +393,32 @@ adminElement('h2', 'Revisionslogg'); for (const entry of result.entries) adminJson(`${entry.created_at} · ${entry.action} · ${entry.admin_account_id || entry.operator_identity}`, entry); } + async function loadAdminLogging() { + adminContent.replaceChildren(); + const result = await request('/api/admin/settings/logging'); + adminElement('h2', 'Driftloggar'); + adminElement('p', 'debug=true skriver detaljerade tekniska händelser för HTTP, AI-anrop, validering och commit. debug=false behåller korta tekniska åtkomstloggar och fel. Ändringen sparas i databasen och börjar gälla utan omstart. Spelartext, promptinnehåll, AI-svar, sessionsuppgifter och hemligheter skrivs aldrig till poddloggarna.'); + const form = adminElement('form'); + const label = adminElement('label', undefined, form); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; checkbox.name = 'debug'; checkbox.checked = result.debug; + label.append(checkbox, document.createTextNode(' Aktivera verbose felsökningsloggar (debug=true)')); + const save = adminElement('button', 'Spara loggnivå', form); save.type = 'submit'; + const status = adminElement('p', `Aktuell nivå: debug=${result.debug}`); + status.setAttribute('role', 'status'); + form.addEventListener('submit', async event => { + event.preventDefault(); save.disabled = true; + try { + const updated = await request('/api/admin/settings/logging', { + method: 'PUT', headers: {'content-type': 'application/json'}, + body: JSON.stringify({debug: checkbox.checked}), + }); + status.textContent = `Aktuell nivå: debug=${updated.debug}. Ändringen gäller utan omstart.`; + } catch (error) { + status.textContent = `Loggnivån kunde inte ändras: ${error.message}`; + } finally { save.disabled = false; } + }); + } async function openAdmin() { showAdmin(); adminContent.setAttribute('aria-busy', 'true'); @@ -632,6 +659,7 @@ if (button.dataset.adminView === 'players') await loadAdminPlayers(); else if (button.dataset.adminView === 'prompts') await loadAdminPrompts(); else if (button.dataset.adminView === 'rules') await loadAdminRules(); + else if (button.dataset.adminView === 'logging') await loadAdminLogging(); else await loadAdminAudit(); } catch (error) { adminContent.replaceChildren(); adminElement('p', `Vyn kunde inte läsas: ${error.message}`); } finally { adminContent.setAttribute('aria-busy', 'false'); } diff --git a/scripts/check-frontend.sh b/scripts/check-frontend.sh index c00bbe5..63e258c 100755 --- a/scripts/check-frontend.sh +++ b/scripts/check-frontend.sh @@ -36,6 +36,8 @@ grep -q 'id="admin-view"' index.html grep -q '/api/admin/players?' index.html grep -q "request('/api/admin/prompts'" index.html grep -q "request('/api/admin/rules'" index.html +grep -q "request('/api/admin/settings/logging'" index.html +grep -q 'data-admin-view="logging"' index.html grep -q '/api/admin/audit?limit=200' index.html grep -q 'entry.data.external_call' index.html grep -q 'Externt tjänsteanrop' index.html