251 lines
9.3 KiB
TypeScript
251 lines
9.3 KiB
TypeScript
import { useState } from 'react';
|
||
import { toast } from 'sonner';
|
||
import { useUserStore } from '@/store';
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from '@/components/ui/dialog';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Copy, Check, Terminal, Download, Settings, Play, Cpu } from 'lucide-react';
|
||
|
||
interface AgentWizardProps {
|
||
open: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}
|
||
|
||
/** 自动检测服务器地址:使用当前浏览器访问的地址(协议+主机+端口) */
|
||
function detectServerAddress(): string {
|
||
if (typeof window !== 'undefined' && window.location) {
|
||
// 直接用当前浏览器的 origin(如 http://10.10.10.150),
|
||
// Nginx 会将 /agent/ 请求代理到后端
|
||
return window.location.origin;
|
||
}
|
||
return 'http://127.0.0.1:8080';
|
||
}
|
||
|
||
export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
|
||
const token = useUserStore((s) => s.token) || '<YOUR_TOKEN>';
|
||
const serverUrl = detectServerAddress();
|
||
const [copied, setCopied] = useState(false);
|
||
const [showManualInput, setShowManualInput] = useState(false);
|
||
|
||
const installCommand = `curl -fsSL ${serverUrl.replace(/^http:/, 'http:')}/agent/install.sh | bash -s -- --server ${serverUrl} --token ${token}`;
|
||
|
||
const fallbackCopy = (text: string): boolean => {
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.left = '-9999px';
|
||
textArea.style.top = '-9999px';
|
||
document.body.appendChild(textArea);
|
||
textArea.focus();
|
||
textArea.select();
|
||
let success = false;
|
||
try {
|
||
<<<<<<< HEAD
|
||
// 优先使用现代 Clipboard API(需要 HTTPS 或 localhost)
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
await navigator.clipboard.writeText(installCommand);
|
||
} else {
|
||
// 降级方案:用隐藏 textarea + execCommand('copy'),兼容 HTTP 环境
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = installCommand;
|
||
textarea.style.position = 'fixed';
|
||
textarea.style.opacity = '0';
|
||
textarea.style.pointerEvents = 'none';
|
||
document.body.appendChild(textarea);
|
||
textarea.select();
|
||
const ok = document.execCommand('copy');
|
||
document.body.removeChild(textarea);
|
||
if (!ok) throw new Error('execCommand failed');
|
||
}
|
||
setCopied(true);
|
||
toast.success('安装命令已复制');
|
||
setTimeout(() => setCopied(false), 2000);
|
||
} catch {
|
||
toast.error('复制失败,请手动选中复制');
|
||
=======
|
||
success = document.execCommand('copy');
|
||
} catch {
|
||
// execCommand also failed
|
||
}
|
||
document.body.removeChild(textArea);
|
||
return success;
|
||
};
|
||
|
||
const handleCopy = async () => {
|
||
const cmd = installCommand;
|
||
let success = false;
|
||
|
||
// 方式1: Clipboard API(HTTPS / localhost)
|
||
try {
|
||
await navigator.clipboard.writeText(cmd);
|
||
success = true;
|
||
} catch {
|
||
// Clipboard API 不可用
|
||
}
|
||
|
||
// 方式2: execCommand fallback(HTTP 也可用)
|
||
if (!success) {
|
||
success = fallbackCopy(cmd);
|
||
}
|
||
|
||
if (success) {
|
||
setCopied(true);
|
||
toast.success('安装命令已复制到剪贴板,粘贴到目标主机执行');
|
||
setTimeout(() => setCopied(false), 3000);
|
||
} else {
|
||
toast.error('自动复制失败,请在下方代码框中手动全选复制 (Ctrl+A → Ctrl+C)');
|
||
// 切换到手选模式,展示可编辑文本框
|
||
setShowManualInput(true);
|
||
>>>>>>> ca18ead (feat: run.sh四模式(dev/docker/build/binary) + CLI flags + Redka内嵌Redis + Agent复制修复 + 服务器编辑IP字段 + 安装Agent按钮)
|
||
}
|
||
};
|
||
|
||
const binaryUrl = `${serverUrl}/agent/aiops-agent-linux-amd64`;
|
||
|
||
const manualSteps = [
|
||
{
|
||
icon: Download,
|
||
title: '1. 下载二进制',
|
||
commands: [
|
||
`curl -fsSL ${binaryUrl} -o /usr/local/bin/aiops-agent`,
|
||
],
|
||
},
|
||
{
|
||
icon: Settings,
|
||
title: '2. 赋予执行权限',
|
||
commands: ['chmod +x /usr/local/bin/aiops-agent'],
|
||
},
|
||
{
|
||
icon: Cpu,
|
||
title: '3. 创建 systemd 服务',
|
||
commands: [
|
||
`cat > /etc/systemd/system/aiops-agent.service << 'EOF'`,
|
||
`[Unit]`,
|
||
`Description=AIOps Agent`,
|
||
`After=network.target`,
|
||
``,
|
||
`[Service]`,
|
||
`Type=simple`,
|
||
`ExecStart=/usr/local/bin/aiops-agent --server ${serverUrl} --token ${token}`,
|
||
`Restart=always`,
|
||
`RestartSec=5`,
|
||
``,
|
||
`[Install]`,
|
||
`WantedBy=multi-user.target`,
|
||
`EOF`,
|
||
],
|
||
},
|
||
{
|
||
icon: Play,
|
||
title: '4. 启动并设置开机自启',
|
||
commands: [
|
||
'systemctl daemon-reload',
|
||
'systemctl enable --now aiops-agent',
|
||
'systemctl status aiops-agent',
|
||
],
|
||
},
|
||
];
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent className="max-w-2xl bg-slate-900 border-slate-800 text-slate-100">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-slate-100">部署 Agent</DialogTitle>
|
||
<DialogDescription className="text-slate-400">
|
||
在目标主机上执行以下命令即可完成 Agent 安装与注册
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<Tabs defaultValue="auto" className="w-full">
|
||
<TabsList className="bg-slate-800 border border-slate-700">
|
||
<TabsTrigger value="auto" className="data-[state=active]:bg-slate-700">
|
||
<Terminal className="h-4 w-4 mr-1.5" />
|
||
安装Agent
|
||
</TabsTrigger>
|
||
<TabsTrigger value="manual" className="data-[state=active]:bg-slate-700">
|
||
手动安装
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
|
||
{/* 自动安装 */}
|
||
<TabsContent value="auto" className="mt-4 space-y-3">
|
||
<p className="text-sm text-slate-400">
|
||
在目标主机执行以下命令,Agent 将自动下载、安装并启动:
|
||
</p>
|
||
<div className="relative rounded-lg bg-slate-950 border border-slate-800 p-4">
|
||
<pre className="text-xs text-emerald-400 font-mono whitespace-pre-wrap break-all pr-10">
|
||
{installCommand}
|
||
</pre>
|
||
<Button
|
||
size="icon"
|
||
variant="ghost"
|
||
onClick={handleCopy}
|
||
className="absolute top-2 right-2 h-8 w-8 text-slate-400 hover:text-slate-100 hover:bg-slate-800"
|
||
title="复制到剪贴板"
|
||
>
|
||
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 手动复制模式:当自动复制失败时展示可全选的文本域 */}
|
||
{showManualInput && (
|
||
<div className="rounded-lg bg-amber-950/30 border border-amber-800/50 p-4">
|
||
<p className="text-xs text-amber-400 mb-2">
|
||
⚠️ 自动复制失败。请点击下方输入框全选 (Ctrl+A) 后复制 (Ctrl+C):
|
||
</p>
|
||
<textarea
|
||
readOnly
|
||
value={installCommand}
|
||
className="w-full h-24 bg-slate-950 border border-amber-800/30 text-emerald-400 font-mono text-xs p-3 rounded resize-none focus:outline-none focus:border-amber-600"
|
||
onClick={(e) => (e.target as HTMLTextAreaElement).select()}
|
||
/>
|
||
</div>
|
||
)}
|
||
<div className="flex items-start gap-2 text-xs text-slate-500">
|
||
<Terminal className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||
<span>
|
||
服务器地址已自动检测为当前访问地址({serverUrl})。Agent 安装成功后将自动连接并开始上报指标。
|
||
</span>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 手动安装 */}
|
||
<TabsContent value="manual" className="mt-4 space-y-4">
|
||
<div className="max-h-[420px] overflow-y-auto space-y-3 pr-1">
|
||
{manualSteps.map((step) => (
|
||
<div key={step.title} className="rounded-lg bg-slate-950 border border-slate-800 overflow-hidden">
|
||
<div className="flex items-center gap-2 px-4 py-2.5 bg-slate-800/50">
|
||
<step.icon className="h-4 w-4 text-blue-400" />
|
||
<span className="text-sm font-medium text-slate-200">{step.title}</span>
|
||
</div>
|
||
<div className="p-3 space-y-1">
|
||
{step.commands.map((cmd, idx) => (
|
||
<div key={idx} className="flex items-start gap-2">
|
||
<span className="text-slate-600 font-mono text-xs select-none mt-0.5">$</span>
|
||
<code className="text-xs text-slate-300 font-mono whitespace-pre-wrap break-all flex-1">
|
||
{cmd}
|
||
</code>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<p className="text-xs text-slate-500">
|
||
完成上述步骤后,Agent 将作为 systemd 服务运行并自动上报指标。
|
||
</p>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|
||
|
||
export default AgentWizard;
|