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:
266
backend/frontend/meetings/pages/meeting_room.py
Normal file
266
backend/frontend/meetings/pages/meeting_room.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user