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>
This commit is contained in:
267
backend/frontend/meetings/pages/trainings.py
Normal file
267
backend/frontend/meetings/pages/trainings.py
Normal file
@@ -0,0 +1,267 @@
|
||||
"""
|
||||
Meetings Module - Trainings Page
|
||||
Training sessions management
|
||||
"""
|
||||
|
||||
from ..templates import ICONS, render_base_page
|
||||
|
||||
|
||||
def trainings_page() -> str:
|
||||
"""Training sessions management"""
|
||||
content = f'''
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Schulungen</h1>
|
||||
<p class="page-subtitle">Schulungen und Workshops verwalten</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="openTrainingModal()">
|
||||
{ICONS['plus']} Neue Schulung
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Training Cards -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="training-card">
|
||||
<div class="training-card-header">
|
||||
<div class="training-card-title">Go Grundlagen Workshop</div>
|
||||
<div class="training-card-subtitle">Einführung in die Go-Programmierung</div>
|
||||
</div>
|
||||
<div class="training-card-body">
|
||||
<div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem; color: var(--bp-text-muted);">
|
||||
{ICONS['calendar']} 18.12.2025, 14:00
|
||||
</span>
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem; color: var(--bp-text-muted);">
|
||||
{ICONS['clock']} 120 Min
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem;">
|
||||
<span class="meeting-badge badge-scheduled">Geplant</span>
|
||||
<span class="meeting-badge" style="background: var(--bp-accent-soft); color: var(--bp-accent);">Aufzeichnung</span>
|
||||
</div>
|
||||
<p style="font-size: 0.875rem; color: var(--bp-text-muted);">
|
||||
12 von 20 Teilnehmern angemeldet
|
||||
</p>
|
||||
</div>
|
||||
<div class="training-card-footer">
|
||||
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">Trainer: Max Mustermann</span>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-secondary" onclick="editTraining('go-basics')">{ICONS['settings']}</button>
|
||||
<button class="btn btn-primary" onclick="startTraining('go-basics')">Starten</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="training-card">
|
||||
<div class="training-card-header">
|
||||
<div class="training-card-title">PWA Entwicklung</div>
|
||||
<div class="training-card-subtitle">Progressive Web Apps mit JavaScript</div>
|
||||
</div>
|
||||
<div class="training-card-body">
|
||||
<div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem; color: var(--bp-text-muted);">
|
||||
{ICONS['calendar']} 20.12.2025, 10:00
|
||||
</span>
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem; color: var(--bp-text-muted);">
|
||||
{ICONS['clock']} 180 Min
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem;">
|
||||
<span class="meeting-badge badge-scheduled">Geplant</span>
|
||||
<span class="meeting-badge" style="background: var(--bp-accent-soft); color: var(--bp-accent);">Aufzeichnung</span>
|
||||
<span class="meeting-badge" style="background: var(--bp-info); color: white;">Breakout</span>
|
||||
</div>
|
||||
<p style="font-size: 0.875rem; color: var(--bp-text-muted);">
|
||||
8 von 15 Teilnehmern angemeldet
|
||||
</p>
|
||||
</div>
|
||||
<div class="training-card-footer">
|
||||
<span style="font-size: 0.875rem; color: var(--bp-text-muted);">Trainer: Lisa Schmidt</span>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-secondary" onclick="editTraining('pwa-dev')">{ICONS['settings']}</button>
|
||||
<button class="btn btn-primary" onclick="startTraining('pwa-dev')">Starten</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Past Trainings -->
|
||||
<div class="card" style="margin-top: 2rem;">
|
||||
<div class="card-header">
|
||||
<span class="card-title">Vergangene Schulungen</span>
|
||||
</div>
|
||||
<div class="meeting-list">
|
||||
<div class="meeting-item">
|
||||
<div class="meeting-time">
|
||||
<div class="meeting-time-value">10:00</div>
|
||||
<div class="meeting-time-date">10.12.</div>
|
||||
</div>
|
||||
<div class="meeting-info">
|
||||
<div class="meeting-title">Docker Grundlagen</div>
|
||||
<div class="meeting-meta">
|
||||
<span>{ICONS['clock']} 90 Min</span>
|
||||
<span>{ICONS['users']} 15 Teilnehmer</span>
|
||||
<span>{ICONS['record']} Aufzeichnung verfügbar</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="meeting-badge badge-ended">Beendet</span>
|
||||
<div class="meeting-actions">
|
||||
<button class="btn btn-secondary" onclick="viewRecording('docker-basics')">{ICONS['play']} Ansehen</button>
|
||||
<button class="btn-icon" onclick="downloadRecording('docker-basics')">{ICONS['download']}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Training Modal -->
|
||||
<div class="modal-overlay" id="trainingModal">
|
||||
<div class="modal" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Neue Schulung erstellen</h2>
|
||||
<button class="modal-close" onclick="closeTrainingModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titel</label>
|
||||
<input type="text" class="form-input" id="trainingTitle" placeholder="Schulungstitel">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung</label>
|
||||
<textarea class="form-textarea" id="trainingDescription" placeholder="Beschreibung der Schulung..."></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Datum</label>
|
||||
<input type="date" class="form-input" id="trainingDate">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Uhrzeit</label>
|
||||
<input type="time" class="form-input" id="trainingTime">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Dauer (Minuten)</label>
|
||||
<select class="form-select" id="trainingDuration">
|
||||
<option value="60">60 Minuten</option>
|
||||
<option value="90">90 Minuten</option>
|
||||
<option value="120" selected>120 Minuten</option>
|
||||
<option value="180">180 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max. Teilnehmer</label>
|
||||
<input type="number" class="form-input" id="trainingMaxParticipants" value="20">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Trainer</label>
|
||||
<input type="text" class="form-input" id="trainingTrainer" placeholder="Name des Trainers">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="trainingRecording" checked> Aufzeichnung aktivieren
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="trainingBreakout"> Breakout-Rooms aktivieren
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="trainingLobby" checked> Warteraum aktivieren (Trainer lässt Teilnehmer ein)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeTrainingModal()">Abbrechen</button>
|
||||
<button class="btn btn-primary" onclick="createTraining()">Schulung erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openTrainingModal() {{
|
||||
document.getElementById('trainingModal').classList.add('active');
|
||||
}}
|
||||
|
||||
function closeTrainingModal() {{
|
||||
document.getElementById('trainingModal').classList.remove('active');
|
||||
}}
|
||||
|
||||
async function createTraining() {{
|
||||
const title = document.getElementById('trainingTitle').value;
|
||||
const description = document.getElementById('trainingDescription').value;
|
||||
const date = document.getElementById('trainingDate').value;
|
||||
const time = document.getElementById('trainingTime').value;
|
||||
const duration = document.getElementById('trainingDuration').value;
|
||||
const maxParticipants = document.getElementById('trainingMaxParticipants').value;
|
||||
const trainer = document.getElementById('trainingTrainer').value;
|
||||
const enableRecording = document.getElementById('trainingRecording').checked;
|
||||
const enableBreakout = document.getElementById('trainingBreakout').checked;
|
||||
const enableLobby = document.getElementById('trainingLobby').checked;
|
||||
|
||||
if (!title || !date || !time || !trainer) {{
|
||||
alert('Bitte füllen Sie alle Pflichtfelder aus.');
|
||||
return;
|
||||
}}
|
||||
|
||||
const payload = {{
|
||||
title,
|
||||
description,
|
||||
scheduled_at: `${{date}}T${{time}}`,
|
||||
duration: parseInt(duration),
|
||||
max_participants: parseInt(maxParticipants),
|
||||
trainer,
|
||||
config: {{
|
||||
enable_recording: enableRecording,
|
||||
enable_breakout: enableBreakout,
|
||||
enable_lobby: enableLobby,
|
||||
start_with_audio_muted: true
|
||||
}}
|
||||
}};
|
||||
|
||||
try {{
|
||||
const response = await fetch('/api/meetings/training', {{
|
||||
method: 'POST',
|
||||
headers: {{ 'Content-Type': 'application/json' }},
|
||||
body: JSON.stringify(payload)
|
||||
}});
|
||||
|
||||
if (response.ok) {{
|
||||
alert('Schulung erfolgreich erstellt!');
|
||||
closeTrainingModal();
|
||||
location.reload();
|
||||
}}
|
||||
}} catch (error) {{
|
||||
console.error('Error:', error);
|
||||
}}
|
||||
}}
|
||||
|
||||
function startTraining(trainingId) {{
|
||||
window.location.href = '/meetings/room/training-' + trainingId;
|
||||
}}
|
||||
|
||||
function editTraining(trainingId) {{
|
||||
console.log('Edit training:', trainingId);
|
||||
}}
|
||||
|
||||
function viewRecording(trainingId) {{
|
||||
window.location.href = '/meetings/recordings/' + trainingId;
|
||||
}}
|
||||
|
||||
function downloadRecording(trainingId) {{
|
||||
console.log('Download recording:', trainingId);
|
||||
}}
|
||||
</script>
|
||||
'''
|
||||
|
||||
return render_base_page("Schulungen", content, "trainings")
|
||||
Reference in New Issue
Block a user