diff --git a/mocks/loki/flow_records.json b/mocks/loki/flow_records.json index f1b0dffe4..c61dc8c55 100644 --- a/mocks/loki/flow_records.json +++ b/mocks/loki/flow_records.json @@ -14,7 +14,7 @@ "values": [ [ "1708011867120999936", - "{\"SrcK8S_Name\":\"ip-10-0-1-7.ec2.internal\",\"Bytes\":66,\"TimeFlowRttNs\":10000,\"Packets\":1,\"Interface\":\"br-ex\",\"SrcMac\":\"02:27:A1:A8:84:B9\",\"Proto\":6,\"SrcK8S_HostIP\":\"10.0.1.7\",\"SrcK8S_HostName\":\"ip-10-0-1-7.ec2.internal\",\"Flags\":16,\"DnsErrno\":0,\"TimeFlowStartMs\":1708011867121,\"TimeReceived\":1708011867,\"DstAddr\":\"10.0.1.140\",\"Etype\":2048,\"SrcPort\":50104,\"AgentIP\":\"10.0.1.7\",\"SrcK8S_OwnerType\":\"Node\",\"Dscp\":0,\"TimeFlowEndMs\":1708011867121,\"IfDirection\":1,\"SrcAddr\":\"10.0.1.7\",\"Duplicate\":false,\"DstMac\":\"02:7B:32:68:BE:65\",\"DstPort\":443}" + "{\"SrcK8S_Name\":\"ip-10-0-1-7.ec2.internal\",\"Bytes\":66,\"TimeFlowRttNs\":10000,\"Packets\":1,\"Interfaces\":[\"br-ex\",\"test\"],\"SrcMac\":\"02:27:A1:A8:84:B9\",\"Proto\":6,\"SrcK8S_HostIP\":\"10.0.1.7\",\"SrcK8S_HostName\":\"ip-10-0-1-7.ec2.internal\",\"Flags\":16,\"DnsErrno\":0,\"TimeFlowStartMs\":1708011867121,\"TimeReceived\":1708011867,\"DstAddr\":\"10.0.1.140\",\"Etype\":2048,\"SrcPort\":50104,\"AgentIP\":\"10.0.1.7\",\"SrcK8S_OwnerType\":\"Node\",\"Dscp\":0,\"TimeFlowEndMs\":1708011867121,\"IfDirections\":[\"1\",\"0\"],\"SrcAddr\":\"10.0.1.7\",\"Duplicate\":false,\"DstMac\":\"02:7B:32:68:BE:65\",\"DstPort\":443,\"IcmpType\":1,\"IcmpCode\":1}" ], [ "1708011867120999936", diff --git a/web/.prettierrc.json b/web/.prettierrc.json index 6dd621814..0c621f82f 100644 --- a/web/.prettierrc.json +++ b/web/.prettierrc.json @@ -2,5 +2,6 @@ "arrowParens": "avoid", "printWidth": 120, "singleQuote": true, - "trailingComma": "none" + "trailingComma": "none", + "plugins": ["prettier-plugin-organize-imports"] } diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 574cff7ad..c2bad9282 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -45,12 +45,13 @@ "Dropped packets": "Dropped packets", "DNS latencies": "DNS latencies", "RTT": "RTT", + "Display options": "Display options", "The level of details represented.": "The level of details represented.", "Scope": "Scope", "Long labels can reduce visibility.": "Long labels can reduce visibility.", "Truncate labels": "Truncate labels", "Single graph focus": "Single graph focus", - "Display options": "Display options", + "Query options": "Query options", "Conversation": "Conversation", "Flow": "Flow", "Loki": "Loki", @@ -87,7 +88,6 @@ "Depending on the matching and filter settings, several queries can be performed under the cover, each with this parameter set, resulting in more results after aggregation.": "Depending on the matching and filter settings, several queries can be performed under the cover, each with this parameter set, resulting in more results after aggregation.", "Top / Bottom": "Top / Bottom", "Limit": "Limit", - "Query options": "Query options", "Refresh off": "Refresh off", "15 seconds": "15 seconds", "30 seconds": "30 seconds", @@ -149,9 +149,6 @@ "Switch between one way / back and forth filtering": "Switch between one way / back and forth filtering", "One way shows traffic strictly as defined per your filters": "One way shows traffic strictly as defined per your filters", "Back and forth shows traffic according to your filters, plus the related return traffic": "Back and forth shows traffic according to your filters, plus the related return traffic", - "Source": "Source", - "Destination": "Destination", - "Common": "Common", "Filter already exists": "Filter already exists", "Hide filters": "Hide filters", "Show {{count}} filters": "Show {{count}} filters", @@ -160,6 +157,8 @@ "Collapse": "Collapse", "Expand": "Expand", "Quick filters": "Quick filters", + "Source": "Source", + "Destination": "Destination", "Step {{index}}/{{count}}": "Step {{index}}/{{count}}", "Step {{index}}/{{count}}_plural": "Step {{index}}/{{count}}", "Previous tip": "Previous tip", @@ -212,9 +211,6 @@ "Reset time range": "Reset time range", "Others": "Others", "Total dropped": "Total dropped", - "(non nodes)": "(non nodes)", - "(non pods)": "(non pods)", - "internal": "internal", "Manage columns": "Manage columns", "Selected columns will appear in the table.": "Selected columns will appear in the table.", "Click and drag the items to reorder the columns in the table.": "Click and drag the items to reorder the columns in the table.", @@ -302,6 +298,7 @@ "Name": "Name", "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.", + "Cluster name": "Cluster name", "Stats": "Stats", "Top 5 DNS latency": "Top 5 DNS latency", "Top 5 flow RTT": "Top 5 flow RTT", @@ -315,7 +312,6 @@ "Average rate": "Average rate", "Latest time": "Latest time", "Latest rate": "Latest rate", - "Cluster name": "Cluster name", "Edge": "Edge", "Drops": "Drops", "Query is slow": "Query is slow", @@ -424,6 +420,10 @@ "Not a valid MAC address": "Not a valid MAC address", "Unknown protocol": "Unknown protocol", "Unknown direction": "Unknown direction", + "Common": "Common", + "(non nodes)": "(non nodes)", + "(non pods)": "(non pods)", + "internal": "internal", "P": "P", "Pps": "Pps", "minimum": "minimum", diff --git a/web/package-lock.json b/web/package-lock.json index d1d8d16c4..96266421a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -75,6 +75,7 @@ "mobx": "^5.15.7", "parse-duration": "^1.1.0", "prettier": "^2.5.1", + "prettier-plugin-organize-imports": "^3.2.4", "pretty-quick": "^3.1.2", "react-transition-group": "^4.4.2", "style-loader": "^3.3.1", @@ -15294,6 +15295,26 @@ "node": ">=10.13.0" } }, + "node_modules/prettier-plugin-organize-imports": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", + "dev": true, + "peerDependencies": { + "@volar/vue-language-plugin-pug": "^1.0.4", + "@volar/vue-typescript": "^1.0.4", + "prettier": ">=2.0", + "typescript": ">=2.9" + }, + "peerDependenciesMeta": { + "@volar/vue-language-plugin-pug": { + "optional": true + }, + "@volar/vue-typescript": { + "optional": true + } + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -31806,6 +31827,13 @@ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, + "prettier-plugin-organize-imports": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", + "dev": true, + "requires": {} + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/web/package.json b/web/package.json index ee6350f1c..017befc2a 100644 --- a/web/package.json +++ b/web/package.json @@ -72,6 +72,7 @@ "mobx": "^5.15.7", "parse-duration": "^1.1.0", "prettier": "^2.5.1", + "prettier-plugin-organize-imports": "^3.2.4", "pretty-quick": "^3.1.2", "react-transition-group": "^4.4.2", "style-loader": "^3.3.1", diff --git a/web/src/api/loki.ts b/web/src/api/loki.ts index 146e91e67..ddf5b8901 100644 --- a/web/src/api/loki.ts +++ b/web/src/api/loki.ts @@ -1,6 +1,6 @@ -import { getFunctionFromId, getRateFunctionFromId } from '../utils/overview-panels'; import { FlowScope, MetricType, StatFunction } from '../model/flow-query'; import { cyrb53 } from '../utils/hash'; +import { getFunctionFromId, getRateFunctionFromId } from '../utils/overview-panels'; import { Field, Fields, Labels, Record } from './ipfix'; export interface AggregatedQueryResponse { diff --git a/web/src/api/routes.ts b/web/src/api/routes.ts index 8d2d5b7ba..0e663e768 100644 --- a/web/src/api/routes.ts +++ b/web/src/api/routes.ts @@ -4,18 +4,18 @@ import { buildExportQuery } from '../model/export-query'; import { FlowQuery, FlowScope, isTimeMetric } from '../model/flow-query'; import { ContextSingleton } from '../utils/context'; import { TimeRange } from '../utils/datetime'; -import { parseTopologyMetrics, parseGenericMetrics } from '../utils/metrics'; +import { parseGenericMetrics, parseTopologyMetrics } from '../utils/metrics'; import { AlertsResult, SilencedAlert } from './alert'; import { Field } from './ipfix'; import { AggregatedQueryResponse, + FlowMetricsResult, GenericMetricsResult, parseStream, RawTopologyMetrics, RecordsResult, Stats, - StreamResult, - FlowMetricsResult + StreamResult } from './loki'; export const getFlowRecords = (params: FlowQuery): Promise => { diff --git a/web/src/components/__tests-data__/filters.ts b/web/src/components/__tests-data__/filters.ts index 516cba363..bc1970d3b 100644 --- a/web/src/components/__tests-data__/filters.ts +++ b/web/src/components/__tests-data__/filters.ts @@ -1,6 +1,6 @@ /* eslint-disable max-len */ -import { findFilter, getFilterDefinitions } from '../../utils/filter-definitions'; import { Filter, FilterId, FilterValue } from '../../model/filters'; +import { findFilter, getFilterDefinitions } from '../../utils/filter-definitions'; import { ColumnConfigSampleDefs } from './columns'; export const FilterConfigSampleDefs = [ diff --git a/web/src/components/__tests-data__/flows.ts b/web/src/components/__tests-data__/flows.ts index ee748fe20..7878f679c 100644 --- a/web/src/components/__tests-data__/flows.ts +++ b/web/src/components/__tests-data__/flows.ts @@ -1,6 +1,6 @@ -import { parseStream, RecordsResult, StreamResult } from '../../api/loki'; -import { FlowDirection, Record } from '../../api/ipfix'; import flowsJson from '../../../../mocks/loki/flow_records.json'; +import { FlowDirection, Record } from '../../api/ipfix'; +import { parseStream, RecordsResult, StreamResult } from '../../api/loki'; export const FlowsMock: Record[] = (flowsJson.data.result as StreamResult[]).flatMap(r => parseStream(r)); diff --git a/web/src/components/__tests-data__/metrics.ts b/web/src/components/__tests-data__/metrics.ts index 63284920d..f82fe6ac0 100644 --- a/web/src/components/__tests-data__/metrics.ts +++ b/web/src/components/__tests-data__/metrics.ts @@ -1,5 +1,5 @@ -import { parseTopologyMetrics } from '../../utils/metrics'; import { RawTopologyMetrics, TopologyMetrics } from '../../api/loki'; +import { parseTopologyMetrics } from '../../utils/metrics'; export const metric1: RawTopologyMetrics = { metric: { diff --git a/web/src/components/__tests__/netflow-traffic.spec.tsx b/web/src/components/__tests__/netflow-traffic.spec.tsx index 17aa80d12..55eb96a7b 100644 --- a/web/src/components/__tests__/netflow-traffic.spec.tsx +++ b/web/src/components/__tests__/netflow-traffic.spec.tsx @@ -1,17 +1,17 @@ import { useResolvedExtensions } from '@openshift-console/dynamic-plugin-sdk'; +import { waitFor } from '@testing-library/react'; import { mount, render, shallow } from 'enzyme'; import * as React from 'react'; -import { waitFor } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; -import { getConfig, getFlowGenericMetrics, getFlowRecords, getFlowMetrics } from '../../api/routes'; +import { AlertsResult, SilencedAlert } from '../../api/alert'; +import { FlowMetricsResult, GenericMetricsResult } from '../../api/loki'; +import { getConfig, getFlowGenericMetrics, getFlowMetrics, getFlowRecords } from '../../api/routes'; +import { FlowQuery } from '../../model/flow-query'; import NetflowTraffic from '../netflow-traffic'; -import { extensionsMock } from '../__tests-data__/extensions'; -import { FlowsResultSample } from '../__tests-data__/flows'; import NetflowTrafficParent from '../netflow-traffic-parent'; -import { GenericMetricsResult, FlowMetricsResult } from '../../api/loki'; -import { AlertsResult, SilencedAlert } from '../../api/alert'; import { FullConfigResultSample, SimpleConfigResultSample } from '../__tests-data__/config'; -import { FlowQuery } from '../../model/flow-query'; +import { extensionsMock } from '../__tests-data__/extensions'; +import { FlowsResultSample } from '../__tests-data__/flows'; const useResolvedExtensionsMock = useResolvedExtensions as jest.Mock; diff --git a/web/src/components/alerts/banner.tsx b/web/src/components/alerts/banner.tsx index 95b03edbe..3fbe299b1 100644 --- a/web/src/components/alerts/banner.tsx +++ b/web/src/components/alerts/banner.tsx @@ -1,21 +1,23 @@ -import { useTranslation } from 'react-i18next'; -import * as React from 'react'; +import { Rule } from '@openshift-console/dynamic-plugin-sdk'; import { Alert, - AlertActionLink, AlertActionCloseButton, - TextContent, + AlertActionLink, Text, + TextContent, TextVariants } from '@patternfly/react-core'; -import './banner.css'; -import { Rule } from '@openshift-console/dynamic-plugin-sdk'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { navigate } from '../dynamic-loader/dynamic-loader'; +import './banner.css'; -export const AlertBanner: React.FC<{ +export interface AlertBannerProps { rule: Rule; onDelete: () => void; -}> = ({ rule, onDelete }) => { +} + +export const AlertBanner: React.FC = ({ rule, onDelete }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const routeAlert = () => { let path = `/monitoring/alerts/${rule.id}`; diff --git a/web/src/components/alerts/fetcher.tsx b/web/src/components/alerts/fetcher.tsx index 7c3b8eec3..c342f2765 100644 --- a/web/src/components/alerts/fetcher.tsx +++ b/web/src/components/alerts/fetcher.tsx @@ -1,11 +1,11 @@ +import { Rule } from '@openshift-console/dynamic-plugin-sdk'; import * as React from 'react'; -import AlertBanner from './banner'; import { getAlerts, getSilencedAlerts } from '../../api/routes'; -import { Rule } from '@openshift-console/dynamic-plugin-sdk'; +import AlertBanner from './banner'; import { murmur3 } from 'murmurhash-js'; -type AlertFetcherProps = {}; +export interface AlertFetcherProps {} export const AlertFetcher: React.FC = ({ children }) => { const [alerts, setAlerts] = React.useState([]); diff --git a/web/src/components/dropdowns/__tests__/group-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/group-dropdown.spec.tsx index 22d8a4b45..40a630b63 100644 --- a/web/src/components/dropdowns/__tests__/group-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/group-dropdown.spec.tsx @@ -1,15 +1,15 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import GroupDropdown from '../group-dropdown'; -import { TopologyGroupTypes } from '../../../model/topology'; -import { MetricScopeOptions } from '../../../model/metrics'; import { FlowScope } from '../../../model/flow-query'; +import { MetricScopeOptions } from '../../../model/metrics'; +import { TopologyGroupTypes } from '../../../model/topology'; +import { GroupDropdown } from '../group-dropdown'; describe('', () => { const props = { scope: MetricScopeOptions.RESOURCE, - selected: TopologyGroupTypes.HOSTS, + selected: TopologyGroupTypes.hosts, setGroupType: jest.fn(), id: 'group', allowedScopes: ['host', 'namespace', 'owner'] as FlowScope[] @@ -41,13 +41,13 @@ describe('', () => { //open dropdown and select NONE dropdown.at(0).simulate('click'); wrapper.find('[id="none"]').at(0).simulate('click'); - expect(props.setGroupType).toHaveBeenCalledWith(TopologyGroupTypes.NONE); + expect(props.setGroupType).toHaveBeenCalledWith(TopologyGroupTypes.none); expect(wrapper.find('li').length).toBe(0); //open dropdown and select OWNERS dropdown.at(0).simulate('click'); wrapper.find('[id="owners"]').at(0).simulate('click'); - expect(props.setGroupType).toHaveBeenCalledWith(TopologyGroupTypes.OWNERS); + expect(props.setGroupType).toHaveBeenCalledWith(TopologyGroupTypes.owners); expect(wrapper.find('li').length).toBe(0); //setGroupType should be called twice diff --git a/web/src/components/dropdowns/__tests__/layout-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/layout-dropdown.spec.tsx index 64a257f0e..ee995d8ce 100644 --- a/web/src/components/dropdowns/__tests__/layout-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/layout-dropdown.spec.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import LayoutDropdown from '../layout-dropdown'; import { LayoutName } from '../../../model/topology'; +import { LayoutDropdown } from '../layout-dropdown'; describe('', () => { const props = { - selected: LayoutName.Cola, + selected: LayoutName.cola, setLayout: jest.fn(), id: 'layout' }; @@ -37,13 +37,13 @@ describe('', () => { //open dropdown and select Dagre dropdown.at(0).simulate('click'); wrapper.find('[id="Dagre"]').at(0).simulate('click'); - expect(props.setLayout).toHaveBeenCalledWith(LayoutName.Dagre); + expect(props.setLayout).toHaveBeenCalledWith(LayoutName.dagre); expect(wrapper.find('li').length).toBe(0); //open dropdown and select Force dropdown.at(0).simulate('click'); wrapper.find('[id="Force"]').at(0).simulate('click'); - expect(props.setLayout).toHaveBeenCalledWith(LayoutName.Force); + expect(props.setLayout).toHaveBeenCalledWith(LayoutName.force); expect(wrapper.find('li').length).toBe(0); //setLayout should be called twice diff --git a/web/src/components/dropdowns/__tests__/metric-function-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/metric-function-dropdown.spec.tsx index 9ad7d412e..1ad2cbb69 100644 --- a/web/src/components/dropdowns/__tests__/metric-function-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/metric-function-dropdown.spec.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import MetricFunctionDropdown from '../metric-function-dropdown'; +import { MetricFunctionDropdown } from '../metric-function-dropdown'; describe('', () => { const props = { diff --git a/web/src/components/dropdowns/__tests__/metric-type-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/metric-type-dropdown.spec.tsx index 53f0e167e..528e6b546 100644 --- a/web/src/components/dropdowns/__tests__/metric-type-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/metric-type-dropdown.spec.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import MetricTypeDropdown from '../metric-type-dropdown'; +import { MetricTypeDropdown } from '../metric-type-dropdown'; describe('', () => { const props = { diff --git a/web/src/components/dropdowns/__tests__/query-options-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/query-options-dropdown.spec.tsx index 7cb9cff7f..0634b5937 100644 --- a/web/src/components/dropdowns/__tests__/query-options-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/query-options-dropdown.spec.tsx @@ -1,11 +1,12 @@ -import * as React from 'react'; import { Checkbox, Radio, Select } from '@patternfly/react-core'; import { shallow } from 'enzyme'; +import * as React from 'react'; import { act } from 'react-dom/test-utils'; -import QueryOptionsDropdown, { QueryOptionsDropdownProps, QueryOptionsPanel } from '../query-options-dropdown'; +import { QueryOptionsDropdown, QueryOptionsProps } from '../query-options-dropdown'; +import { QueryOptionsPanel } from '../query-options-panel'; describe('', () => { - const props: QueryOptionsDropdownProps = { + const props: QueryOptionsProps = { recordType: 'allConnections', dataSource: 'auto', showDuplicates: true, @@ -35,7 +36,7 @@ describe('', () => { }); describe('', () => { - const props: QueryOptionsDropdownProps = { + const props: QueryOptionsProps = { recordType: 'allConnections', dataSource: 'auto', showDuplicates: true, diff --git a/web/src/components/dropdowns/__tests__/refresh-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/refresh-dropdown.spec.tsx index 36d978666..381f52b36 100644 --- a/web/src/components/dropdowns/__tests__/refresh-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/refresh-dropdown.spec.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import RefreshDropdown, { RefreshDropdownProps } from '../refresh-dropdown'; +import { RefreshDropdown, RefreshDropdownProps } from '../refresh-dropdown'; describe('', () => { const props: RefreshDropdownProps = { diff --git a/web/src/components/dropdowns/__tests__/table-display-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/table-display-dropdown.spec.tsx index 1d5ee76f2..e2cdb3cb3 100644 --- a/web/src/components/dropdowns/__tests__/table-display-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/table-display-dropdown.spec.tsx @@ -3,7 +3,8 @@ import { mount, shallow } from 'enzyme'; import * as React from 'react'; import { act } from 'react-dom/test-utils'; -import { Size, TableDisplayDropdown, TableDisplayOptions } from '../table-display-dropdown'; +import { Size, TableDisplayDropdown } from '../table-display-dropdown'; +import { TableDisplayOptions } from '../table-display-options'; describe('', () => { const props = { diff --git a/web/src/components/dropdowns/__tests__/time-range-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/time-range-dropdown.spec.tsx index 167768997..d04b0f5a3 100644 --- a/web/src/components/dropdowns/__tests__/time-range-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/time-range-dropdown.spec.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import TimeRangeDropdown, { TimeRangeDropdownProps } from '../time-range-dropdown'; +import { TimeRangeDropdown, TimeRangeDropdownProps } from '../time-range-dropdown'; describe('', () => { const props: TimeRangeDropdownProps = { diff --git a/web/src/components/dropdowns/__tests__/truncate-dropdown.spec.tsx b/web/src/components/dropdowns/__tests__/truncate-dropdown.spec.tsx index 84c964474..12b664521 100644 --- a/web/src/components/dropdowns/__tests__/truncate-dropdown.spec.tsx +++ b/web/src/components/dropdowns/__tests__/truncate-dropdown.spec.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; import { TruncateDropdown, TruncateLength } from '../truncate-dropdown'; diff --git a/web/src/components/dropdowns/group-dropdown.tsx b/web/src/components/dropdowns/group-dropdown.tsx index 6cd1d036d..81d6b5825 100644 --- a/web/src/components/dropdowns/group-dropdown.tsx +++ b/web/src/components/dropdowns/group-dropdown.tsx @@ -1,57 +1,66 @@ import { Dropdown, DropdownItem, DropdownToggle } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { FlowScope } from '../../model/flow-query'; import { MetricScopeOptions } from '../../model/metrics'; import { getGroupsForScope, isGroupEnabled, TopologyGroupTypes } from '../../model/topology'; -import { FlowScope } from '../../model/flow-query'; -export const GroupDropdown: React.FC<{ +export interface GroupDropdownProps { disabled?: boolean; scope: MetricScopeOptions; selected: TopologyGroupTypes; setGroupType: (v: TopologyGroupTypes) => void; id?: string; allowedScopes: FlowScope[]; -}> = ({ disabled, scope, selected, setGroupType, id, allowedScopes }) => { +} + +export const GroupDropdown: React.FC = ({ + disabled, + scope, + selected, + setGroupType, + id, + allowedScopes +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [groupDropdownOpen, setGroupDropdownOpen] = React.useState(false); const getGroupDisplay = (groupType: TopologyGroupTypes) => { switch (groupType) { /** Clusters aggregation and groups */ - case TopologyGroupTypes.CLUSTERS: + case TopologyGroupTypes.clusters: return t('Clusters'); - case TopologyGroupTypes.CLUSTERS_HOSTS: + case TopologyGroupTypes.clustersHosts: return t('Clusters + Nodes'); - case TopologyGroupTypes.CLUSTERS_ZONES: + case TopologyGroupTypes.clustersZones: return t('Clusters + Zones'); - case TopologyGroupTypes.CLUSTERS_NAMESPACES: + case TopologyGroupTypes.clustersNamespaces: return t('Clusters + Namespaces'); - case TopologyGroupTypes.CLUSTERS_OWNERS: + case TopologyGroupTypes.clustersOwners: return t('Clusters + Owners'); /** Zones aggregation and groups */ - case TopologyGroupTypes.ZONES: + case TopologyGroupTypes.zones: return t('Zones'); - case TopologyGroupTypes.ZONES_HOSTS: + case TopologyGroupTypes.zonesHosts: return t('Zones + Nodes'); - case TopologyGroupTypes.ZONES_NAMESPACES: + case TopologyGroupTypes.zonesNamespaces: return t('Zones + Namespaces'); - case TopologyGroupTypes.ZONES_OWNERS: + case TopologyGroupTypes.zonesOwners: return t('Zones + Owners'); /** Hosts aggregation and groups */ - case TopologyGroupTypes.HOSTS: + case TopologyGroupTypes.hosts: return t('Nodes'); - case TopologyGroupTypes.HOSTS_NAMESPACES: + case TopologyGroupTypes.hostsNamespaces: return t('Nodes + Namespaces'); - case TopologyGroupTypes.HOSTS_OWNERS: + case TopologyGroupTypes.hostsOwners: return t('Nodes + Owners'); /** Namespaces aggregation and groups */ - case TopologyGroupTypes.NAMESPACES: + case TopologyGroupTypes.namespaces: return t('Namespaces'); - case TopologyGroupTypes.NAMESPACES_OWNERS: + case TopologyGroupTypes.namespacesOwners: return t('Namespaces + Owners'); /** Owner aggregation */ - case TopologyGroupTypes.OWNERS: + case TopologyGroupTypes.owners: return t('Owners'); default: return t('None'); diff --git a/web/src/components/dropdowns/layout-dropdown.tsx b/web/src/components/dropdowns/layout-dropdown.tsx index debcf3a41..a843e0fb7 100644 --- a/web/src/components/dropdowns/layout-dropdown.tsx +++ b/web/src/components/dropdowns/layout-dropdown.tsx @@ -1,36 +1,38 @@ import { Dropdown, DropdownItem, DropdownToggle } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Feature, isAllowed } from '../../utils/features-gate'; import { LayoutName } from '../../model/topology'; +import { Feature, isAllowed } from '../../utils/features-gate'; -export const LayoutDropdown: React.FC<{ +export interface LayoutDropdownProps { selected: LayoutName; setLayout: (l: LayoutName) => void; id?: string; -}> = ({ selected, setLayout, id }) => { +} + +export const LayoutDropdown: React.FC = ({ selected, setLayout, id }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [layoutDropdownOpen, setLayoutDropdownOpen] = React.useState(false); const getLayoutDisplay = (layoutName: LayoutName) => { switch (layoutName) { - case LayoutName.ThreeD: + case LayoutName.threeD: return t('3D'); - case LayoutName.BreadthFirst: + case LayoutName.breadthFirst: return t('BreadthFirst'); - case LayoutName.Cola: + case LayoutName.cola: return t('Cola'); - case LayoutName.ColaNoForce: + case LayoutName.colaNoForce: return t('ColaNoForce'); - case LayoutName.Concentric: + case LayoutName.concentric: return t('Concentric'); - case LayoutName.Dagre: + case LayoutName.dagre: return t('Dagre'); - case LayoutName.Force: + case LayoutName.force: return t('Force'); - case LayoutName.Grid: + case LayoutName.grid: return t('Grid'); - case LayoutName.ColaGroups: + case LayoutName.colaGroups: return t('ColaGroups'); default: return t('Invalid'); @@ -52,7 +54,7 @@ export const LayoutDropdown: React.FC<{ } isOpen={layoutDropdownOpen} dropdownItems={Object.values(LayoutName) - .filter(v => v != LayoutName.ThreeD || isAllowed(Feature.ThreeD)) + .filter(v => v != LayoutName.threeD || isAllowed(Feature.ThreeD)) .map(v => ( void; metricType?: MetricType; id?: string; -}> = ({ selected, setMetricFunction, metricType, id }) => { +} + +export const MetricFunctionDropdown: React.FC = ({ + selected, + setMetricFunction, + metricType, + id +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [metricDropdownOpen, setMetricDropdownOpen] = React.useState(false); @@ -19,9 +26,9 @@ export const MetricFunctionDropdown: React.FC<{ switch (metricType) { case 'DnsLatencyMs': case 'TimeFlowRttNs': - return TIME_METRIC_FUNCTIONS; + return timeMetricFunctions; default: - return RATE_METRIC_FUNCTIONS; + return rateMetricFunctions; } }, [metricType]); diff --git a/web/src/components/dropdowns/metric-type-dropdown.tsx b/web/src/components/dropdowns/metric-type-dropdown.tsx index fa61e7ae5..491ccde00 100644 --- a/web/src/components/dropdowns/metric-type-dropdown.tsx +++ b/web/src/components/dropdowns/metric-type-dropdown.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { MetricType } from '../../model/flow-query'; -export const MetricTypeDropdown: React.FC<{ +export interface MetricTypeDropdownProps { selected?: string; setMetricType: (v: MetricType) => void; isTopology?: boolean; @@ -11,7 +11,17 @@ export const MetricTypeDropdown: React.FC<{ allowDNSMetric?: boolean; allowRTTMetric?: boolean; id?: string; -}> = ({ selected, setMetricType, id, isTopology, allowPktDrop, allowDNSMetric, allowRTTMetric }) => { +} + +export const MetricTypeDropdown: React.FC = ({ + selected, + setMetricType, + id, + isTopology, + allowPktDrop, + allowDNSMetric, + allowRTTMetric +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [metricDropdownOpen, setMetricDropdownOpen] = React.useState(false); diff --git a/web/src/components/dropdowns/overview-display-dropdown.tsx b/web/src/components/dropdowns/overview-display-dropdown.tsx index 92bc74d48..a530267d9 100644 --- a/web/src/components/dropdowns/overview-display-dropdown.tsx +++ b/web/src/components/dropdowns/overview-display-dropdown.tsx @@ -1,16 +1,14 @@ -import { Select, Tooltip, Switch } from '@patternfly/react-core'; -import { InfoAltIcon } from '@patternfly/react-icons'; +import { Select, Text, TextVariants } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { FlowScope } from '../../model/flow-query'; - import './overview-display-dropdown.css'; -import ScopeDropdown from './scope-dropdown'; -import TruncateDropdown, { TruncateLength } from './truncate-dropdown'; +import { OverviewDisplayOptions } from './overview-display-options'; +import { TruncateLength } from './truncate-dropdown'; export type Size = 's' | 'm' | 'l'; -export const OverviewDisplayOptions: React.FC<{ +export interface OverviewDisplayDropdownProps { metricScope: FlowScope; setMetricScope: (s: FlowScope) => void; truncateLength: TruncateLength; @@ -18,63 +16,17 @@ export const OverviewDisplayOptions: React.FC<{ focus: boolean; setFocus: (v: boolean) => void; allowedScopes: FlowScope[]; -}> = ({ metricScope, setMetricScope, truncateLength, setTruncateLength, focus, setFocus, allowedScopes }) => { - const { t } = useTranslation('plugin__netobserv-plugin'); - - return ( - <> -
- -
- <> - {t('Scope')} - -
-
-
- -
-
-
- -
- <> - {t('Truncate labels')} - -
-
-
- -
-
-
- -
- - ); -}; +} -export const OverviewDisplayDropdown: React.FC<{ - metricScope: FlowScope; - setMetricScope: (s: FlowScope) => void; - truncateLength: TruncateLength; - setTruncateLength: (v: TruncateLength) => void; - focus: boolean; - setFocus: (v: boolean) => void; - allowedScopes: FlowScope[]; -}> = ({ metricScope, setMetricScope, truncateLength, setTruncateLength, focus, setFocus, allowedScopes }) => { +export const OverviewDisplayDropdown: React.FC = ({ + metricScope, + setMetricScope, + truncateLength, + setTruncateLength, + focus, + setFocus, + allowedScopes +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [isOpen, setOpen] = React.useState(false); @@ -82,7 +34,7 @@ export const OverviewDisplayDropdown: React.FC<{
{t('Query options')}} + placeholderText={{t('Query options')}} isOpen={isOpen} onToggle={() => setOpen(!isOpen)} customContent={} diff --git a/web/src/components/dropdowns/query-options-panel.tsx b/web/src/components/dropdowns/query-options-panel.tsx new file mode 100644 index 000000000..cd009a485 --- /dev/null +++ b/web/src/components/dropdowns/query-options-panel.tsx @@ -0,0 +1,358 @@ +import { Checkbox, Radio, Text, TextContent, TextVariants, Tooltip } from '@patternfly/react-core'; +import { InfoAltIcon } from '@patternfly/react-icons'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { DataSource, Match, PacketLoss, RecordType } from '../../model/flow-query'; +import { QueryOptionsProps } from './query-options-dropdown'; + +export const topValues = [5, 10, 15]; +export const limitValues = [50, 100, 500, 1000]; + +type RecordTypeOption = { label: string; value: RecordType }; +type DataSourceOption = { label: string; value: DataSource }; +type MatchOption = { label: string; value: Match }; + +type PacketLossOption = { label: string; value: PacketLoss }; + +// Exported for tests +export const QueryOptionsPanel: React.FC = ({ + recordType, + setRecordType, + dataSource, + setDataSource, + showDuplicates, + setShowDuplicates, + allowLoki, + allowProm, + allowFlow, + allowConnection, + allowShowDuplicates, + deduperMark, + allowPktDrops, + useTopK, + limit, + setLimit, + match, + setMatch, + packetLoss, + setPacketLoss +}) => { + const { t } = useTranslation('plugin__netobserv-plugin'); + + const recordTypeOptions: RecordTypeOption[] = [ + { + label: t('Conversation'), + value: 'allConnections' + }, + { + label: t('Flow'), + value: 'flowLog' + } + ]; + + const dataSourceOptions: DataSourceOption[] = [ + { + label: t('Loki'), + value: 'loki' + }, + { + label: t('Prometheus'), + value: 'prom' + }, + { + label: t('Auto'), + value: 'auto' + } + ]; + + const matchOptions: MatchOption[] = [ + { + label: t('Match all'), + value: 'all' + }, + { + label: t('Match any'), + value: 'any' + } + ]; + + const packetLossOptions: PacketLossOption[] = [ + { + label: t('Fully dropped'), + value: 'dropped' + }, + { + label: t('Containing drops'), + value: 'hasDrops' + }, + { + label: t('Without drops'), + value: 'sent' + }, + { + label: t('All'), + value: 'all' + } + ]; + + const values = useTopK ? topValues : limitValues; + + return ( + <> +
+ +
+ + {t('Log type')} + +
+
+ {recordTypeOptions.map(opt => { + const disabled = + (!allowFlow && opt.value === 'flowLog') || (!allowConnection && opt.value === 'allConnections'); + return ( +
+ +
+ ); + })} +
+
+ +
+ + {t('Datasource')} + +
+
+ {dataSourceOptions.map(opt => { + const disabled = (!allowProm && opt.value === 'prom') || (!allowLoki && opt.value === 'loki'); + return ( +
+ +
+ ); + })} +
+ {deduperMark && ( +
+ +
+ + {t('Duplicated flows')} + +
+
+ +
+ )} +
+ +
+ + {t('Match filters')} + +
+
+ {matchOptions.map(opt => ( +
+ +
+ ))} +
+
+ + + {t('Filter flows by their drop status. Only packets dropped by the kernel are monitored here.')} + + + - {t('Fully dropped shows the flows that are 100% dropped')} + + + - {t('Containing drops shows the flows having at least one packet dropped')} + + + - {t('Without drops show the flows having 0% dropped')} + + + - {t('All shows everything')} + + + } + > +
+ + {t('Drops filter')} + +
+
+ {packetLossOptions.map(opt => { + const disabled = !allowPktDrops && opt.value !== 'all'; + return ( +
+ +
+ ); + })} +
+
+ +
+ + {useTopK ? t('Top / Bottom') : t('Limit')} + +
+
+ {values.map(l => ( +
+ +
+ ))} +
+ + ); +}; + +export default QueryOptionsPanel; diff --git a/web/src/components/dropdowns/refresh-dropdown.tsx b/web/src/components/dropdowns/refresh-dropdown.tsx index b450d202e..417325a71 100644 --- a/web/src/components/dropdowns/refresh-dropdown.tsx +++ b/web/src/components/dropdowns/refresh-dropdown.tsx @@ -1,24 +1,24 @@ +import { Dropdown, DropdownItem, DropdownToggle } from '@patternfly/react-core'; +import * as _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Dropdown, DropdownToggle, DropdownItem } from '@patternfly/react-core'; -import * as _ from 'lodash'; -import { parseDuration, formatDuration } from '../../utils/duration'; +import { formatDuration, parseDuration } from '../../utils/duration'; -export type RefreshDropdownProps = { +export interface RefreshDropdownProps { disabled?: boolean; interval?: number; setInterval: (v?: number) => void; id?: string; -}; +} -const OFF_KEY = 'OFF_KEY'; +const offKey = 'OFF_KEY'; export const RefreshDropdown: React.FC = ({ disabled, id, interval, setInterval }) => { const [isOpen, setIsOpen] = React.useState(false); const { t } = useTranslation('plugin__netobserv-plugin'); const onChange = React.useCallback( - (v: string) => setInterval(v === OFF_KEY ? undefined : parseDuration(v)), + (v: string) => setInterval(v === offKey ? undefined : parseDuration(v)), [setInterval] ); @@ -35,7 +35,7 @@ export const RefreshDropdown: React.FC = ({ disabled, id, '1d': t('1 day') }; - const selectedKey = interval === undefined ? OFF_KEY : formatDuration(interval); + const selectedKey = interval === undefined ? offKey : formatDuration(interval); //unselect interval when dropdown is disabled React.useEffect(() => { diff --git a/web/src/components/dropdowns/scope-dropdown.tsx b/web/src/components/dropdowns/scope-dropdown.tsx index 81b856901..96baa4c2c 100644 --- a/web/src/components/dropdowns/scope-dropdown.tsx +++ b/web/src/components/dropdowns/scope-dropdown.tsx @@ -4,12 +4,14 @@ import { useTranslation } from 'react-i18next'; import { FlowScope } from '../../model/flow-query'; import { MetricScopeOptions } from '../../model/metrics'; -export const ScopeDropdown: React.FC<{ +export interface ScopeDropdownProps { selected: FlowScope; setScopeType: (v: FlowScope) => void; id?: string; allowedScopes: FlowScope[]; -}> = ({ selected, setScopeType, id, allowedScopes }) => { +} + +export const ScopeDropdown: React.FC = ({ selected, setScopeType, id, allowedScopes }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [scopeDropdownOpen, setScopeDropdownOpen] = React.useState(false); diff --git a/web/src/components/dropdowns/table-display-dropdown.tsx b/web/src/components/dropdowns/table-display-dropdown.tsx index 49baf5607..3cc449d18 100644 --- a/web/src/components/dropdowns/table-display-dropdown.tsx +++ b/web/src/components/dropdowns/table-display-dropdown.tsx @@ -1,60 +1,17 @@ -import { Radio, Select, Tooltip } from '@patternfly/react-core'; -import { InfoAltIcon } from '@patternfly/react-icons'; +import { Select, Text, TextVariants } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import * as _ from 'lodash'; import './table-display-dropdown.css'; +import { TableDisplayOptions } from './table-display-options'; export type Size = 's' | 'm' | 'l'; -export const TableDisplayOptions: React.FC<{ +export interface TableDisplayDropdownProps { size: Size; setSize: (v: Size) => void; -}> = ({ size, setSize }) => { - const { t } = useTranslation('plugin__netobserv-plugin'); - - const sizeOptions = { - s: t('Compact'), - m: t('Normal'), - l: t('Large') - }; - - return ( - <> -
- -
- <> - {t('Row size')} - -
-
- {_.map(sizeOptions, (name, key) => { - return ( -
- -
- ); - })} -
- - ); -}; +} -export const TableDisplayDropdown: React.FC<{ - size: Size; - setSize: (v: Size) => void; -}> = ({ size, setSize }) => { +export const TableDisplayDropdown: React.FC = ({ size, setSize }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [isOpen, setOpen] = React.useState(false); @@ -62,7 +19,7 @@ export const TableDisplayDropdown: React.FC<{
{t('Display options')}} + placeholderText={{t('Display options')}} isOpen={isOpen} onToggle={() => setOpen(!isOpen)} customContent={ diff --git a/web/src/components/dropdowns/topology-display-options.tsx b/web/src/components/dropdowns/topology-display-options.tsx new file mode 100644 index 000000000..dfb47294a --- /dev/null +++ b/web/src/components/dropdowns/topology-display-options.tsx @@ -0,0 +1,238 @@ +import { Checkbox, Flex, FlexItem, Switch, Text, TextVariants, Tooltip } from '@patternfly/react-core'; +import { InfoAltIcon } from '@patternfly/react-icons'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { FlowScope, MetricType, StatFunction } from '../../model/flow-query'; +import { MetricScopeOptions } from '../../model/metrics'; +import { LayoutName, TopologyGroupTypes, TopologyOptions } from '../../model/topology'; +import GroupDropdown from './group-dropdown'; +import LayoutDropdown from './layout-dropdown'; +import TruncateDropdown, { TruncateLength } from './truncate-dropdown'; + +import MetricFunctionDropdown from './metric-function-dropdown'; +import MetricTypeDropdown from './metric-type-dropdown'; +import ScopeDropdown from './scope-dropdown'; + +export type Size = 's' | 'm' | 'l'; + +export interface TopologyDisplayOptionsProps { + metricFunction: StatFunction; + setMetricFunction: (f: StatFunction) => void; + metricType: MetricType; + setMetricType: (t: MetricType) => void; + metricScope: FlowScope; + setMetricScope: (s: FlowScope) => void; + topologyOptions: TopologyOptions; + setTopologyOptions: (o: TopologyOptions) => void; + allowPktDrop: boolean; + allowDNSMetric: boolean; + allowRTTMetric: boolean; + allowedScopes: FlowScope[]; +} + +export const TopologyDisplayOptions: React.FC = ({ + metricFunction, + setMetricFunction, + metricType, + setMetricType, + metricScope, + setMetricScope, + topologyOptions, + setTopologyOptions, + allowPktDrop, + allowDNSMetric, + allowRTTMetric, + allowedScopes +}) => { + const { t } = useTranslation('plugin__netobserv-plugin'); + + const setLayout = (layout: LayoutName) => { + setTopologyOptions({ + ...topologyOptions, + layout + }); + }; + + const setGroupType = (groupTypes: TopologyGroupTypes) => { + setTopologyOptions({ + ...topologyOptions, + groupTypes + }); + }; + + const setTruncateLength = (truncateLength: TruncateLength) => { + setTopologyOptions({ + ...topologyOptions, + truncateLength + }); + }; + + return ( + <> +
+ +
+ + {t('Edge labels')} + +
+
+
+ + + + + + + + +
+
+
+ +
+ + {t('Scope')} + +
+
+
+ +
+
+
+ +
+ + {t('Groups')} + +
+
+
+ +
+
+
+ +
+ + {t('Layout')} + +
+
+
+ +
+
+
+ +
+ + {t('Show')} + +
+
+ + setTopologyOptions({ + ...topologyOptions, + edges: !topologyOptions.edges + }) + } + /> + + setTopologyOptions({ + ...topologyOptions, + edgeTags: !topologyOptions.edgeTags + }) + } + /> + + setTopologyOptions({ + ...topologyOptions, + nodeBadges: !topologyOptions.nodeBadges + }) + } + /> +
+
+ +
+ + {t('Truncate labels')} + +
+
+
+ +
+
+
+
+ + setTopologyOptions({ + ...topologyOptions, + startCollapsed: !topologyOptions.startCollapsed + }) + } + isReversed + /> +
+
+ + ); +}; + +export default TopologyDisplayOptions; diff --git a/web/src/components/dropdowns/truncate-dropdown.tsx b/web/src/components/dropdowns/truncate-dropdown.tsx index c2c3ddfdd..4bef71fa1 100644 --- a/web/src/components/dropdowns/truncate-dropdown.tsx +++ b/web/src/components/dropdowns/truncate-dropdown.tsx @@ -11,11 +11,13 @@ export enum TruncateLength { XL = 40 } -export const TruncateDropdown: React.FC<{ +export interface TruncateDropdownProps { selected: TruncateLength; setTruncateLength: (v: TruncateLength) => void; id?: string; -}> = ({ selected, setTruncateLength, id }) => { +} + +export const TruncateDropdown: React.FC = ({ selected, setTruncateLength, id }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [truncateDropdownOpen, setTruncateDropdownOpen] = React.useState(false); diff --git a/web/src/components/filters/__tests__/compare-filter.spec.tsx b/web/src/components/filters/__tests__/compare-filter.spec.tsx index c3fe08389..a519d9783 100644 --- a/web/src/components/filters/__tests__/compare-filter.spec.tsx +++ b/web/src/components/filters/__tests__/compare-filter.spec.tsx @@ -4,7 +4,7 @@ import CompareFilter, { CompareFilterProps, FilterCompare } from '../compare-fil describe('', () => { const props: CompareFilterProps = { - value: FilterCompare.EQUAL, + value: FilterCompare.equal, setValue: jest.fn(), component: 'text' }; @@ -23,27 +23,27 @@ describe('', () => { // No initial call expect(props.setValue).toHaveBeenCalledTimes(0); - //open dropdown and select NOT EQUAL + //open dropdown and select not equal dropdownToggleButton.last().simulate('click'); wrapper.find('[id="not-equal"]').last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.NOT_EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.notEqual); expect(wrapper.find('li').length).toBe(0); //open dropdown and select EQUAL dropdownToggleButton.last().simulate('click'); wrapper.find('[id="equal"]').last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.equal); expect(wrapper.find('li').length).toBe(0); - //open dropdown and check for MORE_THAN_OR_EQUAL + //open dropdown and check for more than or equal dropdownToggleButton.last().simulate('click'); expect(wrapper.find('[id="more-than"]').length).toBe(0); //switch directly switchButton.last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.NOT_EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.notEqual); switchButton.last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.equal); //setState should be called 3 times expect(props.setValue).toHaveBeenCalledTimes(4); @@ -54,18 +54,18 @@ describe('', () => { const switchButton = wrapper.find('#filter-compare-switch-button').last(); const dropdownToggleButton = wrapper.find('#filter-compare-toggle-button').last(); - //open dropdown and select MORE_THAN_OR_EQUAL + //open dropdown and select more than or equal dropdownToggleButton.last().simulate('click'); wrapper.find('[id="more-than"]').last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.MORE_THAN_OR_EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.moreThanOrEqual); expect(wrapper.find('li').length).toBe(0); //switch directly switchButton.last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.NOT_EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.notEqual); switchButton.last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.MORE_THAN_OR_EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.moreThanOrEqual); switchButton.last().simulate('click'); - expect(props.setValue).toHaveBeenCalledWith(FilterCompare.EQUAL); + expect(props.setValue).toHaveBeenCalledWith(FilterCompare.equal); }); }); diff --git a/web/src/components/filters/autocomplete-filter.tsx b/web/src/components/filters/autocomplete-filter.tsx index 71fc80e5e..95346e79f 100644 --- a/web/src/components/filters/autocomplete-filter.tsx +++ b/web/src/components/filters/autocomplete-filter.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; -import * as _ from 'lodash'; import { Button, Menu, @@ -10,11 +8,13 @@ import { TextInput, ValidatedOptions } from '@patternfly/react-core'; -import { SearchIcon, CaretDownIcon } from '@patternfly/react-icons'; +import { CaretDownIcon, SearchIcon } from '@patternfly/react-icons'; +import * as _ from 'lodash'; +import * as React from 'react'; import { createFilterValue, FilterDefinition, FilterOption, FilterValue } from '../../model/filters'; -import { getHTTPErrorDetails } from '../../utils/errors'; import { autoCompleteCache } from '../../utils/autocomplete-cache'; -import { Indicator } from './filters-helper'; +import { getHTTPErrorDetails } from '../../utils/errors'; +import { Indicator } from '../../utils/filters-helper'; import { usePrevious } from '../../utils/previous-hook'; import './autocomplete-filter.css'; diff --git a/web/src/components/filters/compare-filter.tsx b/web/src/components/filters/compare-filter.tsx index 50e132e08..435e5b4f4 100644 --- a/web/src/components/filters/compare-filter.tsx +++ b/web/src/components/filters/compare-filter.tsx @@ -1,13 +1,13 @@ import { Dropdown, DropdownItem, DropdownToggle, DropdownToggleAction } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { usePrevious } from '../../utils/previous-hook'; import { FilterComponent } from '../../model/filters'; +import { usePrevious } from '../../utils/previous-hook'; export enum FilterCompare { - EQUAL = 1, - NOT_EQUAL, - MORE_THAN_OR_EQUAL + equal = 1, + notEqual, + moreThanOrEqual } export interface CompareFilterProps { value: FilterCompare; @@ -21,10 +21,10 @@ export const CompareFilter: React.FC = ({ value, setValue, c const prevComponent = usePrevious(component); const dropdownItems = [ - onSelect(FilterCompare.EQUAL)}> + onSelect(FilterCompare.equal)}> {t('Equals')} , - onSelect(FilterCompare.NOT_EQUAL)}> + onSelect(FilterCompare.notEqual)}> {t('Not equals')} ]; @@ -35,7 +35,7 @@ export const CompareFilter: React.FC = ({ value, setValue, c key="more-than" id="more-than" component="button" - onClick={() => onSelect(FilterCompare.MORE_THAN_OR_EQUAL)} + onClick={() => onSelect(FilterCompare.moreThanOrEqual)} > {t('More than')} @@ -52,9 +52,9 @@ export const CompareFilter: React.FC = ({ value, setValue, c }; const onSwitch = React.useCallback(() => { - const filterCompareValues = [FilterCompare.EQUAL, FilterCompare.NOT_EQUAL]; + const filterCompareValues = [FilterCompare.equal, FilterCompare.notEqual]; if (component === 'number') { - filterCompareValues.push(FilterCompare.MORE_THAN_OR_EQUAL); + filterCompareValues.push(FilterCompare.moreThanOrEqual); } const nextIndex = filterCompareValues.indexOf(value) + 1; @@ -67,11 +67,11 @@ export const CompareFilter: React.FC = ({ value, setValue, c const getSymbol = React.useCallback(() => { switch (value) { - case FilterCompare.NOT_EQUAL: + case FilterCompare.notEqual: return '!='; - case FilterCompare.MORE_THAN_OR_EQUAL: + case FilterCompare.moreThanOrEqual: return '>='; - case FilterCompare.EQUAL: + case FilterCompare.equal: default: return '='; } @@ -80,7 +80,7 @@ export const CompareFilter: React.FC = ({ value, setValue, c React.useEffect(() => { // reset to equal when component change if (prevComponent !== undefined && prevComponent !== component) { - setValue(FilterCompare.EQUAL); + setValue(FilterCompare.equal); } }, [component, prevComponent, setValue]); diff --git a/web/src/components/filters/filter-hints.tsx b/web/src/components/filters/filter-hints.tsx index 7a8b87cc7..90f7bd8f7 100644 --- a/web/src/components/filters/filter-hints.tsx +++ b/web/src/components/filters/filter-hints.tsx @@ -1,10 +1,10 @@ +import { Button, Popover, Text, TextVariants } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Popover, Text, TextVariants } from '@patternfly/react-core'; -import { FilterDefinition } from '../../model/filters'; import { Link } from 'react-router-dom'; +import { FilterDefinition } from '../../model/filters'; -interface FilterHintsProps { +export interface FilterHintsProps { def: FilterDefinition; } diff --git a/web/src/components/filters/filters-chips.tsx b/web/src/components/filters/filters-chips.tsx index b3b38c2e4..0fe46b718 100644 --- a/web/src/components/filters/filters-chips.tsx +++ b/web/src/components/filters/filters-chips.tsx @@ -1,9 +1,8 @@ -import { Button, Text, TextVariants, ToolbarGroup, ToolbarItem, Tooltip } from '@patternfly/react-core'; -import { TimesIcon, TimesCircleIcon, LongArrowAltDownIcon, LongArrowAltUpIcon } from '@patternfly/react-icons'; +import { Button, Text, TextContent, TextVariants, ToolbarGroup, ToolbarItem, Tooltip } from '@patternfly/react-core'; +import { LongArrowAltDownIcon, LongArrowAltUpIcon, TimesCircleIcon, TimesIcon } from '@patternfly/react-icons'; import * as _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { navigate } from '../dynamic-loader/dynamic-loader'; import { Filter, FilterDefinition, @@ -14,8 +13,9 @@ import { } from '../../model/filters'; import { QuickFilter } from '../../model/quick-filters'; import { autoCompleteCache } from '../../utils/autocomplete-cache'; +import { getFilterFullName, hasSrcDstFilters, swapFilters } from '../../utils/filters-helper'; import { getPathWithParams, netflowTrafficPath } from '../../utils/url'; -import { hasSrcDstFilters, getFilterFullName, swapFilters } from './filters-helper'; +import { navigate } from '../dynamic-loader/dynamic-loader'; import { LinksOverflow } from '../overflow/links-overflow'; export interface FiltersChipsProps { @@ -196,15 +196,15 @@ export const FiltersChips: React.FC = ({ ), tooltip: ( -
-
{`${t('Switch between one way / back and forth filtering')}:`}
-
{`- ${t( - 'One way shows traffic strictly as defined per your filters' - )}`}
-
{`- ${t( - 'Back and forth shows traffic according to your filters, plus the related return traffic' - )}`}
-
+ + {t('Switch between one way / back and forth filtering')} + + - {t('One way shows traffic strictly as defined per your filters')} + + + - {t('Back and forth shows traffic according to your filters, plus the related return traffic')} + + ), enabled: isSrcDst } diff --git a/web/src/components/filters/filters-dropdown.tsx b/web/src/components/filters/filters-dropdown.tsx index 7dc337d6c..2c4712ac1 100644 --- a/web/src/components/filters/filters-dropdown.tsx +++ b/web/src/components/filters/filters-dropdown.tsx @@ -10,9 +10,9 @@ import { import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { FilterDefinition } from '../../model/filters'; -import { buildGroups, getFilterFullName } from './filters-helper'; +import { buildGroups, getFilterFullName } from '../../utils/filters-helper'; -interface FiltersDropdownProps { +export interface FiltersDropdownProps { filterDefinitions: FilterDefinition[]; selectedFilter: FilterDefinition; setSelectedFilter: (f: FilterDefinition) => void; diff --git a/web/src/components/filters/filters-toolbar.css b/web/src/components/filters/filters-toolbar.css index d87eec629..154e7a206 100644 --- a/web/src/components/filters/filters-toolbar.css +++ b/web/src/components/filters/filters-toolbar.css @@ -44,13 +44,6 @@ button.pf-c-button.pf-m-link.pf-m-inline:empty { width: 260px; } -/* align tips baseline */ -#tips { - display: flex; - flex-direction: row; - align-items: baseline; -} - /* stick "Learn more" text */ #more { padding: 5px 0px 0px 5px; diff --git a/web/src/components/filters/filters-toolbar.tsx b/web/src/components/filters/filters-toolbar.tsx index ec10bd96b..1d3eaefdb 100644 --- a/web/src/components/filters/filters-toolbar.tsx +++ b/web/src/components/filters/filters-toolbar.tsx @@ -11,21 +11,21 @@ import { CompressIcon, ExpandIcon } from '@patternfly/react-icons'; import * as _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Filter, FilterDefinition, FilterValue, Filters, findFromFilters } from '../../model/filters'; +import { Filter, FilterDefinition, Filters, FilterValue, findFromFilters } from '../../model/filters'; import { QuickFilter } from '../../model/quick-filters'; import { findFilter } from '../../utils/filter-definitions'; -import { QueryOptionsDropdown, QueryOptionsDropdownProps } from '../dropdowns/query-options-dropdown'; -import { QuickFilters } from './quick-filters'; +import { Indicator } from '../../utils/filters-helper'; +import { localStorageShowFiltersKey, useLocalStorage } from '../../utils/local-storage-hook'; +import { QueryOptionsDropdown, QueryOptionsProps } from '../dropdowns/query-options-dropdown'; +import { LinksOverflow } from '../overflow/links-overflow'; import AutocompleteFilter from './autocomplete-filter'; +import CompareFilter, { FilterCompare } from './compare-filter'; import { FilterHints } from './filter-hints'; -import FiltersDropdown from './filters-dropdown'; -import { Indicator } from './filters-helper'; -import TextFilter from './text-filter'; -import { LOCAL_STORAGE_SHOW_FILTERS_KEY, useLocalStorage } from '../../utils/local-storage-hook'; import { FiltersChips } from './filters-chips'; -import CompareFilter, { FilterCompare } from './compare-filter'; -import { LinksOverflow } from '../overflow/links-overflow'; +import FiltersDropdown from './filters-dropdown'; import './filters-toolbar.css'; +import { QuickFilters } from './quick-filters'; +import TextFilter from './text-filter'; export interface FiltersToolbarProps { id: string; @@ -35,7 +35,7 @@ export interface FiltersToolbarProps { setFilters: (v: Filters) => void; clearFilters: () => void; resetFilters: () => void; - queryOptionsProps: QueryOptionsDropdownProps; + queryOptionsProps: QueryOptionsProps; quickFilters: QuickFilter[]; filterDefinitions: FilterDefinition[]; isFullScreen: boolean; @@ -62,8 +62,8 @@ export const FiltersToolbar: React.FC = ({ const [selectedFilter, setSelectedFilter] = React.useState( findFilter(filterDefinitions, 'src_namespace')! ); - const [selectedCompare, setSelectedCompare] = React.useState(FilterCompare.EQUAL); - const [showFilters, setShowFilters] = useLocalStorage(LOCAL_STORAGE_SHOW_FILTERS_KEY, true); + const [selectedCompare, setSelectedCompare] = React.useState(FilterCompare.equal); + const [showFilters, setShowFilters] = useLocalStorage(localStorageShowFiltersKey, true); // reset and delay message state to trigger tooltip properly const setMessageWithDelay = React.useCallback( @@ -90,8 +90,8 @@ export const FiltersToolbar: React.FC = ({ const addFilter = React.useCallback( (filterValue: FilterValue) => { const newFilters = _.cloneDeep(filters?.list) || []; - const not = selectedCompare === FilterCompare.NOT_EQUAL; - const moreThan = selectedCompare === FilterCompare.MORE_THAN_OR_EQUAL; + const not = selectedCompare === FilterCompare.notEqual; + const moreThan = selectedCompare === FilterCompare.moreThanOrEqual; const found = findFromFilters(newFilters, { def: selectedFilter, not, moreThan }); if (found) { if (found.values.map(value => value.v).includes(filterValue.v)) { diff --git a/web/src/components/filters/quick-filters.tsx b/web/src/components/filters/quick-filters.tsx index 8674a2afb..21ac1be0e 100644 --- a/web/src/components/filters/quick-filters.tsx +++ b/web/src/components/filters/quick-filters.tsx @@ -1,10 +1,10 @@ -import * as React from 'react'; -import _ from 'lodash'; import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; -import { QuickFilter } from '../../model/quick-filters'; -import { doesIncludeFilter, Filter, findFromFilters, removeFromFilters } from '../../model/filters'; +import _ from 'lodash'; +import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { doesIncludeFilter, Filter, findFromFilters, removeFromFilters } from '../../model/filters'; +import { QuickFilter } from '../../model/quick-filters'; export interface QuickFiltersProps { quickFilters: QuickFilter[]; diff --git a/web/src/components/filters/summary-filter-button.tsx b/web/src/components/filters/summary-filter-button.tsx index b3de8e978..31d435af7 100644 --- a/web/src/components/filters/summary-filter-button.tsx +++ b/web/src/components/filters/summary-filter-button.tsx @@ -1,11 +1,11 @@ -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; import { Checkbox, OptionsMenu, OptionsMenuItem, OptionsMenuPosition, OptionsMenuToggle } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; -import { Filter, FilterDefinition } from '../../model/filters'; -import { FilterDir, isDirElementFiltered, toggleDirElementFilter } from '../../model/topology'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { TopologyMetricPeer } from '../../api/loki'; +import { Filter, FilterDefinition } from '../../model/filters'; import { NodeType } from '../../model/flow-query'; +import { FilterDir, isDirElementFiltered, toggleDirElementFilter } from '../../model/topology'; import './summary-filter-button.css'; export interface SummaryFilterButtonProps { diff --git a/web/src/components/filters/text-filter.tsx b/web/src/components/filters/text-filter.tsx index cf64bb86a..4b3d7e08f 100644 --- a/web/src/components/filters/text-filter.tsx +++ b/web/src/components/filters/text-filter.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; -import * as _ from 'lodash'; import { Button, TextInput, ValidatedOptions } from '@patternfly/react-core'; import { SearchIcon } from '@patternfly/react-icons'; +import * as _ from 'lodash'; +import * as React from 'react'; import { createFilterValue, FilterDefinition, FilterValue } from '../../model/filters'; -import { Indicator } from './filters-helper'; +import { Indicator } from '../../utils/filters-helper'; export interface TextFilterProps { filterDefinition: FilterDefinition; diff --git a/web/src/components/guided-tour/guided-tour.tsx b/web/src/components/guided-tour/guided-tour.tsx index 1b8eebfe5..b6b37eab8 100644 --- a/web/src/components/guided-tour/guided-tour.tsx +++ b/web/src/components/guided-tour/guided-tour.tsx @@ -24,157 +24,161 @@ export type GuidedTourHandle = { clearOnIndexChangeListener: () => void; }; -const onIndexChangeFunctions: IndexChangeFunction[] = []; -export const GuidedTourPopover: React.FC<{ +export interface GuidedTourPopoverProps { id: string; ref?: React.Ref; isDark: boolean; - // eslint-disable-next-line react/display-name -}> = React.forwardRef((props, ref: React.Ref) => { - const { t } = useTranslation('plugin__netobserv-plugin'); - const isStandalone = ContextSingleton.isStandalone(); - const { highlightElement, clearHighlights } = useHighLight(props.isDark); - const [items, setItems] = React.useState([]); - const [index, setIndex] = React.useState(); - - React.useImperativeHandle(ref, () => ({ - startTour, - updateTourItems, - addOnIndexChangeListener, - clearOnIndexChangeListener - })); - - const startTour = () => { - setIndex(0); - if (!items.length) { - console.error('startTour called while items = ', items); - } - }; +} - const updateTourItems = (items: GuidedTourItem[]) => { - setItems(items); +const onIndexChangeFunctions: IndexChangeFunction[] = []; +// eslint-disable-next-line react/display-name +export const GuidedTourPopover: React.FC = React.forwardRef( + (props, ref: React.Ref) => { + const { t } = useTranslation('plugin__netobserv-plugin'); + const isStandalone = ContextSingleton.isStandalone(); + const { highlightElement, clearHighlights } = useHighLight(props.isDark); + const [items, setItems] = React.useState([]); + const [index, setIndex] = React.useState(); + + React.useImperativeHandle(ref, () => ({ + startTour, + updateTourItems, + addOnIndexChangeListener, + clearOnIndexChangeListener + })); + + const startTour = () => { + setIndex(0); + if (!items.length) { + console.error('startTour called while items = ', items); + } + }; - if (!items.length) { - console.error('updateTourItems called while items = ', items); - } - }; - - const addOnIndexChangeListener = (fn: IndexChangeFunction) => { - onIndexChangeFunctions.push(fn); - }; - - const clearOnIndexChangeListener = () => { - onIndexChangeFunctions.splice(0); - }; - - const clearIndex = React.useCallback(() => { - setIndex(undefined); - clearHighlights(); - }, [clearHighlights]); - - const previous = React.useCallback(() => { - if (index) { - setIndex(index - 1); - } else { - clearIndex(); - } - }, [clearIndex, index]); + const updateTourItems = (items: GuidedTourItem[]) => { + setItems(items); - const hasNext = React.useCallback(() => { - return index != undefined && index < items.length - 1; - }, [index, items.length]); + if (!items.length) { + console.error('updateTourItems called while items = ', items); + } + }; - const next = React.useCallback(() => { - if (hasNext()) { - setIndex(index! + 1); - } else { - clearIndex(); - } - }, [clearIndex, hasNext, index]); + const addOnIndexChangeListener = (fn: IndexChangeFunction) => { + onIndexChangeFunctions.push(fn); + }; - const handleHighlights = React.useCallback(() => { - if (index != undefined && items.length > index && items[index].ref.current) { - highlightElement(items[index].ref.current!); - } else { - clearHighlights(); - } - }, [clearHighlights, highlightElement, index, items]); + const clearOnIndexChangeListener = () => { + onIndexChangeFunctions.splice(0); + }; - React.useEffect(() => { - onIndexChangeFunctions.forEach(fn => { - fn(index); - }); + const clearIndex = React.useCallback(() => { + setIndex(undefined); + clearHighlights(); + }, [clearHighlights]); - handleHighlights(); - }, [highlightElement, index, items, clearHighlights, handleHighlights]); + const previous = React.useCallback(() => { + if (index) { + setIndex(index - 1); + } else { + clearIndex(); + } + }, [clearIndex, index]); - React.useEffect(() => { - window.addEventListener('resize', handleHighlights); - return () => { - window.removeEventListener('resize', handleHighlights); - }; - }, [handleHighlights]); + const hasNext = React.useCallback(() => { + return index != undefined && index < items.length - 1; + }, [index, items.length]); - let currentItem = undefined; - if (index !== undefined) { - currentItem = items[index]; - } - return currentItem ? ( - - {currentItem!.title} - - - - + const next = React.useCallback(() => { + if (hasNext()) { + setIndex(index! + 1); + } else { + clearIndex(); } - bodyContent={ - - {currentItem!.description} - - {currentItem!.assetName && ( - - )} - - + }, [clearIndex, hasNext, index]); + + const handleHighlights = React.useCallback(() => { + if (index != undefined && items.length > index && items[index].ref.current) { + highlightElement(items[index].ref.current!); + } else { + clearHighlights(); } - footerContent={ - - - - {t('Step {{index}}/{{count}}', { index: (index || 0) + 1, count: items.length })} - - - - {index ? ( - - ) : ( - <> - )} - - - - - - } - reference={currentItem!.ref} - /> - ) : null; -}); + + + } + bodyContent={ + + {currentItem!.description} + + {currentItem!.assetName && ( + + )} + + + } + footerContent={ + + + + {t('Step {{index}}/{{count}}', { index: (index || 0) + 1, count: items.length })} + + + + {index ? ( + + ) : ( + <> + )} + + + + + + } + reference={currentItem!.ref} + /> + ) : null; + } +); export default GuidedTourPopover; diff --git a/web/src/components/messages/error.tsx b/web/src/components/messages/error.tsx index 7c0f84f1b..b98b204cd 100644 --- a/web/src/components/messages/error.tsx +++ b/web/src/components/messages/error.tsx @@ -32,13 +32,13 @@ enum LokiInfo { Limits } -type Props = { +export interface ErrorProps { title: string; error: string; isLokiRelated: boolean; -}; +} -export const Error: React.FC = ({ title, error, isLokiRelated }) => { +export const Error: React.FC = ({ title, error, isLokiRelated }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [loading, setLoading] = React.useState(isLokiRelated); const [ready, setReady] = React.useState(); diff --git a/web/src/components/metrics/__tests__/metrics-donut.spec.tsx b/web/src/components/metrics/__tests__/metrics-donut.spec.tsx index b3b5c6e37..e093618a5 100644 --- a/web/src/components/metrics/__tests__/metrics-donut.spec.tsx +++ b/web/src/components/metrics/__tests__/metrics-donut.spec.tsx @@ -2,9 +2,9 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { ChartDonut } from '@patternfly/react-charts'; +import { NamedMetric } from '../../../api/loki'; import { metrics } from '../../__tests-data__/metrics'; import { MetricsDonut, MetricsDonutProps } from '../metrics-donut'; -import { NamedMetric } from '../../../api/loki'; describe('', () => { const props: MetricsDonutProps = { diff --git a/web/src/components/metrics/__tests__/metrics-graph.spec.tsx b/web/src/components/metrics/__tests__/metrics-graph.spec.tsx index 7db43b991..8a64c8494 100644 --- a/web/src/components/metrics/__tests__/metrics-graph.spec.tsx +++ b/web/src/components/metrics/__tests__/metrics-graph.spec.tsx @@ -1,7 +1,7 @@ import { mount } from 'enzyme'; import * as React from 'react'; -import { Chart, ChartDonut, ChartBar, ChartArea, ChartScatter, ChartGroup } from '@patternfly/react-charts'; +import { Chart, ChartArea, ChartBar, ChartDonut, ChartGroup, ChartScatter } from '@patternfly/react-charts'; import { metrics } from '../../__tests-data__/metrics'; import { MetricsGraph, MetricsGraphProps } from '../metrics-graph'; diff --git a/web/src/components/metrics/brush-handle.tsx b/web/src/components/metrics/brush-handle.tsx index 023e39bed..4be613ef9 100644 --- a/web/src/components/metrics/brush-handle.tsx +++ b/web/src/components/metrics/brush-handle.tsx @@ -1,12 +1,14 @@ import * as React from 'react'; -export const BrushHandleComponent: React.FC<{ +export interface BrushHandleComponentProps { x?: number; y?: number; width?: number; height?: number; isDark?: boolean; -}> = ({ x, y, width, height, isDark }) => { +} + +export const BrushHandleComponent: React.FC = ({ x, y, width, height, isDark }) => { if (x === undefined || y === undefined || width === undefined || height === undefined) { return null; } diff --git a/web/src/components/metrics/chart-voronoi.tsx b/web/src/components/metrics/chart-voronoi.tsx new file mode 100644 index 000000000..27af57611 --- /dev/null +++ b/web/src/components/metrics/chart-voronoi.tsx @@ -0,0 +1,20 @@ +import { ChartLegendTooltip, createContainer } from '@patternfly/react-charts'; +import React from 'react'; +import { ChartDataPoint, LegendDataItem } from '../../utils/metrics-helper'; + +export const chartVoronoi = (legendData: LegendDataItem[], f: (v: number) => string) => { + const CursorVoronoiContainer = createContainer('voronoi', 'cursor'); + const tooltipData = legendData.map(item => ({ ...item, name: item.tooltipName || item.name })); + return ( + { + return dp.datum.y || dp.datum.y === 0 ? f(dp.datum.y) : 'n/a'; + }} + labelComponent={ datum.date} />} + mouseFollowTooltips + voronoiDimension="x" + voronoiPadding={50} + /> + ); +}; diff --git a/web/src/components/metrics/histogram.tsx b/web/src/components/metrics/histogram.tsx index ebd037390..44d687931 100644 --- a/web/src/components/metrics/histogram.tsx +++ b/web/src/components/metrics/histogram.tsx @@ -27,12 +27,8 @@ import { useTranslation } from 'react-i18next'; import { NamedMetric, TopologyMetrics } from '../../api/loki'; import { TimeRange } from '../../utils/datetime'; import { getDateMsInSeconds } from '../../utils/duration'; -import { LOCAL_STORAGE_HISTOGRAM_GUIDED_TOUR_DONE_KEY, useLocalStorage } from '../../utils/local-storage-hook'; +import { localStorageHistogramGuidedTourDoneKey, useLocalStorage } from '../../utils/local-storage-hook'; import { getFormattedValue } from '../../utils/metrics'; -import { TruncateLength } from '../dropdowns/truncate-dropdown'; -import { GuidedTourHandle } from '../guided-tour/guided-tour'; -import BrushHandleComponent from './brush-handle'; -import './histogram.css'; import { ChartDataPoint, Dimensions, @@ -42,11 +38,15 @@ import { observeDimensions, toHistogramDatapoints, toNamedMetric -} from './metrics-helper'; +} from '../../utils/metrics-helper'; +import { TruncateLength } from '../dropdowns/truncate-dropdown'; +import { GuidedTourHandle } from '../guided-tour/guided-tour'; +import BrushHandleComponent from './brush-handle'; +import './histogram.css'; export const VoronoiContainer = createContainer('voronoi', 'brush'); -export const Histogram: React.FC<{ +export interface HistogramProps { id: string; loading: boolean; totalMetric: NamedMetric; @@ -57,7 +57,20 @@ export const Histogram: React.FC<{ setRange: (tr: TimeRange) => void; moveRange: (next: boolean) => void; zoomRange: (zoom: boolean) => void; -}> = ({ id, loading, totalMetric, limit, isDark, range, guidedTourHandle, setRange, moveRange, zoomRange }) => { +} + +export const Histogram: React.FC = ({ + id, + loading, + totalMetric, + limit, + isDark, + range, + guidedTourHandle, + setRange, + moveRange, + zoomRange +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const datapoints: ChartDataPoint[] = toHistogramDatapoints(totalMetric); @@ -170,7 +183,7 @@ export const Histogram: React.FC<{ ); }, [t]); - const [guidedTourDone, setGuidedTourDone] = useLocalStorage(LOCAL_STORAGE_HISTOGRAM_GUIDED_TOUR_DONE_KEY); + const [guidedTourDone, setGuidedTourDone] = useLocalStorage(localStorageHistogramGuidedTourDoneKey); React.useEffect(() => { if (!guidedTourHandle) { return; diff --git a/web/src/components/metrics/metrics-content.css b/web/src/components/metrics/metrics-content.css index 76a8a45c5..d72ef16b3 100644 --- a/web/src/components/metrics/metrics-content.css +++ b/web/src/components/metrics/metrics-content.css @@ -3,19 +3,6 @@ height: 100%; } -.empty-metrics-content-div { - width: 100%; - height: 100%; - min-height: 320px; - display: flex; - align-items: center; -} - -.empty-metrics-content-div>span { - flex: 1; - text-align: center; -} - .metrics-content-div.loading, .metrics-content-button.loading { cursor: wait !important; diff --git a/web/src/components/metrics/metrics-donut.tsx b/web/src/components/metrics/metrics-donut.tsx index 19f7d62c2..9bf7d3b78 100644 --- a/web/src/components/metrics/metrics-donut.tsx +++ b/web/src/components/metrics/metrics-donut.tsx @@ -2,14 +2,14 @@ import { ChartDonut, ChartLabel, ChartLegend, ChartThemeColor } from '@patternfl import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { GenericMetric, MetricStats, NamedMetric } from '../../api/loki'; -import { MetricType, MetricFunction } from '../../model/flow-query'; +import { MetricFunction, MetricType } from '../../model/flow-query'; import { getStat } from '../../model/metrics'; -import { LOCAL_STORAGE_OVERVIEW_DONUT_DIMENSION_KEY, useLocalStorage } from '../../utils/local-storage-hook'; +import { localStorageOverviewDonutDimensionKey, useLocalStorage } from '../../utils/local-storage-hook'; import { getFormattedValue, isUnknownPeer } from '../../utils/metrics'; +import { defaultDimensions, Dimensions, observeDimensions } from '../../utils/metrics-helper'; import './metrics-content.css'; -import { defaultDimensions, Dimensions, observeDimensions } from './metrics-helper'; -export type MetricsDonutProps = { +export interface MetricsDonutProps { id: string; subTitle?: string; limit: number; @@ -26,7 +26,7 @@ export type MetricsDonutProps = { showLegend?: boolean; animate?: boolean; isDark?: boolean; -}; +} export const MetricsDonut: React.FC = ({ id, @@ -124,7 +124,7 @@ export const MetricsDonut: React.FC = ({ const containerRef = React.createRef(); const [dimensions, setDimensions] = useLocalStorage( - `${LOCAL_STORAGE_OVERVIEW_DONUT_DIMENSION_KEY}${showLegend ? '-legend' : ''}`, + `${localStorageOverviewDonutDimensionKey}${showLegend ? '-legend' : ''}`, defaultDimensions ); React.useEffect(() => { diff --git a/web/src/components/metrics/metrics-graph-total.tsx b/web/src/components/metrics/metrics-graph-total.tsx index 053afbaa3..0ff946675 100644 --- a/web/src/components/metrics/metrics-graph-total.tsx +++ b/web/src/components/metrics/metrics-graph-total.tsx @@ -15,21 +15,21 @@ import { TextContent } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { GenericMetric, NamedMetric } from '../../api/loki'; -import { MetricType, MetricFunction } from '../../model/flow-query'; -import { LOCAL_STORAGE_OVERVIEW_METRICS_TOTAL_DIMENSION_KEY, useLocalStorage } from '../../utils/local-storage-hook'; +import { MetricFunction, MetricType } from '../../model/flow-query'; +import { localStorageOverviewMetricsTotalDimensionKey, useLocalStorage } from '../../utils/local-storage-hook'; import { getFormattedValue, isUnknownPeer } from '../../utils/metrics'; -import './metrics-content.css'; import { ChartDataPoint, - chartVoronoi, defaultDimensions, Dimensions, LegendDataItem, observeDimensions, toDatapoints -} from './metrics-helper'; +} from '../../utils/metrics-helper'; +import { chartVoronoi } from './chart-voronoi'; +import './metrics-content.css'; -export type MetricsGraphWithTotalProps = { +export interface MetricsGraphWithTotalProps { id: string; metricType: MetricType; metricFunction: MetricFunction; @@ -49,7 +49,7 @@ export type MetricsGraphWithTotalProps = { showLegend?: boolean; animate?: boolean; isDark?: boolean; -}; +} export const MetricsGraphWithTotal: React.FC = ({ id, @@ -125,7 +125,7 @@ export const MetricsGraphWithTotal: React.FC = ({ const containerRef = React.createRef(); const [dimensions, setDimensions] = useLocalStorage( - `${LOCAL_STORAGE_OVERVIEW_METRICS_TOTAL_DIMENSION_KEY}${showLegend ? '-legend' : ''}`, + `${localStorageOverviewMetricsTotalDimensionKey}${showLegend ? '-legend' : ''}`, defaultDimensions ); diff --git a/web/src/components/metrics/metrics-graph.tsx b/web/src/components/metrics/metrics-graph.tsx index d5e151e46..f8fa80f26 100644 --- a/web/src/components/metrics/metrics-graph.tsx +++ b/web/src/components/metrics/metrics-graph.tsx @@ -15,20 +15,20 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { GenericMetric, NamedMetric } from '../../api/loki'; import { MetricFunction, MetricType } from '../../model/flow-query'; -import { LOCAL_STORAGE_OVERVIEW_METRICS_DIMENSION_KEY, useLocalStorage } from '../../utils/local-storage-hook'; +import { localStorageOverviewMetricsDimensionKey, useLocalStorage } from '../../utils/local-storage-hook'; import { getFormattedValue } from '../../utils/metrics'; -import './metrics-content.css'; import { ChartDataPoint, - chartVoronoi, defaultDimensions, Dimensions, LegendDataItem, observeDimensions, toDatapoints -} from './metrics-helper'; +} from '../../utils/metrics-helper'; +import { chartVoronoi } from './chart-voronoi'; +import './metrics-content.css'; -export type MetricsGraphProps = { +export interface MetricsGraphProps { id: string; metricType: MetricType; metricFunction: MetricFunction; @@ -44,7 +44,7 @@ export type MetricsGraphProps = { showLegend?: boolean; animate?: boolean; isDark?: boolean; -}; +} export const MetricsGraph: React.FC = ({ id, @@ -86,7 +86,7 @@ export const MetricsGraph: React.FC = ({ const containerRef = React.createRef(); const [dimensions, setDimensions] = useLocalStorage( - `${LOCAL_STORAGE_OVERVIEW_METRICS_DIMENSION_KEY}${showLegend ? '-legend' : ''}`, + `${localStorageOverviewMetricsDimensionKey}${showLegend ? '-legend' : ''}`, defaultDimensions ); diff --git a/web/src/components/modals/__tests__/columns-modal.spec.tsx b/web/src/components/modals/__tests__/columns-modal.spec.tsx index 8beae0e31..ec80cf788 100644 --- a/web/src/components/modals/__tests__/columns-modal.spec.tsx +++ b/web/src/components/modals/__tests__/columns-modal.spec.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import ColumnsModal from '../columns-modal'; -import { ColumnConfigSampleDefs, ShuffledColumnSample } from '../../__tests-data__/columns'; import { Config, defaultConfig } from '../../../model/config'; +import { ColumnConfigSampleDefs, ShuffledColumnSample } from '../../__tests-data__/columns'; +import ColumnsModal from '../columns-modal'; describe('', () => { const props = { diff --git a/web/src/components/modals/__tests__/export-modal.spec.tsx b/web/src/components/modals/__tests__/export-modal.spec.tsx index 186e2cd90..a1019e9aa 100644 --- a/web/src/components/modals/__tests__/export-modal.spec.tsx +++ b/web/src/components/modals/__tests__/export-modal.spec.tsx @@ -1,8 +1,8 @@ -import * as React from 'react'; import { shallow } from 'enzyme'; +import * as React from 'react'; -import ExportModal, { ExportModalProps } from '../export-modal'; import { ShuffledColumnSample } from '../../../components/__tests-data__/columns'; +import ExportModal, { ExportModalProps } from '../export-modal'; describe('', () => { const props: ExportModalProps = { diff --git a/web/src/components/modals/__tests__/overview-panels-modal.spec.tsx b/web/src/components/modals/__tests__/overview-panels-modal.spec.tsx index 8fb550bd4..f0296b43a 100644 --- a/web/src/components/modals/__tests__/overview-panels-modal.spec.tsx +++ b/web/src/components/modals/__tests__/overview-panels-modal.spec.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import OverviewPanelsModal from '../overview-panels-modal'; -import { ShuffledDefaultPanels } from '../../__tests-data__/panels'; import { RecordType } from '../../../model/flow-query'; +import { ShuffledDefaultPanels } from '../../__tests-data__/panels'; +import OverviewPanelsModal from '../overview-panels-modal'; describe('', () => { const props = { diff --git a/web/src/components/modals/__tests__/time-range-modal.spec.tsx b/web/src/components/modals/__tests__/time-range-modal.spec.tsx index 4f7b5df67..a991673fa 100644 --- a/web/src/components/modals/__tests__/time-range-modal.spec.tsx +++ b/web/src/components/modals/__tests__/time-range-modal.spec.tsx @@ -1,10 +1,10 @@ -import * as React from 'react'; import { mount, shallow } from 'enzyme'; +import * as React from 'react'; -import TimeRangeModal, { TimeRangeModalProps } from '../time-range-modal'; -import { TimeRange } from '../../../utils/datetime'; import { DatePicker, TimePicker } from '@patternfly/react-core'; import { act } from 'react-dom/test-utils'; +import { TimeRange } from '../../../utils/datetime'; +import TimeRangeModal, { TimeRangeModalProps } from '../time-range-modal'; describe('', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/web/src/components/modals/columns-modal.tsx b/web/src/components/modals/columns-modal.tsx index cdc86f876..e03e04e72 100644 --- a/web/src/components/modals/columns-modal.tsx +++ b/web/src/components/modals/columns-modal.tsx @@ -26,9 +26,9 @@ import { Column, ColumnSizeMap, getDefaultColumns, getFullColumnName } from '../ import './columns-modal.css'; import Modal from './modal'; -export const COLUMN_FILTER_KEYS = ['source', 'destination', 'time', 'host', 'namespace', 'owner', 'ip', 'dns']; +export const columnFilterKeys = ['source', 'destination', 'time', 'host', 'namespace', 'owner', 'ip', 'dns']; -export const ColumnsModal: React.FC<{ +export interface ColumnsModalProps { isModalOpen: boolean; setModalOpen: (v: boolean) => void; columns: Column[]; @@ -36,7 +36,17 @@ export const ColumnsModal: React.FC<{ setColumnSizes: (v: ColumnSizeMap) => void; config: Config; id?: string; -}> = ({ id, config, isModalOpen, setModalOpen, columns, setColumns, setColumnSizes }) => { +} + +export const ColumnsModal: React.FC = ({ + id, + config, + isModalOpen, + setModalOpen, + columns, + setColumns, + setColumnSizes +}) => { const [resetClicked, setResetClicked] = React.useState(false); const [updatedColumns, setUpdatedColumns] = React.useState([]); const [filterKeys, setFilterKeys] = React.useState([]); @@ -112,7 +122,7 @@ export const ColumnsModal: React.FC<{ }, []); const getColumnFilterKeys = React.useCallback(() => { - return COLUMN_FILTER_KEYS.filter(fk => columns.some(c => isFilteredColumn(c, [fk]))); + return columnFilterKeys.filter(fk => columns.some(c => isFilteredColumn(c, [fk]))); }, [columns, isFilteredColumn]); const filteredColumns = React.useCallback(() => { @@ -153,7 +163,7 @@ export const ColumnsModal: React.FC<{ if (filterKeys.includes(key)) { setFilterKeys(filterKeys.filter(k => k !== key)); } else { - setFilterKeys(COLUMN_FILTER_KEYS.filter(f => f === key || filterKeys.includes(f))); + setFilterKeys(columnFilterKeys.filter(f => f === key || filterKeys.includes(f))); } }, [filterKeys] diff --git a/web/src/components/modals/export-modal.tsx b/web/src/components/modals/export-modal.tsx index 4aa4a2d77..35ae85fb0 100644 --- a/web/src/components/modals/export-modal.tsx +++ b/web/src/components/modals/export-modal.tsx @@ -14,20 +14,20 @@ import { TextContent, TextVariants } from '@patternfly/react-core'; -import Modal from './modal'; import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { getExportFlowsURL } from '../../api/routes'; +import { Filter } from '../../model/filters'; import { FlowQuery } from '../../model/flow-query'; import { Column, getFullColumnName } from '../../utils/columns'; import { getTimeRangeOptions, TimeRange } from '../../utils/datetime'; import { formatDuration, getDateSInMiliseconds } from '../../utils/duration'; -import { Filter } from '../../model/filters'; -import { getFilterFullName } from '../filters/filters-helper'; +import { getFilterFullName } from '../../utils/filters-helper'; +import { getLocalStorage, localStorageExportColsKey, useLocalStorage } from '../../utils/local-storage-hook'; import './export-modal.css'; -import { LOCAL_STORAGE_EXPORT_COLS_KEY, getLocalStorage, useLocalStorage } from '../../utils/local-storage-hook'; +import Modal from './modal'; export interface ExportModalProps { isModalOpen: boolean; @@ -50,7 +50,7 @@ export const ExportModal: React.FC = ({ }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [selectedColumns, setSelectedColumns] = useLocalStorage( - LOCAL_STORAGE_EXPORT_COLS_KEY, + localStorageExportColsKey, //select all columns by default columns.map(c => ({ ...c, isSelected: true })), { @@ -127,7 +127,7 @@ export const ExportModal: React.FC = ({ // reload selected columns when config is loaded and popup closed if (!isModalOpen) { setSelectedColumns( - getLocalStorage(LOCAL_STORAGE_EXPORT_COLS_KEY, _.cloneDeep(columns), { + getLocalStorage(localStorageExportColsKey, _.cloneDeep(columns), { id: 'id', criteria: 'isSelected' }) @@ -205,11 +205,12 @@ export const ExportModal: React.FC = ({ label={t('Export all datas')} aria-label="Export all" description={ - <> - {t('Use this option to export every fields and labels from flows.')} -
- {t('Else pick from available columns.')} - + + + {t('Use this option to export every fields and labels from flows.')} + + {t('Else pick from available columns.')} + } body={ !isExportAll && ( diff --git a/web/src/components/modals/modal.tsx b/web/src/components/modals/modal.tsx index 1963ee480..9d5fda4dc 100644 --- a/web/src/components/modals/modal.tsx +++ b/web/src/components/modals/modal.tsx @@ -4,12 +4,7 @@ import React from 'react'; import Modal from 'react-modal'; import './modal.css'; -/* This Modal component replace patternfly one that has issues with overflows - * it is based on console approach but their component doesn't manage non scrollable content & overflow - * in @openshift-console/dynamic-plugin-sdk - * https://github.com/openshift/console/blob/master/frontend/public/components/factory/modal.tsx - */ -const CustomModal: React.FC<{ +export interface CustomModalProps { title: string; description?: JSX.Element; footer?: JSX.Element; @@ -17,7 +12,23 @@ const CustomModal: React.FC<{ isOpen: boolean; scrollable: boolean; id?: string; -}> = ({ id, scrollable, isOpen, onClose, title, description, children, footer }) => { +} + +/* This Modal component replace patternfly one that has issues with overflows + * it is based on console approach but their component doesn't manage non scrollable content & overflow + * in @openshift-console/dynamic-plugin-sdk + * https://github.com/openshift/console/blob/master/frontend/public/components/factory/modal.tsx + */ +const CustomModal: React.FC = ({ + id, + scrollable, + isOpen, + onClose, + title, + description, + children, + footer +}) => { return isOpen ? ( void; recordType: RecordType; @@ -36,7 +36,17 @@ export const OverviewPanelsModal: React.FC<{ setPanels: (v: OverviewPanel[]) => void; customIds?: string[]; id?: string; -}> = ({ id, isModalOpen, setModalOpen, recordType, panels, setPanels, customIds }) => { +} + +export const OverviewPanelsModal: React.FC = ({ + id, + isModalOpen, + setModalOpen, + recordType, + panels, + setPanels, + customIds +}) => { const [updatedPanels, setUpdatedPanels] = React.useState([]); const [filterKeys, setFilterKeys] = React.useState([]); const { t } = useTranslation('plugin__netobserv-plugin'); @@ -131,7 +141,7 @@ export const OverviewPanelsModal: React.FC<{ if (filterKeys.includes(key)) { setFilterKeys(filterKeys.filter(k => k !== key)); } else { - setFilterKeys(PANEL_FILTER_KEYS.filter(f => f === key || filterKeys.includes(f))); + setFilterKeys(panelFilterKeys.filter(f => f === key || filterKeys.includes(f))); } }, [filterKeys] @@ -193,7 +203,7 @@ export const OverviewPanelsModal: React.FC<{ - {PANEL_FILTER_KEYS.map(key => { + {panelFilterKeys.map(key => { return ( ', () => { const panelProps = { diff --git a/web/src/components/netflow-overview/__tests__/netflow-overview.spec.tsx b/web/src/components/netflow-overview/__tests__/netflow-overview.spec.tsx index 0bbfed724..1b7634925 100644 --- a/web/src/components/netflow-overview/__tests__/netflow-overview.spec.tsx +++ b/web/src/components/netflow-overview/__tests__/netflow-overview.spec.tsx @@ -2,13 +2,13 @@ import { mount, shallow } from 'enzyme'; import * as React from 'react'; import { EmptyState } from '@patternfly/react-core'; -import { metrics, droppedMetrics } from '../../../components/__tests-data__/metrics'; +import { droppedMetrics, metrics } from '../../../components/__tests-data__/metrics'; +import { TruncateLength } from '../../../components/dropdowns/truncate-dropdown'; import { RecordType } from '../../../model/flow-query'; import { SamplePanel, ShuffledDefaultPanels } from '../../__tests-data__/panels'; import { NetflowOverview, NetflowOverviewProps } from '../netflow-overview'; import { NetflowOverviewPanel } from '../netflow-overview-panel'; -import { TruncateLength } from '../../../components/dropdowns/truncate-dropdown'; describe('', () => { const props: NetflowOverviewProps = { diff --git a/web/src/components/netflow-overview/netflow-overview-panel.tsx b/web/src/components/netflow-overview/netflow-overview-panel.tsx index 8896d046b..d19534bd3 100644 --- a/web/src/components/netflow-overview/netflow-overview-panel.tsx +++ b/web/src/components/netflow-overview/netflow-overview-panel.tsx @@ -1,11 +1,11 @@ -import * as React from 'react'; import { Button, Card, Flex, FlexItem, Text, TextVariants, Tooltip } from '@patternfly/react-core'; -import { InfoAltIcon, ExpandIcon, CompressIcon } from '@patternfly/react-icons'; +import { CompressIcon, ExpandIcon, InfoAltIcon } from '@patternfly/react-icons'; +import * as React from 'react'; -import './netflow-overview-panel.css'; import { useTranslation } from 'react-i18next'; +import './netflow-overview-panel.css'; -export const NetflowOverviewPanel: React.FC<{ +export interface NetflowOverviewPanelProps { doubleWidth: boolean; bodyClassName: string; title: string; @@ -16,7 +16,9 @@ export const NetflowOverviewPanel: React.FC<{ isSelected?: boolean; isFocus?: boolean; id?: string; -}> = ({ +} + +export const NetflowOverviewPanel: React.FC = ({ id, doubleWidth, bodyClassName, diff --git a/web/src/components/netflow-overview/netflow-overview.tsx b/web/src/components/netflow-overview/netflow-overview.tsx index 53ef6f955..8e0b999d3 100644 --- a/web/src/components/netflow-overview/netflow-overview.tsx +++ b/web/src/components/netflow-overview/netflow-overview.tsx @@ -13,20 +13,21 @@ import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Field, FlowDirection, getDirectionDisplayString } from '../../api/ipfix'; -import { GenericMetric, NamedMetric, NetflowMetrics, TopologyMetrics, isValidTopologyMetrics } from '../../api/loki'; +import { GenericMetric, isValidTopologyMetrics, NamedMetric, NetflowMetrics, TopologyMetrics } from '../../api/loki'; import { RecordType } from '../../model/flow-query'; import { getStat } from '../../model/metrics'; import { getDNSErrorDescription, getDNSRcodeDescription } from '../../utils/dns'; import { getDSCPServiceClassName } from '../../utils/dscp'; -import { LOCAL_STORAGE_OVERVIEW_KEBAB_KEY, useLocalStorage } from '../../utils/local-storage-hook'; +import { localStorageOverviewKebabKey, useLocalStorage } from '../../utils/local-storage-hook'; +import { observeDOMRect, toNamedMetric } from '../../utils/metrics-helper'; import { - CUSTOM_PANEL_MATCHER, - OverviewPanel, - OverviewPanelId, - OverviewPanelInfo, + customPanelMatcher, getFunctionFromId, getOverviewPanelInfo, getRateFunctionFromId, + OverviewPanel, + OverviewPanelId, + OverviewPanelInfo, parseCustomMetricId } from '../../utils/overview-panels'; import { convertRemToPixels } from '../../utils/panel'; @@ -37,7 +38,6 @@ import { TruncateLength } from '../dropdowns/truncate-dropdown'; import { MetricsDonut } from '../metrics/metrics-donut'; import { MetricsGraph } from '../metrics/metrics-graph'; import { MetricsGraphWithTotal } from '../metrics/metrics-graph-total'; -import { observeDOMRect, toNamedMetric } from '../metrics/metrics-helper'; import { NetflowOverviewPanel } from './netflow-overview-panel'; import './netflow-overview.css'; import { PanelKebab, PanelKebabOptions } from './panel-kebab'; @@ -50,7 +50,7 @@ type PanelContent = { doubleWidth?: boolean; }; -export type NetflowOverviewProps = { +export interface NetflowOverviewProps { limit: number; panels: OverviewPanel[]; recordType: RecordType; @@ -61,7 +61,7 @@ export type NetflowOverviewProps = { truncateLength: TruncateLength; focus?: boolean; setFocus?: (v: boolean) => void; -}; +} export const NetflowOverview: React.FC = ({ limit, @@ -77,7 +77,7 @@ export const NetflowOverview: React.FC = ({ }) => { const { t } = useTranslation('plugin__netobserv-plugin'); const [kebabMap, setKebabMap] = useLocalStorage>( - LOCAL_STORAGE_OVERVIEW_KEBAB_KEY, + localStorageOverviewKebabKey, new Map() ); const [selectedPanel, setSelectedPanel] = React.useState(); @@ -288,7 +288,7 @@ export const NetflowOverview: React.FC = ({ const getTopKCustomMetrics = React.useCallback( (id: string) => { - return metrics.customMetrics.get(id.replaceAll(CUSTOM_PANEL_MATCHER + '_', '')) || []; + return metrics.customMetrics.get(id.replaceAll(customPanelMatcher + '_', '')) || []; }, [metrics.customMetrics] ); @@ -310,7 +310,7 @@ export const NetflowOverview: React.FC = ({ const getTotalCustomMetrics = React.useCallback( (id: string) => { - return metrics.totalCustomMetrics.get(id.replaceAll(CUSTOM_PANEL_MATCHER + '_', '')); + return metrics.totalCustomMetrics.get(id.replaceAll(customPanelMatcher + '_', '')); }, [metrics.totalCustomMetrics] ); diff --git a/web/src/components/netflow-overview/panel-kebab.tsx b/web/src/components/netflow-overview/panel-kebab.tsx index 5061ba85b..2bb6d5347 100644 --- a/web/src/components/netflow-overview/panel-kebab.tsx +++ b/web/src/components/netflow-overview/panel-kebab.tsx @@ -13,9 +13,9 @@ import { } from '@patternfly/react-core'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { exportToPng } from '../../utils/export'; import { OverviewPanelId } from '../../utils/overview-panels'; import './panel-kebab.css'; -import { exportToPng } from '../../utils/export'; export type GraphOptipn = { type: GraphType; @@ -35,12 +35,12 @@ export type PanelKebabOptions = { graph?: GraphOptipn; }; -export type PanelKebabProps = { +export interface PanelKebabProps { id: OverviewPanelId; options?: PanelKebabOptions; setOptions?: (opts: PanelKebabOptions) => void; isDark?: boolean; -}; +} export const PanelKebab: React.FC = ({ id, options, setOptions, isDark }) => { const { t } = useTranslation('plugin__netobserv-plugin'); diff --git a/web/src/components/netflow-record/__tests__/record-field.spec.tsx b/web/src/components/netflow-record/__tests__/record-field.spec.tsx index 26b8a7316..6f34f862c 100644 --- a/web/src/components/netflow-record/__tests__/record-field.spec.tsx +++ b/web/src/components/netflow-record/__tests__/record-field.spec.tsx @@ -1,12 +1,12 @@ import { Button } from '@patternfly/react-core'; import { shallow } from 'enzyme'; import * as React from 'react'; +import { compareNumbers } from '../../../utils/base-compare'; +import { ColumnsId } from '../../../utils/columns'; +import { Size } from '../../dropdowns/table-display-dropdown'; import { DefaultColumnSample } from '../../__tests-data__/columns'; import { FlowsSample } from '../../__tests-data__/flows'; import RecordField, { RecordFieldFilter } from '../record-field'; -import { Size } from '../../dropdowns/table-display-dropdown'; -import { ColumnsId } from '../../../utils/columns'; -import { compareNumbers } from '../../../utils/base-compare'; describe('', () => { const filterMock: RecordFieldFilter = { @@ -54,7 +54,7 @@ describe('', () => { /> ); expect(wrapper.find(RecordField)).toBeTruthy(); - expect(wrapper.find('.record-field-content')).toHaveLength(1); - expect(wrapper.find('.record-field-content span').text()).toBe('< 1ms'); + expect(wrapper.find('.record-field-value')).toHaveLength(1); + expect(wrapper.find('.record-field-value').childAt(0).text()).toBe('< 1ms'); }); }); diff --git a/web/src/components/netflow-record/__tests__/record-panel.spec.tsx b/web/src/components/netflow-record/__tests__/record-panel.spec.tsx index c614f29b2..5750d266a 100644 --- a/web/src/components/netflow-record/__tests__/record-panel.spec.tsx +++ b/web/src/components/netflow-record/__tests__/record-panel.spec.tsx @@ -1,10 +1,10 @@ +import { DrawerCloseButton } from '@patternfly/react-core'; import { shallow } from 'enzyme'; import * as React from 'react'; -import { FilterDefinitionSample, FiltersSample } from '../../__tests-data__/filters'; import { DefaultColumnSample } from '../../__tests-data__/columns'; +import { FilterDefinitionSample, FiltersSample } from '../../__tests-data__/filters'; import { FlowsSample, UnknownFlow } from '../../__tests-data__/flows'; import RecordPanel, { RecordDrawerProps } from '../record-panel'; -import { DrawerCloseButton } from '@patternfly/react-core'; describe('', () => { const mocks: RecordDrawerProps = { diff --git a/web/src/components/netflow-record/record-field.css b/web/src/components/netflow-record/record-field.css index 730c8500e..2a5354fc9 100644 --- a/web/src/components/netflow-record/record-field.css +++ b/web/src/components/netflow-record/record-field.css @@ -3,6 +3,10 @@ white-space: nowrap } +.co-resource-item { + display: flex; +} + /* max content lines to show */ .co-resource-item.s>.co-resource-item__resource-name, .co-resource-item.m>.co-resource-item__resource-name, @@ -16,21 +20,15 @@ .record-field-flex-container { height: 100%; - flex: 1; - display: flex; gap: .3rem; } .record-field-flex { - flex: 1; align-self: center; } -.record-field-content-flex { - display: flex; - flex-direction: row; - flex: 1; - flex-basis: 0; +.record-field-content { + width: 100%; } .co-resource-item.s>.co-resource-item__resource-name { @@ -71,10 +69,6 @@ left: 50%; } -.record-field-tooltip-margin{ - margin-top: 0.5em; -} - /* show tooltip on content hover */ .record-field-content-flex.truncated:hover .record-field-tooltip, .record-field-content.truncated:hover .record-field-tooltip { @@ -91,13 +85,12 @@ } .record-field-date { - display: flex; align-items: baseline; + flex-wrap: nowrap; } .record-field-date-icon { flex-shrink: 0; - margin-right: 5px; } button.record-field-value-popover-button { diff --git a/web/src/components/netflow-record/record-field.tsx b/web/src/components/netflow-record/record-field.tsx index db75ef479..86d146081 100644 --- a/web/src/components/netflow-record/record-field.tsx +++ b/web/src/components/netflow-record/record-field.tsx @@ -1,5 +1,5 @@ import { ResourceIcon, ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; -import { Button, Flex, FlexItem, Popover, Text, TextVariants, Tooltip } from '@patternfly/react-core'; +import { Button, Flex, FlexItem, Popover, Text, TextContent, TextVariants, Tooltip } from '@patternfly/react-core'; import { FilterIcon, GlobeAmericasIcon, TimesIcon, ToggleOffIcon, ToggleOnIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,19 +7,19 @@ import { Link } from 'react-router-dom'; import { FlowDirection, getDirectionDisplayString, Record } from '../../api/ipfix'; import { Column, ColumnsId, getFullColumnName } from '../../utils/columns'; import { dateFormatter, getFormattedDate, timeMSFormatter, utcDateTimeFormatter } from '../../utils/datetime'; -import { DNS_CODE_NAMES, DNS_ERRORS_VALUES, getDNSErrorDescription, getDNSRcodeDescription } from '../../utils/dns'; +import { dnsCodesNames, dnsErrorsValues, getDNSErrorDescription, getDNSRcodeDescription } from '../../utils/dns'; +import { getDSCPDocUrl, getDSCPServiceClassDescription, getDSCPServiceClassName } from '../../utils/dscp'; +import { formatDurationAboveMillisecond, formatDurationAboveNanosecond } from '../../utils/duration'; import { getICMPCode, getICMPDocUrl, getICMPType, - ICMP_ALL_CODES_VALUES, - ICMP_ALL_TYPES_VALUES, + icmpAllCodesValues, + icmpAllTypesValues, isValidICMPProto } from '../../utils/icmp'; -import { DROP_CAUSES_NAMES, getDropCauseDescription, getDropCauseDocUrl } from '../../utils/pkt-drop'; -import { formatDurationAboveMillisecond, formatDurationAboveNanosecond } from '../../utils/duration'; +import { dropCausesNames, getDropCauseDescription, getDropCauseDocUrl } from '../../utils/pkt-drop'; import { formatPort } from '../../utils/port'; -import { getDSCPDocUrl, getDSCPServiceClassDescription, getDSCPServiceClassName } from '../../utils/dscp'; import { formatProtocol, getProtocolDocUrl } from '../../utils/protocol'; import { Size } from '../dropdowns/table-display-dropdown'; import './record-field.css'; @@ -30,7 +30,7 @@ export type RecordFieldFilter = { isDelete: boolean; }; -export const RecordField: React.FC<{ +export interface RecordFieldProps { allowPktDrops: boolean; flow: Record; column: Column; @@ -39,7 +39,18 @@ export const RecordField: React.FC<{ filter?: RecordFieldFilter; detailed?: boolean; isDark?: boolean; -}> = ({ allowPktDrops, flow, column, size, filter, useLinks, detailed, isDark }) => { +} + +export const RecordField: React.FC = ({ + allowPktDrops, + flow, + column, + size, + filter, + useLinks, + detailed, + isDark +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const onMouseOver = (event: React.MouseEvent, className: string) => { @@ -57,14 +68,18 @@ export const RecordField: React.FC<{
+ {text} - + ]} > -
+ {value} -
+
); @@ -74,14 +89,14 @@ export const RecordField: React.FC<{ if (errorText) { return errorTextValue(t('n/a'), errorText); } - return
{t('n/a')}
; + return {t('n/a')}; }; const emptyDnsErrorText = () => { return emptyText( flow.fields.DnsErrno ? `${t('DNS Error')} ${flow.fields.DnsErrno}: ${getDNSErrorDescription( - flow.fields.DnsErrno as DNS_ERRORS_VALUES + flow.fields.DnsErrno as dnsErrorsValues )}` : undefined ); @@ -90,11 +105,15 @@ export const RecordField: React.FC<{ const simpleTextWithTooltip = (text?: string, color?: string, child?: JSX.Element) => { if (text) { return ( -
- {text} -
{text}
+ + + {text} + + + {text} + {child} -
+ ); } return undefined; @@ -106,12 +125,12 @@ export const RecordField: React.FC<{ !ResourceIcon || useLinks ? ( ) : ( - + - + {value} - - + + ) ); }; @@ -122,16 +141,16 @@ export const RecordField: React.FC<{ return (
{resourceIconText(value, kind, ns)} -
+ {ns && ( <> -

{t('Namespace')}

- {ns} + {t('Namespace')} + {ns} )} -

{kind}

- {value} -
+ {kind} + {value} +
); } @@ -152,10 +171,10 @@ export const RecordField: React.FC<{ return (
{resourceIconText(value, kind)} -
-

{t(kind)}

- {value} -
+ + {t(kind)} + {value} +
); } @@ -183,37 +202,45 @@ export const RecordField: React.FC<{ const dateText = getFormattedDate(date, dateFormatter) + ','; const timeText = getFormattedDate(date, timeMSFormatter); return singleContainer( -
- - - {fullDateText} - - ]} - > -
- {dateText} {timeText} -
-
-
+ + + + + + + {fullDateText} + + ]} + > + + {dateText}{' '} + + {timeText} + + + + + ); }; - const nthContainer = (children: (JSX.Element | undefined)[], asChild = true, childIcon = true, flex = true) => { + const nthContainer = (children: (JSX.Element | undefined)[], asChild = true, childIcon = true) => { return ( -
+ {children.map((c, i) => ( -
onMouseOver(e, `record-field-content${flex ? '-flex' : ''}`)} + className={`record-field-content`} + onMouseOver={e => onMouseOver(e, `record-field-content`)} + flex={{ default: 'flex_1' }} > {i > 0 && asChild && childIcon && {'↪'}} {c ? c : emptyText()} -
+ ))} -
+
); }; @@ -447,7 +474,7 @@ export const RecordField: React.FC<{ let child = emptyText(); if (Array.isArray(value) && value.length) { if (isValidICMPProto(Number(value[0]))) { - const type = getICMPType(Number(value[0]), Number(value[1]) as ICMP_ALL_TYPES_VALUES); + const type = getICMPType(Number(value[0]), Number(value[1]) as icmpAllTypesValues); if (type && detailed) { child = clickableContent(type.name, type.description || '', getICMPDocUrl(Number(value[0]))); } else { @@ -468,8 +495,8 @@ export const RecordField: React.FC<{ if (isValidICMPProto(Number(value[0]))) { const code = getICMPCode( Number(value[0]), - Number(value[1]) as ICMP_ALL_TYPES_VALUES, - Number(value[2]) as ICMP_ALL_CODES_VALUES + Number(value[1]) as icmpAllTypesValues, + Number(value[2]) as icmpAllCodesValues ); if (code && detailed) { child = clickableContent(code.name, code.description || '', getICMPDocUrl(Number(value[0]))); @@ -491,7 +518,6 @@ export const RecordField: React.FC<{ return nthContainer( value.map(dir => simpleTextWithTooltip(getDirectionDisplayString(String(dir) as FlowDirection, t))), true, - false, false ); } @@ -502,7 +528,6 @@ export const RecordField: React.FC<{ return nthContainer( value.map(iName => simpleTextWithTooltip(String(iName))), true, - false, false ); } @@ -526,7 +551,6 @@ export const RecordField: React.FC<{ ) ), true, - false, false ); } else { @@ -543,8 +567,8 @@ export const RecordField: React.FC<{ droppedText = t('dropped by'); child = clickableContent( flow.fields.PktDropLatestDropCause, - getDropCauseDescription(flow.fields.PktDropLatestDropCause as DROP_CAUSES_NAMES), - getDropCauseDocUrl(flow.fields.PktDropLatestDropCause as DROP_CAUSES_NAMES) + getDropCauseDescription(flow.fields.PktDropLatestDropCause as dropCausesNames), + getDropCauseDocUrl(flow.fields.PktDropLatestDropCause as dropCausesNames) ); } @@ -587,7 +611,7 @@ export const RecordField: React.FC<{ case ColumnsId.dnsresponsecode: { return singleContainer( typeof value === 'string' && value.length - ? simpleTextWithTooltip(detailed ? `${value}: ${getDNSRcodeDescription(value as DNS_CODE_NAMES)}` : value) + ? simpleTextWithTooltip(detailed ? `${value}: ${getDNSRcodeDescription(value as dnsCodesNames)}` : value) : emptyDnsErrorText() ); } @@ -595,7 +619,7 @@ export const RecordField: React.FC<{ return singleContainer( typeof value === 'number' && !isNaN(value) ? simpleTextWithTooltip( - detailed && value ? `${value}: ${getDNSErrorDescription(value as DNS_ERRORS_VALUES)}` : String(value) + detailed && value ? `${value}: ${getDNSErrorDescription(value as dnsErrorsValues)}` : String(value) ) : emptyText() ); @@ -614,32 +638,36 @@ export const RecordField: React.FC<{ } }; return filter ? ( -
-
{content(column)}
- - - -
+ + )} + + +
+
) : ( content(column) ); diff --git a/web/src/components/netflow-record/record-panel.tsx b/web/src/components/netflow-record/record-panel.tsx index bc855f9f4..2cf00c94c 100644 --- a/web/src/components/netflow-record/record-panel.tsx +++ b/web/src/components/netflow-record/record-panel.tsx @@ -23,15 +23,15 @@ import { import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { defaultSize, maxSize, minSize } from '../../utils/panel'; -import { defaultTimeRange } from '../../utils/router'; import { FlowDirection, getDirectionDisplayString, Record } from '../../api/ipfix'; +import { doesIncludeFilter, Filter, FilterDefinition, findFromFilters, removeFromFilters } from '../../model/filters'; +import { RecordType } from '../../model/flow-query'; import { Column, ColumnGroup, ColumnsId, getColumnGroups, getShortColumnName } from '../../utils/columns'; import { TimeRange } from '../../utils/datetime'; -import { doesIncludeFilter, Filter, FilterDefinition, findFromFilters, removeFromFilters } from '../../model/filters'; import { findFilter } from '../../utils/filter-definitions'; +import { defaultSize, maxSize, minSize } from '../../utils/panel'; +import { defaultTimeRange } from '../../utils/router'; import RecordField, { RecordFieldFilter } from './record-field'; -import { RecordType } from '../../model/flow-query'; import './record-panel.css'; export type RecordDrawerProps = { diff --git a/web/src/components/netflow-tab.tsx b/web/src/components/netflow-tab.tsx index 757df82f5..07334e6b5 100644 --- a/web/src/components/netflow-tab.tsx +++ b/web/src/components/netflow-tab.tsx @@ -16,8 +16,8 @@ import { Filters } from '../model/filters'; import { loadConfig } from '../utils/config'; import { findFilter, getFilterDefinitions } from '../utils/filter-definitions'; import { usePrevious } from '../utils/previous-hook'; -import NetflowTrafficParent from './netflow-traffic-parent'; import Error from './messages/error'; +import NetflowTrafficParent from './netflow-traffic-parent'; type RouteProps = K8sResourceCommon & { spec: { diff --git a/web/src/components/netflow-table/__tests__/netflow-table-row.spec.tsx b/web/src/components/netflow-table/__tests__/netflow-table-row.spec.tsx index 4d22c039b..2e9c8312d 100644 --- a/web/src/components/netflow-table/__tests__/netflow-table-row.spec.tsx +++ b/web/src/components/netflow-table/__tests__/netflow-table-row.spec.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; +import { Td, Tr } from '@patternfly/react-table'; import { shallow } from 'enzyme'; -import { Tr, Td } from '@patternfly/react-table'; +import * as React from 'react'; -import NetflowTableRow from '../netflow-table-row'; import { Record } from '../../../api/ipfix'; +import { Size } from '../../dropdowns/table-display-dropdown'; import { DefaultColumnSample } from '../../__tests-data__/columns'; import { FlowsSample } from '../../__tests-data__/flows'; -import { Size } from '../../dropdowns/table-display-dropdown'; +import NetflowTableRow from '../netflow-table-row'; describe('', () => { let flows: Record[] = []; diff --git a/web/src/components/netflow-table/__tests__/netflow-table.spec.tsx b/web/src/components/netflow-table/__tests__/netflow-table.spec.tsx index 95335c662..a30cf3b24 100644 --- a/web/src/components/netflow-table/__tests__/netflow-table.spec.tsx +++ b/web/src/components/netflow-table/__tests__/netflow-table.spec.tsx @@ -1,16 +1,16 @@ +import { Tbody, Td } from '@patternfly/react-table'; +import { mount, shallow } from 'enzyme'; import * as React from 'react'; -import { shallow, mount } from 'enzyme'; -import { Td, Tbody } from '@patternfly/react-table'; import NetflowTable from '../netflow-table'; -import NetflowTableRow from '../netflow-table-row'; import { NetflowTableHeader } from '../netflow-table-header'; +import NetflowTableRow from '../netflow-table-row'; -import { ShuffledColumnSample } from '../../__tests-data__/columns'; -import { FlowsMock, FlowsSample } from '../../__tests-data__/flows'; -import { Size } from '../../dropdowns/table-display-dropdown'; import { ColumnsId } from '../../../utils/columns'; import { dateTimeMSFormatter, getFormattedDate } from '../../../utils/datetime'; +import { Size } from '../../dropdowns/table-display-dropdown'; +import { ShuffledColumnSample } from '../../__tests-data__/columns'; +import { FlowsMock, FlowsSample } from '../../__tests-data__/flows'; const errorStateQuery = `EmptyState[data-test="error-state"]`; const loadingContentsQuery = `Bullseye[data-test="loading-contents"]`; diff --git a/web/src/components/netflow-table/netflow-table-header.tsx b/web/src/components/netflow-table/netflow-table-header.tsx index a36e9ea53..e53292651 100644 --- a/web/src/components/netflow-table/netflow-table-header.tsx +++ b/web/src/components/netflow-table/netflow-table-header.tsx @@ -24,7 +24,7 @@ export type ResizedElement = { startClentWidth: number; }; -export const NetflowTableHeader: React.FC<{ +export interface NetflowTableHeaderProps { onSort: (id: ColumnsId, direction: SortByDirection) => void; sortId: ColumnsId; sortDirection: SortByDirection; @@ -34,7 +34,19 @@ export const NetflowTableHeader: React.FC<{ setColumnSizes: (v: ColumnSizeMap) => void; tableWidth: number; isDark?: boolean; -}> = ({ onSort, sortId, sortDirection, columns, setColumns, columnSizes, setColumnSizes, tableWidth, isDark }) => { +} + +export const NetflowTableHeader: React.FC = ({ + onSort, + sortId, + sortDirection, + columns, + setColumns, + columnSizes, + setColumnSizes, + tableWidth, + isDark +}) => { const resizedElement = React.useRef(); const draggedElement = React.useRef(); diff --git a/web/src/components/netflow-table/netflow-table-row.tsx b/web/src/components/netflow-table/netflow-table-row.tsx index a4793e8ca..62cf986e8 100644 --- a/web/src/components/netflow-table/netflow-table-row.tsx +++ b/web/src/components/netflow-table/netflow-table-row.tsx @@ -1,13 +1,13 @@ import { Td, Tr } from '@patternfly/react-table'; import * as React from 'react'; +import CSSTransition from 'react-transition-group/CSSTransition'; import { Record } from '../../api/ipfix'; import { Column } from '../../utils/columns'; import { Size } from '../dropdowns/table-display-dropdown'; import { RecordField } from '../netflow-record/record-field'; import './netflow-table-row.css'; -import CSSTransition from 'react-transition-group/CSSTransition'; -const NetflowTableRow: React.FC<{ +export interface NetflowTableRowProps { allowPktDrops: boolean; lastRender?: string; flow: Record; @@ -20,7 +20,9 @@ const NetflowTableRow: React.FC<{ showContent?: boolean; tableWidth: number; isDark?: boolean; -}> = ({ +} + +export const NetflowTableRow: React.FC = ({ allowPktDrops, lastRender, flow, diff --git a/web/src/components/netflow-table/netflow-table.tsx b/web/src/components/netflow-table/netflow-table.tsx index 8ae7e41bb..a04baa71e 100644 --- a/web/src/components/netflow-table/netflow-table.tsx +++ b/web/src/components/netflow-table/netflow-table.tsx @@ -1,6 +1,3 @@ -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SortByDirection, TableComposable, Tbody } from '@patternfly/react-table'; import { Bullseye, EmptyState, @@ -11,23 +8,22 @@ import { Title } from '@patternfly/react-core'; import { SearchIcon } from '@patternfly/react-icons'; +import { SortByDirection, TableComposable, Tbody } from '@patternfly/react-table'; import * as _ from 'lodash'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { Record } from '../../api/ipfix'; -import { NetflowTableHeader } from './netflow-table-header'; -import NetflowTableRow from './netflow-table-row'; import { Column, ColumnsId, ColumnSizeMap } from '../../utils/columns'; -import { Size } from '../dropdowns/table-display-dropdown'; +import { localStorageSortDirectionKey, localStorageSortIdKey, useLocalStorage } from '../../utils/local-storage-hook'; +import { convertRemToPixels } from '../../utils/panel'; import { usePrevious } from '../../utils/previous-hook'; +import { Size } from '../dropdowns/table-display-dropdown'; +import { NetflowTableHeader } from './netflow-table-header'; +import NetflowTableRow from './netflow-table-row'; import './netflow-table.css'; -import { - LOCAL_STORAGE_SORT_DIRECTION_KEY, - LOCAL_STORAGE_SORT_ID_KEY, - useLocalStorage -} from '../../utils/local-storage-hook'; -import { convertRemToPixels } from '../../utils/panel'; -const NetflowTable: React.FC<{ +export interface NetflowTableProps { allowPktDrops: boolean; flows: Record[]; selectedRecord?: Record; @@ -40,7 +36,9 @@ const NetflowTable: React.FC<{ loading?: boolean; filterActionLinks: JSX.Element; isDark?: boolean; -}> = ({ +} + +export const NetflowTable: React.FC = ({ allowPktDrops, flows, selectedRecord, @@ -63,11 +61,11 @@ const NetflowTable: React.FC<{ const previousScrollPosition = usePrevious(scrollPosition); const [lastRender, setLastRender] = React.useState(''); // index of the currently active column - const [activeSortId, setActiveSortId] = useLocalStorage(LOCAL_STORAGE_SORT_ID_KEY, ColumnsId.endtime); + const [activeSortId, setActiveSortId] = useLocalStorage(localStorageSortIdKey, ColumnsId.endtime); const previousActiveSortIndex = usePrevious(activeSortId); // sort direction of the currently active column const [activeSortDirection, setActiveSortDirection] = useLocalStorage( - LOCAL_STORAGE_SORT_DIRECTION_KEY, + localStorageSortDirectionKey, SortByDirection.asc ); const previousActiveSortDirection = usePrevious(activeSortDirection); diff --git a/web/src/components/netflow-topology/2d/componentFactories/shapesComponentFactory.ts b/web/src/components/netflow-topology/2d/componentFactories/shapesComponentFactory.ts index a42c1fb4e..404e351fb 100644 --- a/web/src/components/netflow-topology/2d/componentFactories/shapesComponentFactory.ts +++ b/web/src/components/netflow-topology/2d/componentFactories/shapesComponentFactory.ts @@ -1,5 +1,5 @@ +import { ComponentFactory, DefaultNode, ModelKind } from '@patternfly/react-topology'; import { ComponentType } from 'react'; -import { ComponentFactory, ModelKind, DefaultNode } from '@patternfly/react-topology'; import { GraphElementPeer } from '../../../../model/topology'; export const shapesComponentFactory: ComponentFactory = ( diff --git a/web/src/components/netflow-topology/2d/componentFactories/stylesComponentFactory.tsx b/web/src/components/netflow-topology/2d/componentFactories/stylesComponentFactory.tsx index ba48a83aa..7e7c0c581 100644 --- a/web/src/components/netflow-topology/2d/componentFactories/stylesComponentFactory.tsx +++ b/web/src/components/netflow-topology/2d/componentFactories/stylesComponentFactory.tsx @@ -5,7 +5,7 @@ import { groupDropTargetSpec, ModelKind, nodeDragSourceSpec, - NODE_DRAG_TYPE, + NODE_DRAG_TYPE as nodeDragType, withDndDrop, withDragNode, withPanZoom, @@ -24,7 +24,7 @@ export const stylesComponentFactory: ComponentFactory = ( type: string ): React.ComponentType<{ element: GraphElementPeer }> | undefined => { if (kind === ModelKind.graph) { - return withDndDrop(graphDropTargetSpec([NODE_DRAG_TYPE]))(withPanZoom()(GraphComponent)); + return withDndDrop(graphDropTargetSpec([nodeDragType]))(withPanZoom()(GraphComponent)); } switch (type) { case 'node': diff --git a/web/src/components/netflow-topology/2d/components/edge.tsx b/web/src/components/netflow-topology/2d/components/edge.tsx index 023c5b399..1a48750e9 100644 --- a/web/src/components/netflow-topology/2d/components/edge.tsx +++ b/web/src/components/netflow-topology/2d/components/edge.tsx @@ -1,29 +1,29 @@ -import * as React from 'react'; -import * as _ from 'lodash'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-topology/src/css/topology-components'; import { + DefaultConnectorTerminal, Edge, EdgeTerminalType, - NodeStatus, - WithRemoveConnectorProps, - WithSourceDragProps, - WithTargetDragProps, - WithSelectionProps, - WithContextMenuProps, - useHover, getEdgeAnimationDuration, getEdgeStyleClassModifier, - Point, Layer, - TOP_LAYER, - DefaultConnectorTerminal, - observer + NodeStatus, + observer, + Point, + TOP_LAYER as topLayer, + useHover, + WithContextMenuProps, + WithRemoveConnectorProps, + WithSelectionProps, + WithSourceDragProps, + WithTargetDragProps } from '@patternfly/react-topology'; import DefaultConnectorTag from '@patternfly/react-topology/dist/esm/components/edges/DefaultConnectorTag'; import { getConnectorStartPoint } from '@patternfly/react-topology/dist/esm/components/edges/terminals/terminalUtils'; +import styles from '@patternfly/react-topology/src/css/topology-components'; +import * as _ from 'lodash'; +import * as React from 'react'; -import { HOVER_EVENT } from '../topology-content'; +import { hoverEvent } from '../topology-content'; type BaseEdgeProps = { children?: React.ReactNode; @@ -94,7 +94,7 @@ const BaseEdge: React.FC = ({ } else { onHideRemoveConnector && onHideRemoveConnector(); } - element.getController().fireEvent(HOVER_EVENT, { + element.getController().fireEvent(hoverEvent, { ...element.getData(), id: element.getId(), isHovered: hover @@ -137,7 +137,7 @@ const BaseEdge: React.FC = ({ .join('')}L${bgEndPoint[0]} ${bgEndPoint[1]}`; return ( - + | undefined} data-test-id="edge-handler" diff --git a/web/src/components/netflow-topology/2d/components/node.tsx b/web/src/components/netflow-topology/2d/components/node.tsx index b24f6b6ce..48170fd95 100644 --- a/web/src/components/netflow-topology/2d/components/node.tsx +++ b/web/src/components/netflow-topology/2d/components/node.tsx @@ -1,15 +1,13 @@ -import * as React from 'react'; -import { css } from '@patternfly/react-styles'; import { Tooltip, TooltipPosition } from '@patternfly/react-core'; import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import styles from '@patternfly/react-topology/src/css/topology-components'; +import { css } from '@patternfly/react-styles'; import { BadgeLocation, createSvgIdUrl, Decorator, - DEFAULT_DECORATOR_RADIUS, + DEFAULT_DECORATOR_RADIUS as defaultDecoratorRadius, getDefaultShapeDecoratorCenter, getShapeComponent, LabelPosition, @@ -30,11 +28,13 @@ import { WithSelectionProps } from '@patternfly/react-topology'; import { - NODE_SHADOW_FILTER_ID_DANGER, - NODE_SHADOW_FILTER_ID_HOVER + NODE_SHADOW_FILTER_ID_DANGER as nodeShadowFilterIdDanger, + NODE_SHADOW_FILTER_ID_HOVER as nodeShadowFilterIdHover } from '@patternfly/react-topology/dist/esm/components/nodes/NodeShadows'; -import { HOVER_EVENT } from '../topology-content'; +import styles from '@patternfly/react-topology/src/css/topology-components'; +import * as React from 'react'; import { GraphElementPeer } from '../../../../model/topology'; +import { hoverEvent } from '../topology-content'; const StatusQuadrant = TopologyQuadrant.upperLeft; @@ -100,7 +100,7 @@ type BaseNodeProps = { WithContextMenuProps >; -const SCALE_UP_TIME = 200; +const scaleUpTime = 200; // BaseNode: slightly modified from @patternfly/react-topology/src/components/nodes/DefaultNode.tsx // to support shadow / hover behaviors @@ -176,7 +176,7 @@ const BaseNode: React.FunctionComponent = ({ onStatusDecoratorClick && onStatusDecoratorClick(e, element)} icon={{icon}} @@ -200,7 +200,7 @@ const BaseNode: React.FunctionComponent = ({ } else { onHideCreateConnector && onHideCreateConnector(); } - element.getController().fireEvent(HOVER_EVENT, { + element.getController().fireEvent(hoverEvent, { ...element.getData(), id: element.getId(), isHovered: isHover @@ -233,9 +233,9 @@ const BaseNode: React.FunctionComponent = ({ let filter; if (status === 'danger') { - filter = createSvgIdUrl(NODE_SHADOW_FILTER_ID_DANGER); + filter = createSvgIdUrl(nodeShadowFilterIdDanger); } else if (isHover || dragging || edgeDragging || dropTarget) { - filter = createSvgIdUrl(NODE_SHADOW_FILTER_ID_HOVER); + filter = createSvgIdUrl(nodeShadowFilterIdHover); } const nodeLabelPosition = labelPosition || element.getLabelPosition(); @@ -259,7 +259,7 @@ const BaseNode: React.FunctionComponent = ({ const initTime = performance.now(); const bumpScale = (bumpTime: number) => { - const scalePercent = (bumpTime - initTime) / SCALE_UP_TIME; + const scalePercent = (bumpTime - initTime) / scaleUpTime; const nextScale = Math.min(scale + scaleDelta * scalePercent, scaleGoal.current); setNodeScale(nextScale); if (nextScale < scaleGoal.current) { diff --git a/web/src/components/netflow-topology/2d/layouts/layoutFactory.ts b/web/src/components/netflow-topology/2d/layouts/layoutFactory.ts index 0942fa919..9a3d4dbfb 100644 --- a/web/src/components/netflow-topology/2d/layouts/layoutFactory.ts +++ b/web/src/components/netflow-topology/2d/layouts/layoutFactory.ts @@ -1,14 +1,14 @@ import { - Graph, - Layout, - LayoutFactory, - ForceLayout, + BreadthFirstLayout, + ColaGroupsLayout, ColaLayout, + ConcentricLayout, DagreLayout, + ForceLayout, + Graph, GridLayout, - ConcentricLayout, - BreadthFirstLayout, - ColaGroupsLayout + Layout, + LayoutFactory } from '@patternfly/react-topology'; import { LayoutName } from '../../../../model/topology'; diff --git a/web/src/components/netflow-topology/2d/styles/styleDecorator.tsx b/web/src/components/netflow-topology/2d/styles/styleDecorator.tsx index a57920867..46d1313ae 100644 --- a/web/src/components/netflow-topology/2d/styles/styleDecorator.tsx +++ b/web/src/components/netflow-topology/2d/styles/styleDecorator.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; import { Tooltip, TooltipPosition } from '@patternfly/react-core'; -import { ContextMenu, Decorator, DEFAULT_DECORATOR_RADIUS } from '@patternfly/react-topology'; +import { ContextMenu, Decorator, DEFAULT_DECORATOR_RADIUS as defaultDecoratorRadius } from '@patternfly/react-topology'; +import * as React from 'react'; type Reference = React.ComponentProps['reference']; @@ -26,7 +26,7 @@ export const ClickableDecorator: React.FC = ({ >; @@ -78,19 +78,19 @@ export const NodeDecorators: React.FC = ({ const onFilterDirClick = React.useCallback( (dir: FilterDir) => () => { const currentState = dir === 'src' ? isSrcFiltered : isDstFiltered; - controller.fireEvent(FILTER_EVENT, eltId, data, dir, currentState); + controller.fireEvent(filterEvent, eltId, data, dir, currentState); dir === 'src' ? setSrcFiltered(!currentState) : setDstFiltered(!currentState); }, [eltId, controller, data, isSrcFiltered, isDstFiltered, setSrcFiltered, setDstFiltered] ); const onFilterClick = React.useCallback(() => { - controller.fireEvent(FILTER_EVENT, eltId, data, 'src', isSrcFiltered); + controller.fireEvent(filterEvent, eltId, data, 'src', isSrcFiltered); setSrcFiltered(!isSrcFiltered); }, [controller, data, eltId, isSrcFiltered, setSrcFiltered]); const onStepIntoClick = React.useCallback(() => { - controller.fireEvent(STEP_INTO_EVENT, { ...data, id: eltId }); + controller.fireEvent(stepIntoEvent, { ...data, id: eltId }); }, [eltId, controller, data]); const getPosition = React.useCallback( @@ -134,7 +134,7 @@ export const NodeDecorators: React.FC = ({ tooltip={t('Step into this {{name}}', { name: data.peer.resourceKind?.toLowerCase() })} isActive={false} onClick={onStepIntoClick} - padding={MEDIUM_DECORATOR_PADDING} + padding={mediumDecoratorPadding} /> )} {(data.peer.namespace || @@ -148,7 +148,7 @@ export const NodeDecorators: React.FC = ({ icon={} tooltip={t('Filter by source or destination {{name}}', { name: data.peer.resourceKind?.toLowerCase() })} isActive={isSrcFiltered || isDstFiltered} - padding={isSrcFiltered || isDstFiltered ? DEFAULT_DECORATOR_PADDING : LARGE_DECORATOR_PADDING} + padding={isSrcFiltered || isDstFiltered ? defaultDecoratorPadding : largeDecoratorPadding} menuItems={filterMenu} /> )} @@ -159,7 +159,7 @@ export const NodeDecorators: React.FC = ({ tooltip={t('Filter by {{name}}', { name: data.peer.resourceKind?.toLowerCase() })} isActive={isSrcFiltered || isDstFiltered} onClick={onFilterClick} - padding={isSrcFiltered || isDstFiltered ? DEFAULT_DECORATOR_PADDING : LARGE_DECORATOR_PADDING} + padding={isSrcFiltered || isDstFiltered ? defaultDecoratorPadding : largeDecoratorPadding} /> )} { @@ -169,7 +169,7 @@ export const NodeDecorators: React.FC = ({ tooltip={isPinned ? t('Unpin this element') : t('Pin this element')} isActive={isPinned} onClick={onPinClick} - padding={MEDIUM_DECORATOR_PADDING} + padding={mediumDecoratorPadding} /> } diff --git a/web/src/components/netflow-topology/2d/styles/styleEdge.tsx b/web/src/components/netflow-topology/2d/styles/styleEdge.tsx index 1c02706e7..3cd6a4a9b 100644 --- a/web/src/components/netflow-topology/2d/styles/styleEdge.tsx +++ b/web/src/components/netflow-topology/2d/styles/styleEdge.tsx @@ -1,7 +1,7 @@ import { Edge, observer, ScaleDetailsLevel, WithSelectionProps } from '@patternfly/react-topology'; -import BaseEdge from '../components/edge'; import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; import * as React from 'react'; +import BaseEdge from '../components/edge'; type StyleEdgeProps = { element: Edge; diff --git a/web/src/components/netflow-topology/2d/styles/styleGroup.tsx b/web/src/components/netflow-topology/2d/styles/styleGroup.tsx index 3a2396782..175079458 100644 --- a/web/src/components/netflow-topology/2d/styles/styleGroup.tsx +++ b/web/src/components/netflow-topology/2d/styles/styleGroup.tsx @@ -11,7 +11,7 @@ import { import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; import * as React from 'react'; -const ICON_PADDING = 20; +const iconPadding = 20; export enum DataTypes { Default, @@ -39,7 +39,7 @@ const StyleGroup: React.FunctionComponent = ({ const detailsLevel = useDetailsLevel(); const renderIcon = (): React.ReactNode => { - const iconSize = Math.min(collapsedWidth, collapsedHeight) - ICON_PADDING * 2; + const iconSize = Math.min(collapsedWidth, collapsedHeight) - iconPadding * 2; return ( diff --git a/web/src/components/netflow-topology/2d/styles/styleNode.tsx b/web/src/components/netflow-topology/2d/styles/styleNode.tsx index b13cbf35e..0a3225993 100644 --- a/web/src/components/netflow-topology/2d/styles/styleNode.tsx +++ b/web/src/components/netflow-topology/2d/styles/styleNode.tsx @@ -1,17 +1,15 @@ -import * as React from 'react'; -import * as _ from 'lodash'; import { + ClusterIcon, CubeIcon, CubesIcon, OutlinedHddIcon, QuestionCircleIcon, ServiceIcon, UsersIcon, - ClusterIcon, ZoneIcon } from '@patternfly/react-icons'; import { - DEFAULT_LAYER, + DEFAULT_LAYER as defaultLayer, Layer, Node, NodeModel, @@ -20,12 +18,14 @@ import { ScaleDetailsLevel, ShapeProps, TopologyQuadrant, - TOP_LAYER, + TOP_LAYER as topLayer, useHover, WithDragNodeProps, WithSelectionProps } from '@patternfly/react-topology'; import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; +import * as _ from 'lodash'; +import * as React from 'react'; import { Decorated, NodeData } from '../../../../model/topology'; import BaseNode from '../components/node'; import { NodeDecorators } from './styleDecorators'; @@ -33,7 +33,7 @@ import { NodeDecorators } from './styleDecorators'; export enum DataTypes { Default } -const ICON_PADDING = 20; +const iconPadding = 20; type NodePeer = Node>; @@ -79,7 +79,7 @@ const renderIcon = (data: Decorated, element: NodePeer): React.ReactNo const shape = element.getNodeShape(); const iconSize = (shape === NodeShape.trapezoid ? width : Math.min(width, height)) - - (shape === NodeShape.stadium ? 5 : ICON_PADDING) * 2; + (shape === NodeShape.stadium ? 5 : iconPadding) * 2; const Component = getTypeIcon(data.peer.resourceKind); return ( @@ -129,7 +129,7 @@ const StyleNode: React.FC = ({ } return ( - + = ({ +} + +export const TopologyContent: React.FC = ({ k8sModels, metricFunction, metricType, @@ -198,23 +200,23 @@ export const TopologyContent: React.FC<{ switch (metricScope) { case MetricScopeOptions.CLUSTER: scope = MetricScopeOptions.ZONE; - groupTypes = TopologyGroupTypes.CLUSTERS; + groupTypes = TopologyGroupTypes.clusters; break; case MetricScopeOptions.ZONE: scope = MetricScopeOptions.HOST; - groupTypes = TopologyGroupTypes.ZONES; + groupTypes = TopologyGroupTypes.zones; break; case MetricScopeOptions.HOST: scope = MetricScopeOptions.NAMESPACE; - groupTypes = TopologyGroupTypes.NONE; + groupTypes = TopologyGroupTypes.none; break; case MetricScopeOptions.NAMESPACE: scope = MetricScopeOptions.OWNER; - groupTypes = TopologyGroupTypes.NAMESPACES; + groupTypes = TopologyGroupTypes.namespaces; break; default: scope = MetricScopeOptions.RESOURCE; - groupTypes = TopologyGroupTypes.OWNERS; + groupTypes = TopologyGroupTypes.owners; } if (data.nodeType && data.peer) { setMetricScope(scope); @@ -247,7 +249,7 @@ export const TopologyContent: React.FC<{ //fit view to elements const fitView = React.useCallback(() => { if (controller && controller.hasGraph()) { - controller.getGraph().fit(FIT_PADDING); + controller.getGraph().fit(fitPadding); } else { console.error('fitView called before controller graph'); } @@ -257,7 +259,7 @@ export const TopologyContent: React.FC<{ //fit view to new loaded elements if (requestFit) { requestFit = false; - if ([LayoutName.Concentric, LayoutName.Dagre, LayoutName.Grid].includes(options.layout)) { + if ([LayoutName.concentric, LayoutName.dagre, LayoutName.grid].includes(options.layout)) { fitView(); } else { //TODO: find a smoother way to fit while elements are still moving @@ -462,12 +464,12 @@ export const TopologyContent: React.FC<{ // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchEvent]); - useEventListener(SELECTION_EVENT, onSelectIds); - useEventListener(FILTER_EVENT, onFilter); - useEventListener(STEP_INTO_EVENT, onStepInto); - useEventListener(HOVER_EVENT, onHover); - useEventListener(GRAPH_LAYOUT_END_EVENT, onLayoutEnd); - useEventListener(GRAPH_POSITION_CHANGE_EVENT, onLayoutPositionChange); + useEventListener(selectionEvent, onSelectIds); + useEventListener(filterEvent, onFilter); + useEventListener(stepIntoEvent, onStepInto); + useEventListener(hoverEvent, onHover); + useEventListener(graphLayoutEndEvent, onLayoutEnd); + useEventListener(graphPositionChangeEvent, onLayoutPositionChange); return ( { - controller && controller.getGraph().scaleBy(ZOOM_IN); + controller && controller.getGraph().scaleBy(zoomIn); }, zoomOutCallback: () => { - controller && controller.getGraph().scaleBy(ZOOM_OUT); + controller && controller.getGraph().scaleBy(zoomOut); }, resetViewCallback: () => { if (controller) { diff --git a/web/src/components/netflow-topology/3d/three-d-topology-content.tsx b/web/src/components/netflow-topology/3d/three-d-topology-content.tsx index a873f2bda..0c09d6ea1 100644 --- a/web/src/components/netflow-topology/3d/three-d-topology-content.tsx +++ b/web/src/components/netflow-topology/3d/three-d-topology-content.tsx @@ -1,18 +1,18 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import * as RTTopology from '@jpinsonneau/react-three-topology'; import { K8sModel } from '@openshift-console/dynamic-plugin-sdk'; +import { BaseNode } from '@patternfly/react-topology'; +import _ from 'lodash'; import React from 'react'; import { TopologyMetrics } from '../../../api/loki'; -import { SearchHandle, SearchEvent } from '../../../components/search/search'; +import { SearchEvent, SearchHandle } from '../../../components/search/search'; import { Filter } from '../../../model/filters'; -import { StatFunction, MetricType, FlowScope } from '../../../model/flow-query'; -import { TopologyOptions, GraphElementPeer, NodeData } from '../../../model/topology'; -import * as RTTopology from '@jpinsonneau/react-three-topology'; -import _ from 'lodash'; -import './three-d-topology-content.css'; -import { BaseNode } from '@patternfly/react-topology'; +import { FlowScope, MetricType, StatFunction } from '../../../model/flow-query'; +import { GraphElementPeer, NodeData, TopologyOptions } from '../../../model/topology'; import { createPeer } from '../../../utils/metrics'; +import './three-d-topology-content.css'; -export const ThreeDTopologyContent: React.FC<{ +export interface ThreeDTopologyContentProps { k8sModels: { [key: string]: K8sModel }; metricFunction: StatFunction; metricType: MetricType; @@ -28,7 +28,9 @@ export const ThreeDTopologyContent: React.FC<{ searchHandle: SearchHandle | null; searchEvent?: SearchEvent; isDark?: boolean; -}> = ({ +} + +export const ThreeDTopologyContent: React.FC = ({ k8sModels, metricFunction, metricType, diff --git a/web/src/components/netflow-topology/__tests__/element-panel.spec.tsx b/web/src/components/netflow-topology/__tests__/element-panel.spec.tsx index cec65fa9f..8afa5bbce 100644 --- a/web/src/components/netflow-topology/__tests__/element-panel.spec.tsx +++ b/web/src/components/netflow-topology/__tests__/element-panel.spec.tsx @@ -2,16 +2,17 @@ import { DrawerCloseButton, OptionsMenuToggle } from '@patternfly/react-core'; import { BaseEdge, BaseNode, NodeModel } from '@patternfly/react-topology'; import { mount, shallow } from 'enzyme'; import * as React from 'react'; -import { Filter } from '../../../model/filters'; import { TopologyMetrics } from '../../../api/loki'; +import { TruncateLength } from '../../../components/dropdowns/truncate-dropdown'; +import { FilterDefinitionSample } from '../../../components/__tests-data__/filters'; +import { Filter } from '../../../model/filters'; import { FlowScope, MetricType } from '../../../model/flow-query'; -import { ElementPanel, ElementPanelDetailsContent } from '../element-panel'; -import { dataSample } from '../__tests-data__/metrics'; import { NodeData } from '../../../model/topology'; -import { ElementPanelMetrics } from '../element-panel-metrics'; import { createPeer } from '../../../utils/metrics'; -import { TruncateLength } from '../../../components/dropdowns/truncate-dropdown'; -import { FilterDefinitionSample } from '../../../components/__tests-data__/filters'; +import { ElementPanel } from '../element-panel'; +import { ElementPanelContent } from '../element-panel-content'; +import { ElementPanelMetrics } from '../element-panel-metrics'; +import { dataSample } from '../__tests-data__/metrics'; describe('', () => { const getNode = (kind: string, name: string, addr: string) => { @@ -62,8 +63,8 @@ describe('', () => { }); it('should render ', async () => { - const wrapper = mount(); - expect(wrapper.find(ElementPanelDetailsContent)).toBeTruthy(); + const wrapper = mount(); + expect(wrapper.find(ElementPanelContent)).toBeTruthy(); //check node infos expect(wrapper.find('#node-info-address').last().text()).toBe('IP10.129.0.15'); @@ -118,7 +119,7 @@ describe('', () => { }); it('should filter ', async () => { - const wrapper = mount(); + const wrapper = mount(); const ipFilters = wrapper.find(OptionsMenuToggle).last(); // Two buttons: first for pod filter, second for IP filter => click on second ipFilters.last().simulate('click'); diff --git a/web/src/components/netflow-topology/__tests__/netflow-topology.spec.tsx b/web/src/components/netflow-topology/__tests__/netflow-topology.spec.tsx index f8493cd9f..4d3e51b66 100644 --- a/web/src/components/netflow-topology/__tests__/netflow-topology.spec.tsx +++ b/web/src/components/netflow-topology/__tests__/netflow-topology.spec.tsx @@ -3,13 +3,13 @@ import { TopologyView, VisualizationSurface } from '@patternfly/react-topology'; import { shallow } from 'enzyme'; import * as React from 'react'; import { TopologyMetrics } from '../../../api/loki'; -import { StatFunction, FlowScope, MetricType } from '../../../model/flow-query'; +import { FilterDefinitionSample } from '../../../components/__tests-data__/filters'; +import { FlowScope, MetricType, StatFunction } from '../../../model/flow-query'; import { DefaultOptions, LayoutName } from '../../../model/topology'; import { defaultTimeRange } from '../../../utils/router'; -import { NetflowTopology } from '../netflow-topology'; import { TopologyContent } from '../2d/topology-content'; +import { NetflowTopology } from '../netflow-topology'; import { dataSample } from '../__tests-data__/metrics'; -import { FilterDefinitionSample } from '../../../components/__tests-data__/filters'; describe('', () => { const mocks = { @@ -23,7 +23,7 @@ describe('', () => { setMetricScope: jest.fn(), metrics: [] as TopologyMetrics[], droppedMetrics: [] as TopologyMetrics[], - layout: LayoutName.Cola, + layout: LayoutName.cola, options: DefaultOptions, setOptions: jest.fn(), lowScale: 0.3, diff --git a/web/src/components/netflow-topology/element-field.tsx b/web/src/components/netflow-topology/element-field.tsx index dad3b03f4..72cf73adc 100644 --- a/web/src/components/netflow-topology/element-field.tsx +++ b/web/src/components/netflow-topology/element-field.tsx @@ -1,12 +1,12 @@ import { Flex, FlexItem, Text, TextContent, TextVariants } from '@patternfly/react-core'; import * as React from 'react'; -import { NodeType } from '../../model/flow-query'; import { TopologyMetricPeer } from '../../api/loki'; import { Filter, FilterDefinition } from '../../model/filters'; +import { NodeType } from '../../model/flow-query'; import { SummaryFilterButton } from '../filters/summary-filter-button'; import { PeerResourceLink } from './peer-resource-link'; -export const ElementField: React.FC<{ +export interface ElementFieldProps { id: string; label: string; filterType: NodeType; @@ -15,7 +15,18 @@ export const ElementField: React.FC<{ activeFilters: Filter[]; setFilters: (filters: Filter[]) => void; filterDefinitions: FilterDefinition[]; -}> = ({ id, label, filterType, forcedText, peer, activeFilters, setFilters, filterDefinitions }) => { +} + +export const ElementField: React.FC = ({ + id, + label, + filterType, + forcedText, + peer, + activeFilters, + setFilters, + filterDefinitions +}) => { return ( {label} diff --git a/web/src/components/netflow-topology/element-fields.tsx b/web/src/components/netflow-topology/element-fields.tsx index f02fd2cf1..331b51657 100644 --- a/web/src/components/netflow-topology/element-fields.tsx +++ b/web/src/components/netflow-topology/element-fields.tsx @@ -1,19 +1,28 @@ -import * as React from 'react'; import { Text, TextContent, TextVariants } from '@patternfly/react-core'; +import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Filter, FilterDefinition } from '../../model/filters'; import { NodeData } from '../../model/topology'; -import { ElementField } from './element-field'; import { createPeer } from '../../utils/metrics'; +import { ElementField } from './element-field'; -export const ElementFields: React.FC<{ +export interface ElementFieldsProps { id: string; data: NodeData; forceFirstAsText?: boolean; activeFilters: Filter[]; setFilters: (filters: Filter[]) => void; filterDefinitions: FilterDefinition[]; -}> = ({ id, data, forceFirstAsText, activeFilters, setFilters, filterDefinitions }) => { +} + +export const ElementFields: React.FC = ({ + id, + data, + forceFirstAsText, + activeFilters, + setFilters, + filterDefinitions +}) => { const { t } = useTranslation('plugin__netobserv-plugin'); const fragments = []; diff --git a/web/src/components/netflow-topology/element-panel-content.tsx b/web/src/components/netflow-topology/element-panel-content.tsx new file mode 100644 index 000000000..8af8dc627 --- /dev/null +++ b/web/src/components/netflow-topology/element-panel-content.tsx @@ -0,0 +1,155 @@ +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionToggle, + Button, + Divider, + Flex, + FlexItem, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { FilterIcon, TimesIcon } from '@patternfly/react-icons'; +import { BaseEdge, BaseNode } from '@patternfly/react-topology'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Filter, FilterDefinition } from '../../model/filters'; +import { GraphElementPeer, isElementFiltered, NodeData, toggleElementFilter } from '../../model/topology'; +import { createPeer } from '../../utils/metrics'; +import { ElementFields } from './element-fields'; + +export interface ElementPanelContentProps { + element: GraphElementPeer; + filters: Filter[]; + setFilters: (filters: Filter[]) => void; + filterDefinitions: FilterDefinition[]; +} + +export const ElementPanelContent: React.FC = ({ + element, + filters, + setFilters, + filterDefinitions +}) => { + const { t } = useTranslation('plugin__netobserv-plugin'); + const [hidden, setHidden] = React.useState([]); + const data = element.getData(); + + const toggle = React.useCallback( + (id: string) => { + const index = hidden.indexOf(id); + const newExpanded: string[] = + index >= 0 ? [...hidden.slice(0, index), ...hidden.slice(index + 1, hidden.length)] : [...hidden, id]; + setHidden(newExpanded); + }, + [hidden] + ); + + const clusterName = React.useCallback( + (d: NodeData) => { + if (!d.peer.clusterName) { + return <>; + } + const fields = createPeer({ clusterName: d.peer.clusterName }); + const isFiltered = isElementFiltered(fields, filters, filterDefinitions); + return ( + + {t('Cluster name')} + + {d.peer.clusterName} + + - ) : ( - <> - - + {!_.isEmpty(searchResultCount) ? ( + + ) : ( + <> + )} + {_.isEmpty(searchResultCount) ? ( - - )} - - ); -}); + ) : ( + <> + + + + + )} + + ); + } +); export default SearchComponent; diff --git a/web/src/components/slider/Slider.tsx b/web/src/components/slider/Slider.tsx index b9c58154a..72f1feefe 100644 --- a/web/src/components/slider/Slider.tsx +++ b/web/src/components/slider/Slider.tsx @@ -1,9 +1,9 @@ +import { InputGroup, InputGroupText, TextInput, Tooltip } from '@patternfly/react-core'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Slider/slider'; import * as React from 'react'; import { useState } from 'react'; -import styles from '@patternfly/react-styles/css/components/Slider/slider'; -import { css } from '@patternfly/react-styles'; import { SliderStep } from './SliderStep'; -import { InputGroup, InputGroupText, TextInput, Tooltip } from '@patternfly/react-core'; /** Properties for creating custom steps in a slider. These properties should be passed in as * an object within an array to the slider component's customSteps property. diff --git a/web/src/components/slider/SliderStep.tsx b/web/src/components/slider/SliderStep.tsx index ad7d6f939..d301c82d3 100644 --- a/web/src/components/slider/SliderStep.tsx +++ b/web/src/components/slider/SliderStep.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; -import styles from '@patternfly/react-styles/css/components/Slider/slider'; import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Slider/slider'; +import * as React from 'react'; export interface SliderStepProps extends Omit, 'label'> { /** Additional classes added to the slider step. */ diff --git a/web/src/components/tooltip/maybe-tooltip.tsx b/web/src/components/tooltip/maybe-tooltip.tsx index 064703c47..0bd9e2b98 100644 --- a/web/src/components/tooltip/maybe-tooltip.tsx +++ b/web/src/components/tooltip/maybe-tooltip.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; import { Tooltip, TooltipProps } from '@patternfly/react-core'; +import * as React from 'react'; export type MaybeTooltipProps = Omit & { content?: React.ReactNode }; diff --git a/web/src/index.tsx b/web/src/index.tsx index c9fa452cf..a8c131a12 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -1,17 +1,17 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; import i18n from 'i18next'; import httpBackend from 'i18next-http-backend'; -import { initReactI18next } from 'react-i18next'; import { configure } from 'mobx'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { initReactI18next } from 'react-i18next'; import '@patternfly/patternfly/patternfly-charts-theme-dark.css'; import '@patternfly/patternfly/patternfly-theme-dark.css'; import '@patternfly/react-core/dist/styles/base.css'; import App from './app'; -import { getLanguage } from './utils/language'; import './index.css'; +import { getLanguage } from './utils/language'; configure({ isolateGlobalState: true }); diff --git a/web/src/model/config.ts b/web/src/model/config.ts index 948c387aa..fff85a19c 100644 --- a/web/src/model/config.ts +++ b/web/src/model/config.ts @@ -1,5 +1,5 @@ -import { FieldConfig } from '../utils/fields'; import { ColumnConfigDef } from '../utils/columns'; +import { FieldConfig } from '../utils/fields'; import { FilterConfigDef } from './filters'; import { RecordType } from './flow-query'; import { RawQuickFilter } from './quick-filters'; diff --git a/web/src/model/metrics.ts b/web/src/model/metrics.ts index 300a6d0a5..f391c7594 100644 --- a/web/src/model/metrics.ts +++ b/web/src/model/metrics.ts @@ -1,6 +1,6 @@ import { MetricStats } from '../api/loki'; -import { StatFunction } from './flow-query'; import { PERCENTILE_VALUES } from '../utils/metrics'; +import { StatFunction } from './flow-query'; export enum MetricScopeOptions { CLUSTER = 'cluster', diff --git a/web/src/model/topology.ts b/web/src/model/topology.ts index 0f29892ac..04d2fc180 100644 --- a/web/src/model/topology.ts +++ b/web/src/model/topology.ts @@ -1,3 +1,4 @@ +import { K8sModel } from '@openshift-console/dynamic-plugin-sdk'; import { EdgeAnimationSpeed, EdgeModel, @@ -12,79 +13,78 @@ import { NodeStatus, Point } from '@patternfly/react-topology'; +import { TFunction } from 'i18next'; import _ from 'lodash'; import { MetricStats, TopologyMetricPeer, TopologyMetrics } from '../api/loki'; +import { TruncateLength } from '../components/dropdowns/truncate-dropdown'; import { Filter, FilterDefinition, Filters, findFromFilters } from '../model/filters'; -import { defaultMetricFunction, defaultMetricType } from '../utils/router'; import { findFilter } from '../utils/filter-definitions'; -import { TFunction } from 'i18next'; -import { K8sModel } from '@openshift-console/dynamic-plugin-sdk'; import { getTopologyEdgeId } from '../utils/ids'; -import { getStat, MetricScopeOptions } from './metrics'; -import { StatFunction, FlowScope, MetricType, NodeType, MetricFunction } from './flow-query'; import { createPeer, getFormattedValue } from '../utils/metrics'; -import { TruncateLength } from '../components/dropdowns/truncate-dropdown'; +import { defaultMetricFunction, defaultMetricType } from '../utils/router'; +import { FlowScope, MetricFunction, MetricType, NodeType, StatFunction } from './flow-query'; +import { getStat, MetricScopeOptions } from './metrics'; export enum LayoutName { - ThreeD = '3d', - BreadthFirst = 'BreadthFirst', - Cola = 'Cola', - ColaNoForce = 'ColaNoForce', - Concentric = 'Concentric', - Dagre = 'Dagre', - Force = 'Force', - Grid = 'Grid', - ColaGroups = 'ColaGroups' + threeD = '3d', + breadthFirst = 'BreadthFirst', + cola = 'Cola', + colaNoForce = 'ColaNoForce', + concentric = 'Concentric', + dagre = 'Dagre', + force = 'Force', + grid = 'Grid', + colaGroups = 'ColaGroups' } export enum TopologyGroupTypes { - NONE = 'none', - CLUSTERS = 'clusters', - CLUSTERS_ZONES = 'clusters+zones', - CLUSTERS_HOSTS = 'clusters+hosts', - CLUSTERS_NAMESPACES = 'clusters+namespaces', - CLUSTERS_OWNERS = 'clusters+owners', - ZONES = 'zones', - ZONES_HOSTS = 'zones+hosts', - ZONES_NAMESPACES = 'zones+namespaces', - ZONES_OWNERS = 'zones+owners', - HOSTS = 'hosts', - HOSTS_NAMESPACES = 'hosts+namespaces', - HOSTS_OWNERS = 'hosts+owners', - NAMESPACES = 'namespaces', - NAMESPACES_OWNERS = 'namespaces+owners', - OWNERS = 'owners' + none = 'none', + clusters = 'clusters', + clustersZones = 'clusters+zones', + clustersHosts = 'clusters+hosts', + clustersNamespaces = 'clusters+namespaces', + clustersOwners = 'clusters+owners', + zones = 'zones', + zonesHosts = 'zones+hosts', + zonesNamespaces = 'zones+namespaces', + zonesOwners = 'zones+owners', + hosts = 'hosts', + hostsNamespaces = 'hosts+namespaces', + hostsOwners = 'hosts+owners', + namespaces = 'namespaces', + namespacesOwners = 'namespaces+owners', + owners = 'owners' } export const getGroupsForScope = (scope: MetricScopeOptions) => { switch (scope) { case MetricScopeOptions.CLUSTER: - return [TopologyGroupTypes.NONE]; + return [TopologyGroupTypes.none]; case MetricScopeOptions.ZONE: - return [TopologyGroupTypes.NONE, TopologyGroupTypes.CLUSTERS]; + return [TopologyGroupTypes.none, TopologyGroupTypes.clusters]; case MetricScopeOptions.HOST: - return [TopologyGroupTypes.NONE, TopologyGroupTypes.CLUSTERS, TopologyGroupTypes.ZONES]; + return [TopologyGroupTypes.none, TopologyGroupTypes.clusters, TopologyGroupTypes.zones]; case MetricScopeOptions.NAMESPACE: return [ - TopologyGroupTypes.NONE, - TopologyGroupTypes.CLUSTERS, - TopologyGroupTypes.CLUSTERS_HOSTS, - TopologyGroupTypes.ZONES, - TopologyGroupTypes.ZONES_HOSTS, - TopologyGroupTypes.HOSTS + TopologyGroupTypes.none, + TopologyGroupTypes.clusters, + TopologyGroupTypes.clustersHosts, + TopologyGroupTypes.zones, + TopologyGroupTypes.zonesHosts, + TopologyGroupTypes.hosts ]; case MetricScopeOptions.OWNER: return [ - TopologyGroupTypes.NONE, - TopologyGroupTypes.CLUSTERS, - TopologyGroupTypes.CLUSTERS_ZONES, - TopologyGroupTypes.ZONES, - TopologyGroupTypes.ZONES_HOSTS, - TopologyGroupTypes.ZONES_NAMESPACES, - TopologyGroupTypes.ZONES_OWNERS, - TopologyGroupTypes.HOSTS, - TopologyGroupTypes.HOSTS_NAMESPACES, - TopologyGroupTypes.NAMESPACES + TopologyGroupTypes.none, + TopologyGroupTypes.clusters, + TopologyGroupTypes.clustersZones, + TopologyGroupTypes.zones, + TopologyGroupTypes.zonesHosts, + TopologyGroupTypes.zonesNamespaces, + TopologyGroupTypes.zonesOwners, + TopologyGroupTypes.hosts, + TopologyGroupTypes.hostsNamespaces, + TopologyGroupTypes.namespaces ]; case MetricScopeOptions.RESOURCE: default: @@ -94,11 +94,11 @@ export const getGroupsForScope = (scope: MetricScopeOptions) => { export const isGroupEnabled = (group: TopologyGroupTypes, enabledScopes: FlowScope[]): boolean => { return ( - (enabledScopes.includes('cluster') || !group.includes(TopologyGroupTypes.CLUSTERS)) && - (enabledScopes.includes('zone') || !group.includes(TopologyGroupTypes.ZONES)) && - (enabledScopes.includes('host') || !group.includes(TopologyGroupTypes.HOSTS)) && - (enabledScopes.includes('namespace') || !group.includes(TopologyGroupTypes.NAMESPACES)) && - (enabledScopes.includes('owner') || !group.includes(TopologyGroupTypes.OWNERS)) + (enabledScopes.includes('cluster') || !group.includes(TopologyGroupTypes.clusters)) && + (enabledScopes.includes('zone') || !group.includes(TopologyGroupTypes.zones)) && + (enabledScopes.includes('host') || !group.includes(TopologyGroupTypes.hosts)) && + (enabledScopes.includes('namespace') || !group.includes(TopologyGroupTypes.namespaces)) && + (enabledScopes.includes('owner') || !group.includes(TopologyGroupTypes.owners)) ); }; @@ -124,8 +124,8 @@ export const DefaultOptions: TopologyOptions = { maxEdgeStat: 0, startCollapsed: false, truncateLength: TruncateLength.M, - layout: LayoutName.ColaNoForce, - groupTypes: TopologyGroupTypes.NONE, + layout: LayoutName.colaNoForce, + groupTypes: TopologyGroupTypes.none, lowScale: 0.3, medScale: 0.5, metricFunction: defaultMetricFunction, @@ -317,11 +317,9 @@ const generateNode = ( const resourceKind = data.peer.resourceKind; const secondaryLabel = data.nodeType !== 'namespace' && - ![ - TopologyGroupTypes.NAMESPACES, - TopologyGroupTypes.NAMESPACES_OWNERS, - TopologyGroupTypes.HOSTS_NAMESPACES - ].includes(options.groupTypes) + ![TopologyGroupTypes.namespaces, TopologyGroupTypes.namespacesOwners, TopologyGroupTypes.hostsNamespaces].includes( + options.groupTypes + ) ? data.peer.namespace : undefined; const shadowed = !_.isEmpty(searchValue) && !(label.includes(searchValue) || secondaryLabel?.includes(searchValue)); @@ -597,54 +595,54 @@ export const generateDataModel = ( const addPossibleGroups = (peer: TopologyMetricPeer): NodeModel | undefined => { const clusterGroup = [ - TopologyGroupTypes.CLUSTERS_HOSTS, - TopologyGroupTypes.CLUSTERS_ZONES, - TopologyGroupTypes.CLUSTERS_NAMESPACES, - TopologyGroupTypes.CLUSTERS_OWNERS, - TopologyGroupTypes.CLUSTERS + TopologyGroupTypes.clustersHosts, + TopologyGroupTypes.clustersZones, + TopologyGroupTypes.clustersNamespaces, + TopologyGroupTypes.clustersOwners, + TopologyGroupTypes.clusters ].includes(options.groupTypes) && !_.isEmpty(peer.clusterName) ? addGroup({ clusterName: peer.clusterName }, 'cluster', undefined, true) : undefined; const zoneGroup = [ - TopologyGroupTypes.CLUSTERS_ZONES, - TopologyGroupTypes.ZONES_HOSTS, - TopologyGroupTypes.ZONES_NAMESPACES, - TopologyGroupTypes.ZONES_OWNERS, - TopologyGroupTypes.ZONES + TopologyGroupTypes.clustersZones, + TopologyGroupTypes.zonesHosts, + TopologyGroupTypes.zonesNamespaces, + TopologyGroupTypes.zonesOwners, + TopologyGroupTypes.zones ].includes(options.groupTypes) && !_.isEmpty(peer.zone) ? addGroup({ zone: peer.zone }, 'cluster', clusterGroup, true) : undefined; const hostGroup = [ - TopologyGroupTypes.CLUSTERS_HOSTS, - TopologyGroupTypes.ZONES_HOSTS, - TopologyGroupTypes.CLUSTERS_HOSTS, - TopologyGroupTypes.HOSTS_NAMESPACES, - TopologyGroupTypes.HOSTS_OWNERS, - TopologyGroupTypes.HOSTS + TopologyGroupTypes.clustersHosts, + TopologyGroupTypes.zonesHosts, + TopologyGroupTypes.clustersHosts, + TopologyGroupTypes.hostsNamespaces, + TopologyGroupTypes.hostsOwners, + TopologyGroupTypes.hosts ].includes(options.groupTypes) && !_.isEmpty(peer.hostName) ? addGroup({ hostName: peer.hostName }, 'host', zoneGroup || clusterGroup, true) : undefined; const namespaceGroup = [ - TopologyGroupTypes.ZONES_NAMESPACES, - TopologyGroupTypes.CLUSTERS_NAMESPACES, - TopologyGroupTypes.CLUSTERS_NAMESPACES, - TopologyGroupTypes.HOSTS_NAMESPACES, - TopologyGroupTypes.NAMESPACES_OWNERS, - TopologyGroupTypes.NAMESPACES + TopologyGroupTypes.zonesNamespaces, + TopologyGroupTypes.clustersNamespaces, + TopologyGroupTypes.clustersNamespaces, + TopologyGroupTypes.hostsNamespaces, + TopologyGroupTypes.namespacesOwners, + TopologyGroupTypes.namespaces ].includes(options.groupTypes) && !_.isEmpty(peer.namespace) ? addGroup({ namespace: peer.namespace }, 'namespace', hostGroup || zoneGroup || clusterGroup) : undefined; const ownerGroup = [ - TopologyGroupTypes.CLUSTERS_OWNERS, - TopologyGroupTypes.ZONES_OWNERS, - TopologyGroupTypes.CLUSTERS_OWNERS, - TopologyGroupTypes.NAMESPACES_OWNERS, - TopologyGroupTypes.HOSTS_OWNERS, - TopologyGroupTypes.OWNERS + TopologyGroupTypes.clustersOwners, + TopologyGroupTypes.zonesOwners, + TopologyGroupTypes.clustersOwners, + TopologyGroupTypes.namespacesOwners, + TopologyGroupTypes.hostsOwners, + TopologyGroupTypes.owners ].includes(options.groupTypes) && peer.owner ? addGroup( { namespace: peer.namespace, owner: peer.owner }, diff --git a/web/src/utils/__tests__/back-and-forth.spec.ts b/web/src/utils/__tests__/back-and-forth.spec.ts index 0cea7dd15..562ec3e9e 100644 --- a/web/src/utils/__tests__/back-and-forth.spec.ts +++ b/web/src/utils/__tests__/back-and-forth.spec.ts @@ -1,11 +1,11 @@ -import { findFilter } from '../filter-definitions'; -import { Filter, FilterId, FilterValue, Filters } from '../../model/filters'; -import { getFlowRecords, getFlowMetrics } from '../../api/routes'; -import { getFetchFunctions, mergeMetricsBNF } from '../back-and-forth'; +import { FlowMetricsResult, RawTopologyMetrics } from '../../api/loki'; +import { getFlowMetrics, getFlowRecords } from '../../api/routes'; +import { FilterDefinitionSample } from '../../components/__tests-data__/filters'; +import { Filter, FilterId, Filters, FilterValue } from '../../model/filters'; import { filtersToString } from '../../model/flow-query'; -import { RawTopologyMetrics, FlowMetricsResult } from '../../api/loki'; +import { getFetchFunctions, mergeMetricsBNF } from '../back-and-forth'; +import { findFilter } from '../filter-definitions'; import { parseTopologyMetrics } from '../metrics'; -import { FilterDefinitionSample } from '../../components/__tests-data__/filters'; jest.mock('../../api/routes', () => ({ getFlowRecords: jest.fn(() => Promise.resolve({ records: [] })), diff --git a/web/src/utils/__tests__/flows.spec.ts b/web/src/utils/__tests__/flows.spec.ts index 717e452b4..300aa17a9 100644 --- a/web/src/utils/__tests__/flows.spec.ts +++ b/web/src/utils/__tests__/flows.spec.ts @@ -1,4 +1,4 @@ -import { Fields, Record, FlowDirection, IfDirection } from '../../api/ipfix'; +import { Fields, FlowDirection, IfDirection, Record } from '../../api/ipfix'; import { mergeFlowReporters } from '../flows'; describe('mergeFlowReporters', () => { diff --git a/web/src/utils/__tests__/port.spec.ts b/web/src/utils/__tests__/port.spec.ts index 8b67f4acd..2a84cf807 100644 --- a/web/src/utils/__tests__/port.spec.ts +++ b/web/src/utils/__tests__/port.spec.ts @@ -1,6 +1,6 @@ -import { formatPort, comparePorts } from '../port'; -import { config } from '../config'; import { FilterDefinitionSample } from '../../components/__tests-data__/filters'; +import { config } from '../config'; +import { comparePorts, formatPort } from '../port'; describe('formatport', () => { beforeEach(() => { diff --git a/web/src/utils/__tests__/protocol.spec.ts b/web/src/utils/__tests__/protocol.spec.ts index 4289a18e9..badbe4479 100644 --- a/web/src/utils/__tests__/protocol.spec.ts +++ b/web/src/utils/__tests__/protocol.spec.ts @@ -1,5 +1,5 @@ -import { compareProtocols, formatProtocol } from '../protocol'; import { FilterDefinitionSample } from '../../components/__tests-data__/filters'; +import { compareProtocols, formatProtocol } from '../protocol'; describe('formatProtocol', () => { it('should format protocol', () => { diff --git a/web/src/utils/__tests__/router.spec.ts b/web/src/utils/__tests__/router.spec.ts index 08cd61475..c79a18fa0 100644 --- a/web/src/utils/__tests__/router.spec.ts +++ b/web/src/utils/__tests__/router.spec.ts @@ -1,8 +1,8 @@ -import { findFilter } from '../filter-definitions'; -import { Filters } from '../../model/filters'; -import { getFiltersFromURL, setURLFilters } from '../router'; import { setNavFunction } from '../../components/dynamic-loader/dynamic-loader'; import { FilterDefinitionSample } from '../../components/__tests-data__/filters'; +import { Filters } from '../../model/filters'; +import { findFilter } from '../filter-definitions'; +import { getFiltersFromURL, setURLFilters } from '../router'; const nav = jest.fn(); setNavFunction(nav); diff --git a/web/src/utils/back-and-forth.ts b/web/src/utils/back-and-forth.ts index 0a9af551d..96c862c27 100644 --- a/web/src/utils/back-and-forth.ts +++ b/web/src/utils/back-and-forth.ts @@ -1,9 +1,9 @@ -import { RecordsResult, FlowMetricsResult } from '../api/loki'; -import { getFlowRecords, getFlowMetrics } from '../api/routes'; -import { swapFilters } from '../components/filters/filters-helper'; +import { FlowMetricsResult, RecordsResult } from '../api/loki'; +import { getFlowMetrics, getFlowRecords } from '../api/routes'; import { Filter, FilterDefinition, Filters } from '../model/filters'; -import { FlowQuery, filtersToString } from '../model/flow-query'; -import { TimeRange, computeStepInterval } from './datetime'; +import { filtersToString, FlowQuery } from '../model/flow-query'; +import { computeStepInterval, TimeRange } from './datetime'; +import { swapFilters } from './filters-helper'; import { mergeStats, substractMetrics, sumMetrics } from './metrics'; export const getFetchFunctions = (filterDefinitions: FilterDefinition[], filters: Filters, matchAny: boolean) => { diff --git a/web/src/utils/columns.ts b/web/src/utils/columns.ts index 27721b07a..4ed2948fa 100644 --- a/web/src/utils/columns.ts +++ b/web/src/utils/columns.ts @@ -1,12 +1,12 @@ import _ from 'lodash'; import { getRecordValue, Record } from '../api/ipfix'; +import { Feature } from '../model/config'; import { FilterId } from '../model/filters'; import { compareNumbers, compareStrings } from './base-compare'; +import { FieldConfig, FieldType } from './fields'; import { compareIPs } from './ip'; import { comparePorts } from './port'; import { compareProtocols } from './protocol'; -import { Feature } from '../model/config'; -import { FieldConfig, FieldType } from './fields'; export enum ColumnsId { starttime = 'StartTime', diff --git a/web/src/utils/config.ts b/web/src/utils/config.ts index 72bb51977..909591ea3 100644 --- a/web/src/utils/config.ts +++ b/web/src/utils/config.ts @@ -1,7 +1,7 @@ -import { defaultConfig } from '../model/config'; import { getConfig, getIngesterMaxChunkAge } from '../api/routes'; -import { getHTTPErrorDetails } from './errors'; +import { defaultConfig } from '../model/config'; import { parseDuration } from './duration'; +import { getHTTPErrorDetails } from './errors'; export let config = defaultConfig; diff --git a/web/src/utils/datetime.ts b/web/src/utils/datetime.ts index ab09ee87a..48599e35d 100644 --- a/web/src/utils/datetime.ts +++ b/web/src/utils/datetime.ts @@ -1,5 +1,5 @@ import { TFunction } from 'i18next'; -import { CUSTOM_TIME_RANGE_KEY } from '../components/dropdowns/time-range-dropdown'; +import { customTimeRangeKey } from '../components/dropdowns/time-range-dropdown'; import { getLanguage } from './language'; const zeroPad = (number: number) => (number < 10 ? `0${number}` : number); @@ -41,7 +41,7 @@ export const getTimeRangeOptions = (t: TFunction, includeCustom = true) => { if (includeCustom) { return { - [CUSTOM_TIME_RANGE_KEY]: t('Custom time range'), + [customTimeRangeKey]: t('Custom time range'), ...timeOptions }; } else { diff --git a/web/src/utils/dns.ts b/web/src/utils/dns.ts index 02194ae34..8e8530c15 100644 --- a/web/src/utils/dns.ts +++ b/web/src/utils/dns.ts @@ -1,7 +1,7 @@ import { ReadOnlyValues } from './values'; // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 -export const DNS_RCODES: ReadOnlyValues = [ +export const dnsRCodes: ReadOnlyValues = [ { value: 0, name: 'NoError', description: 'No Error' }, { value: 1, name: 'FormErr', description: 'Format Error' }, { value: 2, name: 'ServFail', description: 'Server Failure' }, @@ -26,18 +26,18 @@ export const DNS_RCODES: ReadOnlyValues = [ { value: 23, name: 'BADCOOKIE', description: 'Bad/missing Server Cookie' } ] as const; -const dnsRcodesValues = DNS_RCODES.map(v => v.value); -export type DNS_RCODES_VALUES = typeof dnsRcodesValues[number]; +const dnsRcodesValues = dnsRCodes.map(v => v.value); +export type dnsRCodesValues = typeof dnsRcodesValues[number]; -const dnsRcodesNames = DNS_RCODES.map(v => v.name); -export type DNS_CODE_NAMES = typeof dnsRcodesNames[number]; +const dnsRcodesNames = dnsRCodes.map(v => v.name); +export type dnsCodesNames = typeof dnsRcodesNames[number]; -export const getDNSRcodeDescription = (name: DNS_CODE_NAMES): string => { - return DNS_RCODES.find(v => v.name === name)?.description || 'Unassigned'; +export const getDNSRcodeDescription = (name: dnsCodesNames): string => { + return dnsRCodes.find(v => v.name === name)?.description || 'Unassigned'; }; // https://elixir.bootlin.com/linux/v4.7/source/include/uapi/asm-generic/errno-base.h -export const DNS_ERRORS: ReadOnlyValues = [ +export const dnsErrors: ReadOnlyValues = [ { value: 1, name: 'EPERM', description: 'Operation not permitted' }, { value: 2, name: 'ENOENT', description: 'No such file or directory' }, { value: 3, name: 'ESRCH', description: 'No such process' }, @@ -74,12 +74,12 @@ export const DNS_ERRORS: ReadOnlyValues = [ { value: 34, name: 'ERANGE', description: 'Math result not representable' } ] as const; -const dnsErrorsValues = DNS_ERRORS.map(v => v.value); -export type DNS_ERRORS_VALUES = typeof dnsErrorsValues[number]; +const dnsErrorsValues = dnsErrors.map(v => v.value); +export type dnsErrorsValues = typeof dnsErrorsValues[number]; -const dnsErrorsNames = DNS_ERRORS.map(v => v.name); -export type DNS_ERRORS_NAMES = typeof dnsErrorsNames[number]; +const dnsErrorsNames = dnsErrors.map(v => v.name); +export type dnsErrorsNames = typeof dnsErrorsNames[number]; -export const getDNSErrorDescription = (value: DNS_ERRORS_VALUES): string => { - return DNS_ERRORS.find(v => v.value === value)?.description || ''; +export const getDNSErrorDescription = (value: dnsErrorsValues): string => { + return dnsErrors.find(v => v.value === value)?.description || ''; }; diff --git a/web/src/utils/filter-definitions.ts b/web/src/utils/filter-definitions.ts index 90b2b2a17..fa67807e1 100644 --- a/web/src/utils/filter-definitions.ts +++ b/web/src/utils/filter-definitions.ts @@ -1,39 +1,39 @@ -import * as _ from 'lodash'; import { TFunction } from 'i18next'; -import { getPort } from '../utils/port'; -import { validateK8SName, validateStrictK8SName } from './label'; -import { joinResource, SplitResource, splitResource, SplitStage } from '../model/resource'; -import { validateIPFilter } from './ip'; +import * as _ from 'lodash'; import { Field } from '../api/ipfix'; import { - FilterId, - FilterValue, + FilterCategory, + FilterComponent, + FilterConfigDef, FilterDefinition, + FilterId, + FilterOption, FiltersEncoder, - FilterConfigDef, - FilterComponent, - FilterCategory, - FilterOption + FilterValue } from '../model/filters'; +import { joinResource, SplitResource, splitResource, SplitStage } from '../model/resource'; +import { getPort } from '../utils/port'; +import { ColumnConfigDef } from './columns'; import { + findDirectionOption, findProtocolOption, + getClusterOptions, + getDirectionOptionsAsync, + getDnsErrorCodeOptions, + getDnsResponseCodeOptions, + getDropCauseOptions, + getDropStateOptions, + getDSCPOptions, getKindOptions, getNamespaceOptions, getPortOptions, getProtocolOptions, getResourceOptions, - noOption, - getDnsResponseCodeOptions, - getDropStateOptions, - getDropCauseOptions, - getDirectionOptionsAsync, - findDirectionOption, - getDnsErrorCodeOptions, - getDSCPOptions, getZoneOptions, - getClusterOptions + noOption } from './filter-options'; -import { ColumnConfigDef } from './columns'; +import { validateIPFilter } from './ip'; +import { validateK8SName, validateStrictK8SName } from './label'; // Convenience string to filter by undefined field values export const undefinedValue = '""'; diff --git a/web/src/utils/filter-options.ts b/web/src/utils/filter-options.ts index d5a4f3fa4..b16e2ebb3 100644 --- a/web/src/utils/filter-options.ts +++ b/web/src/utils/filter-options.ts @@ -1,15 +1,15 @@ +import { TFunction } from 'i18next'; import * as _ from 'lodash'; import protocols from 'protocol-numbers'; -import { getClusters, getNamespaces, getResources, getZones } from '../api/routes'; import { FlowDirection } from '../api/ipfix'; +import { getClusters, getNamespaces, getResources, getZones } from '../api/routes'; import { FilterOption } from '../model/filters'; import { splitResource, SplitStage } from '../model/resource'; import { autoCompleteCache } from './autocomplete-cache'; -import { DNS_ERRORS, DNS_RCODES } from './dns'; -import { getPort, getService } from './port'; -import { DROP_CAUSES, DROP_STATES } from './pkt-drop'; -import { TFunction } from 'i18next'; +import { dnsErrors, dnsRCodes } from './dns'; import { DSCP_VALUES } from './dscp'; +import { dropCauses, dropStates } from './pkt-drop'; +import { getPort, getService } from './port'; export const noOption: (value: string) => Promise = () => Promise.resolve([]); @@ -131,33 +131,33 @@ export const getPortOptions = (value: string): Promise => { export const getDropStateOptions = (value: string): Promise => { return Promise.resolve( - DROP_STATES.filter( - opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) - ).map(v => ({ name: v.name.replace('TCP_', ''), value: v.name })) // map only names here since codes are stringified in storage + dropStates + .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name.replace('TCP_', ''), value: v.name })) // map only names here since codes are stringified in storage ); }; export const getDropCauseOptions = (value: string): Promise => { return Promise.resolve( - DROP_CAUSES.filter( - opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) - ).map(v => ({ name: v.name.replace('SKB_DROP_REASON_', ''), value: v.name })) // map only names here since codes are stringified in storage + dropCauses + .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name.replace('SKB_DROP_REASON_', ''), value: v.name })) // map only names here since codes are stringified in storage ); }; export const getDnsResponseCodeOptions = (value: string): Promise => { return Promise.resolve( - DNS_RCODES.filter( - opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) - ).map(v => ({ name: v.name, value: v.name })) // map only names here since codes are stringified in storage + dnsRCodes + .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name, value: v.name })) // map only names here since codes are stringified in storage ); }; export const getDnsErrorCodeOptions = (value: string): Promise => { return Promise.resolve( - DNS_ERRORS.filter( - opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) - ).map(v => ({ name: v.name, value: String(v.value) })) + dnsErrors + .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name, value: String(v.value) })) ); }; diff --git a/web/src/components/filters/filters-helper.ts b/web/src/utils/filters-helper.ts similarity index 92% rename from web/src/components/filters/filters-helper.ts rename to web/src/utils/filters-helper.ts index 1d7039841..336618590 100644 --- a/web/src/components/filters/filters-helper.ts +++ b/web/src/utils/filters-helper.ts @@ -1,6 +1,6 @@ import { TFunction } from 'i18next'; -import { findFilter } from '../../utils/filter-definitions'; -import { Filter, FilterDefinition, FilterId } from '../../model/filters'; +import { Filter, FilterDefinition, FilterId } from '../model/filters'; +import { findFilter } from './filter-definitions'; export type Indicator = 'default' | 'success' | 'warning' | 'error' | undefined; diff --git a/web/src/utils/icmp.ts b/web/src/utils/icmp.ts index 4103c24f4..a2bff3bce 100644 --- a/web/src/utils/icmp.ts +++ b/web/src/utils/icmp.ts @@ -1,12 +1,12 @@ import { ReadOnlyValue, ReadOnlyValues } from './values'; // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml -export const ICMP_PROTO = 1; -export const ICMP_V6_PROTO = 58; -export const ICMP_PROTOS = [ICMP_PROTO, ICMP_V6_PROTO]; +export const icmpProto = 1; +export const icmpV6Proto = 58; +export const icmpProtos = [icmpProto, icmpV6Proto]; //https://github.com/torvalds/linux/blob/master/include/uapi/linux/icmp.h -export const ICMP_TYPES: ReadOnlyValues = [ +export const icmpTypes: ReadOnlyValues = [ { value: 0, name: 'ICMP_ECHOREPLY', description: 'Echo Reply' }, { value: 3, name: 'ICMP_DEST_UNREACH', description: 'Destination Unreachable' }, { value: 4, name: 'ICMP_SOURCE_QUENCH', description: 'Source Quench' }, @@ -23,13 +23,13 @@ export const ICMP_TYPES: ReadOnlyValues = [ //{ value: 18, name: 'NR_ICMP_TYPES' }, ] as const; -const icmpTypesValues = ICMP_TYPES.map(v => v.value); -export type ICMP_TYPES_VALUES = typeof icmpTypesValues[number]; +const icmpTypesValues = icmpTypes.map(v => v.value); +export type icmpTypesValues = typeof icmpTypesValues[number]; -const icmpTypesNames = ICMP_TYPES.map(v => v.name); -export type ICMP_TYPE_NAMES = typeof icmpTypesNames[number]; +const icmpTypesNames = icmpTypes.map(v => v.name); +export type icmpTypeNames = typeof icmpTypesNames[number]; -export const ICMP_UNREACH_CODES: ReadOnlyValues = [ +export const icmpUnreachCodes: ReadOnlyValues = [ { value: 0, name: 'ICMP_NET_UNREACH', description: 'Network Unreachable' }, { value: 1, name: 'ICMP_HOST_UNREACH', description: 'Host Unreachable' }, { value: 2, name: 'ICMP_PROT_UNREACH', description: 'Protocol Unreachable' }, @@ -49,38 +49,38 @@ export const ICMP_UNREACH_CODES: ReadOnlyValues = [ //{ value: 15, name: 'NR_ICMP_UNREACH', description: 'instead of hardcoding immediate value' }, ] as const; -const icmpUnreachCodesValues = ICMP_UNREACH_CODES.map(v => v.value); -export type ICMP_UNREACH_CODES_VALUES = typeof icmpUnreachCodesValues[number]; +const icmpUnreachCodesValues = icmpUnreachCodes.map(v => v.value); +export type icmpUnreachCodesValues = typeof icmpUnreachCodesValues[number]; -const icmpUnreachCodesNames = ICMP_UNREACH_CODES.map(v => v.name); -export type ICMP_UNREACH_CODES_NAMES = typeof icmpUnreachCodesNames[number]; +const icmpUnreachCodesNames = icmpUnreachCodes.map(v => v.name); +export type icmpUnreachCodesNames = typeof icmpUnreachCodesNames[number]; -export const ICMP_REDIRECT_CODES: ReadOnlyValues = [ +export const icmpRedirectCodes: ReadOnlyValues = [ { value: 0, name: 'ICMP_REDIR_NET', description: 'Redirect Net' }, { value: 1, name: 'ICMP_REDIR_HOST', description: 'Redirect Host' }, { value: 2, name: 'ICMP_REDIR_NETTOS', description: 'Redirect Net for TOS' }, { value: 3, name: 'ICMP_REDIR_HOSTTOS', description: 'Redirect Host for TOS' } ] as const; -const icmpRedirectCodesValues = ICMP_REDIRECT_CODES.map(v => v.value); -export type ICMP_REDIRECT_CODES_VALUES = typeof icmpRedirectCodesValues[number]; +const icmpRedirectCodesValues = icmpRedirectCodes.map(v => v.value); +export type icmpRedirectCodesValues = typeof icmpRedirectCodesValues[number]; -const icmpRedirectCodesNames = ICMP_REDIRECT_CODES.map(v => v.name); -export type ICMP_REDIRECT_CODES_NAMES = typeof icmpRedirectCodesNames[number]; +const icmpRedirectCodesNames = icmpRedirectCodes.map(v => v.name); +export type icmpRedirectCodesNames = typeof icmpRedirectCodesNames[number]; -export const ICMP_TIME_EXCEEDED_CODES: ReadOnlyValues = [ +export const icmpTimeExceededCodes: ReadOnlyValues = [ { value: 0, name: 'ICMP_EXC_TTL', description: 'TTL count exceeded' }, { value: 1, name: 'ICMP_EXC_FRAGTIME', description: 'Fragment Reass time exceeded' } ] as const; -const icmpTimeExceededCodesValues = ICMP_TIME_EXCEEDED_CODES.map(v => v.value); -export type ICMP_TIME_EXCEEDED_CODES_VALUES = typeof icmpTimeExceededCodesValues[number]; +const icmpTimeExceededCodesValues = icmpTimeExceededCodes.map(v => v.value); +export type icmpTimeExceededCodesValues = typeof icmpTimeExceededCodesValues[number]; -const icmpTimeExceededCodesNames = ICMP_TIME_EXCEEDED_CODES.map(v => v.name); -export type ICMP_TIME_EXCEEDED_CODES_NAMES = typeof icmpTimeExceededCodesNames[number]; +const icmpTimeExceededCodesNames = icmpTimeExceededCodes.map(v => v.name); +export type icmpTimeExceededCodesNames = typeof icmpTimeExceededCodesNames[number]; // https://github.com/torvalds/linux/blob/master/include/uapi/linux/icmpv6.h -export const ICMP_V6_TYPES: ReadOnlyValues = [ +export const icmpV6Types: ReadOnlyValues = [ { value: 1, name: 'ICMPV6_DEST_UNREACH', description: 'Destination Unreachable' }, { value: 2, name: 'ICMPV6_PKT_TOOBIG', description: 'Packet Too Big' }, { value: 3, name: 'ICMPV6_TIME_EXCEED', description: 'Time Exceeded' }, @@ -103,13 +103,13 @@ export const ICMP_V6_TYPES: ReadOnlyValues = [ { value: 255, name: 'ICMPV6_MSG_MAX', description: 'Reserved for expansion of ICMPv6 informational messages' } ] as const; -const icmpv6TypesValues = ICMP_V6_TYPES.map(v => v.value); -export type ICMP_V6_TYPES_VALUES = typeof icmpv6TypesValues[number]; +const icmpv6TypesValues = icmpV6Types.map(v => v.value); +export type icmpV6TypesValues = typeof icmpv6TypesValues[number]; -const icmpv6TypesNames = ICMP_V6_TYPES.map(v => v.name); -export type ICMP_V6_TYPE_NAMES = typeof icmpv6TypesNames[number]; +const icmpv6TypesNames = icmpV6Types.map(v => v.name); +export type icmpV6TypeNames = typeof icmpv6TypesNames[number]; -export const ICMP_V6_UNREACH_CODES: ReadOnlyValues = [ +export const icmpV6UnreachCodes: ReadOnlyValues = [ { value: 0, name: 'ICMPV6_NOROUTE', description: 'no route to destination' }, { value: 1, @@ -123,51 +123,51 @@ export const ICMP_V6_UNREACH_CODES: ReadOnlyValues = [ { value: 6, name: 'ICMPV6_REJECT_ROUTE', description: 'reject route to destination' } ] as const; -const icmpv6UnreachCodesValues = ICMP_V6_UNREACH_CODES.map(v => v.value); -export type ICMP_V6_UNREACH_CODES_VALUES = typeof icmpv6UnreachCodesValues[number]; +const icmpv6UnreachCodesValues = icmpV6UnreachCodes.map(v => v.value); +export type icmpV6UnreachCodesValues = typeof icmpv6UnreachCodesValues[number]; -const icmpv6UnreachCodesNames = ICMP_V6_UNREACH_CODES.map(v => v.name); -export type ICMP_V6_UNREACH_CODES_NAMES = typeof icmpv6UnreachCodesNames[number]; +const icmpv6UnreachCodesNames = icmpV6UnreachCodes.map(v => v.name); +export type icmpV6UnreachCodesNames = typeof icmpv6UnreachCodesNames[number]; -export const ICMP_V6_TIME_EXCEEDED_CODES: ReadOnlyValues = [ +export const icmpV6TimeExceededCodes: ReadOnlyValues = [ { value: 0, name: 'ICMPV6_EXC_HOPLIMIT', description: 'hop limit exceeded in transit ' }, { value: 1, name: 'ICMPV6_EXC_FRAGTIME', description: 'fragment reassembly time exceeded ' } ] as const; -const icmpv6TimeExceededCodesValues = ICMP_V6_TIME_EXCEEDED_CODES.map(v => v.value); -export type ICMP_V6_TIME_EXCEEDED_CODES_VALUES = typeof icmpv6TimeExceededCodesValues[number]; +const icmpv6TimeExceededCodesValues = icmpV6TimeExceededCodes.map(v => v.value); +export type icmpV6TimeExceededCodesValues = typeof icmpv6TimeExceededCodesValues[number]; -const icmpv6TimeExceededCodesNames = ICMP_V6_TIME_EXCEEDED_CODES.map(v => v.name); -export type ICMP_V6_TIME_EXCEEDED_CODES_NAMES = typeof icmpv6TimeExceededCodesNames[number]; +const icmpv6TimeExceededCodesNames = icmpV6TimeExceededCodes.map(v => v.name); +export type icmpV6TimeExceededCodesNames = typeof icmpv6TimeExceededCodesNames[number]; -export const ICMP_V6_PARAMPROB_CODES: ReadOnlyValues = [ +export const icmpV6ParamprobCodes: ReadOnlyValues = [ { value: 0, name: 'ICMPV6_HDR_FIELD', description: 'erroneous header field encountered' }, { value: 1, name: 'ICMPV6_UNK_NEXTHDR', description: 'unrecognized Next Header type encountered' }, { value: 2, name: 'ICMPV6_UNK_OPTION', description: 'unrecognized IPv6 option encountered' }, { value: 3, name: 'ICMPV6_HDR_INCOMP', description: 'IPv6 First Fragment has incomplete IPv6 Header Chain' } ] as const; -const icmpv6ParamprobCodesValues = ICMP_V6_PARAMPROB_CODES.map(v => v.value); -export type ICMP_V6_PARAMPROB_CODES_VALUES = typeof icmpv6ParamprobCodesValues[number]; +const icmpv6ParamprobCodesValues = icmpV6ParamprobCodes.map(v => v.value); +export type icmpV6ParamprobCodesValues = typeof icmpv6ParamprobCodesValues[number]; -const icmpv6ParamprobCodesNames = ICMP_V6_PARAMPROB_CODES.map(v => v.name); -export type ICMP_V6_PARAMPROB_CODES_NAMES = typeof icmpv6ParamprobCodesNames[number]; +const icmpv6ParamprobCodesNames = icmpV6ParamprobCodes.map(v => v.name); +export type icmpV6ParamprobCodesNames = typeof icmpv6ParamprobCodesNames[number]; -export type ICMP_ALL_TYPES_VALUES = ICMP_TYPES_VALUES | ICMP_V6_TYPES_VALUES; +export type icmpAllTypesValues = icmpTypesValues | icmpV6TypesValues; -export type ICMP_ALL_CODES_VALUES = - | ICMP_UNREACH_CODES_VALUES - | ICMP_REDIRECT_CODES_VALUES - | ICMP_TIME_EXCEEDED_CODES_VALUES - | ICMP_V6_UNREACH_CODES_VALUES - | ICMP_V6_TIME_EXCEEDED_CODES_VALUES - | ICMP_V6_PARAMPROB_CODES_VALUES; +export type icmpAllCodesValues = + | icmpUnreachCodesValues + | icmpRedirectCodesValues + | icmpTimeExceededCodesValues + | icmpV6UnreachCodesValues + | icmpV6TimeExceededCodesValues + | icmpV6ParamprobCodesValues; export const getICMPDocUrl = (p: number): string | undefined => { switch (p) { - case ICMP_PROTO: + case icmpProto: return 'https://github.com/torvalds/linux/blob/master/include/uapi/linux/icmp.h'; - case ICMP_V6_PROTO: + case icmpV6Proto: return 'https://github.com/torvalds/linux/blob/master/include/uapi/linux/icmpv6.h'; default: return undefined; @@ -175,47 +175,43 @@ export const getICMPDocUrl = (p: number): string | undefined => { }; export const isValidICMPProto = (p: number) => { - return ICMP_PROTOS.includes(p); + return icmpProtos.includes(p); }; -export const getICMPType = (p: number, v: ICMP_ALL_TYPES_VALUES): ReadOnlyValue | undefined => { +export const getICMPType = (p: number, v: icmpAllTypesValues): ReadOnlyValue | undefined => { if (!isValidICMPProto(p)) { return undefined; } - if (p === ICMP_PROTO) { - return ICMP_TYPES.find(t => t.value === v); + if (p === icmpProto) { + return icmpTypes.find(t => t.value === v); } - return ICMP_V6_TYPES.find(t => t.value === v); + return icmpV6Types.find(t => t.value === v); }; -export const getICMPCode = ( - p: number, - t?: ICMP_ALL_TYPES_VALUES, - c?: ICMP_ALL_CODES_VALUES -): ReadOnlyValue | undefined => { +export const getICMPCode = (p: number, t?: icmpAllTypesValues, c?: icmpAllCodesValues): ReadOnlyValue | undefined => { if (!isValidICMPProto(p) || !t || !c) { return undefined; } - if (p == ICMP_PROTO) { + if (p == icmpProto) { switch (t) { case 3: // ICMP_DEST_UNREACH: - return ICMP_UNREACH_CODES.find(v => v.value === c); + return icmpUnreachCodes.find(v => v.value === c); case 5: // ICMP_REDIRECT: - return ICMP_REDIRECT_CODES.find(v => v.value === c); + return icmpRedirectCodes.find(v => v.value === c); case 11: // ICMP_TIME_EXCEEDED: - return ICMP_TIME_EXCEEDED_CODES.find(v => v.value === c); + return icmpTimeExceededCodes.find(v => v.value === c); default: return undefined; } } switch (t) { case 1: // ICMPV6_DEST_UNREACH - return ICMP_V6_UNREACH_CODES.find(v => v.value === c); + return icmpV6UnreachCodes.find(v => v.value === c); case 3: // ICMPV6_TIME_EXCEED - return ICMP_V6_TIME_EXCEEDED_CODES.find(v => v.value === c); + return icmpV6TimeExceededCodes.find(v => v.value === c); case 4: // ICMPV6_PARAMPROB - return ICMP_V6_PARAMPROB_CODES.find(v => v.value === c); + return icmpV6ParamprobCodes.find(v => v.value === c); default: return undefined; } diff --git a/web/src/utils/local-storage-hook.ts b/web/src/utils/local-storage-hook.ts index 0a0ac7df9..cd2af501a 100644 --- a/web/src/utils/local-storage-hook.ts +++ b/web/src/utils/local-storage-hook.ts @@ -1,41 +1,41 @@ import _ from 'lodash'; import * as React from 'react'; -export const LOCAL_STORAGE_PLUGIN_KEY = 'netobserv-plugin-settings'; -export const LOCAL_STORAGE_COLS_KEY = 'netflow-traffic-columns'; -export const LOCAL_STORAGE_COLS_SIZES_KEY = 'netflow-traffic-column-sizes'; -export const LOCAL_STORAGE_EXPORT_COLS_KEY = 'netflow-traffic-export-columns'; -export const LOCAL_STORAGE_REFRESH_KEY = 'netflow-traffic-refresh'; -export const LOCAL_STORAGE_SIZE_KEY = 'netflow-traffic-size-size'; -export const LOCAL_STORAGE_VIEW_ID_KEY = 'netflow-traffic-view-id'; -export const LOCAL_STORAGE_OVERVIEW_TRUNCATE_KEY = 'netflow-traffic-overview-truncate'; -export const LOCAL_STORAGE_OVERVIEW_FOCUS_KEY = 'netflow-traffic-overview-focus'; -export const LOCAL_STORAGE_TOPOLOGY_OPTIONS_KEY = 'netflow-traffic-topology-options'; -export const LOCAL_STORAGE_QUERY_PARAMS_KEY = 'netflow-traffic-query-params'; -export const LOCAL_STORAGE_DISABLED_FILTERS_KEY = 'netflow-traffic-disabled-filters'; -export const LOCAL_STORAGE_SORT_ID_KEY = 'netflow-traffic-sort-id'; -export const LOCAL_STORAGE_SORT_DIRECTION_KEY = 'netflow-traffic-sort-direction'; -export const LOCAL_STORAGE_OVERVIEW_IDS_KEY = 'netflows-traffic-overview-ids'; -export const LOCAL_STORAGE_LAST_LIMIT_KEY = 'netflow-traffic-limit'; -export const LOCAL_STORAGE_LAST_TOP_KEY = 'netflow-traffic-top'; -export const LOCAL_STORAGE_METRIC_SCOPE_KEY = 'netflow-traffic-metric-scope'; -export const LOCAL_STORAGE_METRIC_FUNCTION_KEY = 'netflow-traffic-metric-function'; -export const LOCAL_STORAGE_METRIC_TYPE_KEY = 'netflow-traffic-metric-type'; -export const LOCAL_STORAGE_SHOW_OPTIONS_KEY = 'netflow-traffic-show-options'; -export const LOCAL_STORAGE_SHOW_HISTOGRAM_KEY = 'netflow-traffic-show-histogram'; -export const LOCAL_STORAGE_SHOW_FILTERS_KEY = 'netflow-traffic-show-filters'; -export const LOCAL_STORAGE_HISTOGRAM_GUIDED_TOUR_DONE_KEY = 'netflow-traffic-histogram-guided-tour-done'; -export const LOCAL_STORAGE_OVERVIEW_DONUT_DIMENSION_KEY = 'netflow-traffic-overview-donut-dimension'; -export const LOCAL_STORAGE_OVERVIEW_METRICS_DIMENSION_KEY = 'netflow-traffic-overview-metrics-dimension'; -export const LOCAL_STORAGE_OVERVIEW_METRICS_TOTAL_DIMENSION_KEY = 'netflow-traffic-overview-metrics-total-dimension'; -export const LOCAL_STORAGE_OVERVIEW_KEBAB_KEY = 'netflow-traffic-overview-kebab-map'; +export const localStoragePluginKey = 'netobserv-plugin-settings'; +export const localStorageColsKey = 'netflow-traffic-columns'; +export const localStorageColsSizesKey = 'netflow-traffic-column-sizes'; +export const localStorageExportColsKey = 'netflow-traffic-export-columns'; +export const localStorageRefreshKey = 'netflow-traffic-refresh'; +export const localStorageSizeKey = 'netflow-traffic-size-size'; +export const localStorageViewIdKey = 'netflow-traffic-view-id'; +export const localStorageOverviewTruncateKey = 'netflow-traffic-overview-truncate'; +export const localStorageOverviewFocusKey = 'netflow-traffic-overview-focus'; +export const localStorageTopologyOptionsKey = 'netflow-traffic-topology-options'; +export const localStorageQueryParamsKey = 'netflow-traffic-query-params'; +export const localStorageDisabledFiltersKey = 'netflow-traffic-disabled-filters'; +export const localStorageSortIdKey = 'netflow-traffic-sort-id'; +export const localStorageSortDirectionKey = 'netflow-traffic-sort-direction'; +export const localStorageOverviewIdsKey = 'netflows-traffic-overview-ids'; +export const localStorageLastLimitKey = 'netflow-traffic-limit'; +export const localStorageLastTopKey = 'netflow-traffic-top'; +export const localStorageMetricScopeKey = 'netflow-traffic-metric-scope'; +export const localStorageMetricFunctionKey = 'netflow-traffic-metric-function'; +export const localStorageMetricTypeKey = 'netflow-traffic-metric-type'; +export const localStorageShowOptionsKey = 'netflow-traffic-show-options'; +export const localStorageShowHistogramKey = 'netflow-traffic-show-histogram'; +export const localStorageShowFiltersKey = 'netflow-traffic-show-filters'; +export const localStorageHistogramGuidedTourDoneKey = 'netflow-traffic-histogram-guided-tour-done'; +export const localStorageOverviewDonutDimensionKey = 'netflow-traffic-overview-donut-dimension'; +export const localStorageOverviewMetricsDimensionKey = 'netflow-traffic-overview-metrics-dimension'; +export const localStorageOverviewMetricsTotalDimensionKey = 'netflow-traffic-overview-metrics-total-dimension'; +export const localStorageOverviewKebabKey = 'netflow-traffic-overview-kebab-map'; export interface ArraySelectionOptions { id: string; criteria: string; } -export const DEFAULT_ARRAY_SELECTION_OPTIONS = { +export const defaultArraySelectionOptions = { id: 'id', criteria: 'isSelected' }; @@ -57,7 +57,7 @@ export function useLocalStorage( setStoredValue(stateValue); // Reload from localStorage - const item = window.localStorage.getItem(LOCAL_STORAGE_PLUGIN_KEY); + const item = window.localStorage.getItem(localStoragePluginKey); const parsedItem = item ? JSON.parse(item) : {}; // Stora maps as object @@ -75,7 +75,7 @@ export function useLocalStorage( } // Save to localStorage - window.localStorage.setItem(LOCAL_STORAGE_PLUGIN_KEY, JSON.stringify(parsedItem)); + window.localStorage.setItem(localStoragePluginKey, JSON.stringify(parsedItem)); } catch (error) { console.error(error); clearLocalStorage(); @@ -86,7 +86,7 @@ export function useLocalStorage( export function getLocalStorage(key: string, initialValue?: T, opts?: ArraySelectionOptions) { try { - const item = window.localStorage.getItem(LOCAL_STORAGE_PLUGIN_KEY); + const item = window.localStorage.getItem(localStoragePluginKey); const param = item ? JSON.parse(item)[key] : undefined; // Manage array selection by ids if opts is set @@ -112,8 +112,8 @@ export function getLocalStorage(key: string, initialValue?: T, opts?: ArraySe export function clearLocalStorage() { try { - console.info('clearing local storage ' + LOCAL_STORAGE_PLUGIN_KEY); - window.localStorage.removeItem(LOCAL_STORAGE_PLUGIN_KEY); + console.info('clearing local storage ' + localStoragePluginKey); + window.localStorage.removeItem(localStoragePluginKey); } catch (error) { console.error(error); } diff --git a/web/src/components/metrics/metrics-helper.tsx b/web/src/utils/metrics-helper.ts similarity index 85% rename from web/src/components/metrics/metrics-helper.tsx rename to web/src/utils/metrics-helper.ts index 5a86c3ca2..c8995af15 100644 --- a/web/src/components/metrics/metrics-helper.tsx +++ b/web/src/utils/metrics-helper.ts @@ -1,13 +1,13 @@ -import { ChartLegendTooltip, createContainer, getResizeObserver } from '@patternfly/react-charts'; +import { getResizeObserver } from '@patternfly/react-charts'; import { TFunction } from 'i18next'; import * as React from 'react'; -import { getDateSInMiliseconds } from '../../utils/duration'; -import { NamedMetric, TopologyMetricPeer, TopologyMetrics, GenericMetric } from '../../api/loki'; -import { FlowScope } from '../../model/flow-query'; -import { NodeData } from '../../model/topology'; -import { getDateFromUnix, getFormattedDate, TimeRange } from '../../utils/datetime'; -import { isUnknownPeer, matchPeer } from '../../utils/metrics'; -import { TruncateLength } from '../dropdowns/truncate-dropdown'; +import { GenericMetric, NamedMetric, TopologyMetricPeer, TopologyMetrics } from '../api/loki'; +import { TruncateLength } from '../components/dropdowns/truncate-dropdown'; +import { FlowScope } from '../model/flow-query'; +import { NodeData } from '../model/topology'; +import { getDateFromUnix, getFormattedDate, TimeRange } from './datetime'; +import { getDateSInMiliseconds } from './duration'; +import { isUnknownPeer, matchPeer } from './metrics'; export type LegendDataItem = { childName?: string; @@ -51,23 +51,6 @@ export const toHistogramDatapoints = (metric: NamedMetric): ChartDataPoint[] => return result; }; -export const chartVoronoi = (legendData: LegendDataItem[], f: (v: number) => string) => { - const CursorVoronoiContainer = createContainer('voronoi', 'cursor'); - const tooltipData = legendData.map(item => ({ ...item, name: item.tooltipName || item.name })); - return ( - { - return dp.datum.y || dp.datum.y === 0 ? f(dp.datum.y) : 'n/a'; - }} - labelComponent={ datum.date} />} - mouseFollowTooltips - voronoiDimension="x" - voronoiPadding={50} - /> - ); -}; - export const getHistogramRangeFromLimit = (totalMetric: NamedMetric, limit: number, start?: number): TimeRange => { let limitCount = 0, from = 0, diff --git a/web/src/utils/overview-panels.ts b/web/src/utils/overview-panels.ts index a0c7d52bc..9d00f5e26 100644 --- a/web/src/utils/overview-panels.ts +++ b/web/src/utils/overview-panels.ts @@ -1,11 +1,11 @@ import { TFunction } from 'i18next'; -import { AggregateBy, StatFunction, MetricType, MetricFunction } from '../model/flow-query'; +import { AggregateBy, MetricFunction, MetricType, StatFunction } from '../model/flow-query'; import { Feature, isAllowed } from './features-gate'; -export const DNS_ID_MATCHER = 'dns_latency'; -export const RTT_ID_MATCHER = 'rtt'; -export const DROPPED_ID_MATCHER = 'dropped'; -export const CUSTOM_PANEL_MATCHER = 'custom'; +export const dnsIdMatcher = 'dns_latency'; +export const rttIdMatcher = 'rtt'; +export const droppedIdMatcher = 'dropped'; +export const customPanelMatcher = 'custom'; export const getRateFunctionFromId = (id: string) => { return id.endsWith('byte_rates') ? 'bytes' : 'packets'; @@ -124,7 +124,7 @@ export const getDefaultOverviewPanels = (customIds?: string[]): OverviewPanel[] }); if (customIds) { - ids = ids.concat(customIds.map(id => `${CUSTOM_PANEL_MATCHER}_${id}` as OverviewPanelId)); + ids = ids.concat(customIds.map(id => `${customPanelMatcher}_${id}` as OverviewPanelId)); } return ids.map(id => { @@ -134,7 +134,7 @@ export const getDefaultOverviewPanels = (customIds?: string[]): OverviewPanel[] export const parseCustomMetricId = (id: string) => { const idParts = id.split('_'); - if (idParts.length === 0 || idParts[0] !== CUSTOM_PANEL_MATCHER) { + if (idParts.length === 0 || idParts[0] !== customPanelMatcher) { console.error('parseCustomMetricId called on non custom metric', id); } diff --git a/web/src/utils/pkt-drop.ts b/web/src/utils/pkt-drop.ts index 953038cfe..afcf19c34 100644 --- a/web/src/utils/pkt-drop.ts +++ b/web/src/utils/pkt-drop.ts @@ -3,7 +3,7 @@ import { ReadOnlyValues } from './values'; const coreDropSubSystem = 0 << 16; const ovsDropSubSystem = 3 << 16; // https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h -export const DROP_STATES: ReadOnlyValues = [ +export const dropStates: ReadOnlyValues = [ { value: 0, name: 'TCP_INVALID_STATE' }, { value: 1, name: 'TCP_ESTABLISHED' }, { value: 2, name: 'TCP_SYN_SENT' }, @@ -18,13 +18,13 @@ export const DROP_STATES: ReadOnlyValues = [ { value: 11, name: 'TCP_NEW_SYN_RECV' } ] as const; -const dropStatesValues = DROP_STATES.map(v => v.value); -export type DROP_STATES_VALUES = typeof dropStatesValues[number]; +const dropStatesValues = dropStates.map(v => v.value); +export type dropStatesValues = typeof dropStatesValues[number]; -const dropStatesNames = DROP_STATES.map(v => v.name); -export type DROP_STATES_NAMES = typeof dropStatesNames[number]; +const dropStatesNames = dropStates.map(v => v.name); +export type dropStatesNames = typeof dropStatesNames[number]; // https://github.com/torvalds/linux/blob/master/include/net/dropreason-core.h -export const DROP_CAUSES: ReadOnlyValues = [ +export const dropCauses: ReadOnlyValues = [ { value: coreDropSubSystem + 2, name: 'SKB_DROP_REASON_NOT_SPECIFIED', description: 'drop reason is not specified' }, { value: coreDropSubSystem + 3, name: 'SKB_DROP_REASON_NO_SOCKET', description: 'socket not found' }, { value: coreDropSubSystem + 4, name: 'SKB_DROP_REASON_PKT_TOO_SMALL', description: 'packet size is too small' }, @@ -340,24 +340,24 @@ export const DROP_CAUSES: ReadOnlyValues = [ } ] as const; -const dropCausesValues = DROP_CAUSES.map(v => v.value); -export type DROP_CAUSES_VALUES = typeof dropCausesValues[number]; +const dropCausesValues = dropCauses.map(v => v.value); +export type dropCausesValues = typeof dropCausesValues[number]; -const dropCausesNames = DROP_CAUSES.map(v => v.name); -export type DROP_CAUSES_NAMES = typeof dropCausesNames[number]; +const dropCausesNames = dropCauses.map(v => v.name); +export type dropCausesNames = typeof dropCausesNames[number]; -export const CORE_DROP_CAUSES_DOC_URL = 'https://github.com/torvalds/linux/blob/master/include/net/dropreason-core.h'; -export const OVS_DROP_CAUSES_DOC_URL = +export const coreDropCausesDocUrl = 'https://github.com/torvalds/linux/blob/master/include/net/dropreason-core.h'; +export const ovsDropCausesDocUrl = 'https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/openvswitch/drop.h'; -export const getDropCauseDescription = (name: DROP_CAUSES_NAMES): string => { - return DROP_CAUSES.find(v => v.name === name)?.description || 'Unknown'; +export const getDropCauseDescription = (name: dropCausesNames): string => { + return dropCauses.find(v => v.name === name)?.description || 'Unknown'; }; -export const getDropCauseDocUrl = (name: DROP_CAUSES_NAMES): string => { +export const getDropCauseDocUrl = (name: dropCausesNames): string => { if (name.startsWith('OVS_')) { - return OVS_DROP_CAUSES_DOC_URL; + return ovsDropCausesDocUrl; } - return CORE_DROP_CAUSES_DOC_URL; + return coreDropCausesDocUrl; }; diff --git a/web/src/utils/router.ts b/web/src/utils/router.ts index 8a39a713b..d01478854 100644 --- a/web/src/utils/router.ts +++ b/web/src/utils/router.ts @@ -1,6 +1,15 @@ -import { findFilter } from './filter-definitions'; +import { + createFilterValue, + DisabledFilters, + Filter, + FilterDefinition, + filterKey, + Filters, + fromFilterKey +} from '../model/filters'; +import { DataSource, Match, MetricType, PacketLoss, RecordType, StatFunction } from '../model/flow-query'; import { TimeRange } from './datetime'; -import { Match, StatFunction, MetricType, PacketLoss, RecordType, DataSource } from '../model/flow-query'; +import { findFilter } from './filter-definitions'; import { getURLParam, getURLParamAsBool, @@ -10,15 +19,6 @@ import { setURLParam, URLParam } from './url'; -import { - createFilterValue, - DisabledFilters, - Filter, - FilterDefinition, - filterKey, - Filters, - fromFilterKey -} from '../model/filters'; const filtersSeparator = ';'; const filterKVSeparator = '=';