diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index adbf2371d..0c40acf5e 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -7,8 +7,10 @@ "View alert details": "View alert details", "View health dashboard": "View health dashboard", "Name": "Name", + "Subnet label": "Subnet label", "IP": "IP", - "No information available for this content. Change scope to get more details.": "No information available for this content. Change scope to get more details.", + "No information available for this content. Decrease scope aggregation to get more details.": "No information available for this content. Decrease scope aggregation to get more details.", + "You may also configure subnet labels in the FlowCollector resource (spec.processor.subnetLabels) to identify IP ranges that are external to the cluster.": "You may also configure subnet labels in the FlowCollector resource (spec.processor.subnetLabels) to identify IP ranges that are external to the cluster.", "Cluster name": "Cluster name", "UDN": "UDN", "Can't find metrics for this element. Check your capture filters to ensure we can monitor it. Else it probably means there is no traffic here.": "Can't find metrics for this element. Check your capture filters to ensure we can monitor it. Else it probably means there is no traffic here.", diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index 0d18f310f..1cac7fe43 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -53,6 +53,8 @@ export interface Flow { DstK8S_Zone?: string; SrcK8S_NetworkName?: string; DstK8S_NetworkName?: string; + SrcSubnetLabel?: string; + DstSubnetLabel?: string; K8S_ClusterName?: string; Proto?: number; Interfaces?: string[]; diff --git a/web/src/api/loki.ts b/web/src/api/loki.ts index 421cc627e..1329edcda 100644 --- a/web/src/api/loki.ts +++ b/web/src/api/loki.ts @@ -68,12 +68,13 @@ export interface TopologyMetricPeer { resourceKind?: string; isAmbiguous: boolean; getDisplayName: (inclNamespace: boolean, disambiguate: boolean) => string | undefined; - // any FlowScope can appear here as optionnal field + // any FlowScope can appear here as optional field [name: string]: unknown; namespace?: string; host?: string; cluster?: string; udn?: string; + subnetLabel?: string; } export type GenericMetric = { diff --git a/web/src/components/drawer/element/element-fields.tsx b/web/src/components/drawer/element/element-fields.tsx index ad0beabae..82688ce9b 100644 --- a/web/src/components/drawer/element/element-fields.tsx +++ b/web/src/components/drawer/element/element-fields.tsx @@ -81,6 +81,20 @@ export const ElementFields: React.FC = ({ forceLabel = forceAsText = undefined; } }); + if (data.peer.subnetLabel) { + fragments.push( + + ); + } if (data.peer.addr) { fragments.push( = ({ { - {t('No information available for this content. Change scope to get more details.')} + {t('No information available for this content. Decrease scope aggregation to get more details.')} +
+ {t( + 'You may also configure subnet labels in the FlowCollector resource (spec.processor.subnetLabels) to identify IP ranges that are external to the cluster.' + )}
}
diff --git a/web/src/components/drawer/element/element-panel.tsx b/web/src/components/drawer/element/element-panel.tsx index e52dc2bec..51dcf3937 100644 --- a/web/src/components/drawer/element/element-panel.tsx +++ b/web/src/components/drawer/element/element-panel.tsx @@ -72,10 +72,11 @@ export const ElementPanel: React.FC = ({ return {t('Edge')}; } else { const data = element.getData(); - if (data?.nodeType === 'unknown') { - return {t('Unknown')}; + const name = data?.peer.getDisplayName(false, false); + if (data && name) { + return ; } - return <>{data && }; + return {t('Unknown')}; } }, [element, t]); diff --git a/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx b/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx index b6e590953..82970d0ff 100644 --- a/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx +++ b/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx @@ -2,6 +2,7 @@ import { ClusterIcon, CubeIcon, CubesIcon, + ExternalLinkAltIcon, NetworkIcon, OutlinedHddIcon, QuestionCircleIcon, @@ -27,6 +28,7 @@ import { import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; import * as _ from 'lodash'; import * as React from 'react'; +import { TopologyMetricPeer } from '../../../../../api/loki'; import { Decorated, NodeData } from '../../../../../model/topology'; import DefaultNode from '../components/node'; import { NodeDecorators } from './styleDecorators'; @@ -50,8 +52,8 @@ type StyleNodeProps = { WithSelectionProps; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const getTypeIcon = (resourceKind?: string): React.ComponentClass => { - switch (resourceKind) { +const getTypeIcon = (peer: TopologyMetricPeer): React.ComponentClass => { + switch (peer.resourceKind) { case 'Service': return ServiceIcon; case 'Pod': @@ -72,9 +74,11 @@ const getTypeIcon = (resourceKind?: string): React.ComponentClass => { case 'StatefulSet': case 'Job': return CubesIcon; - default: - return QuestionCircleIcon; } + if (peer.addr || peer.subnetLabel) { + return ExternalLinkAltIcon; + } + return QuestionCircleIcon; }; const renderIcon = (data: Decorated, element: NodePeer): React.ReactNode => { @@ -83,7 +87,7 @@ const renderIcon = (data: Decorated, element: NodePeer): React.ReactNo const iconSize = (shape === NodeShape.trapezoid ? width : Math.min(width, height)) - (shape === NodeShape.stadium ? 5 : iconPadding) * 2; - const Component = getTypeIcon(data.peer.resourceKind); + const Component = getTypeIcon(data.peer); return ( diff --git a/web/src/utils/ids.ts b/web/src/utils/ids.ts index 801847a42..61b9e3b5e 100644 --- a/web/src/utils/ids.ts +++ b/web/src/utils/ids.ts @@ -41,6 +41,8 @@ export const getPeerId = (fields: Partial): string => { parts.push('r=' + fields.resource.type + '.' + fields.resource.name); } else if (fields.addr) { parts.push('a=' + fields.addr); + } else if (fields.subnetLabel) { + parts.push('sl=' + fields.subnetLabel); } return parts.length > 0 ? parts.join(',') : idUnknown; }; diff --git a/web/src/utils/metrics.ts b/web/src/utils/metrics.ts index 620cb34d9..bf53c482a 100644 --- a/web/src/utils/metrics.ts +++ b/web/src/utils/metrics.ts @@ -114,6 +114,7 @@ export const createPeer = (fields: Partial): TopologyMetricP addr: fields.addr, resource: fields.resource, owner: fields.owner, + subnetLabel: fields.subnetLabel, isAmbiguous: false, getDisplayName: () => undefined }; @@ -146,9 +147,15 @@ export const createPeer = (fields: Partial): TopologyMetricP } }); - // fallback on address if nothing else available - if (!newPeer.resourceKind && fields.addr) { - newPeer.getDisplayName = () => fields.addr; + // fallback on address and/or subnet label if nothing else available + if (!newPeer.resourceKind) { + if (fields.subnetLabel && fields.addr) { + newPeer.getDisplayName = () => `${fields.subnetLabel} (${fields.addr})`; + } else if (fields.subnetLabel) { + newPeer.getDisplayName = () => fields.subnetLabel; + } else if (fields.addr) { + newPeer.getDisplayName = () => fields.addr; + } } return newPeer; }; @@ -173,7 +180,8 @@ const parseTopologyMetric = ( owner: raw.metric.SrcK8S_Type !== raw.metric.SrcK8S_OwnerType ? nameAndType(raw.metric.SrcK8S_OwnerName, raw.metric.SrcK8S_OwnerType) - : undefined + : undefined, + subnetLabel: raw.metric.SrcSubnetLabel }; const destFields: Partial = { addr: raw.metric.DstAddr, @@ -181,7 +189,8 @@ const parseTopologyMetric = ( owner: raw.metric.DstK8S_Type !== raw.metric.DstK8S_OwnerType ? nameAndType(raw.metric.DstK8S_OwnerName, raw.metric.DstK8S_OwnerType) - : undefined + : undefined, + subnetLabel: raw.metric.DstSubnetLabel }; getCustomScopes().forEach(sc => { if (!sc.labels.length) {