diff --git a/server/routes/admin.js b/server/routes/admin.js index 8a3d1fe..fc6c2f6 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -362,4 +362,28 @@ router.delete('/oauth', authenticateToken, requireAdmin, async (req, res) => { } }); +// ── Room Management (admin only) ──────────────────────────────────────────── + +// GET /api/admin/rooms - List all rooms with owner info +router.get('/rooms', authenticateToken, requireAdmin, async (req, res) => { + try { + const db = getDb(); + const rooms = await db.all(` + SELECT r.id, r.uid, r.name, r.user_id, r.max_participants, r.access_code, + r.mute_on_join, r.record_meeting, r.guest_access, r.presentation_file, + r.created_at, r.updated_at, + COALESCE(NULLIF(u.display_name,''), u.name) as owner_name, + u.email as owner_email, + (SELECT COUNT(*) FROM room_shares rs WHERE rs.room_id = r.id) as share_count + FROM rooms r + JOIN users u ON r.user_id = u.id + ORDER BY r.created_at DESC + `); + res.json({ rooms }); + } catch (err) { + log.admin.error(`List rooms error: ${err.message}`); + res.status(500).json({ error: 'Rooms could not be loaded' }); + } +}); + export default router; diff --git a/src/i18n/de.json b/src/i18n/de.json index 52970f4..923da47 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -484,7 +484,20 @@ "oauthRemoveConfirm": "OAuth-Konfiguration wirklich entfernen? Benutzer können sich dann nicht mehr per SSO anmelden.", "oauthNotConfigured": "OAuth ist noch nicht konfiguriert.", "oauthSave": "OAuth speichern", - "oauthRemove": "OAuth entfernen" + "oauthRemove": "OAuth entfernen", + "roomsTitle": "Raumverwaltung", + "roomsDescription": "Alle Räume der Instanz einsehen, verwalten und bei Bedarf löschen.", + "searchRooms": "Räume suchen...", + "roomName": "Name", + "roomOwner": "Besitzer", + "roomShares": "Geteilt", + "roomCreated": "Erstellt", + "roomView": "Raum öffnen", + "deleteRoom": "Raum löschen", + "deleteRoomConfirm": "Raum \"{name}\" wirklich löschen? Dies kann nicht rückgängig gemacht werden.", + "roomDeleted": "Raum gelöscht", + "roomDeleteFailed": "Raum konnte nicht gelöscht werden", + "noRoomsFound": "Keine Räume vorhanden" }, "notifications": { "bell": "Benachrichtigungen", diff --git a/src/i18n/en.json b/src/i18n/en.json index ff3905a..7cd6bc8 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -484,7 +484,20 @@ "oauthRemoveConfirm": "Really remove OAuth configuration? Users will no longer be able to sign in with SSO.", "oauthNotConfigured": "OAuth is not configured yet.", "oauthSave": "Save OAuth", - "oauthRemove": "Remove OAuth" + "oauthRemove": "Remove OAuth", + "roomsTitle": "Room Management", + "roomsDescription": "View, manage, and delete all rooms on this instance.", + "searchRooms": "Search rooms...", + "roomName": "Name", + "roomOwner": "Owner", + "roomShares": "Shared", + "roomCreated": "Created", + "roomView": "View room", + "deleteRoom": "Delete room", + "deleteRoomConfirm": "Really delete room \"{name}\"? This cannot be undone.", + "roomDeleted": "Room deleted", + "roomDeleteFailed": "Room could not be deleted", + "noRoomsFound": "No rooms found" }, "notifications": { "bell": "Notifications", diff --git a/src/pages/Admin.jsx b/src/pages/Admin.jsx index 912e87c..47a8dcf 100644 --- a/src/pages/Admin.jsx +++ b/src/pages/Admin.jsx @@ -4,7 +4,7 @@ import { Users, Shield, Search, Trash2, ChevronDown, Loader2, MoreVertical, Key, UserCheck, UserX, UserPlus, Mail, Lock, User, Upload, X as XIcon, Image, Type, Palette, Send, Copy, Clock, Check, - ShieldCheck, Globe, Link as LinkIcon, LogIn, + ShieldCheck, Globe, Link as LinkIcon, LogIn, DoorOpen, Eye, ExternalLink, } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { useLanguage } from '../contexts/LanguageContext'; @@ -55,6 +55,11 @@ export default function Admin() { const [oauthForm, setOauthForm] = useState({ issuer: '', clientId: '', clientSecret: '', displayName: 'SSO', autoRegister: true }); const [savingOauth, setSavingOauth] = useState(false); + // Rooms state + const [adminRooms, setAdminRooms] = useState([]); + const [adminRoomsLoading, setAdminRoomsLoading] = useState(true); + const [roomSearch, setRoomSearch] = useState(''); + useEffect(() => { if (user?.role !== 'admin') { navigate('/dashboard'); @@ -63,6 +68,7 @@ export default function Admin() { fetchUsers(); fetchInvites(); fetchOauthConfig(); + fetchAdminRooms(); }, [user]); useEffect(() => { @@ -101,6 +107,29 @@ export default function Admin() { } }; + const fetchAdminRooms = async () => { + setAdminRoomsLoading(true); + try { + const res = await api.get('/admin/rooms'); + setAdminRooms(res.data.rooms); + } catch { + // silently fail + } finally { + setAdminRoomsLoading(false); + } + }; + + const handleAdminDeleteRoom = async (uid, name) => { + if (!confirm(t('admin.deleteRoomConfirm', { name }))) return; + try { + await api.delete(`/rooms/${uid}`); + toast.success(t('admin.roomDeleted')); + fetchAdminRooms(); + } catch (err) { + toast.error(err.response?.data?.error || t('admin.roomDeleteFailed')); + } + }; + const handleRoleChange = async (userId, newRole) => { try { await api.put(`/admin/users/${userId}/role`, { role: newRole }); @@ -790,6 +819,114 @@ export default function Admin() { )} + {/* Room Management */} +
{t('admin.roomsDescription')}
+ + {adminRoomsLoading ? ( +| + {t('admin.roomName')} + | ++ {t('admin.roomOwner')} + | ++ {t('admin.roomShares')} + | ++ {t('admin.roomCreated')} + | ++ {t('admin.actions')} + | +
|---|---|---|---|---|
|
+
+
+ {r.name} +{r.uid} + |
+
+
+
+ {r.owner_name} +{r.owner_email} + |
+ + {r.share_count} + | ++ {new Date(r.created_at).toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US')} + | +
+
+
+
+
+ |
+
{t('admin.noRoomsFound')}
+