From 01a05719dc42a7b0b1070eedfa97198b2000f0c4 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Mon, 13 Apr 2026 16:54:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8B=93=E6=89=91=E5=9B=BE):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=BD=91=E7=BB=9C=E6=8B=93=E6=89=91=E5=9B=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/config/routes.js | 4 + backend/routes/topology.js | 323 +++++++++++++++++ frontend/package-lock.json | 33 ++ frontend/package.json | 2 + frontend/public/png/交换机.png | Bin 0 -> 12358 bytes frontend/public/png/服务器.png | Bin 0 -> 5632 bytes frontend/public/png/路由器.png | Bin 0 -> 6883 bytes frontend/public/png/防火墙.png | Bin 0 -> 17272 bytes frontend/src/components/CableWizardModal.jsx | 297 ++++++++-------- frontend/src/components/PortCreateModal.jsx | 3 +- .../components/topology/TopologyControls.jsx | 93 +++++ .../src/components/topology/TopologyGraph.jsx | 264 ++++++++++++++ .../src/components/topology/TopologyModal.jsx | 227 ++++++++++++ .../components/topology/TopologySidebar.jsx | 333 ++++++++++++++++++ .../topology/hooks/useTopologyData.js | 74 ++++ .../topology/hooks/useTopologyLayout.js | 73 ++++ frontend/src/components/topology/index.js | 6 + .../topology/nodes/FirewallNode.jsx | 114 ++++++ .../components/topology/nodes/GenericNode.jsx | 155 ++++++++ .../components/topology/nodes/RouterNode.jsx | 114 ++++++ .../components/topology/nodes/ServerNode.jsx | 114 ++++++ .../components/topology/nodes/StorageNode.jsx | 226 ++++++++++++ .../components/topology/nodes/SwitchNode.jsx | 120 +++++++ .../src/components/topology/nodes/index.js | 6 + frontend/src/pages/CableManagement.jsx | 17 + 25 files changed, 2453 insertions(+), 145 deletions(-) create mode 100644 backend/routes/topology.js create mode 100644 frontend/public/png/交换机.png create mode 100644 frontend/public/png/服务器.png create mode 100644 frontend/public/png/路由器.png create mode 100644 frontend/public/png/防火墙.png create mode 100644 frontend/src/components/topology/TopologyControls.jsx create mode 100644 frontend/src/components/topology/TopologyGraph.jsx create mode 100644 frontend/src/components/topology/TopologyModal.jsx create mode 100644 frontend/src/components/topology/TopologySidebar.jsx create mode 100644 frontend/src/components/topology/hooks/useTopologyData.js create mode 100644 frontend/src/components/topology/hooks/useTopologyLayout.js create mode 100644 frontend/src/components/topology/index.js create mode 100644 frontend/src/components/topology/nodes/FirewallNode.jsx create mode 100644 frontend/src/components/topology/nodes/GenericNode.jsx create mode 100644 frontend/src/components/topology/nodes/RouterNode.jsx create mode 100644 frontend/src/components/topology/nodes/ServerNode.jsx create mode 100644 frontend/src/components/topology/nodes/StorageNode.jsx create mode 100644 frontend/src/components/topology/nodes/SwitchNode.jsx create mode 100644 frontend/src/components/topology/nodes/index.js diff --git a/backend/config/routes.js b/backend/config/routes.js index 4941aea..c991141 100644 --- a/backend/config/routes.js +++ b/backend/config/routes.js @@ -105,6 +105,10 @@ const routesConfig = [ file: 'dangerousOperations.js', path: '/api/dangerous-operations', }, + { + file: 'topology.js', + path: '/api/topology', + }, ]; module.exports = routesConfig; diff --git a/backend/routes/topology.js b/backend/routes/topology.js new file mode 100644 index 0000000..7ec9819 --- /dev/null +++ b/backend/routes/topology.js @@ -0,0 +1,323 @@ +const express = require('express'); +const router = express.Router(); +const { Op } = require('sequelize'); +const Cable = require('../models/Cable'); +const Device = require('../models/Device'); +const DevicePort = require('../models/DevicePort'); +const Rack = require('../models/Rack'); +const Room = require('../models/Room'); + +router.get('/switch/:switchId', async (req, res) => { + try { + const { switchId } = req.params; + const { maxNodes = 100 } = req.query; + + const centerDevice = await Device.findByPk(switchId, { + include: [ + { + model: Rack, + as: 'Rack', + include: [{ model: Room, as: 'Room' }] + } + ] + }); + + if (!centerDevice) { + return res.status(404).json({ success: false, error: '交换机不存在' }); + } + + if (centerDevice.type !== 'switch') { + return res.status(400).json({ success: false, error: '指定设备不是交换机' }); + } + + const cables = await Cable.findAll({ + where: { + [Op.or]: [ + { sourceDeviceId: switchId }, + { targetDeviceId: switchId } + ] + }, + include: [ + { + model: Device, + as: 'sourceDevice', + attributes: ['deviceId', 'name', 'type', 'model', 'status', 'ipAddress', 'rackId', 'position'] + }, + { + model: Device, + as: 'targetDevice', + attributes: ['deviceId', 'name', 'type', 'model', 'status', 'ipAddress', 'rackId', 'position'] + } + ] + }); + + const connectedDeviceIds = new Set(); + cables.forEach(cable => { + if (cable.sourceDeviceId !== switchId) { + connectedDeviceIds.add(cable.sourceDeviceId); + } + if (cable.targetDeviceId !== switchId) { + connectedDeviceIds.add(cable.targetDeviceId); + } + }); + + if (connectedDeviceIds.size > parseInt(maxNodes)) { + return res.status(400).json({ + success: false, + error: `连接设备数量(${connectedDeviceIds.size})超过限制(${maxNodes}),请使用更具体的筛选条件` + }); + } + + const connectedDevices = await Device.findAll({ + where: { deviceId: { [Op.in]: Array.from(connectedDeviceIds) } }, + include: [ + { + model: Rack, + as: 'Rack', + include: [{ model: Room, as: 'Room' }] + } + ] + }); + + const deviceMap = {}; + connectedDevices.forEach(device => { + deviceMap[device.deviceId] = device.toJSON(); + }); + + const centerRack = centerDevice.Rack; + const centerRoom = centerRack?.Room; + + const nodes = [ + { + id: centerDevice.deviceId, + deviceId: centerDevice.deviceId, + name: centerDevice.name, + type: centerDevice.type, + model: centerDevice.model, + status: centerDevice.status, + ipAddress: centerDevice.ipAddress, + rackId: centerDevice.rackId, + rackName: centerRack?.name, + roomId: centerRoom?.roomId, + roomName: centerRoom?.name, + position: centerDevice.position, + isCenter: true + }, + ...connectedDevices.map(device => { + const d = device.toJSON(); + const rack = d.Rack; + const room = rack?.Room; + return { + id: d.deviceId, + deviceId: d.deviceId, + name: d.name, + type: d.type, + model: d.model, + status: d.status, + ipAddress: d.ipAddress, + rackId: d.rackId, + rackName: rack?.name, + roomId: room?.roomId, + roomName: room?.name, + position: d.position, + isCenter: false + }; + }) + ]; + + const edges = cables.map(cable => { + const sourceId = cable.sourceDeviceId === switchId ? cable.targetDeviceId : cable.sourceDeviceId; + return { + id: cable.cableId, + source: cable.sourceDeviceId, + target: cable.targetDeviceId, + sourcePort: cable.sourcePort, + targetPort: cable.targetPort, + cableId: cable.cableId, + cableType: cable.cableType, + cableLength: cable.cableLength, + cableLabel: cable.cableLabel, + cableColor: cable.cableColor, + status: cable.status, + description: cable.description, + installedAt: cable.installedAt + }; + }); + + const portStats = {}; + const allDeviceIds = [switchId, ...Array.from(connectedDeviceIds)]; + const ports = await DevicePort.findAll({ + where: { deviceId: { [Op.in]: allDeviceIds } }, + attributes: ['deviceId', 'status'] + }); + + ports.forEach(port => { + if (!portStats[port.deviceId]) { + portStats[port.deviceId] = { total: 0, used: 0, free: 0, fault: 0 }; + } + portStats[port.deviceId].total++; + if (port.status === 'occupied') portStats[port.deviceId].used++; + else if (port.status === 'free') portStats[port.deviceId].free++; + else if (port.status === 'fault') portStats[port.deviceId].fault++; + }); + + nodes.forEach(node => { + node.portCount = portStats[node.deviceId] || { total: 0, used: 0, free: 0, fault: 0 }; + }); + + const statistics = { + totalDevices: nodes.length, + totalCables: edges.length, + normalCables: edges.filter(e => e.status === 'normal').length, + faultCables: edges.filter(e => e.status === 'fault').length, + disconnectedCables: edges.filter(e => e.status === 'disconnected').length, + byDeviceType: {}, + byCableType: {} + }; + + nodes.forEach(node => { + statistics.byDeviceType[node.type] = (statistics.byDeviceType[node.type] || 0) + 1; + }); + + edges.forEach(edge => { + statistics.byCableType[edge.cableType] = (statistics.byCableType[edge.cableType] || 0) + 1; + }); + + res.json({ + success: true, + data: { + centerDevice: nodes[0], + nodes: nodes.slice(1), + edges, + statistics + } + }); + } catch (error) { + console.error('获取拓扑数据失败:', error); + res.status(500).json({ success: false, error: error.message }); + } +}); + +router.get('/rack/:rackId', async (req, res) => { + try { + const { rackId } = req.params; + const { maxNodes = 100 } = req.query; + + const devices = await Device.findAll({ + where: { rackId }, + attributes: ['deviceId'] + }); + + const deviceIds = devices.map(d => d.deviceId); + + if (deviceIds.length === 0) { + return res.json({ + success: true, + data: { + nodes: [], + edges: [], + statistics: { + totalDevices: 0, + totalCables: 0, + normalCables: 0, + faultCables: 0, + disconnectedCables: 0 + } + } + }); + } + + const cables = await Cable.findAll({ + where: { + [Op.or]: [ + { sourceDeviceId: { [Op.in]: deviceIds } }, + { targetDeviceId: { [Op.in]: deviceIds } } + ] + }, + include: [ + { + model: Device, + as: 'sourceDevice', + attributes: ['deviceId', 'name', 'type', 'model', 'status', 'ipAddress', 'rackId', 'position'] + }, + { + model: Device, + as: 'targetDevice', + attributes: ['deviceId', 'name', 'type', 'model', 'status', 'ipAddress', 'rackId', 'position'] + } + ] + }); + + const relatedDeviceIds = new Set(deviceIds); + cables.forEach(cable => { + relatedDeviceIds.add(cable.sourceDeviceId); + relatedDeviceIds.add(cable.targetDeviceId); + }); + + if (relatedDeviceIds.size > parseInt(maxNodes)) { + return res.status(400).json({ + success: false, + error: `设备数量(${relatedDeviceIds.size})超过限制(${maxNodes})` + }); + } + + const allDevices = await Device.findAll({ + where: { deviceId: { [Op.in]: Array.from(relatedDeviceIds) } }, + include: [ + { + model: Rack, + as: 'Rack', + include: [{ model: Room, as: 'Room' }] + } + ] + }); + + const nodes = allDevices.map(device => { + const d = device.toJSON(); + const rack = d.Rack; + const room = rack?.Room; + return { + id: d.deviceId, + deviceId: d.deviceId, + name: d.name, + type: d.type, + model: d.model, + status: d.status, + ipAddress: d.ipAddress, + rackId: d.rackId, + rackName: rack?.name, + roomId: room?.roomId, + roomName: room?.name, + position: d.position, + isCenter: deviceIds.includes(d.deviceId) + }; + }); + + const edges = cables.map(cable => ({ + id: cable.cableId, + source: cable.sourceDeviceId, + target: cable.targetDeviceId, + sourcePort: cable.sourcePort, + targetPort: cable.targetPort, + cableId: cable.cableId, + cableType: cable.cableType, + cableLength: cable.cableLength, + status: cable.status + })); + + const statistics = { + totalDevices: nodes.length, + totalCables: edges.length, + normalCables: edges.filter(e => e.status === 'normal').length, + faultCables: edges.filter(e => e.status === 'fault').length, + disconnectedCables: edges.filter(e => e.status === 'disconnected').length + }; + + res.json({ success: true, data: { nodes, edges, statistics } }); + } catch (error) { + console.error('获取机柜拓扑数据失败:', error); + res.status(500).json({ success: false, error: error.message }); + } +}); + +module.exports = router; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b6411b8..565b034 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,8 +11,10 @@ "@ant-design/icons": "^6.1.0", "@react-three/drei": "^9.122.0", "@react-three/fiber": "^8.18.0", + "@types/dagre": "^0.7.54", "antd": "^5.8.6", "axios": "^1.13.6", + "dagre": "^0.8.5", "dayjs": "^1.11.19", "framer-motion": "^12.34.0", "html2canvas": "^1.4.1", @@ -2823,6 +2825,12 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/dagre": { + "version": "0.7.54", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.54.tgz", + "integrity": "sha512-QjcRY+adGbYvBFS7cwv5txhVIwX1XXIUswWl+kSQTbI6NjgZydrZkEKX/etzVd7i+bCsCb40Z/xlBY5eoFuvWQ==", + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -4172,6 +4180,16 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/data-urls": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", @@ -5328,6 +5346,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -6249,6 +6276,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b715f8b..cd36079 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,8 +16,10 @@ "@ant-design/icons": "^6.1.0", "@react-three/drei": "^9.122.0", "@react-three/fiber": "^8.18.0", + "@types/dagre": "^0.7.54", "antd": "^5.8.6", "axios": "^1.13.6", + "dagre": "^0.8.5", "dayjs": "^1.11.19", "framer-motion": "^12.34.0", "html2canvas": "^1.4.1", diff --git a/frontend/public/png/交换机.png b/frontend/public/png/交换机.png new file mode 100644 index 0000000000000000000000000000000000000000..9de9a0a33cd14fb9ce83f7254c72a36f38c87d60 GIT binary patch literal 12358 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 literal 0 HcmV?d00001 diff --git a/frontend/public/png/服务器.png b/frontend/public/png/服务器.png new file mode 100644 index 0000000000000000000000000000000000000000..7613a29d250311964b395c8ac7049ee802a8b864 GIT binary patch literal 5632 zcmZ9QcTkh<(#9b~3{nie2pX#Nra;xKt)PG zkRnAo0@9m?^YFfBzBBXvkz{9QGtbO(?|uEQ-B-HWs^mAAZV(UvH#gvtTN}u9Xt8<%O_)&MV;f9igz=b-_UAz(9$%PLw4ffu=zRh{0nXI-D%Y z+CD9t-?p_&wuVhgDt6k|asqaCzjaiUD@rMZ1;YqHt|Uz~NPYdWbnBjoD(Z{E?q9VW z!pIw@#F4#cZ7Nfrs}1#)VTzFyT6A%@etBQO>vU)$$ibjD2q6(ky6e%8E8x`)MpPKx z#5w!7LsiRY5X3aH^U*g%-$oj#(tSfc9c5MKmmx-wlsRQ5+yt1dx9fF!msxB*Js5^K9_+l8>6LJ)vIK_H1TpCc*Io@*d7Fd{D{0ymP@PjS7iBN$6M1ryF z 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 literal 0 HcmV?d00001 diff --git a/frontend/public/png/路由器.png b/frontend/public/png/路由器.png new file mode 100644 index 0000000000000000000000000000000000000000..3a3c9f62a8a8b88f7e599c8e01fe0a846a79d768 GIT binary patch literal 6883 zcma)hby!qi)GjgLfYi|4-6178l)zBZNSDOWB_S|`lHpOH7*(&8orvUk}m2y{OfwfSgh zL}+SCV10k|Q*)dwGlO*5I;r@uVP+uZXU3!BLq@^l81>=s@`w=Y*U#p)RTK`7BZ+1_ zV?>BSKH*6GKF)HMt!Nm}7Fq*KL`=h(f*+IVJA6Ay6X*Mr^0xcp1*j_z5t>sRM{(ECg_2BwFYK(io{R z+7lj#7R!kxb zlSY98V29euj1=f>s{GV})xcdQloL}alpCWB$O-nE`F(9%5>15i0`p3_|3<@z2jr@o zONmevI7M^%_D@7Q;kVJvi=+zwdDlEXrkQ`DDcnQ!v$)TPTd{!!hMQ=1}#J9n;n!8XKA!b zrx40x!|=#nNTuxpY6*S(z#0iWl&^1#ZK2*7WmIB!8q#8;gjoMZ92;|C)b1IDw_=yz z#xbL~IRgz`z3@>&pgLN-hb-DzPR|#!`?ZYSsVQ6pp9Dh_Btb`MZ71Ug2d;wg2esm3 zo*fuRKhK-O4}_PJqXSa3>{j4-!=J`uq4PX$s6(m`V+{{}iAe^Q4&8i-Y`cR_U@g}v zcvwb1{HGe4-91^|EBnEYoq;jJ39@<&chM-@GtuK9WZAHfy;qs_>a^dDmgu5AlG-`) z${#aRq~BK#%(52|W{el+qTx!ffY#iJB)T&!SueC|6?{&d_npK>zNIN$d=Fw%4I`2l zoXGmtJO1(8V(y1R(~-iQ?#=ZinTSos<#$3lpn;^q+llXpsxOwYTZy($PQ?K#yPT&# zqIR#YziOCgj4LY_&@WlHOBorXm1IHv4NZG=MmYp2Awy>ludC z(r`KJbo(a$s~UxudT`o^hZn__t_QTdUX!L(h7V1ic$1OXRkJF;w6rq}ZA9H`?&t#Z zLOq<+bg{)k8^KOsi;culEj=U3DytD6z zmpwTjsXt<#3e+F(3SF+!QI2lH$eN}?wj?nj3f5pSTQHj{7~dsVspV1okS!!nR^Lyp za~&HLpF*IByXQB}no@!_{98GBe_AjqlP%hc^Ba)I@#)){Y{T<}r3s{XmdL=C%dj*l zb$Fp@b`t+RHEbi^?V;t6tqZMM#_TN2r{L>)kwHj01#NaHyHEFuK*(hA-eLevF$Mmb zZ^sc8IS+6#sIl*xh{Z#Y&o%_jrIjoJf`&mU=3#{^%{pi`lKo4 zVfxEpz#K{c7hhG1{HD#q0=!zPFjgQeh+$TAk@ZMSrFNh)_27n~a_`ggQA)~uhDQrK zcYnSBLUNrhahKxfED=yoxns;Ahswu~@*ulSY~<@p@^@&h6v-Kd%{XZxZr|b|Z3*^x z#6N_TBUAlbP6%V9QIp=TCj}BHwR!ixMApL&(_ga?p%3S1Q>t$WINf0eKHvO2%xTnH zFl~=@xA%!^uWCX~dMwS7IZ`qqH=F!UZd@cZDe7Ve{H3rGVBLZ%=you{fmfnMfWa7Y z^xK}Mztmk{p0oS(cX_^cVbOb{{?i}U3QypqV@`;R0kBW1&bYEOdxN`aw?BA!VNwiJ zv2?1d#4AXWb*$4!w=FU`eQV?I%K&V}ItH)aQtH*eT131{{M4H)H1+y__nawdBVP*m zO_R*)2waF8QJGkXn}~gSwf#`rop-Z6EFR8l3?+yNYq4s(#`Y-?!={ui+*Txezv&D_ zySLY2l_RT^m79W678f*G7UhEE9CmArTifV6M{2w+1PQXUMm;V*Ft$qi1g;_|tQiZ4 zYc%>R5B!Y6Uirnnw_n?I`t$~O?*Y?JBTeq&AR+g)oTdffwd_uGO+E==x0SL=;9XCa zu;G0QL|nO$6hnmo#L{A|Yx9{U z3D33GkRk1+fV3R67MI1n2Cyq^xm3q&o-OG0{~{U4YEQ)XVyM0&MYzSm4uDq5m%G8& zDSwv*znOnL0et+X<<@`uo5YQWS}nji4GOg4e5y~piji4jO~mm^XEZjsWm+~PNs5E_ zmW5(Q^*F`!GPG5sLIJ&z(uw)DZ8@V@YKzaV$t1(w#Q~0^}#vr7ieRHERD3Ym@p_xKM_7{N-0f#Ige4D9zT#`H|o%f6vNUK z7r*TNIlVAcC)T*0yoU>}er>|0GV1TKhM!)=62bS2DOEXxf!j+m<|5>hb75v6q~?9_ zO;sz6?}`Sft6X$~>}+jms2`!nvBbh|Ufv_5WIUPYK(&2R$6roj25PJMFS<0|ES5Yg zsqpNC6723Wnc4muErmh;K>MJhQ$m!r{a{UZ(jD$7EBCJRqovlSO~5+r@ZN?@X>T=6 zy7)F#K*&6}RW-RF;inOG_k&*wnLEk0-ay-9R+P2vud{Nx&)XLJ^Xz|CzQR)tY49Y9 z+4JkN9VKRAVS6n^uphZh!rpPNoO@BVodr`ry^a0Y7FlkmUaIa6y^8sbKBC}tp=Qac zJv@~cFuSh-Ulr>Ifum0Bi~h?t=N;ZA?8fUepsJfy0*qvzpFa$R&-`MQeX7_vJ*&P~ zzox}_1j?A+6QztWjw;#zh=f{fr>Fnz<(^uIdM6b>(;nNygOu*(w0LWH*3j7&avJoN zH8!&%4eOaaq1MkQ8rz4@H(cp!4=49si%Wty2h4pxG;@PvUS|D7<3e9b>-{z4}G!UNmS7n zTX@A7xs`8`;@uLn-WAc^p{acp&$|&PsS`aczq;1Wojz&pP-1U3dB&+i2O21zGw%0X zp{4mLasBo6cq%iE1<9RC)$q-#QhSt4g^fZ{iyMVWhsmy6vUi_`tmSg|Xy>;%N?)bq zl%g5=CZn}$QVZri;R;hHQH*Y5m-@XfdPp;58X*@(-f3(hP}dxhW(3RSd81@O&1;@& zdaJ>DATCql`&&5re@V8%At5~wE+)|ygn-xuhRY7zC&b$-0TVaf|UfFNNV>}0GjMZ#HtQQg2^=&iALlb}g zoKN@C4An>oZH~gzKU<^uCO&3)vox*4xT5C7a`xs zQfrAK%~!S(J60@Tc+|F&xlMvA-Qe1|g5*@GZu$qf;BwWaeK)mdJ6%i*u^Jc7F=SM* z3p{H)VREF_+T#&5SjCSJa=>G}T3-`K&Kn6^Qmj_+jk=??O|C!8z^?^G;^JRkLZ%F# zwWM+>&Cw7|GcD3=asG*@AEM;VuIP*3BPwQ2=^U|GNy*y!>Z*)*Ap)9aB7e=9N9hgk zoR#Dm+kOc)ez_<_fKz5Ztu$7G57jTYCCpYY&apn-b2#%TZ|&2k*qr(6a|>wPQ7Oh|lJsR=D7sq$6%(nF8(YcH zEo;?tTjq7odb23w6SzBUlXuc8i6aBy1PPxwjz;mmnFVXW`Y`iv1M**uy-$2W?6Nu=u=Zs+SjvD#H+j({RWn^&*m-9TMC`GZB%`7%R|AWO8} zW76OsUG?Ua&p&vSe{btSDZ2mFwIs|#M*HsiM*kdB&*X#jru%wA?7I}4AYx=%PQ2NT zvCP!q+|Z?{QQ2Y86i@Y+@E#Crm7Ed(%jwTeUCYFGlB4+M%=gT;i`VN935V97{HG$k z@&07UmezT@63lv9;UKV_96dNiUaefrzdhQVAJXB_zP1znb+gJUbVmiPi%M7+kGynwRdkb&>NQ?)`MPC$c^-?1sRHU%l_Pj zB$!ApY|gn~k{wW>D2BZk7OU3(C@LnKMMxO_w>r;dTCmAWMs8AH^bL>gcu912$rz2SHIG0lGZRmzgd z=RQ~(NZ)Kqwa`>63%}T&PQ8U>Ly6k#( z%xba<&d~grE~o{FwxpSyAiuRGXjDb?@Ze&_@(JzZ&S#XtB*_g9G5=s>veYX_F}7`5 zRf&EHmiKRreP?TG>6r3M)BMo&kG+ro3vno8k|YDpC!2v<2Jwp+HX+vlYg24n8_n^E z@`=gMP_?m{Jb0d-6?b~c{f63_fMt_!1 zls7s|j0PUz*w4?KGRMBELOgA_s<9T^YjsvgM3PZ)RSe)FMyIv$ct26Nt!qDqT(d)x z`H;)uelLGl&2+6&(@ z{5)gkGR>w0G4im&eE}}8OC(z63o1CK6tn|qW+@RkO^@b82=6QJ z;l-sz9w~$rD+CzQ6&1U?C23(xzi&^KK{xO_x`FaCZQ|vq2-tfCERSRJOq5Sgi>lxRRGNQ5FBFbgG#4KkO!&^6p}jF_Q1{b2Z+jk z902vz6Ej0be0ZW58K~4|63BeSR}l7j!?3~Y>XSDgvjIlMx{3? zDx@M@+&YG07Je-Q$qIq~;jo1amu%lUl9@zcUmTy>Q(BN_ocuJ=mjSZ_eU2eO1;ge; z@E9Ubi)`j14mJk7)Kpsv{!yVZ1aLC(8z->cB`%9|3{dXT1n}DOuXNaD>^zC}2!S2- zh3Wr>+3$ksp}K&|1Q;lQac14$07quD{S+5$Oag4s7*M(u8Eewj0}>tP^QMC0e>omg zgWqHhh{YN_vAvtu<%)yJ0C?33I{88d$b(95`1h#6oY@fP?+EXJYBkVr8g!Pop3Jg; zWyMc?(;4-77fAn7UDx`0CYR(e>rkWHPC^E0+&S~=8)$1uA{RJz>I;%5q!){U{)sT5 zgRRmlQHy!oBu0Bjgmt4(nbk$@jp}F*q;XlH&}jB8)3E2~npu0&3pisnP27(N+{B^d z#m4&@HIN5BIwYT-E?shjEg_MfjjM$4XNx|$!;)#XEj0D~`_4cXG*4}IHm>Icc+a{* z`q$&{>~j0kl)ulGB)B2)v$%xrToN&LB?d|Mom?}nd6v!brKgRIey9mE{8hY4=37nY`d^{wZ}BSxp+L%R^Cbg zW@RR%%puOdE7Ki_`gEOV-*ms{P+ct6m-gd`@q!C2;^una>rmMplJfxqm1il!DThk4 zZQmJ;HC2LGp&pfO+i@cg53gd_o?^WpL&@pVkI5MUa4JxJmy~NZZ9aUeOSkgA?if(? zVEVKd+-aQN>UdGCCXyhOiOelsRqUa>tFwmAYQT97y8;b)iEgGaz{xJ0E;0w^|5faN z53ORzzaZy*Zpmx2+OXuqU;@x(UKF2^pwDtne2_XQCoT*PV^$^4%&?C>>uLC1Y1iQy zmC`;a@kEN4(;Rz-gF5lu9n}&4)Bi^N`|TZRg${s-aNBkOg3-1z;X!lIVIvtnBx?f~ zeFZNOrx~)sIk!Bau1MVxDwdT+O6d0R;YfoUZcDOh@>Rved1b7giYjbO;=gFz%U@^z z!c2tOZwMF$&Y+JJRiO9-M!apcvCVdt7M|mS8>s7KkmW^odi|Ql?2oLIo&CfoJj-(7 z#in0~{0E4UWKi`$_WP>}ySdYxMT@|BoiF;lHJXP&1XNd;9>V$)-^e!flPDrTHs7AB z_D8}CcyUfQ5HFtXo!TmIiNIm1Qpfr6z)EB4W97D+YZ1FDKWQxTEys+mc@BI?OW8=4 zuF9$`#{PWpLA33zL$cPS0xBrbp^c4A+nAcmz4GNL}S&= zh6C{Bq8XEH3Gr(ln`}$gxNc}T<6R=ulJ-PhbnsVHqd?T0Hnt~0Wu17V#y+~bNo-*& zmHi@2UgE=NPWLReerddPJXZ^x2<4n$W z&+iiz{-;`aG+pt9w;0#2IC7|uXOG(Z6NDIsa`&avzReP!yoN*;q~pvb+hn~ooz4@R zCSO%rLov3)YbFw!?!FBa+M9g-{G^BJtMpbcJk7&?4PnBEx|-2~Wo_7DKGw{ef&!o< zy4W4A28UfMJh{y&Lkg$1m@5Y63zGWX7S+&!b7#CCtI4OSI10G#*uo-7KdUF_2Va_q z`xNr4mg$KftD0AMW3=zwDy%BxIGS3o1d7GOh*{$!6N0_&f&vBa{ZnfAXOmLTqukkc z>g^rVr?*O-FMVv^ z@lOcI%CuK0CD1B^*Utk#rqM~U>lx{tYUEmGL=wzn9cdk_&?!HysGy&q^mQ&{Jl<}~wvktk)( zOEe4>iP+bxRaTWzbQEpCG(8e^7F6>hA!WbZ8U+$PeJ|Si0##?9rF2T9My;vaKWQ`v z`F{}Fu;DbFaHa3*&M_3IDAyrxV6TFW>MGcoRYdutxCfr=8@#ncRrOJjW}BF9KW^~- zpegh@6^b!gQe)u?)u4#!_J_JMDWWt#rU^LzA7bo)^I~9yeO`m5P!!>#ZLMe& z5Tzt0x1{n<5^9^FX#DG_s1YHQvT-Y^PCUyRmIFXl$E}ZQF1DAKni;`)Tgn-8*;A zInVPu6Q-;vh4cmg3jhE>l93iy{rn#K?*j+(d92i`$^rmT0W#tu-`s(x0`Tqx{qq-< zr(j|9NiLXl?p_C}vIUPU zGbq-=v`1YB-73j`x8eTH&V-NVeoR)U(q=ii1Mlqoui5>0TZ_3y;Bj@Ce;G_7ehGf4 zDu=qbIwU|HvUES73Lm&CN0Ns;Itaq` znhmJ6#w>P}^wzkA4?Wc24A113irAE^vN?whV3V0ehH$LferXaIg~T0xEe;HxbRfim z9-kZAti18n3T`7E)!~RGOVDe$y2C!PSd$m1K(d>xQCS_Xc&U5XN7aD1qQNGR7_Skg zAYxOj*%>$;OMEUqbr&Pwuv6N64MjTOoj7%BhmPMV6U(v0g=;HHUy#ZTx+~|s3AAxa zJ=PaN`i4~C7apK+gMyg0u(mqz@rCda(k(iMp=&#u$~~Cv?HlYrf>ZZhY0jW!gh~95 zUy-yJR_;ki2h{R60-W)0e)d#k4I>s2K{oPmLJm3mHfs(0!DG97o)L{6acyGp6+b}n zCe?BU_i!A#@N4qRC6-E#!Ggbse=K`Bf&)5xe!q}W{9BDCfVD4DVu-fASE21mz2M;^ z7NjQn!eyUF1OwH?@^Nt5-Z~C{d0>6(w5+whD>&z2JQg$Pg~h4$q_r4$H!thOyw>2l z>)Y?*{njVq83tzr)`gNwsDrsnUk!TJUhub>tsOegX!)5LEhVTG*fZKoG4`iFa`3d- z!PQTL6R_m~Wls_@QuA=^+vQV&2h>`|XRl0gX7g}eJ5}Pmq_h5dg=7hdmo9BvNFA?v zSac^yeSET7-JDuYQ2TIrNixh?-9#%FeFF-R_?&pQ7YB4r4@jNMP|Yctr3Vq!EP zL%)&>3{%{Rd`D$tGQSvhuxWt>OgL&(JSq(}5Tj7;XY(g61|#u6ma-ji)QDtMrw=!= z_x*&}o(I(6c~wt91f@?+-~Tb*V&tiRPh>mOZ~t;L@^I&Dtins&G8&CP2rvv%p7VPw z(DVq1iFz{-Jv)zXG+em2EAmL-Ouc)e@h713mBy8}*EsorPOmJn5adQp9Srj#2iT(M z{e;RUH9#OGG8&A8nMgb+E?($7RL|J(w$8~m%ag1pLLjN&(SaQ8^m~ d#HCz;-wA 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/CableWizardModal.jsx b/frontend/src/components/CableWizardModal.jsx index de39a1a..b639941 100644 --- a/frontend/src/components/CableWizardModal.jsx +++ b/frontend/src/components/CableWizardModal.jsx @@ -189,7 +189,6 @@ const CableWizardModal = ({ visible, onClose, onSuccess, initialSourceDevice, ed useEffect(() => { if (visible) { setCurrentStep(0); - setSourceDevice(null); setTargetDevice(null); setSourcePort(null); setTargetPort(null); @@ -197,22 +196,30 @@ const CableWizardModal = ({ visible, onClose, onSuccess, initialSourceDevice, ed setSelectedCableLength(3); setCableLabel(''); setCableDescription(''); - setSourcePorts([]); setTargetPorts([]); setConflicts([]); setCompatibilityWarning(null); - setDevices([]); setCablesData([]); - fetchDevices('', 'switch').then(deviceList => { - if (initialSourceDevice?.deviceId) { - const device = deviceList.find(d => d.deviceId === initialSourceDevice.deviceId); - if (device) { - setSourceDevice(device); - fetchDevicePorts(device.deviceId, 'source'); - } - } - }); + if (initialSourceDevice?.deviceId) { + const deviceWithId = { + deviceId: initialSourceDevice.deviceId, + name: initialSourceDevice.name || initialSourceDevice.deviceId, + type: initialSourceDevice.type || 'switch', + model: initialSourceDevice.model, + status: initialSourceDevice.status, + Rack: initialSourceDevice.Rack, + ipAddress: initialSourceDevice.ipAddress, + position: initialSourceDevice.position, + }; + setSourceDevice(deviceWithId); + fetchDevicePorts(deviceWithId.deviceId, 'source'); + fetchDevices('', 'switch'); + } else { + setSourceDevice(null); + setSourcePorts([]); + fetchDevices('', 'switch'); + } fetchCables(); } }, [visible, initialSourceDevice, fetchDevices, fetchDevicePorts, fetchCables]); @@ -677,152 +684,165 @@ const Step1SourceDevice = ({ onPortSelect, onDevicesChange, }) => { + const isSourcePreSelected = !!sourceDevice; + return (
- 步骤 1: 选择源设备 + 步骤 1: 选择源设备端口 } size="small" style={{ marginBottom: '20px' }} > -
- - 选择接线的起点设备 - -
- -
- -
- -
- {fetchingDevices ? ( -
- + {!isSourcePreSelected && ( + <> +
+ + 选择接线的起点设备 +
- ) : ( - devices.map(device => ( - - onDeviceSelect(device)} - hoverable - style={{ - border: sourceDevice?.deviceId === device.deviceId - ? `2px solid ${designTokens.colors.primary}` - : '1px solid #d9d9d9', - background: sourceDevice?.deviceId === device.deviceId - ? 'rgba(24,144,255,0.08)' - : '#fff', - }} - > -
-
+ +
+ +
+ {fetchingDevices ? ( +
+ +
+ ) : ( + devices.map(device => ( + + onDeviceSelect(device)} + hoverable style={{ - width: '48px', - height: '48px', - borderRadius: '10px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontSize: '24px', - flexShrink: 0, + border: sourceDevice?.deviceId === device.deviceId + ? `2px solid ${designTokens.colors.primary}` + : '1px solid #d9d9d9', + background: sourceDevice?.deviceId === device.deviceId + ? 'rgba(24,144,255,0.08)' + : '#fff', }} > - {device.type === 'server' ? '🖥️' : device.type === 'switch' ? '📡' : device.type === 'router' ? '🔀' : device.type === 'storage' ? '💾' : '📦'} -
-
-
-
- {device.name || device.deviceId} +
+
+ {device.type === 'server' ? '🖥️' : device.type === 'switch' ? '📡' : device.type === 'router' ? '🔀' : device.type === 'storage' ? '💾' : '📦'}
- {device.status && getStatusTag(device.status)} -
-
- - ID: - {device.deviceId} - - - 类型: - {device.type === 'server' ? '服务器' : device.type === 'switch' ? '交换机' : device.type === 'router' ? '路由器' : device.type === 'storage' ? '存储设备' : device.type} - -
-
- {device.Rack?.Room?.name && ( - - 机房: - {device.Rack.Room.name} - - )} - {device.Rack?.name && ( - - 机柜: - {device.Rack.name} - - )} - {device.position && ( - - U位: - U{device.position} - - )} - {device.ipAddress && ( - - IP: - {device.ipAddress} - - )} -
- {device.model && ( -
- 型号: - {device.model} +
+
+
+ {device.name || device.deviceId} +
+ {device.status && getStatusTag(device.status)} +
+
+ + ID: + {device.deviceId} + + + 类型: + {device.type === 'server' ? '服务器' : device.type === 'switch' ? '交换机' : device.type === 'router' ? '路由器' : device.type === 'storage' ? '存储设备' : device.type} + +
+
+ {device.Rack?.Room?.name && ( + + 机房: + {device.Rack.Room.name} + + )} + {device.Rack?.name && ( + + 机柜: + {device.Rack.name} + + )} + {device.position && ( + + U位: + U{device.position} + + )} + {device.ipAddress && ( + + IP: + {device.ipAddress} + + )} +
+ {device.model && ( +
+ 型号: + {device.model} +
+ )}
- )} -
- {sourceDevice?.deviceId === device.deviceId && ( - - )} -
- - - )) - )} -
+ {sourceDevice?.deviceId === device.deviceId && ( + + )} +
+ + + )) + )} +
+ + )} {sourceDevice && ( -
+
+ - 源设备已选择: {sourceDevice.name} + 源设备: {sourceDevice.name} + {isSourcePreSelected && ( + 已选择 + )} + + {sourcePorts.filter(p => p.status === 'free').length} 个空闲端口 +
-
- - 端口选择 - - {sourcePorts.filter(p => p.status === 'free').length} 个空闲端口 - -
- {sourcePorts.length === 0 ? ( ) : ( @@ -848,9 +860,6 @@ const Step1SourceDevice = ({
端口总数: {sourcePorts.length} | 空闲端口: {sourcePorts.filter(p => p.status === 'free').length}
-
- 端口状态分布: {sourcePorts.map(p => p.status).filter((v, i, a) => a.indexOf(v) === i).join(', ')} -
{sourcePort && (
+ + 选择交换机 + + } + extra={ + + @@ -1866,6 +1878,11 @@ function CableManagement() {
)} + + setTopologyModalVisible(false)} + /> ); }