diff --git a/frontend/src/components/aiops/AIOpsAssistant.tsx b/frontend/src/components/aiops/AIOpsAssistant.tsx index 7ec563b..891666d 100644 --- a/frontend/src/components/aiops/AIOpsAssistant.tsx +++ b/frontend/src/components/aiops/AIOpsAssistant.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -145,6 +146,7 @@ function fmtTime(iso: string): string { } export function AIOpsAssistant() { + const navigate = useNavigate(); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -381,20 +383,12 @@ export function AIOpsAssistant() {
- {configReady ? ( + {configReady && ( {config.model} - ) : ( - - 未配置 - )} -
@@ -483,7 +477,12 @@ export function AIOpsAssistant() {

{configReady ? '暂无对话记录,输入问题开始与 AI 助手对话' : 'AI 助手尚未配置大模型'}

{!configReady && ( - diff --git a/frontend/src/components/aiops/AgentCLI.tsx b/frontend/src/components/aiops/AgentCLI.tsx index 97fef3f..c799b0a 100644 --- a/frontend/src/components/aiops/AgentCLI.tsx +++ b/frontend/src/components/aiops/AgentCLI.tsx @@ -2,7 +2,6 @@ // 关联主机后,用自然语言描述任务,AI 通过 SSH 工具自主排障/部署,循环重试直到解决。 // 通过 WebSocket 实时展示思考、执行的命令、输出与结果;高危命令弹框确认。 import { useState, useRef, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -12,8 +11,9 @@ import { } from '@/components/ui/select'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { serverApi, buildAgentCliWsUrl } from '@/api'; +import { toast } from 'sonner'; import { - Send, Square, Terminal, Settings, ChevronDown, ChevronRight, + Send, Square, Terminal, ChevronDown, ChevronRight, Shield, Brain, Cpu, Database, Loader2, Bot, AlertTriangle, CheckCircle2, Lightbulb, Server, } from 'lucide-react'; @@ -57,7 +57,6 @@ function riskClass(risk?: string): string { } export default function AgentCLI() { - const navigate = useNavigate(); const confirm = useConfirm(); const [servers, setServers] = useState<{ id: number; hostname: string; ip: string }[]>([]); @@ -67,7 +66,7 @@ export default function AgentCLI() { const [running, setRunning] = useState(false); const [round, setRound] = useState(0); const [tokens, setTokens] = useState(0); - const [tipsOpen, setTipsOpen] = useState(true); + const [tipsOpen, setTipsOpen] = useState(false); const [connected, setConnected] = useState(false); const wsRef = useRef(null); @@ -95,6 +94,7 @@ export default function AgentCLI() { wsRef.current = ws; ws.onopen = () => setConnected(true); ws.onclose = () => { setConnected(false); setRunning(false); }; + ws.onerror = () => { setConnected(false); }; ws.onmessage = (e) => { try { handleEvent(JSON.parse(e.data)); } catch { /* 忽略非 JSON */ } }; @@ -176,7 +176,13 @@ export default function AgentCLI() { // 发送任务 // Logan.Li const send = () => { const q = input.trim(); - if (!q || running || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return; + if (!q) return; + if (!serverId) { toast.error('请先选择目标主机'); return; } + if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { + toast.error('连接未就绪,请检查大模型配置或刷新页面重试'); + return; + } + if (running) return; push({ kind: 'user', content: q }); wsRef.current.send(JSON.stringify({ type: 'start', query: q })); setInput(''); @@ -219,9 +225,6 @@ export default function AgentCLI() { )}
- @@ -232,7 +235,6 @@ export default function AgentCLI() { {tipsOpen ? : } 核心能力与设计思路 - — Agent CLI 由四大模块构成,参考 opsxcli AI Agent 移植
{tipsOpen && ( @@ -254,9 +256,23 @@ export default function AgentCLI() {
{items.length === 0 && ( -
+
-
{serverId ? '描述你要执行的任务,例如「检查磁盘使用率并清理大文件」' : '请先选择一台目标主机'}
+ {!serverId ? ( + <> +
请先选择一台目标主机
+ {servers.length === 0 && ( +
暂无主机,请先前往「资产管理」添加服务器
+ )} + + ) : !connected ? ( + <> + +
正在连接主机...
+ + ) : ( +
描述你要执行的任务,例如「检查磁盘使用率并清理大文件」
+ )}
)} {items.map((it) => ( @@ -270,7 +286,6 @@ export default function AgentCLI() { onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }} placeholder={serverId ? '输入任务描述,回车发送(Shift+回车换行)' : '请先选择目标主机'} - disabled={!connected} rows={1} className="flex-1 resize-none bg-slate-800 border border-slate-700 rounded-md px-3 py-2 text-sm text-slate-200 placeholder:text-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500 max-h-32" /> @@ -279,7 +294,7 @@ export default function AgentCLI() { 停止 ) : ( - )} diff --git a/frontend/src/components/monitor/log-center/LogCenter.tsx b/frontend/src/components/monitor/log-center/LogCenter.tsx index 9048c8b..d91b739 100644 --- a/frontend/src/components/monitor/log-center/LogCenter.tsx +++ b/frontend/src/components/monitor/log-center/LogCenter.tsx @@ -4,7 +4,7 @@ import { Navigate, useParams } from 'react-router-dom'; import { ScrollText } from 'lucide-react'; import { LogQueryProvider } from './LogQueryContext'; import { UnifiedQueryPanel } from './UnifiedQueryPanel'; -import { LogLibraryPage } from './LogLibraryPage'; +import { LogDatasourcePage } from './LogDatasourcePage'; import { LogAlertsPanel } from './LogAlertsPanel'; import type { LogEngine } from './types-v3'; @@ -42,7 +42,7 @@ export default function LogCenter() { {(tab === 'loki' || tab === 'es') && meta.engine && ( setAlertReloadKey(k => k + 1)} /> )} - {tab === 'datasources' && } + {tab === 'datasources' && } {tab === 'alerts' && }
diff --git a/frontend/src/components/monitor/log-center/logLibraryTypes.ts b/frontend/src/components/monitor/log-center/logLibraryTypes.ts index 692be72..75ebb87 100644 --- a/frontend/src/components/monitor/log-center/logLibraryTypes.ts +++ b/frontend/src/components/monitor/log-center/logLibraryTypes.ts @@ -53,19 +53,26 @@ export const STATUS_CONFIG: Record = { untested: { color: 'var(--dg-text-muted)', label: '未测试', dot: '#94a3b8' }, }; -/** Mock 数据源 */ +/** Mock 数据源(仅演示用 — demo-loki / demo-es 各一个) */ export const MOCK_DATASOURCES: LogDatasource[] = [ - { id: 1, engine: 'loki', name: 'prod-loki', url: 'http://loki.prod.local:3100', status: 'connected', description: '生产环境 Loki', isDefault: true, auth: null }, - { id: 2, engine: 'loki', name: 'dev-loki', url: 'http://loki.dev.local:3100', status: 'connected', description: '开发环境 Loki', isDefault: false, auth: null }, - { id: 3, engine: 'elasticsearch', name: 'prod-es', url: 'http://es.prod.local:9200', status: 'connected', description: '生产环境 ES', isDefault: true, auth: null }, - { id: 4, engine: 'elasticsearch', name: 'dev-es', url: 'http://es.dev.local:9200', status: 'error', description: '开发环境 ES', isDefault: false, auth: null }, + { id: 1, engine: 'loki', name: 'demo-loki', url: '', status: 'connected', description: 'Loki 模拟数据', isDefault: true, auth: null }, + { id: 2, engine: 'elasticsearch', name: 'demo-es', url: '', status: 'connected', description: 'ES 模拟数据', isDefault: true, auth: null }, ]; -/** 从 localStorage 加载数据源 */ +/** 从 localStorage 加载数据源(迁移旧数据自动清理) */ export function loadDatasources(): LogDatasource[] { try { const raw = localStorage.getItem(STORAGE_KEY); - if (raw) return JSON.parse(raw); + if (raw) { + const list: LogDatasource[] = JSON.parse(raw); + // 版本迁移:旧数据(含 prod-loki / dev-loki 等)强制重置为 demo + const oldNames = list.map((d) => d.name); + if (oldNames.includes('prod-loki') || oldNames.includes('dev-es') || oldNames.includes('prod-es') || oldNames.includes('dev-loki')) { + localStorage.removeItem(STORAGE_KEY); + return [...MOCK_DATASOURCES]; + } + return list; + } } catch { /* ignore */ } return [...MOCK_DATASOURCES]; }