328 lines
13 KiB
JavaScript
328 lines
13 KiB
JavaScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { ArrowDownTrayIcon, ArrowUpTrayIcon } from "@heroicons/react/24/outline";
|
|
import * as d3 from "d3";
|
|
|
|
|
|
export default function NetworkMonitor(args) {
|
|
const [selectedTab, setSelectedTab] = useState("interfaces");
|
|
const [SelectedNode, setSelectedNode] = useState({});
|
|
const tabs = [
|
|
{ id: "interfaces", label: "Интерфейсы" },
|
|
{ id: "topology", label: "Топология сети" },
|
|
];
|
|
const handleTabClick = (tabId) => {
|
|
setSelectedTab(tabId);
|
|
};
|
|
|
|
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>
|
|
);
|
|
};
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
]
|
|
);
|
|
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)}
|
|
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"
|
|
: "hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-orange-600 duration-300"
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="mt-6">
|
|
{selectedTab === "topology" && (
|
|
<>
|
|
<div className="rounded-xl border border-gray-500 dark:border-gray-700 grid-background anim-pop">
|
|
<NetworkDiagram className="w-full" />
|
|
</div>
|
|
</>
|
|
)}
|
|
{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>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
} |