Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
165 lines
5.5 KiB
TypeScript
165 lines
5.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Key, Copy, Eye, EyeOff, Plus, Trash2, CheckCircle } from "lucide-react"
|
|
|
|
interface ApiKeyManagementDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
}
|
|
|
|
const MOCK_KEYS = [
|
|
{
|
|
id: 1,
|
|
name: "生产环境密钥",
|
|
key: "sk_live_xxxxxxxxxxxx",
|
|
created: "2024-12-01",
|
|
lastUsed: "刚刚",
|
|
status: "active",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "测试环境密钥",
|
|
key: "sk_test_xxxxxxxxxxxx",
|
|
created: "2024-11-15",
|
|
lastUsed: "3天前",
|
|
status: "active",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "开发调试密钥",
|
|
key: "sk_dev_xxxxxxxxxxxx",
|
|
created: "2024-10-20",
|
|
lastUsed: "1周前",
|
|
status: "inactive",
|
|
},
|
|
]
|
|
|
|
export function ApiKeyManagementDialog({ open, onOpenChange }: ApiKeyManagementDialogProps) {
|
|
const [keys, setKeys] = useState(MOCK_KEYS)
|
|
const [showKey, setShowKey] = useState<number | null>(null)
|
|
const [newKeyName, setNewKeyName] = useState("")
|
|
const [showNewKeyForm, setShowNewKeyForm] = useState(false)
|
|
const [copiedId, setCopiedId] = useState<number | null>(null)
|
|
|
|
const copyKey = (id: number, key: string) => {
|
|
navigator.clipboard.writeText(key)
|
|
setCopiedId(id)
|
|
setTimeout(() => setCopiedId(null), 2000)
|
|
}
|
|
|
|
const createKey = () => {
|
|
if (newKeyName) {
|
|
setKeys([
|
|
...keys,
|
|
{
|
|
id: Date.now(),
|
|
name: newKeyName,
|
|
key: `sk_live_${Math.random().toString(36).substr(2, 12)}`,
|
|
created: new Date().toISOString().split("T")[0],
|
|
lastUsed: "从未",
|
|
status: "active",
|
|
},
|
|
])
|
|
setNewKeyName("")
|
|
setShowNewKeyForm(false)
|
|
}
|
|
}
|
|
|
|
const deleteKey = (id: number) => {
|
|
setKeys(keys.filter((k) => k.id !== id))
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-[600px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<Key className="w-5 h-5" />
|
|
API密钥管理
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-4">
|
|
<div className="flex justify-end">
|
|
<Button size="sm" onClick={() => setShowNewKeyForm(true)}>
|
|
<Plus className="w-4 h-4 mr-1" />
|
|
创建密钥
|
|
</Button>
|
|
</div>
|
|
|
|
{showNewKeyForm && (
|
|
<div className="p-4 border rounded-lg bg-gray-50 space-y-3">
|
|
<Label>密钥名称</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
placeholder="例如:生产环境密钥"
|
|
value={newKeyName}
|
|
onChange={(e) => setNewKeyName(e.target.value)}
|
|
/>
|
|
<Button onClick={createKey}>创建</Button>
|
|
<Button variant="outline" onClick={() => setShowNewKeyForm(false)}>
|
|
取消
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-3">
|
|
{keys.map((item) => (
|
|
<div key={item.id} className="p-4 border rounded-lg hover:bg-gray-50">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium">{item.name}</span>
|
|
<Badge
|
|
className={item.status === "active" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-700"}
|
|
>
|
|
{item.status === "active" ? "启用" : "停用"}
|
|
</Badge>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-red-500 hover:text-red-700"
|
|
onClick={() => deleteKey(item.id)}
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<code className="flex-1 px-3 py-2 bg-gray-100 rounded text-sm font-mono">
|
|
{showKey === item.id ? item.key : item.key.replace(/./g, "•").slice(0, 20) + "..."}
|
|
</code>
|
|
<Button variant="ghost" size="sm" onClick={() => setShowKey(showKey === item.id ? null : item.id)}>
|
|
{showKey === item.id ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
</Button>
|
|
<Button variant="ghost" size="sm" onClick={() => copyKey(item.id, item.key)}>
|
|
{copiedId === item.id ? (
|
|
<CheckCircle className="w-4 h-4 text-green-500" />
|
|
) : (
|
|
<Copy className="w-4 h-4" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-xs text-gray-500">
|
|
<span>创建于 {item.created}</span>
|
|
<span>最后使用: {item.lastUsed}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
关闭
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|