Skip to content
Snippets Groups Projects

added maps to the menu

Merged Mohammad Torkashvand requested to merge feature/NAT-574-add-maps-to-gui into develop
Files
9
+ 349
0
import { NetworkTopologyData } from '@/types/types';
import {
EuiToolTip,
EuiSelect,
EuiLoadingSpinner,
EuiPanel,
EuiFormRow,
} from '@elastic/eui';
import cytoscape, { ElementDefinition, Core } from 'cytoscape';
import fcose from 'cytoscape-fcose';
import React, { useEffect, useState, useRef } from 'react';
cytoscape.use(fcose);
interface NetworkMapProps {
data: NetworkTopologyData | null;
}
interface FcoseLayoutOptions {
name: 'fcose';
quality: string;
nodeRepulsion: (node: cytoscape.NodeSingular) => number;
idealEdgeLength: (edge: cytoscape.EdgeSingular) => number;
}
const NetworkMap: React.FC<NetworkMapProps> = ({ data }) => {
const [elements, setElements] = useState<ElementDefinition[]>([]);
const [labelType, setLabelType] = useState('iptrunkIsisMetric');
const [loading, setLoading] = useState(true);
const [tooltipContent, setTooltipContent] = useState<JSX.Element | null>(
null,
);
const [tooltipPosition, setTooltipPosition] = useState<{
x: number;
y: number;
} | null>(null);
const cyRef = useRef<Core | null>(null);
useEffect(() => {
if (data) {
const transformedElements = transformDataToElements(data);
setElements(transformedElements);
setLoading(false);
}
}, [data]);
useEffect(() => {
if (elements.length > 0) {
const layoutOptions: FcoseLayoutOptions = {
name: 'fcose',
quality: 'proof',
nodeRepulsion: () => 10000,
idealEdgeLength: () => 300,
};
const cy: Core = cytoscape({
container: document.getElementById('container'),
elements,
boxSelectionEnabled: false,
autounselectify: true,
minZoom: 0.25,
maxZoom: 4,
style: [
{
selector: 'node',
style: {
label: 'data(label)',
'background-color': '#95a5a6',
'font-size': '12px',
color: 'black',
},
},
{
selector: '.router',
style: {
width: 1,
height: 1,
'background-fit': 'contain',
'background-position-x': '50%',
'background-position-y': '50%',
'background-clip': 'none',
},
},
{
selector: '.nokia',
style: {
'background-image': 'url("/images/nokia_icon.png")',
},
},
{
selector: '.juniper',
style: {
'background-image': 'url("/images/juniper_icon.png")',
},
},
{
selector: 'edge',
style: {
width: 3,
'line-color': '#3498db',
'target-arrow-color': '#3498db',
'curve-style': 'bezier',
label: 'data(label)',
'text-rotation': 'autorotate',
'font-size': '10px',
color: '#2c3e50',
'text-outline-color': '#ecf0f1',
'text-outline-width': 3,
},
},
{
selector: 'edge[insync="true"]',
style: {
'line-color': '#2ecc71',
},
},
{
selector: 'edge[insync="false"]',
style: {
'line-color': '#e74c3c',
},
},
{
selector: '.site',
style: {
'background-opacity': 0.1,
'text-valign': 'top',
'text-halign': 'left',
'font-weight': 'bold',
'font-size': '14px',
'border-color': '#2980b9',
'border-width': 1,
},
},
],
layout: layoutOptions,
});
cyRef.current = cy;
cy.on('zoom', function () {
updateElementStyles(cy);
});
cy.on('mouseover', 'node', function (event) {
const node = event.target;
const router = node.data('self');
if (router) {
const nodeInfo = (
<div style={{ whiteSpace: 'nowrap' }}>
<strong>subscription_id:</strong>{' '}
{router.subscription_instance_id || 'N/A'} <br />
<strong>router_fqdn:</strong> {router.router_fqdn || 'N/A'} <br />
<strong>router_access_via_ts:</strong>{' '}
{router.router_access_via_ts ? 'true' : 'false'} <br />
<strong>router_lo_ipv4_address:</strong>{' '}
{router.router_lo_ipv4_address || 'N/A'} <br />
<strong>router_lo_ipv6_address:</strong>{' '}
{router.router_lo_ipv6_address || 'N/A'} <br />
<strong>router_lo_iso_address:</strong>{' '}
{router.router_lo_iso_address || 'N/A'} <br />
<strong>router_role:</strong> {router.router_role || 'N/A'} <br />
<strong>vendor:</strong> {router.vendor || 'N/A'} <br />
</div>
);
setTooltipContent(nodeInfo);
setTooltipPosition({
x: event.renderedPosition.x,
y: event.renderedPosition.y,
});
}
});
cy.on('mouseout', 'node', function () {
setTooltipContent(null);
setTooltipPosition(null);
});
}
}, [elements]);
const handleLabelChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValue = e.target.value;
setLabelType(selectedValue);
const cy = cyRef.current;
if (cy) {
cy.edges().forEach((edge) => {
const iptrunk = edge.data('self');
let label = '';
switch (selectedValue) {
case 'iptrunkCapacity':
label = iptrunk.iptrunk_capacity;
break;
case 'iptrunkIsisMetric':
label = iptrunk.iptrunk_isis_metric;
break;
case 'iptrunkType':
label = iptrunk.iptrunk_type;
break;
case 'iptrunkIPV4Network':
label = iptrunk.iptrunk_ipv4_network;
break;
case 'iptrunkIPV6Network':
label = iptrunk.iptrunk_ipv6_network;
break;
}
edge.data('label', label);
});
}
};
const transformDataToElements = (
data: NetworkTopologyData,
): ElementDefinition[] => {
const elements: ElementDefinition[] = [];
const sites = new Map<string, boolean>();
data.iptrunks.forEach((trunk) => {
trunk.iptrunk.iptrunk_sides.forEach((side) => {
const siteData = side.iptrunk_side_node.router_site;
const siteId = `site-${siteData.site_internal_id}`;
if (!sites.has(siteId)) {
const { x, y } = latLongToXY(
siteData.site_latitude,
siteData.site_longitude,
);
elements.push({
data: {
id: siteId,
label: `${siteData.site_name} (${siteData.site_city}, ${siteData.site_country})`,
},
position: { x, y },
classes: ['site'],
});
sites.set(siteId, true);
}
elements.push({
data: {
id: side.iptrunk_side_node.subscription_instance_id,
parent: siteId,
label: side.iptrunk_side_node.router_fqdn,
self: side.iptrunk_side_node,
},
classes: ['router', side.iptrunk_side_node.vendor.toLowerCase()],
});
});
elements.push({
data: {
id: trunk.iptrunk.subscription_instance_id,
source:
trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node
.subscription_instance_id,
target:
trunk.iptrunk.iptrunk_sides[1].iptrunk_side_node
.subscription_instance_id,
label: trunk.iptrunk.iptrunk_isis_metric,
insync: trunk.insync.toString(),
self: trunk.iptrunk,
},
});
});
return elements;
};
const latLongToXY = (
latitude: number,
longitude: number,
): { x: number; y: number } => {
const x = (longitude + 180) * (1000 / 360);
const y = (90 - latitude) * (500 / 180);
return { x, y };
};
const updateElementStyles = (cy: Core) => {
const zoom = cy.zoom();
const baseNodeSize = 10;
const baseFontSize = 5;
const scaledNodeSize = baseNodeSize * zoom;
const scaledFontSize = baseFontSize * zoom;
cy.style()
.selector('node')
.style({
width: scaledNodeSize,
height: scaledNodeSize,
'font-size': scaledFontSize,
})
.update();
};
return (
<EuiPanel>
{loading ? (
<EuiLoadingSpinner size="xl" />
) : (
<div id="container" style={{ height: '80vh', marginTop: '20px' }}></div>
)}
{tooltipContent && tooltipPosition && (
<div
style={{
position: 'absolute',
left: tooltipPosition.x + 20,
top: tooltipPosition.y + 20,
pointerEvents: 'none',
zIndex: 9999,
}}
>
<EuiToolTip position="top" display="block">
<div
style={{
display: 'inline-block',
width: '450px',
backgroundColor: 'white',
border: '1px solid #ccc',
padding: '10px',
borderRadius: '5px',
zIndex: 9999,
boxShadow: '0 0 10px rgba(0,0,0,0.2)',
}}
>
{tooltipContent}
</div>
</EuiToolTip>
</div>
)}
<EuiFormRow fullWidth>
<EuiSelect
id="labelSelector"
options={[
{ value: 'iptrunkIsisMetric', text: 'Iptrunk ISIS Metric' },
{ value: 'iptrunkCapacity', text: 'Iptrunk Capacity' },
{ value: 'iptrunkType', text: 'Iptrunk Type' },
{ value: 'iptrunkIPV4Network', text: 'Iptrunk IPV4 Network' },
{ value: 'iptrunkIPV6Network', text: 'Iptrunk IPV6 Network' },
]}
value={labelType}
onChange={handleLabelChange}
/>
</EuiFormRow>
</EuiPanel>
);
};
export default NetworkMap;
Loading