import React, { useState, useEffect, useCallback } from 'react'; import { Table, Button, Space, Tag, Tooltip, Popconfirm, Empty, Spin, Badge, Collapse, Card, } from 'antd'; import { PlusOutlined, DeleteOutlined, ReloadOutlined, ApiOutlined, CloudServerOutlined, FolderOutlined, } from '@ant-design/icons'; import api from '../api'; import PortCreateModal from './PortCreateModal'; import NetworkCardCreateModal from './NetworkCardCreateModal'; import { designTokens } from '../config/theme'; const { Panel } = Collapse; function NetworkCardPanel({ deviceId, deviceName, onRefresh, refreshTrigger }) { const [cards, setCards] = useState([]); const [networkCards, setNetworkCards] = useState([]); const [loading, setLoading] = useState(false); const [createPortModalVisible, setCreatePortModalVisible] = useState(false); const [createCardModalVisible, setCreateCardModalVisible] = useState(false); const [selectedCard, setSelectedCard] = useState(null); const [expandedCards, setExpandedCards] = useState([]); const fetchData = useCallback(async () => { if (!deviceId) return; try { setLoading(true); const [cardsResponse, networkCardsResponse] = await Promise.all([ api.get(`/network-cards/device/${deviceId}/with-ports`), api.get(`/network-cards/device/${deviceId}`), ]); const cardsData = cardsResponse.data || cardsResponse || []; setCards(cardsData); setNetworkCards(networkCardsResponse.data || networkCardsResponse || []); const initialExpanded = cardsData .filter(card => card.ports && card.ports.length > 0) .map(card => card.nicId); setExpandedCards(initialExpanded); } catch (error) { console.error('获取网卡数据失败:', error); setCards([]); setNetworkCards([]); } finally { setLoading(false); } }, [deviceId]); useEffect(() => { fetchData(); }, [fetchData, refreshTrigger]); const handleDeleteCard = useCallback( async card => { try { await api.delete(`/network-cards/${card.nicId}`); import('antd').then(({ message }) => message.success('网卡删除成功')); fetchData(); onRefresh?.(); } catch (error) { import('antd').then(({ message }) => message.error(error.response?.data?.error || '网卡删除失败') ); } }, [fetchData, onRefresh] ); const handleDeletePort = useCallback( async port => { try { await api.delete(`/device-ports/${port.portId}`); import('antd').then(({ message }) => message.success('端口删除成功')); fetchData(); onRefresh?.(); } catch (error) { import('antd').then(({ message }) => message.error('端口删除失败')); } }, [fetchData, onRefresh] ); const handleCreateCardSuccess = useCallback(() => { fetchData(); onRefresh?.(); }, [fetchData, onRefresh]); const handleCreatePortSuccess = useCallback(() => { fetchData(); onRefresh?.(); }, [fetchData, onRefresh]); const handleExpand = nicId => { setExpandedCards(prev => { if (prev.includes(nicId)) { return prev.filter(id => id !== nicId); } return [...prev, nicId]; }); }; const getStatusTag = status => { const config = { free: { color: 'success', text: '空闲' }, occupied: { color: 'processing', text: '占用' }, fault: { color: 'error', text: '故障' }, normal: { color: 'success', text: '正常' }, warning: { color: 'warning', text: '警告' }, offline: { color: 'default', text: '离线' }, }; const { color, text } = config[status] || { color: 'default', text: status }; return {text}; }; const getTypeTag = type => { const config = { RJ45: { color: 'blue', text: 'RJ45' }, SFP: { color: 'green', text: 'SFP' }, 'SFP+': { color: 'cyan', text: 'SFP+' }, SFP28: { color: 'purple', text: 'SFP28' }, QSFP: { color: 'orange', text: 'QSFP' }, QSFP28: { color: 'red', text: 'QSFP28' }, }; const { color, text } = config[type] || { color: 'default', text: type }; return {text}; }; const renderPortTable = (ports, nicId) => { const columns = [ { title: '端口名称', dataIndex: 'portName', key: 'portName', width: 120, render: text => {text}, }, { title: '类型', dataIndex: 'portType', key: 'portType', width: 80, render: type => getTypeTag(type), }, { title: '速率', dataIndex: 'portSpeed', key: 'portSpeed', width: 70, }, { title: '状态', dataIndex: 'status', key: 'status', width: 70, render: status => getStatusTag(status), }, { title: 'VLAN', dataIndex: 'vlanId', key: 'vlanId', width: 60, render: vlanId => vlanId || '-', }, { title: '操作', key: 'action', width: 80, render: (_, record) => ( handleDeletePort(record)} okText="确定" cancelText="取消" > ), }, ]; return ( ); }; const renderCardHeader = card => { const stats = card.stats || { free: 0, occupied: 0, fault: 0, total: 0 }; return (
{card.isUngrouped ? : }
{card.name} {card.slotNumber && ( 插槽 {card.slotNumber} )}
{card.description || (card.isUngrouped ? '未分配到网卡的端口' : '网卡')}
空闲 占用 故障 {!card.isUngrouped && ( handleDeleteCard(card)} okText="确定" cancelText="取消" > )}
); }; if (loading) { return (
); } const totalStats = cards.reduce( (acc, card) => { const stats = card.stats || {}; acc.total += stats.total || 0; acc.free += stats.free || 0; acc.occupied += stats.occupied || 0; acc.fault += stats.fault || 0; return acc; }, { total: 0, free: 0, occupied: 0, fault: 0 } ); return (
个网卡 个端口
{cards.length === 0 ? (
该设备暂无网卡和端口
} >
) : ( setExpandedCards(keys)} expandIconPosition="end" style={{ background: 'transparent' }} > {cards.map(card => ( {card.ports && card.ports.length > 0 ? ( renderPortTable(card.ports, card.nicId) ) : (
该{card.isUngrouped ? '分组' : '网卡'}暂无端口
)}
))}
)} setCreateCardModalVisible(false)} onSuccess={handleCreateCardSuccess} /> { setCreatePortModalVisible(false); setSelectedCard(null); }} onSuccess={handleCreatePortSuccess} defaultNicId={selectedCard?.nicId} networkCard={selectedCard} disableNicChange={true} />
); } export default React.memo(NetworkCardPanel);