SHA256
Adds two optional free-text fields to describe why a transaction
started ("Anlass") and why it ended ("Endgrund") - chosen names instead
of the technical "Startursache"/"Endesuhrsache". Displayed as own table
columns and editable in the form; legacy records without those fields
render as "-". Payload is encrypted client-side like every other field
(no server change needed, opaque ciphertext).
Verified: node --check, crypto roundtrip incl. new fields, legacy-safe
access (?? ''), pytest 3/3.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
126 lines
4.5 KiB
HTML
126 lines
4.5 KiB
HTML
<!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>
|
||
|
||
<!-- First visit: NO records exist yet → user chooses a new passphrase -->
|
||
<section id="setup" hidden>
|
||
<form id="setup-form" autocomplete="off">
|
||
<h2>Willkommen bei crushdb!</h2>
|
||
<p class="intro">
|
||
Du benutzt crushdb zum ersten Mal. Wähle jetzt eine Passphrase –
|
||
sie ist das Master-Passwort für alle deine Daten. Es gibt keine
|
||
Zurücksetzung, speichere sie sicher!
|
||
</p>
|
||
<label>Neue Passphrase
|
||
<input type="password" id="setup-pass1" autocomplete="new-password"
|
||
required minlength="8" placeholder="mindestens 8 Zeichen" />
|
||
</label>
|
||
<label>Passphrase wiederholen
|
||
<input type="password" id="setup-pass2" autocomplete="new-password"
|
||
required placeholder="gleiche Passphrase nochmal" />
|
||
</label>
|
||
<button type="submit" id="setup-btn">Konto einrichten →</button>
|
||
<p id="setup-progress" class="hint" hidden>Schlüssel wird abgeleitet …</p>
|
||
<p class="hint">
|
||
Wichtig: Die Passphrase wird <strong>nicht am Server gespeichert</strong>.
|
||
Sie bleibt nur in deinem Browser-Arbeitsspeicher. Wer sie vergisst,
|
||
verliert Zugriff auf alle Daten (absichtlich so).
|
||
</p>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Returning user: records exist but are undecryptable → enter passphrase -->
|
||
<section id="unlock">
|
||
<form id="unlock-form" autocomplete="off">
|
||
<h2>Entsperren</h2>
|
||
<p class="intro">
|
||
Du hast bereits verschlüsselte Datensätze hinterlegt.
|
||
Gib deine Passphrase ein, um sie zu lesen.
|
||
</p>
|
||
<label>Passphrase
|
||
<input type="password" id="passphrase" autocomplete="current-password"
|
||
required placeholder="deine Passphrase …" />
|
||
</label>
|
||
<button type="submit" id="unlock-btn">Entsperren</button>
|
||
<p id="unlock-progress" class="hint" hidden>Schlüssel wird abgeleitet …</p>
|
||
<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>Anlass</th>
|
||
<th>Endgrund</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, beliebiger Text)
|
||
<input id="f-tsstart" type="text" maxlength="120"
|
||
placeholder="z. B. 12.03.2026, Sommer 2025, unbekannt …" />
|
||
</label>
|
||
<label>Ende (optional, beliebiger Text)
|
||
<input id="f-tsend" type="text" maxlength="120"
|
||
placeholder="z. B. laufend, unbekannt, März 2026 …" />
|
||
</label>
|
||
<label>Anlass (optional)
|
||
<input id="f-startcause" type="text" maxlength="300"
|
||
placeholder="womit/warum es begann, z. B. gemeinsam geplante Reise …" />
|
||
</label>
|
||
<label>Endgrund (optional)
|
||
<input id="f-endcause" type="text" maxlength="300"
|
||
placeholder="warum es endete, z. B. zurückgezahlt, abgelaufen …" />
|
||
</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>
|