diff --git a/packages/frinx-device-topology/src/__generated__/graphql.ts b/packages/frinx-device-topology/src/__generated__/graphql.ts index edc823f45..0bf9e3000 100644 --- a/packages/frinx-device-topology/src/__generated__/graphql.ts +++ b/packages/frinx-device-topology/src/__generated__/graphql.ts @@ -1155,6 +1155,18 @@ export type MplsGraphNodeInterface = { status: GraphEdgeStatus; }; +export type MplsLspCount = { + __typename?: 'MplsLspCount'; + counts: Maybe>>; +}; + +export type MplsLspCountItem = { + __typename?: 'MplsLspCountItem'; + incomingLsps: Maybe; + outcomingLsps: Maybe; + target: Maybe; +}; + export type MplsTopology = { __typename?: 'MplsTopology'; edges: Array; @@ -3432,6 +3444,7 @@ export type DeviceInventoryQuery = { kafkaHealthCheck: Maybe; labels: LabelConnection; locations: LocationConnection; + mplsLspCount: Maybe; mplsTopology: Maybe; netTopology: Maybe; netTopologyVersionData: NetTopologyVersionData; @@ -3515,6 +3528,11 @@ export type DeviceInventoryQueryLocationsArgs = { }; +export type DeviceInventoryQueryMplsLspCountArgs = { + deviceId: Scalars['String']['input']; +}; + + export type DeviceInventoryQueryNetTopologyVersionDataArgs = { version: Scalars['String']['input']; }; @@ -4122,6 +4140,13 @@ export type GetSynceGrandMasterPathQueryVariables = Exact<{ export type GetSynceGrandMasterPathQuery = { __typename?: 'Query', deviceInventory: { __typename?: 'deviceInventoryQuery', syncePathToGrandMaster: Array | null } }; +export type GetMplsLspCountQueryVariables = Exact<{ + deviceId: Scalars['String']['input']; +}>; + + +export type GetMplsLspCountQuery = { __typename?: 'Query', deviceInventory: { __typename?: 'deviceInventoryQuery', mplsLspCount: { __typename?: 'MplsLspCount', counts: Array<{ __typename?: 'MplsLspCountItem', target: string | null, incomingLsps: number | null, outcomingLsps: number | null } | null> | null } | null } }; + export type ShortestPathQueryVariables = Exact<{ from: Scalars['String']['input']; to: Scalars['String']['input']; diff --git a/packages/frinx-device-topology/src/pages/topology/graph.helpers.spec.ts b/packages/frinx-device-topology/src/pages/topology/graph.helpers.spec.ts index f59580583..844858a95 100644 --- a/packages/frinx-device-topology/src/pages/topology/graph.helpers.spec.ts +++ b/packages/frinx-device-topology/src/pages/topology/graph.helpers.spec.ts @@ -1,5 +1,5 @@ import { assert, describe, expect, test } from 'vitest'; -import { getDistanceBetweenPoints, getPointOnCircle, getPointOnSlope } from './graph.helpers'; +import { getDistanceBetweenPoints, getPointAtLength, getPointOnCircle, getPointOnSlope } from './graph.helpers'; describe('graph helpers', () => { test('test edge curve position 0 degrees', () => { @@ -56,4 +56,19 @@ describe('graph helpers', () => { const distance = getDistanceBetweenPoints(p1, p2); expect(distance).toBeCloseTo(1.414); }); + test('test get point at length', () => { + const source = { x: 5, y: 5 }; + const target = { x: 3, y: 3 }; + const { x: x1, y: y1 } = getPointAtLength({ start: source, end: target }, 0.5); + expect(x1).toBeCloseTo(4); + expect(y1).toBeCloseTo(4); + + const { x: x2, y: y2 } = getPointAtLength({ start: source, end: target }, 0.1); + expect(x2).toBeCloseTo(4.8); + expect(y2).toBeCloseTo(4.8); + + const { x: x3, y: y3 } = getPointAtLength({ start: source, end: target }, 0.9); + expect(x3).toBeCloseTo(3.2); + expect(y3).toBeCloseTo(3.2); + }); }); diff --git a/packages/frinx-device-topology/src/pages/topology/graph.helpers.ts b/packages/frinx-device-topology/src/pages/topology/graph.helpers.ts index 8394e98c6..c27e28bdd 100644 --- a/packages/frinx-device-topology/src/pages/topology/graph.helpers.ts +++ b/packages/frinx-device-topology/src/pages/topology/graph.helpers.ts @@ -1,6 +1,6 @@ import unwrap from '@frinx/shared/src/helpers/unwrap'; import { GraphEdgeWithDiff } from '../../helpers/topology-helpers'; -import { DeviceSize } from '../../__generated__/graphql'; +import { DeviceSize, MplsLspCountItem } from '../../__generated__/graphql'; export const width = 1248; export const height = 600; @@ -197,6 +197,12 @@ export type GraphMplsNodeInterface = { status: string; }; +export type LspCount = { + deviceName: string; + incomingLsps: number; + outcomingLsps: number; +}; + export const NODE_CIRCLE_RADIUS = 30; export type PositionsMap = { @@ -490,3 +496,23 @@ export function normalizeNodeInterfaceData< ...details, }; } + +export function getLspCounts(input: MplsLspCountItem): LspCount { + return { + deviceName: input.target ?? '', + incomingLsps: input.incomingLsps ?? 0, + outcomingLsps: input.outcomingLsps ?? 0, + }; +} + +// distance is number between 0-1 - thats ratio distance/length +export function getPointAtLength(line: Line, distance: number): Position { + const { start, end } = line; + const length = Math.sqrt((end.x - start.x) ** 2 + (end.y - start.y) ** 2); + const x = start.x + ((distance * length) / length) * (end.x - start.x); + const y = start.y + ((distance * length) / length) * (end.y - start.y); + return { + x, + y, + }; +} diff --git a/packages/frinx-device-topology/src/pages/topology/mpls/lsp-count-item.tsx b/packages/frinx-device-topology/src/pages/topology/mpls/lsp-count-item.tsx new file mode 100644 index 000000000..7cfeb5136 --- /dev/null +++ b/packages/frinx-device-topology/src/pages/topology/mpls/lsp-count-item.tsx @@ -0,0 +1,94 @@ +import { chakra } from '@chakra-ui/react'; +import React, { useRef, VoidFunctionComponent } from 'react'; +import { getCurvePath, getPointAtLength, Line, LspCount } from '../graph.helpers'; + +const G = chakra('g'); +const Circle = chakra('circle'); +const Text = chakra('text'); + +type Props = { + lspCount: LspCount; + linePoints: Line; +}; + +const LspCountItem: VoidFunctionComponent = ({ linePoints, lspCount }) => { + const edgeRef = useRef(null); + + const { start, end } = linePoints; + + const incomingPosition = getPointAtLength(linePoints, 0.3); + const outcomingPosition = getPointAtLength(linePoints, 0.7); + + return ( + + + + + + + + incoming Laps: {lspCount.incomingLsps} + + + {lspCount.incomingLsps} + + + + + + + + outcoming Laps: {lspCount.outcomingLsps} + + + {lspCount.outcomingLsps} + + + + ); +}; + +export default LspCountItem; diff --git a/packages/frinx-device-topology/src/pages/topology/mpls/lsp-counts.tsx b/packages/frinx-device-topology/src/pages/topology/mpls/lsp-counts.tsx new file mode 100644 index 000000000..495d68a5b --- /dev/null +++ b/packages/frinx-device-topology/src/pages/topology/mpls/lsp-counts.tsx @@ -0,0 +1,48 @@ +import React, { VoidFunctionComponent } from 'react'; +import { GraphEdgeWithDiff } from '../../../helpers/topology-helpers'; +import { useStateContext } from '../../../state.provider'; +import { getLinePoints, getNameFromNode, isTargetingActiveNode, LspCount } from '../graph.helpers'; +import LspCountItem from './lsp-count-item'; + +type Props = { + edges: GraphEdgeWithDiff[]; + lspCounts: LspCount[]; +}; + +const LspCounts: VoidFunctionComponent = ({ edges, lspCounts }) => { + const { state } = useStateContext(); + const { + connectedNodeIds, + selectedNode, + mplsNodePositions: nodePositions, + mplsInterfaceGroupPositions: interfaceGroupPositions, + } = state; + + const activeEdges = edges.filter((e) => + isTargetingActiveNode(e, getNameFromNode(selectedNode), interfaceGroupPositions), + ); + + const lspCountsMap = new Map(lspCounts.map((c) => [c.deviceName, c])); + + return ( + + {activeEdges.map((edge) => { + const linePoints = getLinePoints({ + edge, + connectedNodeIds, + nodePositions, + interfaceGroupPositions, + }); + + const lspCount = lspCountsMap.get(edge.source.nodeId) ?? null; + if (!linePoints || !lspCount) { + return null; + } + + return ; + })} + + ); +}; + +export default LspCounts; diff --git a/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology-graph.tsx b/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology-graph.tsx index 452089847..76c14f605 100644 --- a/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology-graph.tsx +++ b/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology-graph.tsx @@ -1,38 +1,25 @@ -import { Box, Button, Text } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { unwrap } from '@frinx/shared'; import React, { useRef, VoidFunctionComponent } from 'react'; -import { clearGmPathSearch, setSelectedNode, updateMplsNodePosition } from '../../../state.actions'; +import { setSelectedNode, updateMplsNodePosition } from '../../../state.actions'; import { useStateContext } from '../../../state.provider'; import Edges from './mpls-edges'; import { height, Position, width, MplsGraphNode } from '../graph.helpers'; import BackgroundSvg from '../img/background.svg'; import MplsNodes from './mpls-nodes'; import MplsInfoPanel from './mpls-info-panel'; -import { getGmPathHopsCount } from '../../../helpers/topology-helpers'; +import LspCounts from './lsp-counts'; type Props = { - isGrandMasterPathFetching: boolean; onNodePositionUpdate: (positions: { deviceName: string; position: Position }[]) => Promise; - onGrandMasterPathSearch: (nodeIds: string[]) => void; }; -const MplsTopologyGraph: VoidFunctionComponent = ({ - isGrandMasterPathFetching, - onNodePositionUpdate, - onGrandMasterPathSearch, -}) => { +const MplsTopologyGraph: VoidFunctionComponent = ({ onNodePositionUpdate }) => { const { state, dispatch } = useStateContext(); const lastPositionRef = useRef<{ deviceName: string; position: Position } | null>(null); const positionListRef = useRef<{ deviceName: string; position: Position }[]>([]); const timeoutRef = useRef(); - const { - mplsEdges: edges, - mplsNodes: nodes, - gmPathIds, - selectedNode, - unconfirmedSelectedNodeIds, - unconfirmedSelectedGmPathNodeId, - } = state; + const { mplsEdges: edges, mplsNodes: nodes, selectedNode, lspCounts } = state; const handleNodePositionUpdate = (deviceName: string, position: Position) => { if (timeoutRef.current != null) { @@ -62,14 +49,6 @@ const MplsTopologyGraph: VoidFunctionComponent = ({ dispatch(setSelectedNode(null)); }; - const handleClearGmPath = () => { - dispatch(clearGmPathSearch()); - }; - - const handleSearchClick = () => { - onGrandMasterPathSearch(unconfirmedSelectedNodeIds); - }; - return ( @@ -79,23 +58,9 @@ const MplsTopologyGraph: VoidFunctionComponent = ({ onNodePositionUpdate={handleNodePositionUpdate} onNodePositionUpdateFinish={handleNodePositionUpdateFinish} /> + {selectedNode && } {selectedNode != null && } - {unconfirmedSelectedGmPathNodeId && ( - - - - - {gmPathIds.length > 0 && ( - Number of hops: {getGmPathHopsCount(gmPathIds, 'SynceDevice')} - )} - - - )} ); }; diff --git a/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology.container.tsx b/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology.container.tsx index 3a8cd346a..80980d909 100644 --- a/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology.container.tsx +++ b/packages/frinx-device-topology/src/pages/topology/mpls/mpls-topology.container.tsx @@ -1,20 +1,15 @@ +import { omitNullValue } from '@frinx/shared'; import React, { useCallback, useEffect, useRef, VoidFunctionComponent } from 'react'; import { gql, useClient, useMutation, useQuery } from 'urql'; -import { - findGmPath, - getSynceBackupNodesAndEdges, - getMplsNodesAndEdges, - setGmPathIds, - setMode, -} from '../../../state.actions'; +import { getSynceBackupNodesAndEdges, getMplsNodesAndEdges, setMode, setLspCounts } from '../../../state.actions'; import { useStateContext } from '../../../state.provider'; import { - GetSynceGrandMasterPathQuery, - GetSynceGrandMasterPathQueryVariables, + GetMplsLspCountQuery, + GetMplsLspCountQueryVariables, UpdateSyncePositionMutation, UpdateSyncePositionMutationVariables, } from '../../../__generated__/graphql'; -import { height, Position, width } from '../graph.helpers'; +import { getLspCounts, height, Position, width } from '../graph.helpers'; import MplsTopologyGraph from './mpls-topology-graph'; const UPDATE_POSITION_MUTATION = gql` @@ -27,10 +22,16 @@ const UPDATE_POSITION_MUTATION = gql` } `; -const GET_SYNCE_GM_PATH = gql` - query GetSynceGrandMasterPath($deviceFrom: String!) { +const GET_MPLS_LPS_COUNT = gql` + query GetMplsLspCount($deviceId: String!) { deviceInventory { - syncePathToGrandMaster(deviceFrom: $deviceFrom) + mplsLspCount(deviceId: $deviceId) { + counts { + target + incomingLsps + outcomingLsps + } + } } } `; @@ -39,31 +40,31 @@ const MplsTopologyContainer: VoidFunctionComponent = () => { const client = useClient(); const intervalRef = useRef(); const { dispatch, state } = useStateContext(); - const { topologyLayer, selectedGmPathNodeId, selectedVersion } = state; + const { topologyLayer, selectedVersion, selectedNode } = state; const [, updatePosition] = useMutation( UPDATE_POSITION_MUTATION, ); - const [{ data: gmPathData, fetching: isGmPathFetching }] = useQuery< - GetSynceGrandMasterPathQuery, - GetSynceGrandMasterPathQueryVariables - >({ - query: GET_SYNCE_GM_PATH, + const [{ data: lspCountData }] = useQuery({ + query: GET_MPLS_LPS_COUNT, requestPolicy: 'network-only', variables: { - deviceFrom: selectedGmPathNodeId as string, + deviceId: selectedNode?.id as string, }, - pause: selectedGmPathNodeId === null, + pause: selectedNode?.id === null, }); useEffect(() => { - const gmPathDataIds = - gmPathData?.deviceInventory.syncePathToGrandMaster?.map((p) => { - return p; - }) ?? []; - dispatch(setGmPathIds(gmPathDataIds)); - }, [dispatch, gmPathData]); + const lspCounts = + lspCountData?.deviceInventory.mplsLspCount?.counts + ?.map((p) => { + return p; + }) + .filter(omitNullValue) + .map(getLspCounts) ?? []; + dispatch(setLspCounts(lspCounts)); + }, [dispatch, lspCountData]); useEffect(() => { if (selectedVersion == null) { @@ -129,17 +130,7 @@ const MplsTopologyContainer: VoidFunctionComponent = () => { }; }, [handleKeyDown, handleKeyUp]); - const handleSearchClick = () => { - dispatch(findGmPath()); - }; - - return ( - - ); + return ; }; export default MplsTopologyContainer; diff --git a/packages/frinx-device-topology/src/state.actions.ts b/packages/frinx-device-topology/src/state.actions.ts index 1783f1ff4..3bca9ceea 100644 --- a/packages/frinx-device-topology/src/state.actions.ts +++ b/packages/frinx-device-topology/src/state.actions.ts @@ -12,6 +12,7 @@ import { SynceGraphNode, MplsGraphNode, MplsGraphNodeDetails, + LspCount, } from './pages/topology/graph.helpers'; import { ShortestPath, State, TopologyLayer } from './state.reducer'; import { CustomDispatch } from './use-thunk-reducer'; @@ -258,6 +259,10 @@ export type StateAction = deviceName: string; deviceUsage?: SetDeviceUsagePayload | null; }; + } + | { + type: 'SET_LSP_COUNTS'; + lspCounts: LspCount[]; }; export type ThunkAction, S> = ( @@ -1176,6 +1181,13 @@ export function setGmPathIds(nodeIds: string[]): StateAction { }; } +export function setLspCounts(lspCounts: LspCount[]): StateAction { + return { + type: 'SET_LSP_COUNTS', + lspCounts, + }; +} + export function panTopology(panDelta: Position): StateAction { return { type: 'PAN_TOPOLOGY', diff --git a/packages/frinx-device-topology/src/state.reducer.ts b/packages/frinx-device-topology/src/state.reducer.ts index eec16367c..de48c3616 100644 --- a/packages/frinx-device-topology/src/state.reducer.ts +++ b/packages/frinx-device-topology/src/state.reducer.ts @@ -31,6 +31,7 @@ import { height as topologyHeight, GraphMplsNodeInterface, MplsGraphNode, + LspCount, } from './pages/topology/graph.helpers'; import { identity, @@ -111,6 +112,7 @@ export type State = { memoryLoad: number | null; } | null; }; + lspCounts: LspCount[]; // isMouseDown: boolean; }; @@ -160,6 +162,7 @@ export const initialState: State = { deviceName: '', deviceUsage: null, }, + lspCounts: [], // isMouseDown: false, }; @@ -554,6 +557,10 @@ export function stateReducer(state: State, action: StateAction): State { return acc; } + case 'SET_LSP_COUNTS': { + acc.lspCounts = action.lspCounts; + return acc; + } default: throw new Error(); }