diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 4d7b32e15..ed08305b7 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -179,7 +179,10 @@ "Edge": "Edge", "Unknown": "Unknown", "Unable to get topology": "Unable to get topology", - "Kind": "Kind", + "Step into this {{name}}": "Step into this {{name}}", + "Add {{name}} filter": "Add {{name}} filter", + "Unpin this element": "Unpin this element", + "Pin this element": "Pin this element", "Query is slow": "Query is slow", "Overview": "Overview", "Traffic flows": "Traffic flows", @@ -232,6 +235,7 @@ "Kubernetes Objects": "Kubernetes Objects", "Owner Kubernetes Objects": "Owner Kubernetes Objects", "IPs & Ports": "IPs & Ports", + "Kind": "Kind", "Owner Kind": "Owner Kind", "Port": "Port", "Kubernetes Object": "Kubernetes Object", diff --git a/web/package-lock.json b/web/package-lock.json index f6f1749b9..b981b66d8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -12,7 +12,7 @@ "@patternfly/react-charts": "6.92.0", "@patternfly/react-core": "4.239.0", "@patternfly/react-table": "4.108.0", - "@patternfly/react-topology": "4.86.0", + "@patternfly/react-topology": "4.90.11", "history": "^5.1.0", "i18next": "^19.8.3", "i18next-http-backend": "^1.0.21", @@ -3190,18 +3190,18 @@ } }, "node_modules/@patternfly/react-icons": { - "version": "4.90.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.90.0.tgz", - "integrity": "sha512-qEnQKbxbUgyiosiKSkeKEBwmhgJwWEqniIAFyoxj+kpzAdeu7ueWe5iBbqo06mvDOedecFiM5mIE1N0MXwk8Yw==", + "version": "4.92.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.92.10.tgz", + "integrity": "sha512-vwCy7b+OyyuvLDSLqLUG2DkJZgMDogjld8tJTdAaG8HiEhC1sJPZac+5wD7AuS3ym/sQolS4vYtNiVDnMEORxA==", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" } }, "node_modules/@patternfly/react-styles": { - "version": "4.89.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.89.0.tgz", - "integrity": "sha512-SkT+qx3Xqu70T5s+i/AUT2hI2sKAPDX4ffeiJIUDu/oyWiFdk+/9DEivnLSyJMruroXXN33zKibvzb5rH7DKTQ==" + "version": "4.91.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.91.10.tgz", + "integrity": "sha512-fAG4Vjp63ohiR92F4e/Gkw5q1DSSckHKqdnEF75KUpSSBORzYP0EKMpupSd6ItpQFJw3iWs3MJi3/KIAAfU1Jw==" }, "node_modules/@patternfly/react-table": { "version": "4.108.0", @@ -3221,18 +3221,18 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "4.91.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.91.0.tgz", - "integrity": "sha512-QeQCy8o8E/16fAr8mxqXIYRmpTsjCHJXi5p5jmgEDFmYMesN6Pqfv6N5D0FHb+CIaNOZWRps7GkWvlIMIE81sw==" + "version": "4.93.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.93.10.tgz", + "integrity": "sha512-F+j1irDc9M6zvY6qNtDryhbpnHz3R8ymHRdGelNHQzPTIK88YSWEnT1c9iUI+uM/iuZol7sJmO5STtg2aPIDRQ==" }, "node_modules/@patternfly/react-topology": { - "version": "4.86.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-topology/-/react-topology-4.86.0.tgz", - "integrity": "sha512-ecWBYwgdUGlOn0wmCEmdRkZpFNSB4bxB/ksulw9yTgbGxID5jm7ar4jZy7DmmDL7JBRf1hwUoqRtJHuSlzurnA==", + "version": "4.90.11", + "resolved": "https://registry.npmjs.org/@patternfly/react-topology/-/react-topology-4.90.11.tgz", + "integrity": "sha512-VROJvZBmzh/zRB2J0O+Xrwjm/NuimdIRda6XKEpiApKOMF1tECh64skHDOC0jf5SwLCFbQU3kG5u6SeCoP3vbg==", "dependencies": { - "@patternfly/react-core": "^4.239.0", - "@patternfly/react-icons": "^4.90.0", - "@patternfly/react-styles": "^4.89.0", + "@patternfly/react-core": "^4.258.3", + "@patternfly/react-icons": "^4.92.10", + "@patternfly/react-styles": "^4.91.10", "@types/d3": "^5.7.2", "@types/d3-force": "^1.2.1", "@types/dagre": "0.7.42", @@ -3250,8 +3250,26 @@ "webcola": "3.4.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@patternfly/react-topology/node_modules/@patternfly/react-core": { + "version": "4.258.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.258.4.tgz", + "integrity": "sha512-A/DF/i3mRyB0qTURk30Z+vMexD6HGPsPMkZUVnK9vBEGqSNI8XwNE3GP5Sulvm+yxtd4zzCedx/4+Jer0zCSrg==", + "dependencies": { + "@patternfly/react-icons": "^4.92.10", + "@patternfly/react-styles": "^4.91.10", + "@patternfly/react-tokens": "^4.93.10", + "focus-trap": "6.9.2", + "react-dropzone": "9.0.0", + "tippy.js": "5.1.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" } }, "node_modules/@patternfly/react-topology/node_modules/mobx": { @@ -21414,15 +21432,15 @@ } }, "@patternfly/react-icons": { - "version": "4.90.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.90.0.tgz", - "integrity": "sha512-qEnQKbxbUgyiosiKSkeKEBwmhgJwWEqniIAFyoxj+kpzAdeu7ueWe5iBbqo06mvDOedecFiM5mIE1N0MXwk8Yw==", + "version": "4.92.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.92.10.tgz", + "integrity": "sha512-vwCy7b+OyyuvLDSLqLUG2DkJZgMDogjld8tJTdAaG8HiEhC1sJPZac+5wD7AuS3ym/sQolS4vYtNiVDnMEORxA==", "requires": {} }, "@patternfly/react-styles": { - "version": "4.89.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.89.0.tgz", - "integrity": "sha512-SkT+qx3Xqu70T5s+i/AUT2hI2sKAPDX4ffeiJIUDu/oyWiFdk+/9DEivnLSyJMruroXXN33zKibvzb5rH7DKTQ==" + "version": "4.91.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.91.10.tgz", + "integrity": "sha512-fAG4Vjp63ohiR92F4e/Gkw5q1DSSckHKqdnEF75KUpSSBORzYP0EKMpupSd6ItpQFJw3iWs3MJi3/KIAAfU1Jw==" }, "@patternfly/react-table": { "version": "4.108.0", @@ -21438,18 +21456,18 @@ } }, "@patternfly/react-tokens": { - "version": "4.91.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.91.0.tgz", - "integrity": "sha512-QeQCy8o8E/16fAr8mxqXIYRmpTsjCHJXi5p5jmgEDFmYMesN6Pqfv6N5D0FHb+CIaNOZWRps7GkWvlIMIE81sw==" + "version": "4.93.10", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.93.10.tgz", + "integrity": "sha512-F+j1irDc9M6zvY6qNtDryhbpnHz3R8ymHRdGelNHQzPTIK88YSWEnT1c9iUI+uM/iuZol7sJmO5STtg2aPIDRQ==" }, "@patternfly/react-topology": { - "version": "4.86.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-topology/-/react-topology-4.86.0.tgz", - "integrity": "sha512-ecWBYwgdUGlOn0wmCEmdRkZpFNSB4bxB/ksulw9yTgbGxID5jm7ar4jZy7DmmDL7JBRf1hwUoqRtJHuSlzurnA==", + "version": "4.90.11", + "resolved": "https://registry.npmjs.org/@patternfly/react-topology/-/react-topology-4.90.11.tgz", + "integrity": "sha512-VROJvZBmzh/zRB2J0O+Xrwjm/NuimdIRda6XKEpiApKOMF1tECh64skHDOC0jf5SwLCFbQU3kG5u6SeCoP3vbg==", "requires": { - "@patternfly/react-core": "^4.239.0", - "@patternfly/react-icons": "^4.90.0", - "@patternfly/react-styles": "^4.89.0", + "@patternfly/react-core": "^4.258.3", + "@patternfly/react-icons": "^4.92.10", + "@patternfly/react-styles": "^4.91.10", "@types/d3": "^5.7.2", "@types/d3-force": "^1.2.1", "@types/dagre": "0.7.42", @@ -21467,6 +21485,20 @@ "webcola": "3.4.0" }, "dependencies": { + "@patternfly/react-core": { + "version": "4.258.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.258.4.tgz", + "integrity": "sha512-A/DF/i3mRyB0qTURk30Z+vMexD6HGPsPMkZUVnK9vBEGqSNI8XwNE3GP5Sulvm+yxtd4zzCedx/4+Jer0zCSrg==", + "requires": { + "@patternfly/react-icons": "^4.92.10", + "@patternfly/react-styles": "^4.91.10", + "@patternfly/react-tokens": "^4.93.10", + "focus-trap": "6.9.2", + "react-dropzone": "9.0.0", + "tippy.js": "5.1.2", + "tslib": "^2.0.0" + } + }, "mobx": { "version": "5.15.7", "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz", diff --git a/web/package.json b/web/package.json index 924eaf966..a5864c7dc 100644 --- a/web/package.json +++ b/web/package.json @@ -98,7 +98,7 @@ "@patternfly/react-charts": "6.92.0", "@patternfly/react-core": "4.239.0", "@patternfly/react-table": "4.108.0", - "@patternfly/react-topology": "4.86.0", + "@patternfly/react-topology": "4.90.11", "history": "^5.1.0", "i18next": "^19.8.3", "i18next-http-backend": "^1.0.21", diff --git a/web/src/components/netflow-topology/components/edge.tsx b/web/src/components/netflow-topology/components/edge.tsx index d6372ffaa..5d96c6367 100644 --- a/web/src/components/netflow-topology/components/edge.tsx +++ b/web/src/components/netflow-topology/components/edge.tsx @@ -40,7 +40,9 @@ type BaseEdgeProps = { endTerminalStatus?: NodeStatus; endTerminalSize?: number; shadowed?: boolean; + filtered?: boolean; highlighted?: boolean; + isDark?: boolean; tag?: string; tagClass?: string; tagStatus?: NodeStatus; @@ -68,7 +70,9 @@ const BaseEdge: React.FC = ({ endTerminalStatus, endTerminalSize = 14, shadowed, + filtered, highlighted, + isDark, tag, tagClass, tagStatus, @@ -103,7 +107,9 @@ const BaseEdge: React.FC = ({ selected && 'pf-m-selected', 'topology', shadowed && 'shadowed', - highlighted && 'edge-highlighted' + filtered && 'edge-filtered', + highlighted && 'edge-highlighted', + isDark && 'dark' ); const edgeAnimationDuration = animationDuration ?? getEdgeAnimationDuration(element.getEdgeAnimationSpeed()); diff --git a/web/src/components/netflow-topology/components/node.tsx b/web/src/components/netflow-topology/components/node.tsx index b2c368170..3ee8f22d7 100644 --- a/web/src/components/netflow-topology/components/node.tsx +++ b/web/src/components/netflow-topology/components/node.tsx @@ -63,7 +63,9 @@ type BaseNodeProps = { dropTarget?: boolean; scaleNode?: boolean; // Whether or not to scale the node, best on hover of node at lowest scale level shadowed?: boolean; + filtered?: boolean; highlighted?: boolean; + isDark?: boolean; label?: string; // Defaults to element.getLabel() secondaryLabel?: string; showLabel?: boolean; // Defaults to true @@ -112,7 +114,9 @@ const BaseNode: React.FunctionComponent = ({ showLabel = true, label, shadowed, + filtered, highlighted, + isDark, secondaryLabel, labelClassName, labelPosition, @@ -216,7 +220,9 @@ const BaseNode: React.FunctionComponent = ({ StatusModifier[status], 'topology', shadowed && 'shadowed', - highlighted && 'node-highlighted' + filtered && 'node-filtered', + highlighted && 'node-highlighted', + isDark && 'dark' ); const backgroundClassName = css( @@ -342,7 +348,10 @@ const BaseNode: React.FunctionComponent = ({ {children} {statusDecorator} - {isHover && attachments} + { + //isHover && + attachments + } ); }; diff --git a/web/src/components/netflow-topology/netflow-topology.css b/web/src/components/netflow-topology/netflow-topology.css index a6193012a..29d18dde4 100644 --- a/web/src/components/netflow-topology/netflow-topology.css +++ b/web/src/components/netflow-topology/netflow-topology.css @@ -25,15 +25,58 @@ g.netobserv.topology.shadowed { opacity: 0.5; } -g.netobserv.topology.node-highlighted>g>ellipse { +g.netobserv.topology.pf-topology__edge>.pf-topology__edge__background { + stroke-width: 6px; +} + +g.netobserv.topology.pf-topology__edge.dark>.pf-topology__edge__tag>.pf-topology__edge__tag__background{ + fill: #E0E0E0; +} + +g.netobserv.topology.pf-topology__edge>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge>.pf-topology-connector-arrow { + stroke: #6A6E73; + fill: #E0E0E0; +} + +g.netobserv.topology.pf-topology__edge.dark>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge.dark>.pf-topology-connector-arrow { + stroke: #E0E0E0; + fill: #6A6E73; +} + +g.netobserv.topology.node-filtered>g>.pf-topology__node__background { + stroke: #EF9234; + stroke-width: 2px; +} + +g.netobserv.topology.pf-topology__edge.edge-filtered>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge.edge-filtered>.pf-topology-connector-arrow, +g.netobserv.topology.pf-topology__edge.edge-filtered.dark>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge.edge-filtered.dark>.pf-topology-connector-arrow { + stroke: #EF9234; + fill: #773D00; +} + +g.netobserv.topology>g>.pf-topology__node__background { + fill: #FFFFFF; +} + +g.netobserv.topology.dark>g>.pf-topology__node__background { + fill: #26292D; +} + +g.netobserv.topology.node-highlighted>g>.pf-topology__node__background { stroke: #0066CC; - stroke-width: 3px; - --pf-topology__node__background--Fill: white; + stroke-width: 2px; } -g.netobserv.topology.edge-highlighted>.pf-topology__edge__link, -g.netobserv.topology.edge-highlighted>.pf-topology-connector-arrow { +g.netobserv.topology.pf-topology__edge.edge-highlighted>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge.edge-highlighted>.pf-topology-connector-arrow, +g.netobserv.topology.pf-topology__edge.edge-highlighted.dark>.pf-topology__edge__link, +g.netobserv.topology.pf-topology__edge.edge-highlighted.dark>.pf-topology-connector-arrow { stroke: #0066CC; + fill: #002952; } g.netobserv.pf-topology__group>.pf-topology__group__background { @@ -54,6 +97,6 @@ g.netobserv.pf-topology__group.pf-m-selected>.pf-topology__group__background { border: none; } -.topology-icon > text { +.topology-icon>text { fill: white; } \ No newline at end of file diff --git a/web/src/components/netflow-topology/netflow-topology.tsx b/web/src/components/netflow-topology/netflow-topology.tsx index 948344cba..2fedb5de0 100644 --- a/web/src/components/netflow-topology/netflow-topology.tsx +++ b/web/src/components/netflow-topology/netflow-topology.tsx @@ -72,6 +72,7 @@ export const TopologyContent: React.FC<{ onSelect: (e: GraphElementPeer | undefined) => void; searchHandle: SearchHandle | null; searchEvent?: SearchEvent; + isDark?: boolean; }> = ({ k8sModels, range, @@ -87,7 +88,8 @@ export const TopologyContent: React.FC<{ selected, onSelect, searchHandle, - searchEvent + searchEvent, + isDark }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const controller = useVisualizationController(); @@ -324,7 +326,8 @@ export const TopologyContent: React.FC<{ highlightedId, filters, t, - k8sModels + k8sModels, + isDark ); const allIds = [...(updatedModel.nodes || []), ...(updatedModel.edges || [])].map(item => item.id); controller.getElements().forEach(e => { @@ -362,7 +365,8 @@ export const TopologyContent: React.FC<{ searchEvent?.searchValue, filters, t, - k8sModels + k8sModels, + isDark ]); //update model on layout / metrics / filters change @@ -493,6 +497,7 @@ export const NetflowTopology: React.FC<{ onSelect: (e: GraphElementPeer | undefined) => void; searchHandle: SearchHandle | null; searchEvent?: SearchEvent; + isDark?: boolean; }> = ({ loading, k8sModels, @@ -510,7 +515,8 @@ export const NetflowTopology: React.FC<{ selected, onSelect, searchHandle, - searchEvent + searchEvent, + isDark }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [controller, setController] = React.useState(); @@ -552,6 +558,7 @@ export const NetflowTopology: React.FC<{ onSelect={onSelect} searchHandle={searchHandle} searchEvent={searchEvent} + isDark={isDark} /> ); diff --git a/web/src/components/netflow-topology/styles/styleNode.tsx b/web/src/components/netflow-topology/styles/styleNode.tsx index 527444a3d..6100aa204 100644 --- a/web/src/components/netflow-topology/styles/styleNode.tsx +++ b/web/src/components/netflow-topology/styles/styleNode.tsx @@ -1,12 +1,9 @@ -import * as _ from 'lodash'; -import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; -import { Flex, FlexItem, Popover } from '@patternfly/react-core'; +import { Tooltip, TooltipPosition } from '@patternfly/react-core'; import { CubeIcon, CubesIcon, - LevelDownAltIcon, FilterIcon, - InfoCircleIcon, + LevelDownAltIcon, OutlinedHddIcon, QuestionCircleIcon, ServiceIcon, @@ -16,6 +13,7 @@ import { } from '@patternfly/react-icons'; import { Decorator, + DEFAULT_DECORATOR_PADDING, DEFAULT_DECORATOR_RADIUS, DEFAULT_LAYER, getDefaultShapeDecoratorCenter, @@ -32,12 +30,13 @@ import { WithDragNodeProps, WithSelectionProps } from '@patternfly/react-topology'; -import BaseNode from '../components/node'; import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; import { TFunction } from 'i18next'; +import * as _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Decorated, NodeData } from '../../../model/topology'; +import BaseNode from '../components/node'; export const FILTER_EVENT = 'filter'; export const STEP_INTO_EVENT = 'step_into'; @@ -45,6 +44,8 @@ export enum DataTypes { Default } const ICON_PADDING = 20; +const MEDIUM_DECORATOR_PADDING = 5; +const LARGE_DECORATOR_PADDING = 6; type NodePeer = Node>; @@ -81,23 +82,6 @@ const getTypeIcon = (resourceKind?: string): React.ComponentClass => { } }; -const getTypeIconColor = (resourceKind?: string): string => { - switch (resourceKind) { - case 'Service': - case 'Pod': - case 'Namespace': - case 'Node': - case 'CatalogSource': - case 'DaemonSet': - case 'Deployment': - case 'StatefulSet': - case 'Job': - return '#393F44'; - default: - return '#c9190b'; - } -}; - const renderIcon = (data: Decorated, element: NodePeer): React.ReactNode => { const { width, height } = element.getDimensions(); const shape = element.getNodeShape(); @@ -105,112 +89,20 @@ const renderIcon = (data: Decorated, element: NodePeer): React.ReactNo (shape === NodeShape.trapezoid ? width : Math.min(width, height)) - (shape === NodeShape.stadium ? 5 : ICON_PADDING) * 2; const Component = getTypeIcon(data.resourceKind); - const color = getTypeIconColor(data.resourceKind); return ( - + ); }; -const renderPopoverDecorator = ( - t: TFunction, - element: NodePeer, - quadrant: TopologyQuadrant, - icon: React.ReactNode, - data: NodeData, - getShapeDecoratorCenter?: ( - quadrant: TopologyQuadrant, - node: NodePeer, - radius?: number - ) => { - x: number; - y: number; - } -): React.ReactNode => { - const { x, y } = getShapeDecoratorCenter - ? getShapeDecoratorCenter(quadrant, element) - : getDefaultShapeDecoratorCenter(quadrant, element); - - return ( - (data.resourceKind || data.namespace || data.name || data.addr || data.host) && ( - { - const data = element.getData(); - if (data) { - //force hover state when popover is opened - element.setData({ ...data, hover: true }); - } - }} - onHide={() => { - const data = element.getData(); - if (data) { - //restore hover state when popover is closed - element.setData({ ...data, hover: undefined }); - } - }} - hasAutoWidth - headerContent={ - //namespace is optionnal here for Node and Namespace kinds - data.resourceKind && data.name ? ( - - ) : ( - data.addr - ) - } - bodyContent={ - - - {data.resourceKind && ( - - {t('Kind')} - - )} - {data.namespace && ( - - {t('Namespace')} - - )} - {data.name && ( - - {t('Name')} - - )} - {data.addr && ( - - {t('IP')} - - )} - {data.host && ( - - {t('Node')} - - )} - - - {data.resourceKind && {data.resourceKind}} - {data.namespace && {data.namespace}} - {data.name && {data.name}} - {data.addr && {data.addr}} - {data.host && {data.host}} - - - } - > - - - ) - ); -}; - const renderClickableDecorator = ( t: TFunction, element: NodePeer, quadrant: TopologyQuadrant, icon: React.ReactNode, + tooltip: string, isActive: boolean, onClick: (element: NodePeer) => void, getShapeDecoratorCenter?: ( @@ -220,22 +112,26 @@ const renderClickableDecorator = ( ) => { x: number; y: number; - } + }, + padding?: number ): React.ReactNode => { const { x, y } = getShapeDecoratorCenter ? getShapeDecoratorCenter(quadrant, element) : getDefaultShapeDecoratorCenter(quadrant, element); return ( - onClick(element)} - /> + + onClick(element)} + /> + ); }; @@ -302,11 +198,13 @@ const renderDecorators = ( renderClickableDecorator( t, element, - TopologyQuadrant.upperLeft, + TopologyQuadrant.lowerRight, , + t('Step into this {{name}}', { name: data.resourceKind?.toLowerCase() }), false, onStepIntoClick, - getShapeDecoratorCenter + getShapeDecoratorCenter, + MEDIUM_DECORATOR_PADDING )} {(data.namespace || data.name || data.addr || data.host) && renderClickableDecorator( @@ -314,26 +212,24 @@ const renderDecorators = ( element, TopologyQuadrant.lowerLeft, isFiltered ? : , + isFiltered + ? t('Remove {{name}} filter', { name: data.resourceKind?.toLowerCase() }) + : t('Add {{name}} filter', { name: data.resourceKind?.toLowerCase() }), isFiltered, onFilterClick, - getShapeDecoratorCenter + getShapeDecoratorCenter, + isFiltered ? DEFAULT_DECORATOR_PADDING : LARGE_DECORATOR_PADDING )} {renderClickableDecorator( t, element, TopologyQuadrant.upperRight, , + isPinned ? t('Unpin this element') : t('Pin this element'), isPinned, onPinClick, - getShapeDecoratorCenter - )} - {renderPopoverDecorator( - t, - element, - TopologyQuadrant.lowerRight, - , - data, - getShapeDecoratorCenter + getShapeDecoratorCenter, + MEDIUM_DECORATOR_PADDING )} ); diff --git a/web/src/components/netflow-traffic.tsx b/web/src/components/netflow-traffic.tsx index 8fe2f022f..60593edc2 100644 --- a/web/src/components/netflow-traffic.tsx +++ b/web/src/components/netflow-traffic.tsx @@ -844,6 +844,7 @@ export const NetflowTraffic: React.FC<{ onSelect={onElementSelect} searchHandle={searchRef?.current} searchEvent={searchEvent} + isDark={isDarkTheme} /> ); break; diff --git a/web/src/model/topology.ts b/web/src/model/topology.ts index ca178c937..bb975c2e2 100644 --- a/web/src/model/topology.ts +++ b/web/src/model/topology.ts @@ -208,6 +208,7 @@ export type NodeData = { canStepInto?: boolean; parentKind?: string; parentName?: string; + badgeColor?: string; }; export const generateNode = ( @@ -217,7 +218,8 @@ export const generateNode = ( highlightedId: string, filters: Filter[], t: TFunction, - k8sModels: { [key: string]: K8sModel } + k8sModels: { [key: string]: K8sModel }, + isDark?: boolean ): NodeModel => { const id = getTopologyNodeId(data.resourceKind, data.namespace, data.name, data.addr, data.host); const label = data.displayName || data.name || data.addr || ''; // should never be empty @@ -231,6 +233,7 @@ export const generateNode = ( ? data.namespace : undefined; const shadowed = !_.isEmpty(searchValue) && !(label.includes(searchValue) || secondaryLabel?.includes(searchValue)); + const filtered = !_.isEmpty(searchValue) && !shadowed; const highlighted = !shadowed && !_.isEmpty(highlightedId) && highlightedId.includes(id); const k8sModel = options.nodeBadges && data.resourceKind ? k8sModels[data.resourceKind] : undefined; return { @@ -239,13 +242,15 @@ export const generateNode = ( label, width: DEFAULT_NODE_SIZE, height: DEFAULT_NODE_SIZE, - shape: NodeShape.ellipse, + shape: k8sModel ? NodeShape.ellipse : NodeShape.rect, status: NodeStatus.default, style: { padding: 20 }, data: { ...data, shadowed, + filtered, highlighted, + isDark, isFiltered: isElementFiltered(data, filters, t), labelPosition: LabelPosition.bottom, badge: k8sModel?.abbr, @@ -313,9 +318,12 @@ export const generateEdge = ( stat: number, options: TopologyOptions, shadowed = false, - highlightedId: string + filtered = false, + highlightedId: string, + isDark?: boolean ): EdgeModel => { const id = `${sourceId}.${targetId}`; + const highlighted = !shadowed && !_.isEmpty(highlightedId) && id.includes(highlightedId); return { id: getTopologyEdgeId(sourceId, targetId), @@ -328,7 +336,9 @@ export const generateEdge = ( sourceId, targetId, shadowed, + filtered, highlighted, + isDark, //edges are directed from src to dst. It will become bidirectionnal if inverted pair is found startTerminalType: EdgeTerminalType.none, startTerminalStatus: NodeStatus.default, @@ -349,7 +359,8 @@ export const generateDataModel = ( highlightedId: string, filters: Filter[], t: TFunction, - k8sModels: { [key: string]: K8sModel } + k8sModels: { [key: string]: K8sModel }, + isDark?: boolean ): Model => { let nodes: NodeModel[] = []; const edges: EdgeModel[] = []; @@ -378,6 +389,7 @@ export const generateDataModel = ( name, nodeType, resourceKind, + isDark, parentKind: parentData?.resourceKind, parentName: parentData?.name, labelPosition: LabelPosition.bottom, @@ -414,7 +426,7 @@ export const generateDataModel = ( n.data.host === data.host ); if (!node) { - node = generateNode(data, opts, searchValue, highlightedId, filters, t, k8sModels); + node = generateNode(data, opts, searchValue, highlightedId, filters, t, k8sModels, isDark); nodes.push(node); } @@ -425,7 +437,7 @@ export const generateDataModel = ( return node; } - function addEdge(sourceId: string, targetId: string, stats: MetricStats, shadowed = false) { + function addEdge(sourceId: string, targetId: string, stats: MetricStats, shadowed = false, filtered = false) { const stat = getStat(stats, options.metricFunction); let edge = edges.find( e => @@ -439,6 +451,8 @@ export const generateDataModel = ( edge.data = { ...edge.data, shadowed, + filtered, + isDark, //edges are directed from src to dst. It will become bidirectionnal if inverted pair is found startTerminalType: edge.data.sourceId !== sourceId ? EdgeTerminalType.directional : edge.data.startTerminalType, tag: getEdgeTag(stat, options), @@ -446,7 +460,7 @@ export const generateDataModel = ( bps: stat }; } else { - edge = generateEdge(sourceId, targetId, stat, opts, shadowed, highlightedId); + edge = generateEdge(sourceId, targetId, stat, opts, shadowed, filtered, highlightedId, isDark); edges.push(edge); } @@ -546,7 +560,13 @@ export const generateDataModel = ( const dstNode = manageNode(d.destination); if (options.edges && srcNode && dstNode && srcNode.id !== dstNode.id) { - addEdge(srcNode.id, dstNode.id, d.stats, srcNode.data.shadowed || dstNode.data.shadowed); + addEdge( + srcNode.id, + dstNode.id, + d.stats, + srcNode.data.shadowed || dstNode.data.shadowed, + srcNode.data.filtered || dstNode.data.filtered + ); } });