Refactor: Zentralisiertes Admin-Login im Top-Menü

- Admin-Login aus 3 Plugins (Soundboard, Streaming, Game Library) entfernt
- Zentraler 🔒/🔓 Button im Header mit Login-Modal
- isAdmin wird als Prop an alle Plugins weitergegeben
- Settings-Buttons (Gear-Icons) nur sichtbar wenn eingeloggt
- Alle Plugins nutzen weiterhin den shared admin-Cookie für Operationen
- Login/Logout-Formulare und Buttons aus Plugin-Panels entfernt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-09 21:59:12 +01:00
parent bccfee3de2
commit e9931d82af
10 changed files with 5077 additions and 5063 deletions

View file

@ -13,7 +13,7 @@ interface PluginInfo {
}
// Plugin tab components
const tabComponents: Record<string, React.FC<{ data: any }>> = {
const tabComponents: Record<string, React.FC<{ data: any; isAdmin?: boolean }>> = {
radio: RadioTab,
soundboard: SoundboardTab,
lolstats: LolstatsTab,
@ -22,7 +22,7 @@ const tabComponents: Record<string, React.FC<{ data: any }>> = {
'game-library': GameLibraryTab,
};
export function registerTab(pluginName: string, component: React.FC<{ data: any }>) {
export function registerTab(pluginName: string, component: React.FC<{ data: any; isAdmin?: boolean }>) {
tabComponents[pluginName] = component;
}
@ -40,6 +40,12 @@ export default function App() {
const [showVersionModal, setShowVersionModal] = useState(false);
const [pluginData, setPluginData] = useState<Record<string, any>>({});
// Centralized admin login state
const [isAdmin, setIsAdmin] = useState(false);
const [showAdminLogin, setShowAdminLogin] = useState(false);
const [adminPwd, setAdminPwd] = useState('');
const [adminError, setAdminError] = useState('');
// Electron auto-update state
const isElectron = !!(window as any).electronAPI?.isElectron;
const electronVersion = isElectron ? (window as any).electronAPI.version : null;
@ -54,6 +60,48 @@ export default function App() {
}
}, []);
// Check admin status on mount (shared cookie — any endpoint works)
useEffect(() => {
fetch('/api/soundboard/admin/status', { credentials: 'include' })
.then(r => r.json())
.then(d => setIsAdmin(!!d.authenticated))
.catch(() => {});
}, []);
// Escape key closes admin login modal
useEffect(() => {
if (!showAdminLogin) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setShowAdminLogin(false); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [showAdminLogin]);
async function handleAdminLogin() {
setAdminError('');
try {
const resp = await fetch('/api/soundboard/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: adminPwd }),
credentials: 'include',
});
if (resp.ok) {
setIsAdmin(true);
setAdminPwd('');
setShowAdminLogin(false);
} else {
setAdminError('Falsches Passwort');
}
} catch {
setAdminError('Verbindung fehlgeschlagen');
}
}
async function handleAdminLogout() {
await fetch('/api/soundboard/admin/logout', { method: 'POST', credentials: 'include' });
setIsAdmin(false);
}
// Electron auto-update listeners
useEffect(() => {
if (!isElectron) return;
@ -188,6 +236,13 @@ export default function App() {
<span className="hub-download-label">Desktop App</span>
</a>
)}
<button
className={`hub-admin-btn ${isAdmin ? 'active' : ''}`}
onClick={() => isAdmin ? handleAdminLogout() : setShowAdminLogin(true)}
title={isAdmin ? 'Admin abmelden' : 'Admin Login'}
>
{isAdmin ? '\uD83D\uDD13' : '\uD83D\uDD12'}
</button>
<button
className="hub-refresh-btn"
onClick={() => window.location.reload()}
@ -307,6 +362,34 @@ export default function App() {
</div>
)}
{showAdminLogin && (
<div className="hub-admin-overlay" onClick={() => setShowAdminLogin(false)}>
<div className="hub-admin-modal" onClick={e => e.stopPropagation()}>
<div className="hub-admin-modal-header">
<span>{'\uD83D\uDD12'} Admin Login</span>
<button className="hub-admin-modal-close" onClick={() => setShowAdminLogin(false)}>
{'\u2715'}
</button>
</div>
<div className="hub-admin-modal-body">
<input
type="password"
className="hub-admin-input"
placeholder="Admin-Passwort..."
value={adminPwd}
onChange={e => setAdminPwd(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAdminLogin()}
autoFocus
/>
{adminError && <p className="hub-admin-error">{adminError}</p>}
<button className="hub-admin-submit" onClick={handleAdminLogin}>
Login
</button>
</div>
</div>
</div>
)}
<main className="hub-content">
{plugins.length === 0 ? (
<div className="hub-empty">
@ -330,7 +413,7 @@ export default function App() {
: { display: 'none' }
}
>
<Comp data={pluginData[p.name] || {}} />
<Comp data={pluginData[p.name] || {}} isAdmin={isAdmin} />
</div>
);
})