diff --git a/web/src/pages/Agents/AgentInstall.tsx b/web/src/pages/Agents/AgentInstall.tsx index b7ce5ab..db3a673 100644 --- a/web/src/pages/Agents/AgentInstall.tsx +++ b/web/src/pages/Agents/AgentInstall.tsx @@ -1,4 +1,4 @@ -import React, {type ReactElement, useEffect, useState} from 'react'; +import React, {type ReactElement, useCallback, useEffect, useMemo, useState} from 'react'; import {Alert, App, Button, Card, Select, Space, Tabs, Typography} from 'antd'; import {CopyIcon} from 'lucide-react'; import {listApiKeys} from '../../api/apiKey'; @@ -11,84 +11,120 @@ import {useNavigate} from "react-router-dom"; const {Paragraph, Text} = Typography; const {TabPane} = Tabs; +// 常量定义 +const AGENT_NAME = 'pika-agent'; +const AGENT_NAME_EXE = 'pika-agent.exe'; +const CONFIG_PATH = '~/.pika/agent.yaml'; +const DEFAULT_OS = 'linux-amd64' as const; + +// 类型定义 interface OSConfig { name: string; icon: ReactElement; downloadUrl: string; } +interface InstallStep { + title: string; + command: string; +} + +type OSType = 'linux-amd64' | 'linux-arm64' | 'darwin-amd64' | 'darwin-arm64' | 'windows-amd64' | 'windows-arm64'; + const AgentInstall = () => { - const [selectedOS, setSelectedOS] = useState('linux-amd64'); + const [selectedOS, setSelectedOS] = useState(DEFAULT_OS); const [apiKeys, setApiKeys] = useState([]); const [selectedApiKey, setSelectedApiKey] = useState(''); + const [loading, setLoading] = useState(true); const {message} = App.useApp(); - const serverUrl = window.location.origin; - let navigate = useNavigate(); + const navigate = useNavigate(); + const serverUrl = useMemo(() => window.location.origin, []); // 加载API密钥列表 useEffect(() => { const fetchApiKeys = async () => { + setLoading(true); try { const keys = await listApiKeys(); - const enabledKeys = keys.data?.items.filter(k => k.enabled); + const enabledKeys = keys.data?.items.filter(k => k.enabled) || []; setApiKeys(enabledKeys); if (enabledKeys.length > 0) { setSelectedApiKey(enabledKeys[0].key); } } catch (error) { console.error('Failed to load API keys:', error); + message.error('加载 API Token 失败'); + } finally { + setLoading(false); } }; - fetchApiKeys(); - }, []); + void fetchApiKeys(); + }, [message]); - const osConfigs: Record = { + // 操作系统配置 + const osConfigs: Record = useMemo(() => ({ 'linux-amd64': { name: 'Linux (amd64)', - icon: Linux, + icon: Linux, downloadUrl: '/api/agent/downloads/agent-linux-amd64', }, 'linux-arm64': { name: 'Linux (arm64)', - icon: Linux, + icon: Linux, downloadUrl: '/api/agent/downloads/agent-linux-arm64', }, 'darwin-amd64': { name: 'macOS (amd64)', - icon: macOS, + icon: macOS, downloadUrl: '/api/agent/downloads/agent-darwin-amd64', }, 'darwin-arm64': { name: 'macOS (arm64)', - icon: macOS, + icon: macOS, downloadUrl: '/api/agent/downloads/agent-darwin-arm64', }, 'windows-amd64': { name: 'Windows (amd64)', - icon: Windows, + icon: Windows, downloadUrl: '/api/agent/downloads/agent-windows-amd64.exe', }, 'windows-arm64': { name: 'Windows (arm64)', - icon: Windows, + icon: Windows, downloadUrl: '/api/agent/downloads/agent-windows-arm64.exe', }, - }; + }), []); - const copyToClipboard = (text: string) => { - navigator.clipboard.writeText(text); - message.success('已复制到剪贴板'); - }; + // 复制到剪贴板 + const copyToClipboard = useCallback(async (text: string) => { + try { + await navigator.clipboard.writeText(text); + message.success('已复制到剪贴板'); + } catch (error) { + console.error('Failed to copy:', error); + message.error('复制失败,请手动复制'); + } + }, [message]); // 获取一键安装命令 - const getInstallCommand = () => { - // Linux/macOS 使用一键安装脚本 - return `curl -fsSL ${serverUrl}/api/agent/install.sh?token=${selectedApiKey} | sudo bash`; - }; + const installCommand = useMemo( + () => `curl -fsSL ${serverUrl}/api/agent/install.sh?token=${selectedApiKey} | sudo bash`, + [serverUrl, selectedApiKey] + ); - const ApiChooser = () => { - return + // API Key 选择器选项 + const apiKeyOptions = useMemo( + () => apiKeys.map(key => ({ + label: `${key.name} (${key.key.substring(0, 8)}...)`, + value: key.key, + })), + [apiKeys] + ); + + // API Token 选择组件 + const ApiChooser = useCallback(() => ( + {apiKeys.length === 0 ? ( { className="w-full mt-2" value={selectedApiKey} onChange={setSelectedApiKey} - options={apiKeys.map(key => ({ - label: `${key.name} (${key.key.substring(0, 8)}...)`, - value: key.key, - }))} + options={apiKeyOptions} + loading={loading} + placeholder="请选择 API Token" /> )} - } + ), [apiKeys, selectedApiKey, apiKeyOptions, loading]); - const InstallByOneClick = () => { - return + // 一键安装组件 + const InstallByOneClick = useCallback(() => ( + { 脚本会自动检测系统架构并下载对应版本的探针,然后完成注册和安装。 -
-                                    {getInstallCommand()}
-                                
+
+                    {installCommand}
+                
- + - } + ), [ApiChooser, installCommand, copyToClipboard, selectedApiKey]); - const ServiceHelper = ({os}: { os: string }) => { - return - - 注册完成后,您可以使用以下命令管理探针服务: - -
-                {getCommonCommands(os)}
-            
-
- } - - const ConfigHelper = () => { - return - - 注册完成后,配置文件会保存在: - -
    -
  • - ~/.pika/agent.yaml - 配置文件路径 -
  • -
  • - 您可以手动编辑此文件来修改配置,修改后需要重启服务生效 -
  • -
-
- } - - // 获取手动安装步骤 - const getManualInstallSteps = (os: string) => { - const token = selectedApiKey; - const config = osConfigs[os]; - - if (os.startsWith('windows')) { - const agentName = 'pika-agent.exe'; - return [ - { - title: '1. 下载探针', - command: `# 使用 PowerShell 下载 -Invoke-WebRequest -Uri "${serverUrl}${config.downloadUrl}" -OutFile "${agentName}" - -# 或者使用浏览器直接下载 -# ${serverUrl}${config.downloadUrl}` - }, - { - title: '2. 注册探针', - command: `.\\${agentName} register --endpoint "${serverUrl}" --token "${token}"` - }, - { - title: '3. 验证安装', - command: `.\\${agentName} status` - } - ]; - } else { - const agentName = 'pika-agent'; - - return [ - { - title: '1. 下载探针', - command: `# 使用 wget 下载 -wget ${serverUrl}${config.downloadUrl} -O ${agentName} - -# 或使用 curl 下载 -curl -L ${serverUrl}${config.downloadUrl} -o ${agentName}` - }, - { - title: '2. 赋予执行权限', - command: `chmod +x ${agentName}` - }, - { - title: '3. 移动到系统路径', - command: `sudo mv ${agentName} /usr/local/bin/${agentName}` - }, - { - title: '4. 注册探针', - command: `sudo ${agentName} register --endpoint "${serverUrl}" --token "${token}"` - }, - { - title: '5. 验证安装', - command: `sudo ${agentName} status` - } - ]; - } - }; - - const InstallByManual = () => { - return - - - {Object.entries(osConfigs).map(([key, config]) => ( - - {config.icon} - {config.name} - - } - key={key} - > - - - {/* 手动安装步骤 */} - - - {getManualInstallSteps(key).map((step, index) => ( -
- {step.title} -
-                                                {step.command}
-                                            
- -
- ))} -
-
- - - -
-
- ))} -
-
- } - - // 常用命令 - const getCommonCommands = (os: string) => { - const agentCmd = os.startsWith('windows') ? '.\\pika-agent.exe' : 'pika-agent'; + // 常用服务管理命令 + const getCommonCommands = useCallback((os: string) => { + const agentCmd = os.startsWith('windows') ? `.\\${AGENT_NAME_EXE}` : AGENT_NAME; const sudo = os.startsWith('windows') ? '' : 'sudo '; return `# 查看服务状态 @@ -310,39 +207,175 @@ ${sudo}${agentCmd} uninstall # 查看版本 ${agentCmd} version`; - }; + }, []); + + // 服务管理命令组件 + const ServiceHelper = useCallback(({os}: { os: string }) => ( + + + 注册完成后,您可以使用以下命令管理探针服务: + +
+                {getCommonCommands(os)}
+            
+
+ ), [getCommonCommands]); + + // 配置文件说明组件 + const ConfigHelper = useCallback(() => ( + + + 注册完成后,配置文件会保存在: + +
    +
  • + {CONFIG_PATH} - 配置文件路径 +
  • +
  • + 您可以手动编辑此文件来修改配置,修改后需要重启服务生效 +
  • +
+
+ ), []); + + // 获取手动安装步骤 + const getManualInstallSteps = useCallback((os: OSType): InstallStep[] => { + const config = osConfigs[os]; + + if (os.startsWith('windows')) { + return [ + { + title: '1. 下载探针', + command: `# 使用 PowerShell 下载 +Invoke-WebRequest -Uri "${serverUrl}${config.downloadUrl}" -OutFile "${AGENT_NAME_EXE}" + +# 或者使用浏览器直接下载 +# ${serverUrl}${config.downloadUrl}` + }, + { + title: '2. 注册探针', + command: `.\\${AGENT_NAME_EXE} register --endpoint "${serverUrl}" --token "${selectedApiKey}"` + }, + { + title: '3. 验证安装', + command: `.\\${AGENT_NAME_EXE} status` + } + ]; + } + + return [ + { + title: '1. 下载探针', + command: `# 使用 wget 下载 +wget ${serverUrl}${config.downloadUrl} -O ${AGENT_NAME} + +# 或使用 curl 下载 +curl -L ${serverUrl}${config.downloadUrl} -o ${AGENT_NAME}` + }, + { + title: '2. 赋予执行权限', + command: `chmod +x ${AGENT_NAME}` + }, + { + title: '3. 移动到系统路径', + command: `sudo mv ${AGENT_NAME} /usr/local/bin/${AGENT_NAME}` + }, + { + title: '4. 注册探针', + command: `sudo ${AGENT_NAME} register --endpoint "${serverUrl}" --token "${selectedApiKey}"` + }, + { + title: '5. 验证安装', + command: `sudo ${AGENT_NAME} status` + } + ]; + }, [osConfigs, serverUrl, selectedApiKey]); + + // 手动安装组件 + const InstallByManual = useCallback(() => ( + + + setSelectedOS(key as OSType)} + > + {Object.entries(osConfigs).map(([key, config]) => ( + + {config.icon} + {config.name} + + } + key={key} + > + + + + {getManualInstallSteps(key as OSType).map((step, index) => ( +
+ {step.title} +
+                                                {step.command}
+                                            
+ +
+ ))} +
+
+ + + +
+
+ ))} +
+
+ ), [ApiChooser, selectedOS, osConfigs, getManualInstallSteps, copyToClipboard, selectedApiKey, ServiceHelper, ConfigHelper]); + + // 主选项卡配置 + const tabItems = useMemo(() => [ + { + label: '一键安装', + key: 'one-click', + children: + }, + { + label: '手动安装', + key: 'manual', + children: + }, + ], [InstallByOneClick, InstallByManual]); return ( - - +
-
navigate(-1)} - >返回 | +
navigate(-1)} + role="button" + tabIndex={0} + onKeyDown={(e) => e.key === 'Enter' && navigate(-1)} + > + 返回 |

探针部署指南

- }, - { - label: '手动安装', - key: 'manual', - children: - }, - ]} + style={{ marginTop: 24 }} + tabPosition="left" + items={tabItems} /> - - ); };