From 7603906c36d5c84f61fec3ad331322d2ebbd352b Mon Sep 17 00:00:00 2001 From: dushixiang Date: Sun, 23 Aug 2026 20:32:56 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=8E=A2?= =?UTF-8?q?=E9=92=88=E8=AF=A6=E6=83=85=E9=A1=B5=E4=B8=8E=E6=B5=81=E9=87=8F?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 探针详情: /admin/agents 前缀匹配宽屏布局;状态并入页头,返回列表改为名称旁小字链接,编辑探针移至页头右侧;基本信息/流量/防篡改/SSH 配置去卡片嵌套,内容扁平化 - 流量统计: 重构页面布局,配置与使用情况分栏;流量限额支持 GB/TB 单位切换与自动适配(后端字节存储无需改动);限额输入与单位下拉组合;使用情况改为卡片呈现 --- web/src/components/PageHeader.tsx | 2 +- web/src/layouts/AdminLayout/index.tsx | 23 +- web/src/pages/Agents/AgentBasicInfo.tsx | 62 +--- web/src/pages/Agents/AgentDetail.tsx | 68 ++-- web/src/pages/Agents/SSHLoginConfig.tsx | 24 +- .../pages/Agents/TamperProtectionConfig.tsx | 24 +- web/src/pages/Agents/TrafficStats.tsx | 341 ++++++++++-------- 7 files changed, 272 insertions(+), 272 deletions(-) diff --git a/web/src/components/PageHeader.tsx b/web/src/components/PageHeader.tsx index 4f7045f..3be5a24 100644 --- a/web/src/components/PageHeader.tsx +++ b/web/src/components/PageHeader.tsx @@ -14,7 +14,7 @@ export interface Action { } interface PageHeaderProps { - title: string; + title: React.ReactNode; extra?: React.ReactNode; actions?: Action[]; } diff --git a/web/src/layouts/AdminLayout/index.tsx b/web/src/layouts/AdminLayout/index.tsx index 8849f55..dfbc6ac 100644 --- a/web/src/layouts/AdminLayout/index.tsx +++ b/web/src/layouts/AdminLayout/index.tsx @@ -11,17 +11,18 @@ import type {User} from '@/types'; const AdminLayout = () => { const navigate = useNavigate(); const location = useLocation(); - const isWideCompactPage = [ - '/admin/agents', - '/admin/monitors', - '/admin/ddns', - '/admin/api-keys', - '/admin/manage-api-keys', - '/admin/alert-records', - '/admin/agents-install/one-click', - '/admin/agents-install/manual', - '/admin/settings', - ].includes(location.pathname); + // /admin/agents 前缀(含探针详情页)使用全宽布局 + const isWideCompactPage = location.pathname.startsWith('/admin/agents') + || [ + '/admin/monitors', + '/admin/ddns', + '/admin/api-keys', + '/admin/manage-api-keys', + '/admin/alert-records', + '/admin/agents-install/one-click', + '/admin/agents-install/manual', + '/admin/settings', + ].includes(location.pathname); const [userInfo, setUserInfo] = useState(null); const themeButtonRef = useRef(null); const {appliedTheme, toggleTheme} = useThemeToggle(themeButtonRef); diff --git a/web/src/pages/Agents/AgentBasicInfo.tsx b/web/src/pages/Agents/AgentBasicInfo.tsx index 73fbd99..0ccd70f 100644 --- a/web/src/pages/Agents/AgentBasicInfo.tsx +++ b/web/src/pages/Agents/AgentBasicInfo.tsx @@ -1,31 +1,17 @@ -import {useState} from 'react'; -import {App, Button, Card, Descriptions, Space, Spin, Tag} from 'antd'; -import {Clock, Edit, RefreshCw} from 'lucide-react'; +import {Descriptions, Space, Spin, Tag} from 'antd'; +import {Clock} from 'lucide-react'; import {useQuery} from '@tanstack/react-query'; import dayjs from 'dayjs'; -import {getAgentForAdmin, getAgentLatestMetricsForAdmin, getTags} from '@/api/agent.ts'; -import AgentEditModal from './AgentEditModal'; +import {getAgentForAdmin, getAgentLatestMetricsForAdmin} from '@/api/agent.ts'; interface AgentBasicInfoProps { agentId: string; } const AgentBasicInfo = ({agentId}: AgentBasicInfoProps) => { - const {message: messageApi} = App.useApp(); - const [editModalVisible, setEditModalVisible] = useState(false); - - const {data: tags = []} = useQuery({ - queryKey: ['admin', 'agents', 'tags'], - queryFn: async () => { - const response = await getTags(); - return response.data.tags || []; - }, - }); - const { data: agent, isLoading: agentLoading, - refetch: refetchAgent, } = useQuery({ queryKey: ['admin', 'agent', agentId], queryFn: async () => { @@ -38,7 +24,6 @@ const AgentBasicInfo = ({agentId}: AgentBasicInfoProps) => { const { data: latestMetrics, isLoading: metricsLoading, - refetch: refetchMetrics, } = useQuery({ queryKey: ['admin', 'agent', agentId, 'metrics', 'latest'], queryFn: async () => { @@ -48,11 +33,6 @@ const AgentBasicInfo = ({agentId}: AgentBasicInfoProps) => { enabled: !!agentId, }); - const handleRefresh = () => { - refetchAgent(); - refetchMetrics(); - }; - if (agentLoading || metricsLoading) { return (
@@ -98,28 +78,7 @@ const AgentBasicInfo = ({agentId}: AgentBasicInfoProps) => { return (
- - - - - } - > - + {agent.name || '-'} {agent.hostname || '-'} @@ -242,19 +201,6 @@ const AgentBasicInfo = ({agentId}: AgentBasicInfoProps) => { ) : '-'} - - - {/* 编辑模态框 */} - setEditModalVisible(false)} - onSuccess={() => { - setEditModalVisible(false); - refetchAgent(); - }} - />
); }; diff --git a/web/src/pages/Agents/AgentDetail.tsx b/web/src/pages/Agents/AgentDetail.tsx index 033994d..e03296a 100644 --- a/web/src/pages/Agents/AgentDetail.tsx +++ b/web/src/pages/Agents/AgentDetail.tsx @@ -2,11 +2,12 @@ import {useEffect, useState} from 'react'; import {useNavigate, useParams, useSearchParams} from 'react-router-dom'; import type {TabsProps} from 'antd'; import {Alert, Spin, Tabs, Tag} from 'antd'; -import {Activity, ArrowLeft, FileWarning, Lock, Shield, TrendingUp} from 'lucide-react'; -import {useQuery} from '@tanstack/react-query'; -import {getAgentForAdmin} from '@/api/agent.ts'; +import {Activity, ArrowLeft, Edit, FileWarning, Lock, Shield, TrendingUp} from 'lucide-react'; +import {useQuery, useQueryClient} from '@tanstack/react-query'; +import {getAgentForAdmin, getTags} from '@/api/agent.ts'; import AgentBasicInfo from './AgentBasicInfo'; import AgentAudit from './AgentAudit'; +import AgentEditModal from './AgentEditModal'; import TamperProtection from './TamperProtection'; import SSHLoginMonitor from './SSHLoginMonitor'; import TrafficStats from './TrafficStats'; @@ -16,8 +17,10 @@ import {PagePanel} from '@/components/PagePanel'; const AgentDetail = () => { const {id} = useParams<{ id: string }>(); const navigate = useNavigate(); + const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useSearchParams(); const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'info'); + const [editModalVisible, setEditModalVisible] = useState(false); // 获取探针基本信息(用于显示头部卡片) const {data: agent, isLoading} = useQuery({ @@ -30,6 +33,15 @@ const AgentDetail = () => { enabled: !!id, }); + // 获取标签列表(编辑探针弹窗使用) + const {data: tags = []} = useQuery({ + queryKey: ['admin', 'agents', 'tags'], + queryFn: async () => { + const response = await getTags(); + return response.data.tags || []; + }, + }); + useEffect(() => { // 同步 activeTab 到 URL const nextParams = new URLSearchParams(searchParams); @@ -136,29 +148,29 @@ const AgentDetail = () => { return (
+ {agent.name || agent.hostname} + + + } + extra={agent.status === 1 ? 在线 : 离线} actions={[{ - key: 'back', - label: '返回列表', - icon: , - onClick: () => navigate('/admin/agents'), + key: 'edit', + label: '编辑探针', + type: 'primary', + icon: , + onClick: () => setEditModalVisible(true), }]} /> - -
-
- -
-
-
当前连接状态
-
- {agent.status === 1 ? 在线 : 离线} -
-
-
-
- {/* Tabs 内容 */} { items={tabItems} /> + + {/* 编辑探针模态框 */} + setEditModalVisible(false)} + onSuccess={() => { + setEditModalVisible(false); + queryClient.invalidateQueries({queryKey: ['admin', 'agent', id]}); + }} + />
); }; diff --git a/web/src/pages/Agents/SSHLoginConfig.tsx b/web/src/pages/Agents/SSHLoginConfig.tsx index f2c36ec..8f2e410 100644 --- a/web/src/pages/Agents/SSHLoginConfig.tsx +++ b/web/src/pages/Agents/SSHLoginConfig.tsx @@ -1,5 +1,5 @@ import React, {useEffect} from 'react'; -import {Alert, App, Button, Card, Form, Input, Space, Switch} from 'antd'; +import {Alert, App, Button, Form, Input, Space, Spin, Switch} from 'antd'; import {Save, Terminal} from 'lucide-react'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import type {SSHLoginConfig as SSHLoginConfigType} from '@/types'; @@ -96,14 +96,12 @@ const SSHLoginConfig: React.FC = ({agentId}) => { }, [config, form]); return ( - +
+
+
SSH 登录监控配置
- } - extra={ - } - loading={isLoading} - > - +
+ {isLoading ? ( +
+ +
+ ) : ( +
= ({agentId}) => { /> )} - + )} +
); }; diff --git a/web/src/pages/Agents/TamperProtectionConfig.tsx b/web/src/pages/Agents/TamperProtectionConfig.tsx index 25beb45..16fdc99 100644 --- a/web/src/pages/Agents/TamperProtectionConfig.tsx +++ b/web/src/pages/Agents/TamperProtectionConfig.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from 'react'; -import {Alert, App, Button, Card, Form, Input, List, Space, Switch} from 'antd'; +import {Alert, App, Button, Form, Input, List, Space, Spin, Switch} from 'antd'; import {Plus, Save, Shield, Trash2} from 'lucide-react'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {getTamperConfig, updateTamperConfig} from '@/api/tamper'; @@ -70,14 +70,12 @@ const TamperProtectionConfig: React.FC = ({agentId} }, [config, form]); return ( - +
+
+
防篡改保护配置
- } - extra={ - } - loading={isLoading} - > - +
+ {isLoading ? ( +
+ +
+ ) : ( + = ({agentId} /> )} - + )} +
); }; diff --git a/web/src/pages/Agents/TrafficStats.tsx b/web/src/pages/Agents/TrafficStats.tsx index 5c255be..5178bf2 100644 --- a/web/src/pages/Agents/TrafficStats.tsx +++ b/web/src/pages/Agents/TrafficStats.tsx @@ -1,17 +1,15 @@ import React, {useEffect, useState} from 'react'; import { - Alert, App, Button, - Card, Col, - Descriptions, Form, InputNumber, Progress, Row, Select, Space, + Spin, Statistic, Switch, Tag @@ -33,6 +31,22 @@ const TrafficStats: React.FC = ({agentId}) => { const [form] = Form.useForm(); const queryClient = useQueryClient(); const [enabled, setEnabled] = useState(false); + const [limitUnit, setLimitUnit] = useState<'GB' | 'TB'>('GB'); + + const bytesPerUnit = limitUnit === 'TB' ? 1024 * 1024 * 1024 * 1024 : 1024 * 1024 * 1024; + + // 切换单位时把已填写的数值换算到新单位 + const handleUnitChange = (unit: 'GB' | 'TB') => { + if (unit === limitUnit) { + return; + } + const factor = unit === 'TB' ? 1024 : 1 / 1024; + const values = form.getFieldsValue(); + form.setFieldsValue({ + trafficLimit: (values.trafficLimit || 0) * factor, + }); + setLimitUnit(unit); + }; // 获取流量统计 const {data: stats, isLoading} = useQuery({ @@ -61,7 +75,7 @@ const TrafficStats: React.FC = ({agentId}) => { try { const values = enabled ? await form.validateFields() : form.getFieldsValue(); const limitValue = typeof values.trafficLimit === 'number' ? values.trafficLimit : 0; - const limitBytes = enabled ? limitValue * 1024 * 1024 * 1024 : 0; + const limitBytes = enabled ? limitValue * bytesPerUnit : 0; saveMutation.mutate({ enabled: enabled, type: values.trafficType || 'recv', @@ -104,18 +118,24 @@ const TrafficStats: React.FC = ({agentId}) => { if (stats) { // 从服务器返回的 enabled 字段读取启用状态 setEnabled(stats.enabled); + // 限额或已用量达到 1TB 时自动使用 TB 单位展示 + const unit: 'GB' | 'TB' = (stats.limit > 0 && stats.limit >= 1024 * 1024 * 1024 * 1024) + || (stats.used >= 1024 * 1024 * 1024 * 1024) + ? 'TB' + : 'GB'; + setLimitUnit(unit); + const divisor = unit === 'TB' ? 1024 * 1024 * 1024 * 1024 : 1024 * 1024 * 1024; form.setFieldsValue({ trafficType: stats.type || 'recv', - trafficLimit: stats.limit > 0 ? stats.limit / (1024 * 1024 * 1024) : 0, - trafficUsed: stats.used > 0 ? stats.used / (1024 * 1024 * 1024) : 0, + trafficLimit: stats.limit > 0 ? stats.limit / divisor : 0, trafficResetDay: stats.resetDay || 0, }); } else { setEnabled(false); + setLimitUnit('GB'); form.setFieldsValue({ trafficType: 'recv', trafficLimit: 0, - trafficUsed: 0, trafficResetDay: 0, }); } @@ -144,43 +164,26 @@ const TrafficStats: React.FC = ({agentId}) => { }; return ( - - } - /> - - - {/* 左侧:流量配置 */} - - - - 流量统计配置 -
- } - extra={ - - } - loading={isLoading} - > + + {/* 左侧:流量配置 */} + +
+ + 流量统计配置 +
+ {isLoading ? ( +
+ +
+ ) : ( + <> @@ -216,36 +219,32 @@ const TrafficStats: React.FC = ({agentId}) => { - - - - - + + + + +