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/meeting_room.py
Benjamin Admin 21a844cb8a 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

267 lines
8.3 KiB
Python

"""
Meetings Module - Meeting Room Page
Meeting room with embedded Jitsi
"""
from ..templates import ICONS, render_base_page
def meeting_room(room_name: str) -> str:
"""Meeting room with embedded Jitsi"""
content = f'''
<div class="page-header">
<div>
<h1 class="page-title">Meeting: {room_name}</h1>
<p class="page-subtitle">Verbunden mit BreakPilot Meet</p>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="toggleFullscreen()">
Vollbild
</button>
<button class="btn btn-danger" onclick="leaveMeeting()">
{ICONS['phone_off']} Verlassen
</button>
</div>
</div>
<!-- Video Container -->
<div class="video-container" id="jitsiContainer">
<div class="video-placeholder">
{ICONS['video']}
<p>Meeting wird geladen...</p>
</div>
</div>
<!-- Meeting Controls -->
<div class="meeting-controls">
<button class="control-btn active" id="micBtn" onclick="toggleMic()">
{ICONS['mic']}
</button>
<button class="control-btn active" id="videoBtn" onclick="toggleVideo()">
{ICONS['video']}
</button>
<button class="control-btn inactive" id="screenBtn" onclick="toggleScreenShare()">
{ICONS['screen_share']}
</button>
<button class="control-btn inactive" id="chatBtn" onclick="toggleChat()">
{ICONS['chat']}
</button>
<button class="control-btn inactive" id="recordBtn" onclick="toggleRecording()">
{ICONS['record']}
</button>
<button class="control-btn danger" onclick="leaveMeeting()">
{ICONS['phone_off']}
</button>
</div>
<!-- Participants and Chat -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
<!-- Participants Panel -->
<div class="participants-panel">
<div style="font-weight: 600; margin-bottom: 1rem;">Teilnehmer (0)</div>
<div id="participantsList">
<div class="participant-item">
<div class="participant-avatar">Sie</div>
<div class="participant-info">
<div class="participant-name">Sie (Moderator)</div>
<div class="participant-role">Host</div>
</div>
<div class="participant-status">
<span class="status-indicator mic-on" title="Mikrofon an"></span>
<span class="status-indicator video-on" title="Kamera an"></span>
</div>
</div>
</div>
</div>
<!-- Chat Panel -->
<div class="chat-panel">
<div class="chat-header">Chat</div>
<div class="chat-messages" id="chatMessages">
<div class="chat-message">
<div class="chat-message-header">
<span class="chat-message-sender">System</span>
<span class="chat-message-time">Jetzt</span>
</div>
<div class="chat-message-content">Willkommen im Meeting!</div>
</div>
</div>
<div class="chat-input-area">
<input type="text" class="chat-input" id="chatInput" placeholder="Nachricht eingeben..." onkeypress="if(event.key==='Enter')sendMessage()">
<button class="btn btn-primary" onclick="sendMessage()">Senden</button>
</div>
</div>
</div>
<!-- Jitsi Integration Script -->
<script src="https://meet.jit.si/external_api.js"></script>
<script>
let api = null;
let isMuted = false;
let isVideoOff = false;
let isScreenSharing = false;
let isRecording = false;
// Initialize Jitsi
function initJitsi() {{
const domain = 'meet.jit.si';
const options = {{
roomName: 'BreakPilot-{room_name}',
width: '100%',
height: '100%',
parentNode: document.getElementById('jitsiContainer'),
configOverwrite: {{
startWithAudioMuted: false,
startWithVideoMuted: false,
enableWelcomePage: false,
prejoinPageEnabled: false,
}},
interfaceConfigOverwrite: {{
TOOLBAR_BUTTONS: [],
SHOW_JITSI_WATERMARK: false,
SHOW_BRAND_WATERMARK: false,
SHOW_WATERMARK_FOR_GUESTS: false,
DEFAULT_BACKGROUND: '#1a1a1a',
DISABLE_VIDEO_BACKGROUND: true,
}},
userInfo: {{
displayName: 'Lehrer'
}}
}};
api = new JitsiMeetExternalAPI(domain, options);
// Event Listeners
api.addListener('participantJoined', (participant) => {{
console.log('Participant joined:', participant);
updateParticipantsList();
}});
api.addListener('participantLeft', (participant) => {{
console.log('Participant left:', participant);
updateParticipantsList();
}});
api.addListener('readyToClose', () => {{
window.location.href = '/meetings';
}});
}}
// Control Functions
function toggleMic() {{
if (api) {{
api.executeCommand('toggleAudio');
isMuted = !isMuted;
updateControlButton('micBtn', !isMuted);
}}
}}
function toggleVideo() {{
if (api) {{
api.executeCommand('toggleVideo');
isVideoOff = !isVideoOff;
updateControlButton('videoBtn', !isVideoOff);
}}
}}
function toggleScreenShare() {{
if (api) {{
api.executeCommand('toggleShareScreen');
isScreenSharing = !isScreenSharing;
updateControlButton('screenBtn', isScreenSharing);
}}
}}
function toggleChat() {{
if (api) {{
api.executeCommand('toggleChat');
}}
}}
function toggleRecording() {{
if (api) {{
if (!isRecording) {{
api.executeCommand('startRecording', {{
mode: 'file'
}});
}} else {{
api.executeCommand('stopRecording', 'file');
}}
isRecording = !isRecording;
updateControlButton('recordBtn', isRecording);
}}
}}
function updateControlButton(btnId, isActive) {{
const btn = document.getElementById(btnId);
if (isActive) {{
btn.classList.remove('inactive');
btn.classList.add('active');
}} else {{
btn.classList.remove('active');
btn.classList.add('inactive');
}}
}}
function leaveMeeting() {{
if (confirm('Meeting wirklich verlassen?')) {{
if (api) {{
api.executeCommand('hangup');
}}
window.location.href = '/meetings';
}}
}}
function toggleFullscreen() {{
const container = document.getElementById('jitsiContainer');
if (document.fullscreenElement) {{
document.exitFullscreen();
}} else {{
container.requestFullscreen();
}}
}}
// Chat Functions
function sendMessage() {{
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (message && api) {{
api.executeCommand('sendChatMessage', message);
addChatMessage('Sie', message);
input.value = '';
}}
}}
function addChatMessage(sender, text) {{
const container = document.getElementById('chatMessages');
const now = new Date().toLocaleTimeString('de-DE', {{ hour: '2-digit', minute: '2-digit' }});
container.innerHTML += `
<div class="chat-message">
<div class="chat-message-header">
<span class="chat-message-sender">${{sender}}</span>
<span class="chat-message-time">${{now}}</span>
</div>
<div class="chat-message-content">${{text}}</div>
</div>
`;
container.scrollTop = container.scrollHeight;
}}
function updateParticipantsList() {{
if (api) {{
const participants = api.getParticipantsInfo();
console.log('Participants:', participants);
// Update UI with participants
}}
}}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initJitsi);
</script>
'''
return render_base_page(f"Meeting: {room_name}", content, "active")