feat: 新增网卡、端口和接线管理功能
- 添加网卡(NetworkCard)模型及相关路由 - 实现端口(DevicePort)管理功能 - 新增接线(Cable)管理功能 - 添加前端网卡和端口管理界面 - 更新机柜可视化页面显示接线 - 添加设备详情抽屉展示端口和接线信息 - 更新部署文档包含数据库迁移指南 - 添加批量创建端口功能 - 设备删除时自动清理相关接线
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Table, Button, Space, Tag, Tooltip, Popconfirm, Empty, Spin, Badge } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined, ApiOutlined } from '@ant-design/icons';
|
||||
import axios from 'axios';
|
||||
import PortCreateModal from './PortCreateModal';
|
||||
|
||||
const designTokens = {
|
||||
colors: {
|
||||
primary: {
|
||||
main: '#667eea'
|
||||
},
|
||||
success: '#10b981',
|
||||
error: '#ef4444',
|
||||
warning: '#f59e0b'
|
||||
}
|
||||
};
|
||||
|
||||
function PortManagementPanel({ deviceId, deviceName, onRefresh }) {
|
||||
const [ports, setPorts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
|
||||
const fetchPorts = useCallback(async () => {
|
||||
if (!deviceId) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`/api/device-ports/device/${deviceId}`);
|
||||
setPorts(response.data || []);
|
||||
} catch (error) {
|
||||
console.error('获取端口列表失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [deviceId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPorts();
|
||||
}, [fetchPorts]);
|
||||
|
||||
const handleDelete = useCallback(async (port) => {
|
||||
try {
|
||||
await axios.delete(`/api/device-ports/${port.portId}`);
|
||||
import('antd').then(({ message }) => message.success('端口删除成功'));
|
||||
fetchPorts();
|
||||
onRefresh?.();
|
||||
} catch (error) {
|
||||
import('antd').then(({ message }) => message.error('端口删除失败'));
|
||||
}
|
||||
}, [fetchPorts, onRefresh]);
|
||||
|
||||
const handleCreateSuccess = useCallback(() => {
|
||||
fetchPorts();
|
||||
onRefresh?.();
|
||||
}, [fetchPorts, onRefresh]);
|
||||
|
||||
const getStatusTag = (status) => {
|
||||
const config = {
|
||||
free: { color: 'success', text: '空闲' },
|
||||
occupied: { color: 'processing', text: '占用' },
|
||||
fault: { color: 'error', text: '故障' }
|
||||
};
|
||||
const { color, text } = config[status] || { color: 'default', text: status };
|
||||
return <Tag color={color}>{text}</Tag>;
|
||||
};
|
||||
|
||||
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 <Tag color={color}>{text}</Tag>;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '端口名称',
|
||||
dataIndex: 'portName',
|
||||
key: 'portName',
|
||||
width: 120,
|
||||
render: (text) => (
|
||||
<Tooltip title={text}>
|
||||
<span style={{ fontWeight: 500 }}>{text}</span>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'portType',
|
||||
key: 'portType',
|
||||
width: 90,
|
||||
render: (type) => getTypeTag(type)
|
||||
},
|
||||
{
|
||||
title: '速率',
|
||||
dataIndex: 'portSpeed',
|
||||
key: 'portSpeed',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
render: (status) => getStatusTag(status)
|
||||
},
|
||||
{
|
||||
title: 'VLAN',
|
||||
dataIndex: 'vlanId',
|
||||
key: 'vlanId',
|
||||
width: 70,
|
||||
render: (vlanId) => vlanId || '-'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size="small">
|
||||
<Popconfirm
|
||||
title="确定要删除此端口吗?"
|
||||
onConfirm={() => handleDelete(record)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const freeCount = ports.filter(p => p.status === 'free').length;
|
||||
const occupiedCount = ports.filter(p => p.status === 'occupied').length;
|
||||
const faultCount = ports.filter(p => p.status === 'fault').length;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<Spin size="large" tip="加载端口数据中..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="port-management-panel">
|
||||
<div className="port-panel-header">
|
||||
<div className="port-stats">
|
||||
<Space size={16}>
|
||||
<Badge count={freeCount} style={{ backgroundColor: designTokens.colors.success }} />
|
||||
<span className="stat-label">空闲</span>
|
||||
<Badge count={occupiedCount} style={{ backgroundColor: '#1677ff' }} />
|
||||
<span className="stat-label">占用</span>
|
||||
<Badge count={faultCount} style={{ backgroundColor: designTokens.colors.error }} />
|
||||
<span className="stat-label">故障</span>
|
||||
</Space>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={fetchPorts}
|
||||
size="small"
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setCreateModalVisible(true)}
|
||||
style={{
|
||||
background: designTokens.colors.primary.gradient,
|
||||
border: 'none'
|
||||
}}
|
||||
>
|
||||
新增端口
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{ports.length === 0 ? (
|
||||
<div className="port-empty">
|
||||
<Empty
|
||||
description={
|
||||
<span>
|
||||
该设备暂无端口
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setCreateModalVisible(true)}
|
||||
style={{ padding: 0, marginTop: 8 }}
|
||||
>
|
||||
立即添加端口
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<ApiOutlined style={{ fontSize: 48, color: '#d9d9d9' }} />
|
||||
</Empty>
|
||||
</div>
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={ports}
|
||||
rowKey="portId"
|
||||
pagination={false}
|
||||
size="small"
|
||||
scroll={{ x: 600 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PortCreateModal
|
||||
device={{ deviceId, name: deviceName }}
|
||||
visible={createModalVisible}
|
||||
onClose={() => setCreateModalVisible(false)}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(PortManagementPanel);
|
||||
Reference in New Issue
Block a user