| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- "use client"
- import React, { useState, useEffect } from 'react'
- import { Button } from '@/components/ui/button'
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
- import { Input } from '@/components/ui/input'
- import { Label } from '@/components/ui/label'
- import { Alert, AlertDescription } from '@/components/ui/alert'
- import { Badge } from '@/components/ui/badge'
- import { authApi } from '@/lib/api'
- import { useAuth } from '@/lib/auth-context'
- interface ApiKey {
- id: string
- name: string
- key: string
- scopes: string[]
- created_at: string
- last_used?: string
- expires_at?: string
- active: boolean
- }
- export function ApiKeyManager() {
- useAuth()
- const [apiKeys, setApiKeys] = useState<ApiKey[]>([])
- const [loading, setLoading] = useState(true)
- const [error, setError] = useState<string | null>(null)
- const [showCreateForm, setShowCreateForm] = useState(false)
- const [newKeyName, setNewKeyName] = useState('')
- const [newKeyScopes, setNewKeyScopes] = useState<string[]>([])
- const [createdKey, setCreatedKey] = useState<ApiKey | null>(null)
- useEffect(() => {
- loadApiKeys()
- }, [])
- const loadApiKeys = async () => {
- try {
- setLoading(true)
- const data = await authApi.getApiKeys()
- setApiKeys(data.keys || [])
- setError(null)
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load API keys')
- } finally {
- setLoading(false)
- }
- }
- const handleCreateKey = async (e: React.FormEvent) => {
- e.preventDefault()
- if (!newKeyName.trim()) {
- setError('Key name is required')
- return
- }
- try {
- const data = await authApi.createApiKey(newKeyName.trim(), newKeyScopes)
- setCreatedKey(data.key)
- setApiKeys(prev => [data.key, ...prev])
- setNewKeyName('')
- setNewKeyScopes([])
- setShowCreateForm(false)
- setError(null)
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to create API key')
- }
- }
- const handleDeleteKey = async (keyId: string) => {
- if (!confirm('Are you sure you want to delete this API key? This action cannot be undone.')) {
- return
- }
- try {
- await authApi.deleteApiKey(keyId)
- setApiKeys(prev => prev.filter(key => key.id !== keyId))
- setError(null)
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to delete API key')
- }
- }
- const copyToClipboard = (text: string) => {
- navigator.clipboard.writeText(text).then(() => {
- // You could add a toast notification here
- })
- }
- const formatDate = (dateString: string) => {
- return new Date(dateString).toLocaleDateString() + ' ' + new Date(dateString).toLocaleTimeString()
- }
- if (loading) {
- return <div className="p-4">Loading API keys...</div>
- }
- return (
- <div className="space-y-6">
- {error && (
- <Alert variant="destructive">
- <AlertDescription>{error}</AlertDescription>
- </Alert>
- )}
- {createdKey && (
- <Alert>
- <AlertDescription>
- <div className="space-y-2">
- <p><strong>API Key Created Successfully!</strong></p>
- <p className="text-sm text-muted-foreground">
- Please copy this key now. You won't be able to see it again.
- </p>
- <div className="flex items-center space-x-2">
- <code className="bg-muted px-2 py-1 rounded text-sm font-mono">
- {createdKey.key}
- </code>
- <Button
- size="sm"
- variant="outline"
- onClick={() => copyToClipboard(createdKey.key)}
- >
- Copy
- </Button>
- </div>
- </div>
- </AlertDescription>
- </Alert>
- )}
- <Card>
- <CardHeader>
- <div className="flex justify-between items-center">
- <div>
- <CardTitle>API Keys</CardTitle>
- <CardDescription>
- Manage API keys for programmatic access to the Stable Diffusion API
- </CardDescription>
- </div>
- <Button onClick={() => setShowCreateForm(!showCreateForm)}>
- {showCreateForm ? 'Cancel' : 'Create New Key'}
- </Button>
- </div>
- </CardHeader>
- <CardContent>
- {showCreateForm && (
- <form onSubmit={handleCreateKey} className="space-y-4 mb-6 p-4 border rounded-lg">
- <div>
- <Label htmlFor="keyName">Key Name</Label>
- <Input
- id="keyName"
- value={newKeyName}
- onChange={(e) => setNewKeyName(e.target.value)}
- placeholder="e.g., My Application Key"
- required
- />
- </div>
- <div>
- <Label>Scopes (optional)</Label>
- <div className="space-y-2 mt-2">
- {['generate', 'models', 'queue', 'system'].map((scope) => (
- <label key={scope} className="flex items-center space-x-2">
- <input
- type="checkbox"
- checked={newKeyScopes.includes(scope)}
- onChange={(e) => {
- if (e.target.checked) {
- setNewKeyScopes(prev => [...prev, scope])
- } else {
- setNewKeyScopes(prev => prev.filter(s => s !== scope))
- }
- }}
- />
- <span className="text-sm">{scope}</span>
- </label>
- ))}
- </div>
- </div>
- <div className="flex space-x-2">
- <Button type="submit">Create Key</Button>
- <Button type="button" variant="outline" onClick={() => setShowCreateForm(false)}>
- Cancel
- </Button>
- </div>
- </form>
- )}
- {apiKeys.length === 0 ? (
- <div className="text-center py-8 text-muted-foreground">
- No API keys found. Create your first key to get started.
- </div>
- ) : (
- <div className="space-y-4">
- {apiKeys.map((apiKey) => (
- <div key={apiKey.id} className="border rounded-lg p-4">
- <div className="flex justify-between items-start">
- <div className="space-y-2">
- <div className="flex items-center space-x-2">
- <h3 className="font-medium">{apiKey.name}</h3>
- <Badge variant={apiKey.active ? "default" : "secondary"}>
- {apiKey.active ? 'Active' : 'Inactive'}
- </Badge>
- </div>
- <div className="text-sm text-muted-foreground">
- <p>Key: {apiKey.key.substring(0, 8)}...</p>
- <p>Created: {formatDate(apiKey.created_at)}</p>
- {apiKey.last_used && (
- <p>Last used: {formatDate(apiKey.last_used)}</p>
- )}
- {apiKey.expires_at && (
- <p>Expires: {formatDate(apiKey.expires_at)}</p>
- )}
- </div>
- {apiKey.scopes.length > 0 && (
- <div className="flex flex-wrap gap-1">
- {apiKey.scopes.map((scope) => (
- <Badge key={scope} variant="outline" className="text-xs">
- {scope}
- </Badge>
- ))}
- </div>
- )}
- </div>
- <div className="flex space-x-2">
- <Button
- size="sm"
- variant="outline"
- onClick={() => copyToClipboard(apiKey.key)}
- >
- Copy
- </Button>
- <Button
- size="sm"
- variant="destructive"
- onClick={() => handleDeleteKey(apiKey.id)}
- >
- Delete
- </Button>
- </div>
- </div>
- </div>
- ))}
- </div>
- )}
- </CardContent>
- </Card>
- </div>
- )
- }
|