Open registration let anyone with the URL create an account. Two
changes address that:
- REGISTRATION_CODE (.env, optional): when set, registration requires
entering it correctly. Empty/unset keeps registration open, so
existing installs are unaffected until configured.
- is_admin flag on User: the first account ever created on an install
becomes admin automatically (existing installs get their oldest
account promoted via the startup migration, so nobody is locked
out of user management after upgrading).
Admins get a new "Benutzerverwaltung" panel in Einstellungen listing
every account (email, link/prompt counts, join date) with a delete
button per account — deleting cascades to that user's links and
prompts via the existing relationship cascade. Deleting your own
account through this page is blocked (redirects with an error) to
avoid accidental admin lockout. Non-admins get a 403 on the
/settings/users routes.
Also fixes several pre-existing German pluralization bugs found while
writing the new counts ("2 Linke" -> "2 Links", "Kontoen"/"Konton" ->
"Konten") — irregular plurals need a full word swap, not a suffix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
87 lines
3.7 KiB
HTML
87 lines
3.7 KiB
HTML
{% extends "base.html" %}
|
||
{% import "_icons.html" as icons %}
|
||
{% block title %}Einstellungen · LinkVault{% endblock %}
|
||
{% block body %}
|
||
{% include "_topbar.html" %}
|
||
|
||
<div class="layout" style="max-width:720px; margin-left:auto; margin-right:auto;">
|
||
<main class="main">
|
||
<h1 style="margin-top:0;">Einstellungen</h1>
|
||
<p class="muted" style="margin-top:-8px;">
|
||
Angemeldet als {{ user.email }} · {{ link_count }} Link{{ '' if link_count == 1 else 's' }}
|
||
</p>
|
||
|
||
{% if imported is not none %}
|
||
<div class="panel" style="border-color:#16a34a;">
|
||
{{ icons.sparkles(size=15) }} {{ imported }} Link{{ '' if imported == '1' else 's' }} erfolgreich importiert.
|
||
</div>
|
||
{% endif %}
|
||
{% if import_error %}
|
||
<div class="error">{{ import_error }}</div>
|
||
{% endif %}
|
||
|
||
{% if user.is_admin %}
|
||
<div class="panel">
|
||
<h2 style="margin-top:0; font-size:1.05rem;">Benutzerverwaltung</h2>
|
||
<p class="muted">
|
||
{{ user_count }} {{ 'registriertes Konto' if user_count == 1 else 'registrierte Konten' }}.
|
||
Konten ansehen oder entfernen.
|
||
</p>
|
||
<a href="/settings/users"><button type="button">{{ icons.user(size=14) }} Benutzer verwalten</button></a>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="panel">
|
||
<h2 style="margin-top:0; font-size:1.05rem;">CSV-Export</h2>
|
||
<p class="muted">
|
||
Alle deine Links als CSV-Tabelle (Titel, Zusammenfassung, Kategorie,
|
||
Hersteller, Tags, Datum) – zum Öffnen in Excel/Numbers/Sheets.
|
||
</p>
|
||
<a href="/settings/export.csv"><button type="button">{{ icons.download(size=14) }} CSV herunterladen</button></a>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h2 style="margin-top:0; font-size:1.05rem;">Datenbank-Export (SQL-Dump)</h2>
|
||
<p class="muted">
|
||
Alle deine Links als SQL-<code>INSERT</code>-Anweisungen – als Backup
|
||
oder zum späteren Wiedereinspielen über den SQL-Import unten.
|
||
Enthält keine Embeddings (semantische Suche wird beim nächsten
|
||
„KI neu beschreiben lassen" pro Link neu erzeugt).
|
||
</p>
|
||
<a href="/settings/export.sql"><button type="button">{{ icons.download(size=14) }} SQL-Dump herunterladen</button></a>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h2 style="margin-top:0; font-size:1.05rem;">SQL-Import</h2>
|
||
<p class="muted">
|
||
Eine mit obigem SQL-Export erzeugte Datei wieder einspielen. Die
|
||
Links werden deinem Konto <strong>hinzugefügt</strong> (keine
|
||
bestehenden Links werden überschrieben oder gelöscht). Nur Dateien,
|
||
die dem Export-Format dieser App entsprechen, werden akzeptiert
|
||
(max. 2 MB).
|
||
</p>
|
||
<form method="post" action="/settings/import-sql" enctype="multipart/form-data"
|
||
style="display:flex; gap:12px; flex-wrap:wrap; align-items:center;">
|
||
<input type="file" name="file" id="sql-import-file" accept=".sql,text/plain" required class="file-input">
|
||
<label for="sql-import-file" class="ghost file-label" style="padding:9px 16px; border-radius:8px; border:1px solid var(--border);">
|
||
{{ icons.upload(size=14) }} Datei wählen
|
||
</label>
|
||
<span id="sql-import-filename" class="muted" style="font-size:.85rem;">Keine Datei ausgewählt</span>
|
||
<button type="submit" style="margin-left:auto;">Importieren</button>
|
||
</form>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
var input = document.getElementById('sql-import-file');
|
||
var label = document.getElementById('sql-import-filename');
|
||
if (input && label) {
|
||
input.addEventListener('change', function () {
|
||
label.textContent = input.files && input.files[0] ? input.files[0].name : 'Keine Datei ausgewählt';
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
{% endblock %}
|