diff --git a/packages/frinx-device-topology/src/__generated__/graphql.ts b/packages/frinx-device-topology/src/__generated__/graphql.ts index 0678f8ffa..5563d7cc0 100644 --- a/packages/frinx-device-topology/src/__generated__/graphql.ts +++ b/packages/frinx-device-topology/src/__generated__/graphql.ts @@ -3249,6 +3249,11 @@ export type TimeoutPolicy = | 'ALERT_ONLY' | 'TIME_OUT_WF'; +export type Topologies = { + __typename?: 'Topologies'; + topologies: Maybe>>; +}; + export type Topology = { __typename?: 'Topology'; edges: Array; @@ -3284,6 +3289,12 @@ export type TopologyLayer = | 'PHYSICAL_TOPOLOGY' | 'PTP_TOPOLOGY'; +export type TopologyOfDevice = { + __typename?: 'TopologyOfDevice'; + deviceId: Scalars['String']['output']; + topologyId: Scalars['String']['output']; +}; + export type TopologyOverlayDevice = { __typename?: 'TopologyOverlayDevice'; /** Unique identifier of the object. */ @@ -4829,6 +4840,7 @@ export type DeviceInventoryQuery = { deviceMetadata: Maybe; deviceNeighbor: Maybe; devices: DeviceConnection; + devicesTopology: Maybe; kafkaHealthCheck: Maybe; labels: LabelConnection; locations: LocationConnection; @@ -4906,6 +4918,11 @@ export type DeviceInventoryQueryDevicesArgs = { }; +export type DeviceInventoryQueryDevicesTopologyArgs = { + deviceName: Scalars['String']['input']; +}; + + export type DeviceInventoryQueryLabelsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -5959,6 +5976,12 @@ export type NeighboursQueryVariables = Exact<{ export type NeighboursQuery = { __typename?: 'Query', deviceInventory: { __typename?: 'deviceInventoryQuery', deviceNeighbor: { __typename?: 'DeviceNeighbors', neighbors: Array<{ __typename?: 'Neighbor', deviceName: string, deviceId: string } | null> | null } | null } }; +export type DevicesTopologyQueryVariables = Exact<{ + deviceName: Scalars['String']['input']; +}>; + + +export type DevicesTopologyQuery = { __typename?: 'Query', deviceInventory: { __typename?: 'deviceInventoryQuery', devicesTopology: { __typename?: 'Topologies', topologies: Array<{ __typename?: 'TopologyOfDevice', deviceId: string, topologyId: string } | null> | null } | null } }; export type RefreshCoordinatesMutationVariables = Exact<{ topologyType?: InputMaybe; }>; diff --git a/packages/frinx-device-topology/src/components/topology-type-select/topology-type-select.tsx b/packages/frinx-device-topology/src/components/topology-type-select/topology-type-select.tsx index ed7f80fbb..b13870477 100644 --- a/packages/frinx-device-topology/src/components/topology-type-select/topology-type-select.tsx +++ b/packages/frinx-device-topology/src/components/topology-type-select/topology-type-select.tsx @@ -1,40 +1,61 @@ -import { FormControl, FormLabel, Select } from '@chakra-ui/react'; +import { Button, Flex, FormControl, FormLabel, Select } from '@chakra-ui/react'; import React, { VoidFunctionComponent } from 'react'; -import { MapTopologyType, setMapTopologyType } from '../../state.actions'; +import { MapTopologyType, setMapTopologyType, setTopologyLayer } from '../../state.actions'; import { useStateContext } from '../../state.provider'; +import { TopologyLayer } from '../../state.reducer'; + +type Layer = { + name: string; + value: 'PHYSICAL_TOPOLOGY' | 'PTP_TOPOLOGY' | 'ETH_TOPOLOGY' | 'NETWORK_TOPOLOGY' | 'MPLS_TOPOLOGY'; + layer: TopologyLayer; +}; + +export const topologyLayers: Layer[] = [ + { name: 'Physical Topology', value: 'PHYSICAL_TOPOLOGY', layer: 'LLDP' }, + { name: 'Ptp Topology', value: 'PTP_TOPOLOGY', layer: 'PTP' }, + { name: 'Eth Topology', value: 'ETH_TOPOLOGY', layer: 'Synchronous Ethernet' }, + { name: 'Mpls Topology', value: 'MPLS_TOPOLOGY', layer: 'MPLS' }, +]; const TopologyTypeSelect: VoidFunctionComponent = () => { const { state, dispatch } = useStateContext(); const { mapTopologyType } = state; - const topologyLayers = [ - { name: 'Physical Topology', value: 'PHYSICAL_TOPOLOGY' }, - { name: 'Ptp Topology', value: 'PTP_TOPOLOGY' }, - { name: 'Eth Topology', value: 'ETH_TOPOLOGY' }, - { name: 'Mpls Topology', value: 'MPLS_TOPOLOGY' }, - ]; - const handleTopologyTypeChange = (e: React.ChangeEvent) => { const value = e.target.value as MapTopologyType; dispatch(setMapTopologyType(value)); }; + const handleSwitchLayer = () => { + const layer = topologyLayers.find((l) => l.value === mapTopologyType); + if (mapTopologyType && layer) { + dispatch(setTopologyLayer(layer.layer)); + dispatch(setMapTopologyType(null)); + } + }; + return ( - + Select topology type: - + + + + ); }; diff --git a/packages/frinx-device-topology/src/pages/topology/map/map-topology.container.tsx b/packages/frinx-device-topology/src/pages/topology/map/map-topology.container.tsx index cef794001..ab85d4c5c 100644 --- a/packages/frinx-device-topology/src/pages/topology/map/map-topology.container.tsx +++ b/packages/frinx-device-topology/src/pages/topology/map/map-topology.container.tsx @@ -1,13 +1,28 @@ import React, { useEffect, useRef, VoidFunctionComponent, useState } from 'react'; -import { MapContainer, Marker, TileLayer, useMap, Polyline, Popup } from 'react-leaflet'; +import { MapContainer, Marker, TileLayer, useMap, Polyline } from 'react-leaflet'; import { useClient } from 'urql'; import MarkerClusterGroup from 'react-leaflet-cluster'; -import { Box, Button, Card, CardBody, CloseButton, Heading } from '@chakra-ui/react'; +import { Box, Button, Card, CardBody, CloseButton, Divider, Heading } from '@chakra-ui/react'; import L, { LatLngBoundsLiteral, LatLngTuple } from 'leaflet'; import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM_LEVEL, fetchOsmData, OSMData } from '../../../helpers/topology-helpers'; import { DEFAULT_ICON, RED_DEFAULT_ICON } from '../../../helpers/map-marker-helper'; import { useStateContext } from '../../../state.provider'; -import { getDeviceMetadata, getMapDeviceNeighbors, setSelectedMapDeviceName } from '../../../state.actions'; +import { + getDeviceMetadata, + getMapDeviceNeighbors, + getMplsNodesAndEdges, + getNodesAndEdges, + getPtpNodesAndEdges, + getSynceNodesAndEdges, + getTopologiesOfDevice, + setMapTopologyType, + setSelectedMapDeviceName, + setSelectedMplsNode, + setSelectedNode, + setSelectedPtpNode, + setSelectedSynceNode, + setTopologyLayer, +} from '../../../state.actions'; type MarkerLines = { id: string; @@ -24,6 +39,11 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { selectedMapDeviceName, devicesMetadata: deviceData, mapDeviceNeighbors, + ptpNodes, + synceNodes, + mplsNodes, + nodes, + topologiesOfDevice, } = state; const map = useMap(); @@ -58,6 +78,10 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { } }, [deviceData, map]); + useEffect(() => { + dispatch(setSelectedMapDeviceName(null)); + }, [dispatch]); + useEffect(() => { if (deviceData && markerRefs.current.size === deviceData?.length) { if (!markersReady) { @@ -67,9 +91,11 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { const marker = markerRefs.current.get(selectedMapDeviceName); if (marker) { map.setView(marker.getLatLng(), map.getZoom()); + marker.openPopup(); } } } + setShowLocationInfo(false); }, [deviceData, selectedMapDeviceName, map, markersReady]); const handleLocationInfoClick = async (lat: number, lon: number) => { @@ -83,6 +109,7 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { }; const handleMarkerClick = (deviceName: string) => () => { + dispatch(getTopologiesOfDevice(client, deviceName)); dispatch(setSelectedMapDeviceName(deviceName)); }; @@ -106,11 +133,80 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { }) .filter((line): line is MarkerLines => line !== null); - const handlePopupClose = () => { + const handlePopupClose = () => () => { dispatch(setSelectedMapDeviceName(null)); setShowLocationInfo(false); }; + const getTopologyOfDevice = (topology: string) => { + if (topology) { + switch (topology) { + case 'PHYSICAL_TOPOLOGY': { + return 'LLDP'; + } + case 'PTP_TOPOLOGY': { + return 'PTP'; + } + case 'MPLS_TOPOLOGY': { + return 'MPLS'; + } + case 'ETH_TOPOLOGY': { + return 'Synchronous Ethernet'; + } + default: + break; + } + } + return undefined; + }; + + const handleShowDevice = (topology: string) => { + const layerOfDevice = getTopologyOfDevice(topology); + + if (layerOfDevice) { + dispatch(setTopologyLayer(layerOfDevice)); + } + dispatch(setMapTopologyType(null)); + switch (topology) { + case 'PHYSICAL_TOPOLOGY': { + dispatch(getNodesAndEdges(client, [])); + const selectedNode = nodes.find((n) => n.name === selectedMapDeviceName); + if (selectedNode) { + dispatch(setSelectedNode(selectedNode)); + } + return; + } + case 'PTP_TOPOLOGY': { + dispatch(getPtpNodesAndEdges(client)); + const selectedPtpNode = ptpNodes.find((n) => n.name === selectedMapDeviceName); + if (selectedPtpNode) { + dispatch(setSelectedPtpNode(selectedPtpNode)); + } + return; + } + case 'MPLS_TOPOLOGY': { + dispatch(getMplsNodesAndEdges(client)); + const selectedMplsNode = mplsNodes.find((n) => n.name === selectedMapDeviceName); + if (selectedMplsNode) { + dispatch(setSelectedMplsNode(selectedMplsNode)); + } + + return; + } + case 'ETH_TOPOLOGY': { + dispatch(getSynceNodesAndEdges(client)); + const selectedSynceNode = synceNodes.find((n) => n.name === selectedMapDeviceName); + if (selectedSynceNode) { + dispatch(setSelectedSynceNode(selectedSynceNode)); + } + + break; + } + default: + break; + } + }; + return ( <> { const lon = node.geolocation.longitude; return ( { if (ref && node.deviceName) { markerRefs.current.set(node.deviceName, ref); } }} - > - - - - {node?.deviceName ?? '-'} - - - - - Latitude - - {lat} - - - - Longitude - - {lon} - - - - - {showLocationInfo && osmData && ( - - - Location Info - - {osmData.displayName || 'No data available'} - - )} - - + /> ); } @@ -185,11 +249,15 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { position={[selectedDeviceData.geolocation.latitude, selectedDeviceData.geolocation.longitude]} key={selectedDeviceData.id} icon={RED_DEFAULT_ICON} + eventHandlers={{ + click: handleMarkerClick(selectedDeviceData.deviceName || ''), + popupclose: handlePopupClose(), + }} /> )} {selectedDeviceData && ( - - + + @@ -214,6 +282,55 @@ const MapTopologyContainerDescendant: VoidFunctionComponent = () => { {selectedDeviceData.geolocation?.longitude} + + + + + Switch to layer + + {topologiesOfDevice.map((m) => { + if (m.topologyId !== 'NETWORK_TOPOLOGY') { + return ( + + ); + } + return null; + })} + + + + + + {showLocationInfo && osmData && ( + + + Location Info + + {osmData.displayName || 'No data available'} + + )} )} diff --git a/packages/frinx-device-topology/src/pages/topology/topology.tsx b/packages/frinx-device-topology/src/pages/topology/topology.tsx index 1a7b46fba..fa3bcfe5a 100644 --- a/packages/frinx-device-topology/src/pages/topology/topology.tsx +++ b/packages/frinx-device-topology/src/pages/topology/topology.tsx @@ -58,7 +58,7 @@ const Topology: VoidFunctionComponent = () => { Device topology - + Select layer: