This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/meetings/pages/recordings.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

285 lines
11 KiB
Python

"""
Meetings Module - Recordings Page
Recordings and transcripts management
"""
from ..templates import ICONS, render_base_page
def recordings_page() -> str:
"""Recordings and transcripts management"""
content = f'''
<div class="page-header">
<div>
<h1 class="page-title">Aufzeichnungen</h1>
<p class="page-subtitle">Aufzeichnungen und Protokolle verwalten</p>
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="filterRecordings('all')">Alle</button>
<button class="tab" onclick="filterRecordings('trainings')">Schulungen</button>
<button class="tab" onclick="filterRecordings('meetings')">Meetings</button>
</div>
<!-- Recordings List -->
<div class="recording-list">
<div class="card" style="margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 1rem;">
<div class="card-icon primary">{ICONS['record']}</div>
<div style="flex: 1;">
<div style="font-weight: 600; margin-bottom: 0.25rem;">Docker Grundlagen Schulung</div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">
10.12.2025, 10:00 - 11:30 | 1:30:00 | 156 MB
</div>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="playRecording('docker-basics')">
{ICONS['play']} Abspielen
</button>
<button class="btn btn-secondary" onclick="viewTranscript('docker-basics')">
{ICONS['file_text']} Protokoll
</button>
<button class="btn-icon" onclick="downloadRecording('docker-basics')">
{ICONS['download']}
</button>
<button class="btn-icon" onclick="deleteRecording('docker-basics')">
{ICONS['trash']}
</button>
</div>
</div>
</div>
<div class="card" style="margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 1rem;">
<div class="card-icon primary">{ICONS['record']}</div>
<div style="flex: 1;">
<div style="font-weight: 600; margin-bottom: 0.25rem;">Team-Meeting KW 49</div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">
06.12.2025, 14:00 - 15:00 | 1:00:00 | 98 MB
</div>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="playRecording('team-kw49')">
{ICONS['play']} Abspielen
</button>
<button class="btn btn-secondary" onclick="viewTranscript('team-kw49')">
{ICONS['file_text']} Protokoll
</button>
<button class="btn-icon" onclick="downloadRecording('team-kw49')">
{ICONS['download']}
</button>
<button class="btn-icon" onclick="deleteRecording('team-kw49')">
{ICONS['trash']}
</button>
</div>
</div>
</div>
<div class="card" style="margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 1rem;">
<div class="card-icon primary">{ICONS['record']}</div>
<div style="flex: 1;">
<div style="font-weight: 600; margin-bottom: 0.25rem;">Elterngespräch - Max Müller</div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">
02.12.2025, 16:00 - 16:30 | 0:28:00 | 42 MB
</div>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="playRecording('parent-mueller')">
{ICONS['play']} Abspielen
</button>
<button class="btn btn-secondary" onclick="viewTranscript('parent-mueller')">
{ICONS['file_text']} Protokoll
</button>
<button class="btn-icon" onclick="downloadRecording('parent-mueller')">
{ICONS['download']}
</button>
<button class="btn-icon" onclick="deleteRecording('parent-mueller')">
{ICONS['trash']}
</button>
</div>
</div>
</div>
</div>
<!-- Storage Info -->
<div class="card" style="margin-top: 2rem;">
<div class="card-header">
<span class="card-title">Speicherplatz</span>
</div>
<div style="margin-bottom: 1rem;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span>296 MB von 10 GB verwendet</span>
<span>3%</span>
</div>
<div style="background: var(--bp-bg); border-radius: 4px; height: 8px; overflow: hidden;">
<div style="background: var(--bp-primary); width: 3%; height: 100%;"></div>
</div>
</div>
<p style="font-size: 0.875rem; color: var(--bp-text-muted);">
3 Aufzeichnungen | Älteste Aufzeichnung: 02.12.2025
</p>
</div>
<script>
function filterRecordings(filter) {{
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
console.log('Filter:', filter);
}}
function playRecording(recordingId) {{
window.location.href = '/meetings/recordings/' + recordingId + '/play';
}}
function viewTranscript(recordingId) {{
window.location.href = '/meetings/recordings/' + recordingId + '/transcript';
}}
function downloadRecording(recordingId) {{
window.location.href = '/api/recordings/' + recordingId + '/download';
}}
function deleteRecording(recordingId) {{
if (confirm('Aufzeichnung wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) {{
fetch('/api/recordings/' + recordingId, {{ method: 'DELETE' }})
.then(() => location.reload());
}}
}}
</script>
'''
return render_base_page("Aufzeichnungen", content, "recordings")
def play_recording(recording_id: str) -> str:
"""Play a recording"""
content = f'''
<div class="page-header">
<div>
<h1 class="page-title">Aufzeichnung abspielen</h1>
<p class="page-subtitle">{recording_id}</p>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="downloadRecording()">
{ICONS['download']} Herunterladen
</button>
<a href="/meetings/recordings" class="btn btn-secondary">Zurück</a>
</div>
</div>
<div class="video-container">
<div class="video-placeholder">
{ICONS['play']}
<p>Aufzeichnung wird geladen...</p>
<p style="font-size: 0.875rem;">Recording ID: {recording_id}</p>
</div>
</div>
<!-- Recording Info -->
<div class="card">
<div class="card-header">
<span class="card-title">Details</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
<div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">Datum</div>
<div style="font-weight: 600;">10.12.2025, 10:00</div>
</div>
<div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">Dauer</div>
<div style="font-weight: 600;">1:30:00</div>
</div>
<div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">Größe</div>
<div style="font-weight: 600;">156 MB</div>
</div>
<div>
<div style="font-size: 0.875rem; color: var(--bp-text-muted);">Teilnehmer</div>
<div style="font-weight: 600;">15</div>
</div>
</div>
</div>
<script>
function downloadRecording() {{
window.location.href = '/api/recordings/{recording_id}/download';
}}
</script>
'''
return render_base_page("Aufzeichnung", content, "recordings")
def view_transcript(recording_id: str) -> str:
"""View recording transcript"""
content = f'''
<div class="page-header">
<div>
<h1 class="page-title">Protokoll</h1>
<p class="page-subtitle">{recording_id}</p>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="downloadTranscript()">
{ICONS['download']} Als PDF exportieren
</button>
<a href="/meetings/recordings" class="btn btn-secondary">Zurück</a>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">Transkript</span>
</div>
<div style="margin-top: 1rem;">
<div style="margin-bottom: 1rem; padding: 1rem; background: var(--bp-bg); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span style="font-weight: 600;">Max Trainer</span>
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">00:00:15</span>
</div>
<p style="font-size: 0.875rem;">Willkommen zur Docker Grundlagen Schulung. Heute werden wir die Basics von Containern und Images besprechen.</p>
</div>
<div style="margin-bottom: 1rem; padding: 1rem; background: var(--bp-bg); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span style="font-weight: 600;">Max Trainer</span>
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">00:02:30</span>
</div>
<p style="font-size: 0.875rem;">Docker ist eine Open-Source-Plattform, die es ermöglicht, Anwendungen in Containern zu entwickeln, zu versenden und auszuführen.</p>
</div>
<div style="margin-bottom: 1rem; padding: 1rem; background: var(--bp-bg); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span style="font-weight: 600;">Teilnehmer 1</span>
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">00:05:45</span>
</div>
<p style="font-size: 0.875rem;">Was ist der Unterschied zwischen einem Container und einer virtuellen Maschine?</p>
</div>
<div style="margin-bottom: 1rem; padding: 1rem; background: var(--bp-bg); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span style="font-weight: 600;">Max Trainer</span>
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">00:06:00</span>
</div>
<p style="font-size: 0.875rem;">Gute Frage! Container teilen sich den Kernel des Host-Systems, während VMs einen vollständigen Hypervisor und ein eigenes Betriebssystem benötigen...</p>
</div>
<div style="text-align: center; padding: 2rem; color: var(--bp-text-muted);">
<p>... Transkript wird fortgesetzt ...</p>
</div>
</div>
</div>
<script>
function downloadTranscript() {{
alert('PDF-Export wird vorbereitet...');
// In production: window.location.href = '/api/recordings/{recording_id}/transcript/pdf';
}}
</script>
'''
return render_base_page("Protokoll", content, "recordings")