From 2120c56f45c1db01d907d819f24a049a0ee3ccf7003ef2ff51344cb6ff7a1ce0 Mon Sep 17 00:00:00 2001 From: jhkaktus Date: Tue, 13 Oct 2026 15:35:07 +0200 Subject: [PATCH] make rewirte start --- backend/static/index.html | 27 +++++- backend/static/style.css | 29 +++++++ backend/static/ui.js | 143 +++++++++++++++++++++++++++++--- specification.txt | 168 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 353 insertions(+), 14 deletions(-) create mode 100644 specification.txt diff --git a/backend/static/index.html b/backend/static/index.html index 3326873..cb3d832 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -67,6 +67,20 @@ +
+ + + + + +
+ @@ -89,10 +103,12 @@

Neuer Datensatz

@@ -120,6 +137,10 @@
+ + + + diff --git a/backend/static/style.css b/backend/static/style.css index 2eb63f3..eab4479 100644 --- a/backend/static/style.css +++ b/backend/static/style.css @@ -107,6 +107,35 @@ tbody tr:hover { background: var(--panel-2); } td:last-child, td button { white-space: nowrap; } td button + button { margin-left: 0.4rem; } +/* filter bar */ +.filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; +} +.filters input[type="search"] { flex: 1 1 220px; min-width: 170px; } +.filters select { max-width: 220px; } +.filter-count { color: var(--muted); font-size: 0.85rem; margin-left: auto; white-space: nowrap; } + +select { + font: inherit; + color: var(--text); + background: var(--panel-2); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0.5rem 0.7rem; +} +select:focus { outline: 1px solid var(--accent); } +option { background: var(--panel); color: var(--text); } + +.secondary { + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--border); +} + /* modal */ /* NOTE: keep `hidden` winning over the flex display below, otherwise the backdrop is permanently visible and blocks everything underneath. */ diff --git a/backend/static/ui.js b/backend/static/ui.js index 850b304..7aa58aa 100644 --- a/backend/static/ui.js +++ b/backend/static/ui.js @@ -61,6 +61,11 @@ function flash(msg, isError = false) { let sessionKey = null; // Uint8Array(32) or null let busy = false; // Argon2id takes a second; block double submits +// Decrypted records kept in memory so filters/datalist suggestions can +// re-render without re-fetching and re-decrypting everything. +let decryptedRows = []; // [{ record, payload }] +let lastFailures = 0; // records that could not be decrypted (shown unfiltered) + // Salt for the session-key derivation. Fixed domain string, contains // no secret; the per-record salt is separate (see crypto.js). const DOMAIN_SALT = new TextEncoder().encode("crushdb://session-key-v1"); @@ -68,6 +73,8 @@ const DOMAIN_SALT = new TextEncoder().encode("crushdb://session-key-v1"); function resetSession() { wipeKey(sessionKey); sessionKey = null; + decryptedRows = []; + lastFailures = 0; $("#records").replaceChildren(); $("#app").hidden = true; location.reload(); // cheapest and safest way back to a clean state @@ -134,26 +141,125 @@ function renderRow(record, payload) { } async function reload() { - const tbody = $("#records tbody"); - tbody.replaceChildren(); const raw = await api("/api/records"); - let failures = 0; + decryptedRows = []; + lastFailures = 0; for (const record of raw) { try { const payload = decryptRecord(sessionKey, record); payload.__version = record.version; // used for optimistic locking - tbody.appendChild(renderRow(record, payload)); + decryptedRows.push({ record, payload }); } catch { - failures++; - tbody.appendChild( - el("tr", { class: "row-error" }, [ - el("td", { colspan: "8", text: "Datensatz nicht entschlüsselbar (falsche Passphrase oder manipuliert)." }), - ]) - ); + lastFailures++; } } + refreshSuggestions(); + renderRows(); $("#new-btn").disabled = false; - if (failures > 0) flash(`${failures} Datensatz/Datensätze konnten nicht entschlüsselt werden.`, true); + if (lastFailures > 0) flash(`${lastFailures} Datensatz/Datensätze konnten nicht entschlüsselt werden.`, true); +} + +// ---------- filters & suggestions ---------- + +function sorted(values) { + return [...values].sort((a, b) => a.localeCompare(b, "de")); +} + +// Rebuild the dropdowns (editor) and the filter