api-key-manager.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. "use client"
  2. import React, { useState, useEffect } from 'react'
  3. import { Button } from '@/components/ui/button'
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
  5. import { Input } from '@/components/ui/input'
  6. import { Label } from '@/components/ui/label'
  7. import { Alert, AlertDescription } from '@/components/ui/alert'
  8. import { Badge } from '@/components/ui/badge'
  9. import { authApi } from '@/lib/api'
  10. import { useAuth } from '@/lib/auth-context'
  11. interface ApiKey {
  12. id: string
  13. name: string
  14. key: string
  15. scopes: string[]
  16. created_at: string
  17. last_used?: string
  18. expires_at?: string
  19. active: boolean
  20. }
  21. export function ApiKeyManager() {
  22. useAuth()
  23. const [apiKeys, setApiKeys] = useState<ApiKey[]>([])
  24. const [loading, setLoading] = useState(true)
  25. const [error, setError] = useState<string | null>(null)
  26. const [showCreateForm, setShowCreateForm] = useState(false)
  27. const [newKeyName, setNewKeyName] = useState('')
  28. const [newKeyScopes, setNewKeyScopes] = useState<string[]>([])
  29. const [createdKey, setCreatedKey] = useState<ApiKey | null>(null)
  30. useEffect(() => {
  31. loadApiKeys()
  32. }, [])
  33. const loadApiKeys = async () => {
  34. try {
  35. setLoading(true)
  36. const data = await authApi.getApiKeys()
  37. setApiKeys(data.keys || [])
  38. setError(null)
  39. } catch (err) {
  40. setError(err instanceof Error ? err.message : 'Failed to load API keys')
  41. } finally {
  42. setLoading(false)
  43. }
  44. }
  45. const handleCreateKey = async (e: React.FormEvent) => {
  46. e.preventDefault()
  47. if (!newKeyName.trim()) {
  48. setError('Key name is required')
  49. return
  50. }
  51. try {
  52. const data = await authApi.createApiKey(newKeyName.trim(), newKeyScopes)
  53. setCreatedKey(data.key)
  54. setApiKeys(prev => [data.key, ...prev])
  55. setNewKeyName('')
  56. setNewKeyScopes([])
  57. setShowCreateForm(false)
  58. setError(null)
  59. } catch (err) {
  60. setError(err instanceof Error ? err.message : 'Failed to create API key')
  61. }
  62. }
  63. const handleDeleteKey = async (keyId: string) => {
  64. if (!confirm('Are you sure you want to delete this API key? This action cannot be undone.')) {
  65. return
  66. }
  67. try {
  68. await authApi.deleteApiKey(keyId)
  69. setApiKeys(prev => prev.filter(key => key.id !== keyId))
  70. setError(null)
  71. } catch (err) {
  72. setError(err instanceof Error ? err.message : 'Failed to delete API key')
  73. }
  74. }
  75. const copyToClipboard = (text: string) => {
  76. navigator.clipboard.writeText(text).then(() => {
  77. // You could add a toast notification here
  78. })
  79. }
  80. const formatDate = (dateString: string) => {
  81. return new Date(dateString).toLocaleDateString() + ' ' + new Date(dateString).toLocaleTimeString()
  82. }
  83. if (loading) {
  84. return <div className="p-4">Loading API keys...</div>
  85. }
  86. return (
  87. <div className="space-y-6">
  88. {error && (
  89. <Alert variant="destructive">
  90. <AlertDescription>{error}</AlertDescription>
  91. </Alert>
  92. )}
  93. {createdKey && (
  94. <Alert>
  95. <AlertDescription>
  96. <div className="space-y-2">
  97. <p><strong>API Key Created Successfully!</strong></p>
  98. <p className="text-sm text-muted-foreground">
  99. Please copy this key now. You won&apos;t be able to see it again.
  100. </p>
  101. <div className="flex items-center space-x-2">
  102. <code className="bg-muted px-2 py-1 rounded text-sm font-mono">
  103. {createdKey.key}
  104. </code>
  105. <Button
  106. size="sm"
  107. variant="outline"
  108. onClick={() => copyToClipboard(createdKey.key)}
  109. >
  110. Copy
  111. </Button>
  112. </div>
  113. </div>
  114. </AlertDescription>
  115. </Alert>
  116. )}
  117. <Card>
  118. <CardHeader>
  119. <div className="flex justify-between items-center">
  120. <div>
  121. <CardTitle>API Keys</CardTitle>
  122. <CardDescription>
  123. Manage API keys for programmatic access to the Stable Diffusion API
  124. </CardDescription>
  125. </div>
  126. <Button onClick={() => setShowCreateForm(!showCreateForm)}>
  127. {showCreateForm ? 'Cancel' : 'Create New Key'}
  128. </Button>
  129. </div>
  130. </CardHeader>
  131. <CardContent>
  132. {showCreateForm && (
  133. <form onSubmit={handleCreateKey} className="space-y-4 mb-6 p-4 border rounded-lg">
  134. <div>
  135. <Label htmlFor="keyName">Key Name</Label>
  136. <Input
  137. id="keyName"
  138. value={newKeyName}
  139. onChange={(e) => setNewKeyName(e.target.value)}
  140. placeholder="e.g., My Application Key"
  141. required
  142. />
  143. </div>
  144. <div>
  145. <Label>Scopes (optional)</Label>
  146. <div className="space-y-2 mt-2">
  147. {['generate', 'models', 'queue', 'system'].map((scope) => (
  148. <label key={scope} className="flex items-center space-x-2">
  149. <input
  150. type="checkbox"
  151. checked={newKeyScopes.includes(scope)}
  152. onChange={(e) => {
  153. if (e.target.checked) {
  154. setNewKeyScopes(prev => [...prev, scope])
  155. } else {
  156. setNewKeyScopes(prev => prev.filter(s => s !== scope))
  157. }
  158. }}
  159. />
  160. <span className="text-sm">{scope}</span>
  161. </label>
  162. ))}
  163. </div>
  164. </div>
  165. <div className="flex space-x-2">
  166. <Button type="submit">Create Key</Button>
  167. <Button type="button" variant="outline" onClick={() => setShowCreateForm(false)}>
  168. Cancel
  169. </Button>
  170. </div>
  171. </form>
  172. )}
  173. {apiKeys.length === 0 ? (
  174. <div className="text-center py-8 text-muted-foreground">
  175. No API keys found. Create your first key to get started.
  176. </div>
  177. ) : (
  178. <div className="space-y-4">
  179. {apiKeys.map((apiKey) => (
  180. <div key={apiKey.id} className="border rounded-lg p-4">
  181. <div className="flex justify-between items-start">
  182. <div className="space-y-2">
  183. <div className="flex items-center space-x-2">
  184. <h3 className="font-medium">{apiKey.name}</h3>
  185. <Badge variant={apiKey.active ? "default" : "secondary"}>
  186. {apiKey.active ? 'Active' : 'Inactive'}
  187. </Badge>
  188. </div>
  189. <div className="text-sm text-muted-foreground">
  190. <p>Key: {apiKey.key.substring(0, 8)}...</p>
  191. <p>Created: {formatDate(apiKey.created_at)}</p>
  192. {apiKey.last_used && (
  193. <p>Last used: {formatDate(apiKey.last_used)}</p>
  194. )}
  195. {apiKey.expires_at && (
  196. <p>Expires: {formatDate(apiKey.expires_at)}</p>
  197. )}
  198. </div>
  199. {apiKey.scopes.length > 0 && (
  200. <div className="flex flex-wrap gap-1">
  201. {apiKey.scopes.map((scope) => (
  202. <Badge key={scope} variant="outline" className="text-xs">
  203. {scope}
  204. </Badge>
  205. ))}
  206. </div>
  207. )}
  208. </div>
  209. <div className="flex space-x-2">
  210. <Button
  211. size="sm"
  212. variant="outline"
  213. onClick={() => copyToClipboard(apiKey.key)}
  214. >
  215. Copy
  216. </Button>
  217. <Button
  218. size="sm"
  219. variant="destructive"
  220. onClick={() => handleDeleteKey(apiKey.id)}
  221. >
  222. Delete
  223. </Button>
  224. </div>
  225. </div>
  226. </div>
  227. ))}
  228. </div>
  229. )}
  230. </CardContent>
  231. </Card>
  232. </div>
  233. )
  234. }