Refactor: Admin-Login aus allen Plugins entfernt

Duplizierte Auth-Logik aus Notifications, Game Library und Streaming
Plugins komplett entfernt (-251 Zeilen). Alle Plugins nutzen jetzt
die zentrale Auth aus core/auth.ts via isAdmin Prop.

Admin-Buttons (Settings-Zahnrad) erscheinen nur noch wenn global
eingeloggt. Kein separater Login pro Tab mehr noetig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-09 11:22:07 +01:00
parent b3080fb763
commit f27093b87a
5 changed files with 28 additions and 251 deletions

View file

@ -89,7 +89,7 @@ function formatDate(iso: string): string {
COMPONENT
*/
export default function GameLibraryTab({ data }: { data: any }) {
export default function GameLibraryTab({ data, isAdmin: isAdminProp = false }: { data: any; isAdmin?: boolean }) {
// ── State ──
const [profiles, setProfiles] = useState<ProfileSummary[]>([]);
const [mode, setMode] = useState<'overview' | 'user' | 'common'>('overview');
@ -111,11 +111,9 @@ export default function GameLibraryTab({ data }: { data: any }) {
// ── Admin state ──
const [showAdmin, setShowAdmin] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const [adminPwd, setAdminPwd] = useState('');
const isAdmin = isAdminProp;
const [adminProfiles, setAdminProfiles] = useState<any[]>([]);
const [adminLoading, setAdminLoading] = useState(false);
const [adminError, setAdminError] = useState('');
// ── SSE data sync ──
useEffect(() => {
@ -133,42 +131,6 @@ export default function GameLibraryTab({ data }: { data: any }) {
} catch { /* silent */ }
}, []);
// ── Admin: check login status on mount ──
useEffect(() => {
fetch('/api/game-library/admin/status', { credentials: 'include' })
.then(r => r.json())
.then(d => setIsAdmin(d.admin === true))
.catch(() => {});
}, []);
// ── Admin: login ──
const adminLogin = useCallback(async () => {
setAdminError('');
try {
const resp = await fetch('/api/game-library/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: adminPwd }),
credentials: 'include',
});
if (resp.ok) {
setIsAdmin(true);
setAdminPwd('');
} else {
const d = await resp.json();
setAdminError(d.error || 'Fehler');
}
} catch {
setAdminError('Verbindung fehlgeschlagen');
}
}, [adminPwd]);
// ── Admin: logout ──
const adminLogout = useCallback(async () => {
await fetch('/api/game-library/admin/logout', { method: 'POST', credentials: 'include' });
setIsAdmin(false);
setShowAdmin(false);
}, []);
// ── Admin: load profiles ──
const loadAdminProfiles = useCallback(async () => {
@ -552,9 +514,11 @@ export default function GameLibraryTab({ data }: { data: any }) {
</button>
)}
<div className="gl-login-bar-spacer" />
<button className="gl-admin-btn" onClick={openAdmin} title="Admin Panel">
&#x2699;&#xFE0F;
</button>
{isAdmin && (
<button className="gl-admin-btn" onClick={openAdmin} title="Admin Panel">
&#x2699;&#xFE0F;
</button>
)}
</div>
{/* ── Profile Chips ── */}
@ -990,29 +954,10 @@ export default function GameLibraryTab({ data }: { data: any }) {
<button className="gl-admin-close" onClick={() => setShowAdmin(false)}>&#x2715;</button>
</div>
{!isAdmin ? (
<div className="gl-admin-login">
<p>Admin-Passwort eingeben:</p>
<div className="gl-admin-login-row">
<input
type="password"
className="gl-dialog-input"
placeholder="Passwort"
value={adminPwd}
onChange={e => setAdminPwd(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') adminLogin(); }}
autoFocus
/>
<button className="gl-admin-login-btn" onClick={adminLogin}>Login</button>
</div>
{adminError && <p className="gl-dialog-status error">{adminError}</p>}
</div>
) : (
<div className="gl-admin-content">
<div className="gl-admin-toolbar">
<span className="gl-admin-status-text">&#x2705; Eingeloggt als Admin</span>
<button className="gl-admin-refresh-btn" onClick={loadAdminProfiles}>&#x21bb; Aktualisieren</button>
<button className="gl-admin-logout-btn" onClick={adminLogout}>Logout</button>
</div>
{adminLoading ? (
@ -1044,7 +989,6 @@ export default function GameLibraryTab({ data }: { data: any }) {
</div>
)}
</div>
)}
</div>
</div>
)}

View file

@ -1000,13 +1000,15 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp = false }: So
)}
</div>
)}
<button
className={`admin-btn-icon ${isAdmin ? 'active' : ''}`}
onClick={() => setShowAdmin(true)}
title="Admin"
>
<span className="material-icons">settings</span>
</button>
{isAdmin && (
<button
className="admin-btn-icon active"
onClick={() => setShowAdmin(true)}
title="Admin"
>
<span className="material-icons">settings</span>
</button>
)}
</div>
</header>

View file

@ -56,7 +56,7 @@ const QUALITY_PRESETS = [
// ── Component ──
export default function StreamingTab({ data }: { data: any }) {
export default function StreamingTab({ data, isAdmin: isAdminProp = false }: { data: any; isAdmin?: boolean }) {
// ── State ──
const [streams, setStreams] = useState<StreamInfo[]>([]);
const [userName, setUserName] = useState(() => localStorage.getItem('streaming_name') || '');
@ -75,9 +75,7 @@ export default function StreamingTab({ data }: { data: any }) {
// ── Admin / Notification Config ──
const [showAdmin, setShowAdmin] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const [adminPwd, setAdminPwd] = useState('');
const [adminError, setAdminError] = useState('');
const isAdmin = isAdminProp;
const [availableChannels, setAvailableChannels] = useState<Array<{ channelId: string; channelName: string; guildId: string; guildName: string }>>([]);
const [notifyConfig, setNotifyConfig] = useState<Array<{ channelId: string; channelName: string; guildId: string; guildName: string; events: string[] }>>([]);
const [configLoading, setConfigLoading] = useState(false);
@ -138,12 +136,8 @@ export default function StreamingTab({ data }: { data: any }) {
return () => document.removeEventListener('click', handler);
}, [openMenu]);
// Check admin status on mount
// Load notification bot status on mount
useEffect(() => {
fetch('/api/notifications/admin/status', { credentials: 'include' })
.then(r => r.json())
.then(d => setIsAdmin(d.admin === true))
.catch(() => {});
fetch('/api/notifications/status')
.then(r => r.json())
.then(d => setNotifyStatus(d))
@ -610,34 +604,6 @@ export default function StreamingTab({ data }: { data: any }) {
setOpenMenu(null);
}, [buildStreamLink]);
// ── Admin functions ──
const adminLogin = useCallback(async () => {
setAdminError('');
try {
const resp = await fetch('/api/notifications/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: adminPwd }),
credentials: 'include',
});
if (resp.ok) {
setIsAdmin(true);
setAdminPwd('');
loadNotifyConfig();
} else {
const d = await resp.json();
setAdminError(d.error || 'Fehler');
}
} catch {
setAdminError('Verbindung fehlgeschlagen');
}
}, [adminPwd]);
const adminLogout = useCallback(async () => {
await fetch('/api/notifications/admin/logout', { method: 'POST', credentials: 'include' });
setIsAdmin(false);
setShowAdmin(false);
}, []);
const loadNotifyConfig = useCallback(async () => {
setConfigLoading(true);
@ -796,9 +762,11 @@ export default function StreamingTab({ data }: { data: any }) {
{starting ? 'Starte...' : '\u{1F5A5}\uFE0F Stream starten'}
</button>
)}
<button className="stream-admin-btn" onClick={openAdmin} title="Notification Einstellungen">
{'\u2699\uFE0F'}
</button>
{isAdmin && (
<button className="stream-admin-btn" onClick={openAdmin} title="Notification Einstellungen">
{'\u2699\uFE0F'}
</button>
)}
</div>
{streams.length === 0 && !isBroadcasting ? (
@ -912,24 +880,6 @@ export default function StreamingTab({ data }: { data: any }) {
<button className="stream-admin-close" onClick={() => setShowAdmin(false)}>{'\u2715'}</button>
</div>
{!isAdmin ? (
<div className="stream-admin-login">
<p>Admin-Passwort eingeben:</p>
<div className="stream-admin-login-row">
<input
type="password"
className="stream-input"
placeholder="Passwort"
value={adminPwd}
onChange={e => setAdminPwd(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') adminLogin(); }}
autoFocus
/>
<button className="stream-btn" onClick={adminLogin}>Login</button>
</div>
{adminError && <p className="stream-admin-error">{adminError}</p>}
</div>
) : (
<div className="stream-admin-content">
<div className="stream-admin-toolbar">
<span className="stream-admin-status">
@ -937,7 +887,6 @@ export default function StreamingTab({ data }: { data: any }) {
? <>{'\u2705'} Bot online: <b>{notifyStatus.botTag}</b></>
: <>{'\u26A0\uFE0F'} Bot offline <code>DISCORD_TOKEN_NOTIFICATIONS</code> setzen</>}
</span>
<button className="stream-admin-logout" onClick={adminLogout}>Logout</button>
</div>
{configLoading ? (
@ -993,7 +942,6 @@ export default function StreamingTab({ data }: { data: any }) {
</>
)}
</div>
)}
</div>
</div>
)}