root 11ab8b57bf feat: PWA + Mikrofon-Aufnahme + HTTPS via selbst-signiertem Cert
Frontend-Aufnahme (MediaRecorder):
- Aufnahme-Button im Upload-Form, große Touch-Targets
- Start / Pause / Fortsetzen / Stop mit Live-Timer
- Format-Auto-Detect (Opus/WebM, Safari nimmt M4A)
- Wake Lock damit iOS nicht ins Sleep geht
- Aufnahme-Datei landet als File-Objekt im Upload-Flow
- Beim Submit: aufgenommene Datei bevorzugt vor File-Input

PWA:
- manifest.webmanifest, icon.svg + PNG-Icons (192/512)
- apple-mobile-web-app-capable + theme-color
- "Zum Home-Bildschirm" hinzufügen verwandelt Seite in App

Mobile-Layout:
- Single-Column ab 700px, größere Buttons
- Header stapelt sich vertikal
- Tabelle scrollt horizontal statt zu zerquetschen
- Safe-Area-Insets für iPhone-Notch

HTTPS (Pflicht für Mikrofon-Zugriff):
- install.sh erzeugt selbst-signiertes Cert (4096 Bit, 825 Tage, mit
  SAN aus Hostname + LAN-IP)
- Nginx: HTTP → HTTPS Redirect, Cert unter /ssl/voice-agent.crt
  ungesichert abrufbar (für bequemen iOS-Profil-Import)

Backend:
- ALLOWED_EXTS um 'webm' erweitert
2026-05-14 03:43:51 +00:00

170 lines
6.3 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#0f1115" />
<title>Voice-Agent — Transkription</title>
<link rel="stylesheet" href="/static/style.css" />
<link rel="manifest" href="/static/manifest.webmanifest" />
<link rel="icon" href="/static/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/static/icon-192.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Voice-Agent" />
<meta name="mobile-web-app-capable" content="yes" />
</head>
<body>
<header>
<div class="header-row">
<div class="header-left">
<h1>Voice-Agent</h1>
<p class="sub">Sitzungsaufnahmen automatisch transkribieren und protokollieren</p>
</div>
<div class="header-right">
<div id="mac-status" class="badge">Mac-Backend: prüfe …</div>
<div id="user-bar" class="user-bar hidden">
<span class="muted">Angemeldet als</span>
<strong id="user-name"></strong>
<span id="user-admin-badge" class="badge admin-badge hidden">Admin</span>
<span id="header-profile-wrap" class="header-profile-wrap hidden">
<span class="muted">·</span>
<span class="muted">Profil:</span>
<select id="header-profile" class="header-profile" title="Standard-Profil — wird sofort gespeichert"></select>
</span>
<button type="button" id="logout-btn" class="btn-link">Abmelden</button>
</div>
</div>
</div>
</header>
<!-- Auth-Overlay (Login oder Erst-Setup) -->
<div id="auth-overlay" class="overlay hidden">
<div class="auth-card">
<h2 id="auth-title">Anmelden</h2>
<p id="auth-info" class="muted hidden"></p>
<form id="auth-form">
<label>
Benutzername
<input type="text" id="auth-username" autocomplete="username" required />
</label>
<label>
Passwort
<input type="password" id="auth-password" autocomplete="current-password" required />
</label>
<button type="submit" id="auth-submit">Anmelden</button>
</form>
<p id="auth-msg" class="msg"></p>
</div>
</div>
<main id="app" class="hidden">
<section class="card">
<h2>Neue Aufnahme hochladen</h2>
<form id="upload-form">
<label>
Titel (optional)
<input type="text" name="title" id="title" placeholder="z. B. Monatsbesprechung" />
</label>
<label id="profile-label" class="hidden">
Verarbeitungs-Profil
<select id="profile-select"></select>
<small class="muted">Standard: <span id="default-profile-name"></span> (in Einstellungen änderbar)</small>
</label>
<div class="audio-source">
<label class="audio-file-label">
<span>Audiodatei wählen <small class="muted">(MP3, WAV, M4A, MP4, OGG, FLAC, WebM)</small></span>
<input type="file" name="audio" id="audio" accept="audio/*,.mp3,.wav,.m4a,.mp4,.ogg,.flac,.webm" />
</label>
<div class="audio-or muted">— oder —</div>
<button type="button" id="record-toggle" class="btn-record">
<span class="rec-dot"></span>
<span id="record-toggle-label">Aufnahme starten</span>
</button>
</div>
<div id="record-pane" class="record-pane hidden">
<div class="record-timer" id="record-timer">00:00</div>
<div class="record-status" id="record-status">Aufnahme läuft</div>
<div class="record-controls">
<button type="button" id="rec-pause" class="btn-record-ctrl">Pause</button>
<button type="button" id="rec-stop" class="btn-record-ctrl danger">Stoppen</button>
</div>
</div>
<div id="record-result" class="record-result hidden">
<span class="muted">Aufnahme bereit:</span>
<span id="record-filename"></span>
<button type="button" id="record-discard" class="btn-link btn-danger">Verwerfen</button>
</div>
<button type="submit" id="submit-btn">Hochladen &amp; verarbeiten</button>
</form>
<div id="upload-progress" class="hidden">
<div class="bar"><div class="bar-fill"></div></div>
<small class="muted">Upload läuft …</small>
</div>
<p id="upload-msg" class="msg"></p>
</section>
<section class="card">
<h2>Jobs</h2>
<table id="jobs-table">
<thead>
<tr>
<th class="col-id">#</th>
<th class="col-title">Titel / Datei</th>
<th class="col-status">Status</th>
<th class="col-progress">Fortschritt</th>
<th class="col-updated">Aktualisiert</th>
<th class="col-dl">Downloads</th>
</tr>
</thead>
<tbody id="jobs-body">
<tr><td colspan="6" class="muted">Noch keine Jobs.</td></tr>
</tbody>
</table>
</section>
<!-- Nur sichtbar für Admins -->
<section id="admin-card" class="card hidden">
<h2>Benutzer verwalten</h2>
<form id="user-form" class="user-form">
<label>
Benutzername
<input type="text" id="new-username" autocomplete="off" required pattern="[A-Za-z0-9._@\-]+" />
</label>
<label>
Passwort (min. 8 Zeichen)
<input type="password" id="new-password" autocomplete="new-password" required minlength="8" />
</label>
<label class="inline-check">
<input type="checkbox" id="new-is-admin" />
Admin-Rechte
</label>
<button type="submit">Anlegen</button>
</form>
<p id="user-msg" class="msg"></p>
<table id="users-table">
<thead>
<tr>
<th>#</th>
<th>Benutzer</th>
<th>Rolle</th>
<th>Angelegt</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="users-body"></tbody>
</table>
</section>
</main>
<footer>
<small class="muted">DSGVO-konform, alles lokal verarbeitet.</small>
</footer>
<script src="/static/app.js"></script>
</body>
</html>