feat: browser UI – unlock, table view, create/edit/delete with client-side crypto

Vanilla HTML/CSS/JS frontend served by Flask:
- Unlock screen: passphrase -> Argon2id session key, held in memory
  only (never sent to the server); "Sperren" wipes key and DOM rows.
- Records table (Person A/B, start/end, action) rendered via
  textContent only (no HTML injection); undecryptable rows are flagged.
- Create/edit modal encrypts payloads with XChaCha20-Poly1305 before
  any network call; updates carry the server version (optimistic lock,
  conflict toast on 409); delete fetches current version first.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
2026-10-11 00:02:40 +02:00
parent 7cbc2eecd2
commit a963baa8de
3 changed files with 488 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>crushdb</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<h1>crushdb</h1>
<p class="tagline">Verschlüsselte Datensätze zwischen zwei Personen</p>
<section id="flash" class="flash" aria-live="polite"></section>
<!-- unlock screen: passphrase stays in memory only -->
<section id="unlock">
<form id="unlock-form" autocomplete="off">
<label for="passphrase">Passphrase</label>
<input
type="password"
id="passphrase"
autocomplete="off"
required
placeholder="Passphrase eingeben …"
/>
<button type="submit">Entsperren</button>
<p class="hint">
Die Passphrase verlässt niemals deinen Browser. Sie wird per
Argon2id lokal abgeleitet und nur im Arbeitsspeicher gehalten.
</p>
</form>
</section>
<!-- main app, hidden until unlocked -->
<section id="app" hidden>
<div class="toolbar">
<button id="new-btn" type="button">+ Neuer Datensatz</button>
<button id="lock-btn" type="button">Sperren</button>
</div>
<table id="records">
<thead>
<tr>
<th>Person A</th>
<th>Person B</th>
<th>Start</th>
<th>Ende</th>
<th>Aktion</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
<!-- create/edit modal -->
<div id="editor-backdrop" hidden>
<form id="editor" autocomplete="off">
<h2 id="editor-title">Neuer Datensatz</h2>
<label>Person A
<input id="f-persona" type="text" required maxlength="120" />
</label>
<label>Person B
<input id="f-personb" type="text" required maxlength="120" />
</label>
<label>Start (optional)
<input id="f-tsstart" type="datetime-local" />
</label>
<label>Ende (optional)
<input id="f-tsend" type="datetime-local" />
</label>
<label>Aktion
<input id="f-action" type="text" required maxlength="500" />
</label>
<div class="actions">
<button type="submit">Speichern</button>
<button type="button" id="editor-cancel">Abbrechen</button>
</div>
</form>
</div>
<script type="module" src="/static/ui.js"></script>
</body>
</html>