diff --git a/web/src/admin/pages/Agents/AgentList.tsx b/web/src/admin/pages/Agents/AgentList.tsx index 9ceabab..04d45eb 100644 --- a/web/src/admin/pages/Agents/AgentList.tsx +++ b/web/src/admin/pages/Agents/AgentList.tsx @@ -3,7 +3,7 @@ import {Link, useNavigate, useSearchParams} from 'react-router-dom'; import type {MenuProps} from 'antd'; import {App, Button, Divider, Dropdown, Form, Input, Select, Space, Table, Tag} from 'antd'; import type {ColumnsType, TablePaginationConfig} from 'antd/es/table'; -import {Edit, Eye, MoreVertical, Plus, RefreshCw, Shield, Tags, Trash2} from 'lucide-react'; +import {Edit, Eye, FileWarning, Lock, MoreVertical, Plus, RefreshCw, Shield, Tags, Trash2} from 'lucide-react'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import dayjs from 'dayjs'; import {deleteAgent, getAgentPaging, getTags} from '@/api/agent.ts'; @@ -12,6 +12,8 @@ import {getErrorMessage} from '@/lib/utils'; import {PageHeader} from '@admin/components'; import AgentEditModal from './AgentEditModal'; import BatchTagsModal from './BatchTagsModal'; +import BatchTamperProtectionModal from './BatchTamperProtectionModal'; +import BatchSSHLoginConfigModal from './BatchSSHLoginConfigModal'; interface AgentFilters { name?: string; @@ -30,6 +32,8 @@ const AgentList = () => { const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [editModalVisible, setEditModalVisible] = useState(false); const [batchTagModalVisible, setBatchTagModalVisible] = useState(false); + const [batchTamperModalVisible, setBatchTamperModalVisible] = useState(false); + const [batchSSHModalVisible, setBatchSSHModalVisible] = useState(false); const [editingAgentId, setEditingAgentId] = useState(undefined); const current = Number(searchParams.get('page')) || 1; @@ -202,6 +206,22 @@ const AgentList = () => { setBatchTagModalVisible(true); }; + const handleBatchTamperConfig = () => { + if (selectedRowKeys.length === 0) { + messageApi.warning('请先选择要操作的探针'); + return; + } + setBatchTamperModalVisible(true); + }; + + const handleBatchSSHConfig = () => { + if (selectedRowKeys.length === 0) { + messageApi.warning('请先选择要操作的探针'); + return; + } + setBatchSSHModalVisible(true); + }; + const columns: ColumnsType = [ { title: '名称', @@ -377,6 +397,18 @@ const AgentList = () => { icon: , onClick: () => navigate(`/admin/agents/${record.id}?tab=audit`), }, + { + key: 'tamper', + label: '防篡改保护', + icon: , + onClick: () => navigate(`/admin/agents/${record.id}?tab=tamper`), + }, + { + key: 'ssh-login', + label: 'SSH 登录监控', + icon: , + onClick: () => navigate(`/admin/agents/${record.id}?tab=ssh-login`), + }, { key: 'edit', label: '编辑信息', @@ -434,6 +466,20 @@ const AgentList = () => { onClick: handleBatchTags, disabled: selectedRowKeys.length === 0, }, + { + key: 'batch-tamper', + label: `批量配置防篡改保护${selectedRowKeys.length > 0 ? ` (${selectedRowKeys.length})` : ''}`, + icon: , + onClick: handleBatchTamperConfig, + disabled: selectedRowKeys.length === 0, + }, + { + key: 'batch-ssh', + label: `批量配置 SSH 登录监控${selectedRowKeys.length > 0 ? ` (${selectedRowKeys.length})` : ''}`, + icon: , + onClick: handleBatchSSHConfig, + disabled: selectedRowKeys.length === 0, + }, { key: 'register', label: '注册探针', @@ -533,6 +579,26 @@ const AgentList = () => { setSelectedRowKeys([]); }} /> + + setBatchTamperModalVisible(false)} + onSuccess={() => { + setBatchTamperModalVisible(false); + setSelectedRowKeys([]); + }} + /> + + setBatchSSHModalVisible(false)} + onSuccess={() => { + setBatchSSHModalVisible(false); + setSelectedRowKeys([]); + }} + /> ); }; diff --git a/web/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx b/web/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx new file mode 100644 index 0000000..3e95157 --- /dev/null +++ b/web/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx @@ -0,0 +1,133 @@ +import {useEffect} from 'react'; +import {App, Form, Input, Modal, Switch} from 'antd'; +import {useMutation, useQueryClient} from '@tanstack/react-query'; +import {updateSSHLoginConfig} from '@/api/agent'; +import {getErrorMessage} from '@/lib/utils'; + +const {TextArea} = Input; + +// 验证 IP 地址格式(支持 IPv4 和 CIDR) +const validateIPOrCIDR = (value: string): boolean => { + const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const cidrRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[12]?[0-9])$/; + return ipv4Regex.test(value) || cidrRegex.test(value); +}; + +const parseIPWhitelist = (text: string): string[] => { + return text + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0); +}; + +interface BatchSSHLoginConfigModalProps { + open: boolean; + agentIds: string[]; + onCancel: () => void; + onSuccess: () => void; +} + +const BatchSSHLoginConfigModal = ({open, agentIds, onCancel, onSuccess}: BatchSSHLoginConfigModalProps) => { + const {message: messageApi} = App.useApp(); + const [form] = Form.useForm(); + const queryClient = useQueryClient(); + + useEffect(() => { + if (!open) { + return; + } + form.setFieldsValue({ + enabled: false, + ipWhitelistText: '', + }); + }, [open, form]); + + const batchMutation = useMutation({ + mutationFn: async (payload: {enabled: boolean; ipWhitelist: string[]}) => { + const results = await Promise.allSettled( + agentIds.map(agentId => updateSSHLoginConfig(agentId, payload)), + ); + const failedCount = results.filter(result => result.status === 'rejected').length; + return { + total: agentIds.length, + successCount: agentIds.length - failedCount, + failedCount, + }; + }, + onSuccess: (result) => { + if (result.failedCount > 0) { + messageApi.warning(`已配置 ${result.successCount} 个探针,失败 ${result.failedCount} 个`); + } else { + messageApi.success(`成功配置 ${result.total} 个探针的 SSH 登录监控`); + } + queryClient.invalidateQueries({queryKey: ['admin', 'agents']}); + queryClient.invalidateQueries({queryKey: ['sshLoginConfig']}); + onSuccess(); + }, + onError: (error: unknown) => { + messageApi.error(getErrorMessage(error, '批量配置 SSH 登录监控失败')); + }, + }); + + const handleOk = async () => { + if (agentIds.length === 0) { + messageApi.warning('请先选择要操作的探针'); + return; + } + try { + const values = await form.validateFields(); + const ipWhitelistText = values.ipWhitelistText || ''; + const ipWhitelist = parseIPWhitelist(ipWhitelistText); + const invalidIPs = ipWhitelist.filter(ip => !validateIPOrCIDR(ip)); + if (invalidIPs.length > 0) { + messageApi.error(`以下 IP 地址或 CIDR 格式不正确:\n${invalidIPs.join('\n')}`); + return; + } + await batchMutation.mutateAsync({ + enabled: values.enabled, + ipWhitelist: ipWhitelist, + }); + } catch (error: unknown) { + if (typeof error === 'object' && error !== null && 'errorFields' in error) { + return; + } + messageApi.error(getErrorMessage(error, '批量配置 SSH 登录监控失败')); + } + }; + + return ( + +
+ + + + + +