root a1167df4f1 feat: User-Verwaltung mit Login, Sessions und Owner-Filter
Backend:
- User-Modell (sqlmodel) mit bcrypt-Passwort-Hash und is_admin-Flag
- Job.owner_id (FK auf user.id), per Migration nachgerüstet (SQLite ALTER)
- Starlette SessionMiddleware mit signiertem Cookie (SESSION_SECRET)
- /api/auth: login / logout / me / needs-setup / setup (Initial-Admin)
- /api/admin: User-CRUD + Passwort-Reset (Admin-only); letzter Admin /
  eigener Account vor Löschung geschützt
- /api/jobs: Owner-Filter (Admin sieht alle inkl. owner_username),
  404 statt 403 bei fremden Jobs (kein Existenz-Leak)

Frontend:
- Auth-Overlay mit Login bzw. Erst-Setup-Modus (auto-Detection)
- Header zeigt User + Logout, Admin-Badge bei Admin-Konten
- Admin-Karte: User anlegen, Passwort zurücksetzen, Löschen (mit Confirm)
- 401 → Login-Overlay (auch bei XHR-Upload)

Deploy:
- install.sh generiert SESSION_SECRET einmalig per python-secrets
- .env wird auf chmod 600 gesetzt
- Erstaufruf zeigt automatisch das Setup-Formular; bestehende Jobs werden
  dem ersten Admin zugeordnet
2026-05-14 02:38:52 +00:00

255 lines
6.7 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

:root {
--bg: #0f1115;
--card: #181c24;
--border: #262b36;
--text: #e6e8eb;
--muted: #8b93a3;
--accent: #4f8cff;
--accent-hover: #3a78f0;
--ok: #2ecc71;
--warn: #f1c40f;
--err: #e74c3c;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
header {
padding: 32px 24px 16px;
text-align: center;
border-bottom: 1px solid var(--border);
}
header h1 { margin: 0 0 4px; font-size: 28px; letter-spacing: .3px; }
.sub { color: var(--muted); margin: 0 0 12px; }
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 999px;
background: var(--card);
border: 1px solid var(--border);
font-size: 12px;
color: var(--muted);
}
.badge.ok { color: var(--ok); border-color: rgba(46,204,113,.4); }
.badge.err { color: var(--err); border-color: rgba(231,76,60,.4); }
main {
max-width: 1000px;
margin: 24px auto;
padding: 0 16px;
display: grid;
gap: 20px;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px 22px;
}
.card h2 { margin-top: 0; font-size: 18px; }
form { display: grid; gap: 12px; }
label { display: grid; gap: 6px; font-size: 14px; color: var(--muted); }
input[type=text], input[type=file] {
background: #0f1219;
border: 1px solid var(--border);
color: var(--text);
padding: 10px 12px;
border-radius: 8px;
font-size: 14px;
}
button {
background: var(--accent);
color: white;
border: 0;
padding: 10px 16px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
justify-self: start;
}
button:hover { background: var(--accent-hover); }
button:disabled { opacity: .6; cursor: not-allowed; }
.hidden { display: none; }
.msg { min-height: 1em; margin: 8px 0 0; font-size: 13px; }
.msg.ok { color: var(--ok); }
.msg.err { color: var(--err); }
.muted { color: var(--muted); }
.bar {
width: 100%;
height: 8px;
background: #0f1219;
border-radius: 4px;
overflow: hidden;
margin-top: 6px;
}
.bar-fill {
height: 100%;
width: 0%;
background: var(--accent);
transition: width .25s ease;
}
table { width: 100%; border-collapse: collapse; font-size: 14px; table-layout: fixed; }
th, td { padding: 10px 8px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
/* Spaltenbreiten — Status bleibt schmal, Downloads bekommen Platz */
.col-id { width: 56px; }
.col-status { width: 110px; }
.col-progress { width: 130px; }
.col-updated { width: 130px; }
.col-dl { width: 280px; }
/* col-title nimmt den Rest */
.col-title { word-break: break-word; }
.status-pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 12px;
border: 1px solid var(--border);
white-space: nowrap;
}
.status-queued { color: var(--muted); }
.status-transcribing, .status-summarizing, .status-protocolling, .status-exporting { color: var(--warn); border-color: rgba(241,196,15,.4); }
.status-done { color: var(--ok); border-color: rgba(46,204,113,.4); }
.status-failed { color: var(--err); border-color: rgba(231,76,60,.4); }
/* Fehlertext unter dem Pill — bricht um, max. 3 Zeilen */
.status-error {
margin-top: 6px;
font-size: 11px;
color: var(--err);
line-height: 1.35;
word-break: break-word;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Download-Buttons — sauberes 2×2-Grid, alle gleich breit */
.dl {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.btn-dl {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 10px;
border-radius: 6px;
background: rgba(79,140,255,.12);
border: 1px solid rgba(79,140,255,.4);
color: var(--accent);
text-decoration: none;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
transition: background .15s ease, border-color .15s ease;
}
.btn-dl:hover { background: rgba(79,140,255,.22); border-color: var(--accent); }
.btn-dl.disabled {
color: var(--muted);
background: transparent;
border-color: var(--border);
pointer-events: none;
opacity: .55;
}
footer { text-align: center; padding: 24px 16px 32px; }
/* ── Header-Layout ─────────────────────────────────────────────── */
.header-row {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
max-width: 1000px;
margin: 0 auto;
}
.header-left { text-align: left; }
.header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.user-bar {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.user-bar strong { color: var(--text); }
.btn-link {
background: transparent;
color: var(--accent);
border: 0;
padding: 2px 6px;
font-size: 13px;
cursor: pointer;
text-decoration: underline;
}
.btn-link:hover { color: var(--accent-hover); }
.btn-link.btn-danger { color: var(--err); }
.admin-badge {
background: rgba(79,140,255,.18);
color: var(--accent);
border-color: rgba(79,140,255,.45);
text-transform: uppercase;
font-size: 11px;
letter-spacing: .5px;
padding: 2px 8px;
}
/* ── Auth-Overlay ──────────────────────────────────────────────── */
.overlay {
position: fixed;
inset: 0;
background: rgba(15, 17, 21, .92);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
padding: 20px;
}
.auth-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px 28px 22px;
width: 100%;
max-width: 380px;
box-shadow: 0 20px 60px rgba(0,0,0,.4);
}
.auth-card h2 { margin: 0 0 8px; font-size: 20px; }
.auth-card #auth-info { font-size: 13px; margin: 0 0 14px; }
.auth-card form { display: grid; gap: 12px; }
.auth-card button { justify-self: stretch; }
/* ── Admin: Benutzer-Verwaltung ────────────────────────────────── */
.user-form {
display: grid;
grid-template-columns: 1fr 1fr auto auto;
gap: 12px;
align-items: end;
}
.user-form label { font-size: 13px; }
.inline-check {
display: flex !important;
flex-direction: row !important;
align-items: center;
gap: 6px;
color: var(--text);
}
.inline-check input { margin: 0; }
#users-table .user-actions {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
@media (max-width: 700px) {
.user-form { grid-template-columns: 1fr; }
}