Files
hackaton-2025-web-frontend/src/dashboard/NetworkMonitor.js

328 lines
13 KiB
JavaScript
Raw Normal View History

2025-05-12 16:58:48 +03:00
import React, { useState, useEffect, useRef } from "react";
2025-05-08 22:05:05 +03:00
import { ArrowDownTrayIcon, ArrowUpTrayIcon } from "@heroicons/react/24/outline";
2025-05-12 16:58:48 +03:00
import * as d3 from "d3";
2025-05-07 15:43:49 +03:00
export default function NetworkMonitor(args) {
const [selectedTab, setSelectedTab] = useState("interfaces");
2025-05-12 16:58:48 +03:00
const [SelectedNode, setSelectedNode] = useState({});
2025-05-07 15:43:49 +03:00
const tabs = [
{ id: "interfaces", label: "Интерфейсы" },
{ id: "topology", label: "Топология сети" },
];
const handleTabClick = (tabId) => {
setSelectedTab(tabId);
};
2025-05-08 22:05:05 +03:00
2025-05-12 16:58:48 +03:00
const NetworkDiagram = () => {
const svgRef = useRef(null);
useEffect(() => {
const width = 600;
const height = 700;
const nodes = [
{ id: "192.168.2.55", color: "green", shape: "circle" }, // pc
{ id: "192.168.2.1", color: "red", shape: "polygon" }, // router
{ id: "192.168.2.40", color: "gray", shape: "circle" }, // existing pc
{ id: "192.168.2.41", color: "gray", shape: "circle" },
{ id: "192.168.2.42", color: "green", shape: "circle" },
{ id: "192.168.2.43", color: "green", shape: "circle" },
{ id: "192.168.2.44", color: "green", shape: "circle" },
{ id: "192.168.2.45", color: "green", shape: "circle" },
{ id: "192.168.2.46", color: "green", shape: "circle" },
{ id: "192.168.2.47", color: "green", shape: "circle" },
{ id: "192.168.2.48", color: "green", shape: "circle" }
];
const links = [
{ source: "192.168.2.55", target: "192.168.2.1" },
{ source: "192.168.2.1", target: "192.168.2.40" },
{ source: "192.168.2.1", target: "192.168.2.41" },
{ source: "192.168.2.1", target: "192.168.2.42" },
{ source: "192.168.2.1", target: "192.168.2.43" },
{ source: "192.168.2.1", target: "192.168.2.44" },
{ source: "192.168.2.1", target: "192.168.2.45" },
{ source: "192.168.2.1", target: "192.168.2.46" },
{ source: "192.168.2.1", target: "192.168.2.47" },
{ source: "192.168.2.1", target: "192.168.2.48" }
];
// Set initial positions for circular layout
const centralNodeId = "192.168.2.1";
const radius = 225; // Matches the link distance
const centerX = width / 2;
const centerY = height / 2;
// Find nodes connected to the central node
const connectedNodes = links
.filter(link => link.source === centralNodeId || link.target === centralNodeId)
.map(link => link.source === centralNodeId ? link.target : link.source);
// Set central node position
nodes.forEach(node => {
if (node.id === centralNodeId) {
node.x = centerX;
node.y = centerY;
}
});
// Set connected nodes in a circle around the central node
connectedNodes.forEach((nodeId, index) => {
const angle = (2 * Math.PI * index) / connectedNodes.length;
const node = nodes.find(n => n.id === nodeId);
if (node) {
node.x = centerX + radius * Math.cos(angle);
node.y = centerY + radius * Math.sin(angle);
}
});
const svg = d3.select(svgRef.current)
.attr("width", "100%")
.attr("height", height)
.attr("viewBox", [0, 0, width, height]);
// Adding shadow filter
const defs = svg.append("defs");
const filter = defs.append("filter")
.attr("id", "shadow")
.attr("height", "150%")
.attr("width", "150%");
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 3)
.attr("result", "blur");
filter.append("feOffset")
.attr("in", "blur")
.attr("dx", 3)
.attr("dy", 3)
.attr("result", "offsetBlur");
const feComponentTransfer = filter.append("feComponentTransfer");
nodes.forEach(node => {
feComponentTransfer.append("feFuncR")
.attr("type", "linear")
.attr("slope", node.color === "red" ? 1 : 0);
feComponentTransfer.append("feFuncG")
.attr("type", "linear")
.attr("slope", node.color === "green" ? 1 : 0);
feComponentTransfer.append("feFuncB")
.attr("type", "linear")
.attr("slope", node.color === "gray" ? 0.5 : 0);
});
const feMerge = filter.append("feMerge");
feMerge.append("feMergeNode").attr("in", "offsetBlur");
feMerge.append("feMergeNode").attr("in", "SourceGraphic");
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(200))
.force("charge", d3.forceManyBody().strength(-200))
.force("center", d3.forceCenter(width / 2, height / 2));
const link = svg.append("g")
.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "topology_link");
const node = svg.append("g")
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "topology_node")
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragged)
.on("end", dragEnd))
.on("click", (event, d) => {
console.log("Clicked node:", d);
});
node.each(function (d) {
const group = d3.select(this);
group.append("circle")
.attr("r", 25)
.attr("fill", "none")
.attr("class", "topology_circle_" + d.color);
if (d.shape === "polygon") {
group.append("polygon")
.attr("points", () => {
const radius = 22;
const points = [];
for (let i = 0; i < 6; i++) {
const angle = (Math.PI * 2 * i) / 6;
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
points.push(`${x},${y}`);
}
return points.join(" ");
})
.attr("fill", "#202020");
} else {
group.append("polygon")
.attr("points", () => {
const radius = 22;
const points = [];
for (let i = 0; i < 4; i++) {
const angle = (Math.PI * 2 * i) / 4 + Math.PI / 4;
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
points.push(`${x},${y}`);
}
return points.join(" ");
})
.attr("fill", "#202020")
.attr("class", "square");
}
});
node.append("text")
.attr("class", "topology_label")
.attr("dy", -35)
.text(d => d.id);
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("transform", d => `translate(${d.x},${d.y})`);
});
// Drag functions
function dragStart(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnd(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}, []);
return (
<svg ref={svgRef}></svg>
);
};
2025-05-08 22:05:05 +03:00
const [NetIfaces, setNetIfaces] = useState(
[
{
name: "Ethernet 6",
wireless: false,
mac: "FF-FF-FF-FF-FF-FF",
ip: "192.168.2.5",
mask: "255.255.255.0",
mtu: 1500,
speed_mbit: 2500,
status: true,
trafic_down_mb: 100,
trafic_up_mb: 1000,
},
{
name: "Wireles Adapter 1",
wireless: true,
mac: "F1-FF-0F-FF-1F-F0",
ip: "192.168.1.5",
mask: "255.255.255.0",
mtu: 1500,
speed_mbit: 0,
status: false,
trafic_down_mb: 35,
trafic_up_mb: 2,
}
]
);
2025-05-07 15:43:49 +03:00
return (
<>
<div className="flex justify-center md:justify-start">
{/* Контейнер для вкладок */}
<div className="flex p-2 bg-gray-100 dark:bg-gray-900 gap-2 rounded-lg shadow-sm">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => handleTabClick(tab.id)}
2025-05-08 22:05:05 +03:00
className={`rounded-lg px-4 py-2 text-gray-900 dark:text-gray-100 font-semibold ${selectedTab === tab.id
? "bg-gray-200 dark:bg-gray-800 text-orange-600"
2025-05-07 15:43:49 +03:00
: "hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-orange-600 duration-300"
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="mt-6">
2025-05-12 16:58:48 +03:00
{selectedTab === "topology" && (
<>
<div className="rounded-xl border border-gray-500 dark:border-gray-700 grid-background">
<NetworkDiagram className="w-full" />
</div>
</>
)}
2025-05-08 22:05:05 +03:00
{selectedTab === "interfaces" && (
<>
<div className="flex flex-wrap gap-2 flex-col anim-pop">
{NetIfaces.map((iface, index) => (
<div className="max-w-full md:max-w-[420px] min-h-[150px] border bg-gray-100 border-gray-100 dark:border-gray-900 dark:bg-gray-900 p-4 rounded-lg shadow-sm flex flex-col" key={"iface" + index}>
<div className="flex gap-3">
{!iface.wireless &&
<i className="fi fi-rr-ethernet text-2xl dark:text-gray-200"></i>
}
{iface.wireless &&
<i className="fi fi-rr-wifi text-2xl dark:text-gray-200"></i>
}
<div>
<div className="flex gap-3 items-center">
<span className="block text-lg font-bold text-gray-800 dark:text-gray-200">{iface.name}</span>
<span className={"h-3 w-3 " + (iface.status ? "bg-green-500" : "bg-red-500") + " rounded-full shadow-lg " + (iface.status ? "shadow-green-500/50" : "shadow-red-500/50") + " animate-pulse"} />
</div>
<span className="block text-sm font-normal text-gray-800 dark:text-gray-200"><span className="font-semibold">{iface.ip}</span> <span className="text-gray-600 dark:text-gray-300">{iface.mac}</span></span>
<div className="flex gap-5 items-center mt-3">
<span className="flex gap-2 items-center">
<ArrowDownTrayIcon className="h-5 text-gray-700 dark:text-gray-300" />
<span className="text-gray-700 dark:text-gray-300">{iface.trafic_down_mb} МБ</span>
</span>
<span className="flex gap-2 items-center">
<ArrowUpTrayIcon className="h-5 text-gray-700 dark:text-gray-300" />
<span className="text-gray-700 dark:text-gray-300">{iface.trafic_up_mb} МБ</span>
</span>
</div>
<span className="flex gap-2 items-center mt-1">
<span className="text-gray-700 dark:text-gray-300">{iface.speed_mbit} Мбит/c</span>
</span>
</div>
2025-05-07 15:43:49 +03:00
2025-05-08 22:05:05 +03:00
</div>
</div>
))}
</div>
</>
)}
2025-05-07 15:43:49 +03:00
</div>
</>
);
}