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>
267 lines
8.3 KiB
Python
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")
|