diff --git a/frontend/src/components/system/SystemConfig.tsx b/frontend/src/components/system/SystemConfig.tsx index 1f195cc..405ad5c 100644 --- a/frontend/src/components/system/SystemConfig.tsx +++ b/frontend/src/components/system/SystemConfig.tsx @@ -9,13 +9,16 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Settings, Save, RotateCcw, Eye, EyeOff, CheckCircle2, AlertCircle, - Info, Zap, Globe, PlayCircle, Brain + Info, Zap, Globe, PlayCircle, Brain, Key, Copy, Trash2, Terminal, ServerCrash } from "lucide-react"; import { toast } from "sonner"; import { api } from "@/shared/api/database"; import EmbeddingConfig from "@/components/agent/EmbeddingConfig"; +import { generateSSHKey, getSSHKey, deleteSSHKey, testSSHKey, clearKnownHosts } from "@/shared/api/sshKeys"; // LLM Providers - 2025 const LLM_PROVIDERS = [ @@ -54,7 +57,16 @@ export function SystemConfig() { const [llmTestResult, setLlmTestResult] = useState<{ success: boolean; message: string; debug?: Record } | null>(null); const [showDebugInfo, setShowDebugInfo] = useState(true); - useEffect(() => { loadConfig(); }, []); + // SSH Key states + const [sshKey, setSSHKey] = useState<{ has_key: boolean; public_key?: string; fingerprint?: string }>({ has_key: false }); + const [generatingKey, setGeneratingKey] = useState(false); + const [deletingKey, setDeletingKey] = useState(false); + const [clearingKnownHosts, setClearingKnownHosts] = useState(false); + const [testingKey, setTestingKey] = useState(false); + const [testRepoUrl, setTestRepoUrl] = useState(""); + const [showDeleteKeyDialog, setShowDeleteKeyDialog] = useState(false); + + useEffect(() => { loadConfig(); loadSSHKey(); }, []); const loadConfig = async () => { try { @@ -116,6 +128,99 @@ export function SystemConfig() { } }; + // SSH Key functions + const loadSSHKey = async () => { + try { + const data = await getSSHKey(); + setSSHKey(data); + } catch (error) { + console.error('Failed to load SSH key:', error); + } + }; + + const handleGenerateSSHKey = async () => { + try { + setGeneratingKey(true); + const data = await generateSSHKey(); + setSSHKey({ has_key: true, public_key: data.public_key, fingerprint: data.fingerprint }); + toast.success(data.message); + } catch (error: any) { + console.error('Failed to generate SSH key:', error); + toast.error(error.response?.data?.detail || "生成SSH密钥失败"); + } finally { + setGeneratingKey(false); + } + }; + + const handleDeleteSSHKey = async () => { + try { + setDeletingKey(true); + await deleteSSHKey(); + setSSHKey({ has_key: false }); + toast.success("SSH密钥已删除"); + setShowDeleteKeyDialog(false); + } catch (error: any) { + console.error('Failed to delete SSH key:', error); + toast.error(error.response?.data?.detail || "删除SSH密钥失败"); + } finally { + setDeletingKey(false); + } + }; + + const handleTestSSHKey = async () => { + if (!testRepoUrl) { + toast.error("请输入仓库URL"); + return; + } + try { + setTestingKey(true); + const result = await testSSHKey(testRepoUrl); + if (result.success) { + toast.success("SSH连接测试成功"); + if (result.output) { + console.log("SSH测试输出:", result.output); + } + } else { + toast.error(result.message || "SSH连接测试失败", { + description: result.output ? `详情: ${result.output.substring(0, 100)}...` : undefined, + duration: 5000, + }); + if (result.output) { + console.error("SSH测试失败:", result.output); + } + } + } catch (error: any) { + console.error('Failed to test SSH key:', error); + toast.error(error.response?.data?.detail || "测试SSH密钥失败"); + } finally { + setTestingKey(false); + } + }; + + const handleClearKnownHosts = async () => { + try { + setClearingKnownHosts(true); + const result = await clearKnownHosts(); + if (result.success) { + toast.success(result.message || "known_hosts已清理"); + } else { + toast.error("清理known_hosts失败"); + } + } catch (error: any) { + console.error('Failed to clear known_hosts:', error); + toast.error(error.response?.data?.detail || "清理known_hosts失败"); + } finally { + setClearingKnownHosts(false); + } + }; + + const handleCopyPublicKey = () => { + if (sshKey.public_key) { + navigator.clipboard.writeText(sshKey.public_key); + toast.success("公钥已复制到剪贴板"); + } + }; + const saveConfig = async () => { if (!config) return; try { @@ -639,6 +744,160 @@ export function SystemConfig() {

• 私有仓库需要配置对应平台的 Token

+ + {/* SSH Key Management */} +
+
+ +

SSH 密钥管理

+
+ +
+
+
+ +
+
+
+

+ 使用 SSH 密钥访问 Git 仓库 +

+

+ 生成 SSH 密钥对后,将公钥添加到 GitHub/GitLab,即可使用 SSH URL 访问私有仓库。私钥将被加密存储。 +

+
+
+ + {!sshKey.has_key ? ( +
+
+ +
+

尚未生成 SSH 密钥

+ +
+ ) : ( +
+ {/* Public Key Display */} +
+
+ + +
+