import { Key, Loader2, Shield, Trash2, User } from 'lucide-react'; import { useEffect, useState } from 'react'; import { adminApi } from '../../api/client'; import { useTranslation } from '../../i18n'; import { useToast } from '../shared/Toast'; interface AdminOAuthSession { id: number; client_id: string; client_name: string; user_id: number; username: string; scopes: string[]; access_token_expires_at: string; refresh_token_expires_at: string; created_at: string; } interface AdminMcpToken { id: number; name: string; token_prefix: string; created_at: string; last_used_at: string | null; user_id: number; username: string; } const SCOPES_PREVIEW = 6; export default function AdminMcpTokensPanel() { const [sessions, setSessions] = useState([]); const [sessionsLoading, setSessionsLoading] = useState(true); const [tokens, setTokens] = useState([]); const [tokensLoading, setTokensLoading] = useState(true); const [expandedScopes, setExpandedScopes] = useState>(new Set()); const [revokeConfirmId, setRevokeConfirmId] = useState(null); const [deleteConfirmId, setDeleteConfirmId] = useState(null); const toggleScopes = (id: number) => setExpandedScopes((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); const toast = useToast(); const { t, locale } = useTranslation(); useEffect(() => { adminApi .oauthSessions() .then((d) => setSessions(d.sessions || [])) .catch(() => toast.error(t('admin.oauthSessions.loadError'))) .finally(() => setSessionsLoading(false)); adminApi .mcpTokens() .then((d) => setTokens(d.tokens || [])) .catch(() => toast.error(t('admin.mcpTokens.loadError'))) .finally(() => setTokensLoading(false)); }, []); const handleRevoke = async (id: number) => { try { await adminApi.revokeOAuthSession(id); setSessions((prev) => prev.filter((s) => s.id !== id)); setRevokeConfirmId(null); toast.success(t('admin.oauthSessions.revokeSuccess')); } catch { toast.error(t('admin.oauthSessions.revokeError')); } }; const handleDelete = async (id: number) => { try { await adminApi.deleteMcpToken(id); setTokens((prev) => prev.filter((tk) => tk.id !== id)); setDeleteConfirmId(null); toast.success(t('admin.mcpTokens.deleteSuccess')); } catch { toast.error(t('admin.mcpTokens.deleteError')); } }; return (

{t('admin.mcpTokens.title')}

{t('admin.mcpTokens.subtitle')}

{/* OAuth Sessions */}

{t('admin.oauthSessions.sectionTitle')}

{sessionsLoading ? (
) : sessions.length === 0 ? (

{t('admin.oauthSessions.empty')}

) : ( <>
{t('admin.oauthSessions.clientName')} {t('admin.oauthSessions.owner')} {t('admin.oauthSessions.created')}
{sessions.map((session, i) => { const expanded = expandedScopes.has(session.id); const visible = expanded ? session.scopes : session.scopes.slice(0, SCOPES_PREVIEW); const hidden = session.scopes.length - SCOPES_PREVIEW; return (

{session.client_name}

{visible.map((scope) => ( {scope} ))} {!expanded && hidden > 0 && ( )} {expanded && hidden > 0 && ( )}
{session.username}
{new Date(session.created_at).toLocaleDateString(locale)}
); })} )}
{/* MCP Tokens */}

{t('admin.mcpTokens.sectionTitle')}

{tokensLoading ? (
) : tokens.length === 0 ? (

{t('admin.mcpTokens.empty')}

) : ( <>
{t('admin.mcpTokens.tokenName')} {t('admin.mcpTokens.owner')} {t('admin.mcpTokens.created')} {t('admin.mcpTokens.lastUsed')}
{tokens.map((token, i) => (

{token.name}

{token.token_prefix}...

{token.username}
{new Date(token.created_at).toLocaleDateString(locale)} {token.last_used_at ? new Date(token.last_used_at).toLocaleDateString(locale) : t('admin.mcpTokens.never')}
))} )}
{/* Revoke OAuth session modal */} {revokeConfirmId !== null && (
{ if (e.target === e.currentTarget) setRevokeConfirmId(null); }} >

{t('admin.oauthSessions.revokeTitle')}

{t('admin.oauthSessions.revokeMessage')}

)} {/* Delete MCP token modal */} {deleteConfirmId !== null && (
{ if (e.target === e.currentTarget) setDeleteConfirmId(null); }} >

{t('admin.mcpTokens.deleteTitle')}

{t('admin.mcpTokens.deleteMessage')}

)}
); }