From 384d57947902e713f3b9588ba65c724cc6615352 Mon Sep 17 00:00:00 2001 From: Mohammad Torkashvand <mohammad.torkashvand@geant.org> Date: Mon, 3 Jun 2024 18:25:42 +0200 Subject: [PATCH] added maps to the menues --- .env.example | 4 + components/NetworkMap/NetworkMap.tsx | 381 +++++++++++++++++++++++++++ package-lock.json | 306 ++++++++++++--------- package.json | 13 +- pages/_app.tsx | 25 ++ pages/maps/index.tsx | 43 +++ public/images/juniper_icon.png | Bin 0 -> 16464 bytes public/images/nokia_icon.png | Bin 0 -> 5900 bytes 8 files changed, 641 insertions(+), 131 deletions(-) create mode 100644 components/NetworkMap/NetworkMap.tsx create mode 100644 pages/maps/index.tsx create mode 100644 public/images/juniper_icon.png create mode 100644 public/images/nokia_icon.png diff --git a/.env.example b/.env.example index 120c42a..933b4a6 100644 --- a/.env.example +++ b/.env.example @@ -17,8 +17,12 @@ NEXTAUTH_WELL_KNOWN_OVERRIDE="http://localhost:8085/auth/.well-known/openid-conf NEXTAUTH_AUTHORIZATION_SCOPE_OVERRIDE="openid profile" NEXTAUTH_URL=http://localhost:3000/api/auth +# OPA Settings OPA_PUBLIC_BUNDLE_URL=http://localhost:8181/v1/data/opa/public-bundle +#Maps Settings +NETWORK_TOPOLOGY_API_URL="https://orchestrator.uat.gap.geant.org/api/v1/networks/topology" + # docker-compose variables KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=admin diff --git a/components/NetworkMap/NetworkMap.tsx b/components/NetworkMap/NetworkMap.tsx new file mode 100644 index 0000000..73e058e --- /dev/null +++ b/components/NetworkMap/NetworkMap.tsx @@ -0,0 +1,381 @@ +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 IptrunkSideNode { + subscription_instance_id: string; + router_fqdn: string; + router_access_via_ts: boolean; + router_lo_ipv4_address: string; + router_lo_ipv6_address: string; + router_lo_iso_address: string; + router_role: string; + vendor: string; + router_site: { + site_internal_id: string; + site_name: string; + site_city: string; + site_country: string; + site_latitude: number; + site_longitude: number; + }; +} + +interface Iptrunk { + subscription_instance_id: string; + iptrunk_isis_metric: string; + iptrunk_capacity: string; + iptrunk_type: string; + iptrunk_ipv4_network: string; + iptrunk_ipv6_network: string; + iptrunk_sides: Array<{ iptrunk_side_node: IptrunkSideNode }>; +} + +export interface NetworkTopologyData { + iptrunks: Array<{ iptrunk: Iptrunk; insync: boolean }>; +} + +export 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; diff --git a/package-lock.json b/package-lock.json index 57f2d3a..6272edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,9 @@ "@open-policy-agent/opa-wasm": "^1.8.1", "@orchestrator-ui/orchestrator-ui-components": "1.20.0", "@reduxjs/toolkit": "^2.0.1", + "axios": "^1.7.2", + "cytoscape": "^3.29.2", + "cytoscape-fcose": "^2.2.0", "moment": "^2.29.4", "next": "^14.0.4", "next-auth": "^4.24.5", @@ -34,8 +37,10 @@ "@orchestrator-ui/jest-config": "*", "@orchestrator-ui/tsconfig": "*", "@testing-library/jest-dom": "^6.2.0", + "@types/cytoscape": "^3.21.2", + "@types/cytoscape-fcose": "^2.2.4", "@types/node": "^20.10.5", - "@types/react": "^18.2.45", + "@types/react": "^18.3.3", "@types/react-dom": "^18.2.18", "@types/react-no-ssr": "^1.1.7", "esbuild-jest": "^0.5.0", @@ -2365,6 +2370,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2913,6 +3038,21 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cytoscape": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.21.2.tgz", + "integrity": "sha512-eadSCH9hbRV9Ej93S8wact/Oe0DuQV4qlWfST/I6k4lBu6RgqI6mVFzvppcIGzWpzZRu7iyFjFhS2qW0w7KFRA==", + "dev": true + }, + "node_modules/@types/cytoscape-fcose": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@types/cytoscape-fcose/-/cytoscape-fcose-2.2.4.tgz", + "integrity": "sha512-QwWtnT8HI9h+DHhG5krGc1ZY0Ex+cn85MvX96ZNAjSxuXiZDnjIZW/ypVkvvubTjIY4rSdkJY1D/Nsn8NDpmAw==", + "dev": true, + "dependencies": { + "@types/cytoscape": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3076,9 +3216,9 @@ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { - "version": "18.3.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", - "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3794,9 +3934,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -4481,6 +4621,14 @@ "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "hasInstallScript": true }, + "node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dependencies": { + "layout-base": "^2.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -4625,6 +4773,25 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/cytoscape": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.29.2.tgz", + "integrity": "sha512-2G1ycU28Nh7OHT9rkXRLpCDP30MKH1dXJORZuBhtEhEW7pKwgPi77ImqlCWinouyE1PNepIOGZBOrE84DG7LyQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -10388,6 +10555,11 @@ "node": ">=0.10" } }, + "node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -15078,126 +15250,6 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package.json b/package.json index 0814861..3ca3cd0 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "@open-policy-agent/opa-wasm": "^1.8.1", "@orchestrator-ui/orchestrator-ui-components": "1.20.0", "@reduxjs/toolkit": "^2.0.1", + "axios": "^1.7.2", + "cytoscape": "^3.29.2", + "cytoscape-fcose": "^2.2.0", "moment": "^2.29.4", "next": "^14.0.4", "next-auth": "^4.24.5", @@ -40,16 +43,18 @@ "@orchestrator-ui/jest-config": "*", "@orchestrator-ui/tsconfig": "*", "@testing-library/jest-dom": "^6.2.0", + "@types/cytoscape": "^3.21.2", + "@types/cytoscape-fcose": "^2.2.4", "@types/node": "^20.10.5", - "@types/react": "^18.2.45", + "@types/react": "^18.3.3", "@types/react-dom": "^18.2.18", "@types/react-no-ssr": "^1.1.7", "esbuild-jest": "^0.5.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0", "husky": "^9.0.11", - "typescript": "^5.3.2", "prettier": "^2.3.2", - "eslint-plugin-prettier": "^3.4.0", - "eslint-config-prettier": "^8.3.0" + "typescript": "^5.3.2" }, "overrides": { "@elastic/eui": { diff --git a/pages/_app.tsx b/pages/_app.tsx index 1309870..ce0fbae 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -9,6 +9,7 @@ import { TranslationsProvider } from '@/translations/translationsProvider'; import { EuiProvider, EuiThemeColorMode } from '@elastic/eui'; import '@elastic/eui/dist/eui_theme_dark.min.css'; import '@elastic/eui/dist/eui_theme_light.min.css'; +import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types'; import { ApiClientContextProvider, ColorModes, @@ -20,11 +21,13 @@ import { WfoPageTemplate, WfoToastsList, defaultOrchestratorTheme, + WfoMenuItemLink, } from '@orchestrator-ui/orchestrator-ui-components'; import { SessionProvider } from 'next-auth/react'; import { NextAdapter } from 'next-query-params'; import App, { AppContext, AppInitialProps, AppProps } from 'next/app'; import Head from 'next/head'; +import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; import NoSSR from 'react-no-ssr'; import { QueryClient, QueryClientProvider } from 'react-query'; @@ -48,6 +51,8 @@ function CustomApp({ pageProps, orchestratorConfig, }: AppProps & AppOwnProps) { + const router = useRouter(); + const [queryClient] = useState(() => new QueryClient(queryClientConfig)); const [themeMode, setThemeMode] = useState<EuiThemeColorMode>( ColorModes.LIGHT, @@ -84,6 +89,25 @@ function CustomApp({ return null; } + const addMenuItems = ( + defaultMenuItems: EuiSideNavItemType<object>[], + ): EuiSideNavItemType<object>[] => [ + ...defaultMenuItems, + { + name: 'Maps', + id: '10', + isSelected: router.pathname === '/maps', + href: '/maps', + renderItem: () => ( + <WfoMenuItemLink + path={'/maps'} + translationString="Maps" + isSelected={router.pathname === '/maps'} + /> + ), + }, + ]; + return ( <WfoErrorBoundary> <OrchestratorConfigProvider @@ -113,6 +137,7 @@ function CustomApp({ <WfoPageTemplate getAppLogo={getAppLogo} onThemeSwitch={handleThemeSwitch} + overrideMenuItems={addMenuItems} > <QueryParamProvider adapter={NextAdapter} diff --git a/pages/maps/index.tsx b/pages/maps/index.tsx new file mode 100644 index 0000000..49ed824 --- /dev/null +++ b/pages/maps/index.tsx @@ -0,0 +1,43 @@ +import NetworkMap, { + NetworkMapProps, + NetworkTopologyData, +} from '@/components/NetworkMap/NetworkMap'; +import axios from 'axios'; +import { NextPage, GetServerSideProps } from 'next'; +import Head from 'next/head'; +import React from 'react'; + +const Maps: NextPage<NetworkMapProps> = ({ data }) => { + return ( + <> + <Head> + <title>Maps</title> + </Head> + <div> + <NetworkMap data={data} /> + </div> + </> + ); +}; + +export const getServerSideProps: GetServerSideProps = async () => { + try { + const response = await axios.get(process.env.NETWORK_TOPOLOGY_API_URL!); + const networkTopologyData: NetworkTopologyData = response.data; + + return { + props: { + networkTopologyData, + }, + }; + } catch (error) { + console.error('Failed to fetch data', error); + return { + props: { + networkTopologyData: null, + }, + }; + } +}; + +export default Maps; diff --git a/public/images/juniper_icon.png b/public/images/juniper_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3cd1f608c7006c42c918b17808682b171f73e88e GIT binary patch literal 16464 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 literal 0 HcmV?d00001 diff --git a/public/images/nokia_icon.png b/public/images/nokia_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7f4a5010e50e457983a3ac2a7b11ed0267477a04 GIT binary patch literal 5900 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 literal 0 HcmV?d00001 -- GitLab