import { useState, useEffect } from 'react'; import api from '@/lib/api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select } from '@/components/ui/select'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { showToast } from '@/components/ui/toast-utils'; import { Key, Copy, Trash2, RefreshCw, Plus, Loader2, AlertTriangle, Check } from 'lucide-react'; interface ApiKey { id: string; name: string; key_prefix: string; scopes: string[]; created_at: string; expires_at: string | null; last_used_at: string | null; is_active: boolean; } interface CreateKeyResponse { id: string; name: string; key: string; prefix: string; scopes: string[]; created_at: string; } const AVAILABLE_SCOPES = [ { value: 'read:scenarios', label: 'Read Scenarios' }, { value: 'write:scenarios', label: 'Write Scenarios' }, { value: 'read:reports', label: 'Read Reports' }, { value: 'write:reports', label: 'Write Reports' }, { value: 'read:metrics', label: 'Read Metrics' }, { value: 'admin', label: 'Admin (Full Access)' }, ]; const EXPIRATION_OPTIONS = [ { value: '7', label: '7 days' }, { value: '30', label: '30 days' }, { value: '90', label: '90 days' }, { value: '365', label: '365 days' }, { value: 'never', label: 'Never' }, ]; export function ApiKeys() { const [apiKeys, setApiKeys] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isCreating, setIsCreating] = useState(false); const [showCreateForm, setShowCreateForm] = useState(false); // Create form state const [newKeyName, setNewKeyName] = useState(''); const [selectedScopes, setSelectedScopes] = useState(['read:scenarios']); const [expirationDays, setExpirationDays] = useState('30'); // New key modal state const [newKeyData, setNewKeyData] = useState(null); const [copied, setCopied] = useState(false); // Revoke confirmation const [keyToRevoke, setKeyToRevoke] = useState(null); useEffect(() => { fetchApiKeys(); }, []); const fetchApiKeys = async () => { try { const response = await api.get('/api-keys'); setApiKeys(response.data); } catch (error) { showToast({ title: 'Error', description: 'Failed to load API keys', variant: 'destructive' }); } finally { setIsLoading(false); } }; const handleCreateKey = async (e: React.FormEvent) => { e.preventDefault(); setIsCreating(true); try { const expiresDays = expirationDays === 'never' ? null : parseInt(expirationDays); const response = await api.post('/api-keys', { name: newKeyName, scopes: selectedScopes, expires_days: expiresDays, }); setNewKeyData(response.data); setShowCreateForm(false); setNewKeyName(''); setSelectedScopes(['read:scenarios']); setExpirationDays('30'); fetchApiKeys(); showToast({ title: 'API Key Created', description: 'Copy your key now - you won\'t see it again!' }); } catch (error: any) { showToast({ title: 'Error', description: error.response?.data?.detail || 'Failed to create API key', variant: 'destructive' }); } finally { setIsCreating(false); } }; const handleRevokeKey = async () => { if (!keyToRevoke) return; try { await api.delete(`/api-keys/${keyToRevoke.id}`); setApiKeys(apiKeys.filter(k => k.id !== keyToRevoke.id)); setKeyToRevoke(null); showToast({ title: 'API Key Revoked', description: 'The key has been revoked successfully' }); } catch (error) { showToast({ title: 'Error', description: 'Failed to revoke API key', variant: 'destructive' }); } }; const handleRotateKey = async (keyId: string) => { try { const response = await api.post(`/api-keys/${keyId}/rotate`); setNewKeyData(response.data); fetchApiKeys(); showToast({ title: 'API Key Rotated', description: 'New key generated - copy it now!' }); } catch (error) { showToast({ title: 'Error', description: 'Failed to rotate API key', variant: 'destructive' }); } }; const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); showToast({ title: 'Copied!', description: 'API key copied to clipboard' }); } catch { showToast({ title: 'Error', description: 'Failed to copy to clipboard', variant: 'destructive' }); } }; const formatDate = (dateString: string | null) => { if (!dateString) return 'Never'; return new Date(dateString).toLocaleDateString(); }; const toggleScope = (scope: string) => { setSelectedScopes(prev => prev.includes(scope) ? prev.filter(s => s !== scope) : [...prev, scope] ); }; return (

API Keys

Manage API keys for programmatic access

{/* Create New Key Form */} {showCreateForm && ( Create New API Key Generate a new API key for programmatic access to the API
setNewKeyName(e.target.value)} required />
{AVAILABLE_SCOPES.map((scope) => (
toggleScope(scope.value)} />
))}
)} {/* API Keys Table */} Your API Keys {apiKeys.length} active key{apiKeys.length !== 1 ? 's' : ''} {isLoading ? (
) : apiKeys.length === 0 ? (

No API keys yet

Create your first key to get started

) : ( Name Prefix Scopes Created Last Used Actions {apiKeys.map((key) => ( {key.name} {key.key_prefix}...
{key.scopes.slice(0, 2).map((scope) => ( {scope} ))} {key.scopes.length > 2 && ( +{key.scopes.length - 2} )}
{formatDate(key.created_at)} {key.last_used_at ? formatDate(key.last_used_at) : 'Never'}
))}
)}
{/* New Key Modal - Show full key only once */} setNewKeyData(null)}> API Key Created Copy your API key now. You won't be able to see it again! {newKeyData && (

{newKeyData.name}

{newKeyData.key}

Important: This is the only time you'll see the full key. Please copy it now and store it securely. If you lose it, you'll need to generate a new one.

)}
{/* Revoke Confirmation Dialog */} setKeyToRevoke(null)}> Revoke API Key Are you sure you want to revoke the key "{keyToRevoke?.name}"? This action cannot be undone. Any applications using this key will stop working immediately.
); }