From a3db9c2defa5f1e0a7597302c4be2495912f9be1 Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Tue, 12 Mar 2024 10:14:26 +0100 Subject: [PATCH] NodeDirection / IfDirection --- config/sample-config.yaml | 43 +++++++++------ pkg/handler/topology.go | 7 ++- pkg/loki/filter.go | 12 +++-- pkg/loki/flow_query.go | 12 +---- pkg/model/fields/fields.go | 13 +++++ pkg/server/server_test.go | 54 ------------------- .../{FlowDirection.md => NodeDirection.md} | 0 web/locales/en/plugin__netobserv-plugin.json | 1 + web/src/api/ipfix.ts | 22 +++----- .../__tests__/record-panel.spec.tsx | 1 - .../netflow-record/record-field.tsx | 41 +++++++++----- .../netflow-record/record-panel.tsx | 37 ++++++------- web/src/components/netflow-traffic.tsx | 1 - web/src/utils/columns.ts | 6 +-- web/src/utils/filter-definitions.ts | 2 +- web/src/utils/filter-options.ts | 21 +++++--- 16 files changed, 123 insertions(+), 150 deletions(-) rename web/docs/enums/{FlowDirection.md => NodeDirection.md} (100%) diff --git a/config/sample-config.yaml b/config/sample-config.yaml index 0faae9fab..815108834 100644 --- a/config/sample-config.yaml +++ b/config/sample-config.yaml @@ -12,6 +12,7 @@ loki: - DstK8S_Type - K8S_FlowLayer - FlowDirection +# - Duplicate # - _RecordType # - K8S_ClusterName # - SrcK8S_Zone @@ -443,23 +444,30 @@ frontend: default: false width: 10 - id: FlowDirection - name: Direction - tooltip: The direction of the flow observed at the Node observation point. + name: Node Direction + tooltip: The interpreted direction of the flow observed at the Node observation point. field: FlowDirection - filter: direction + filter: flowDirection default: false width: 10 - - id: Interface - name: Interface - tooltip: The network interface of the Flow. - field: Interface - filter: interface + - id: Interfaces + name: Interfaces + tooltip: The network interfaces of the Flow. + field: Interfaces + filter: interfaces + default: false + width: 10 + - id: IfDirections + name: Interface Directions + tooltip: The directions of the Flow observed at the network interface observation point. + field: IfDirections + filter: ifdirections default: false width: 10 - id: FlowDirInts name: Interfaces and Directions - tooltip: Pairs of network interface and direction of the Flow observed at the Node observation point. - field: FlowDirection + tooltip: Pairs of network interface and direction of the Flow observed at the network interface observation point. + field: Interfaces default: false width: 15 - id: Bytes @@ -819,21 +827,26 @@ frontend: name: ICMP code component: number hint: Specify an ICMP code value as integer number. - - id: direction - name: Direction + - id: flowDirection + name: Node Direction component: autocomplete placeholder: 'E.g: Ingress, Egress, Inner' - hint: Specify the direction of the Flow observed at the Node observation point. + hint: Specify the interpreted direction of the Flow observed at the Node observation point. - id: flow_layer name: Flow layer component: text placeholder: 'Either infra or app' hint: Specify the layer of Flow. - - id: interface - name: Network interface + - id: interfaces + name: Network interfaces component: text placeholder: 'E.g: br-ex, ovn-k8s-mp0' hint: Specify a network interface. + - id: ifdirections + name: Interface Directions + component: autocomplete + placeholder: 'E.g: Ingress, Egress' + hint: Specify the direction of the Flow observed at the network interface observation point. - id: id name: Conversation Id component: text diff --git a/pkg/handler/topology.go b/pkg/handler/topology.go index f16439d23..4feb04a90 100644 --- a/pkg/handler/topology.go +++ b/pkg/handler/topology.go @@ -93,7 +93,7 @@ func getTopologyFlows(cfg *loki.Config, client httpclient.Caller, params url.Val if err != nil { return nil, http.StatusBadRequest, err } - if shouldMergeReporters(metricType, cfg.Deduper) { + if shouldMergeReporters(metricType) { filterGroups = expandReportersMergeQueries(filterGroups) } @@ -135,9 +135,8 @@ func getTopologyFlows(cfg *loki.Config, client httpclient.Caller, params url.Val return qr, http.StatusOK, nil } -func shouldMergeReporters(metricType constants.MetricType, deduper loki.Deduper) bool { - return !deduper.Merge && deduper.Mark && (metricType == constants.MetricTypeBytes || - metricType == constants.MetricTypePackets) +func shouldMergeReporters(metricType constants.MetricType) bool { + return metricType == constants.MetricTypeBytes || metricType == constants.MetricTypePackets } func expandReportersMergeQueries(queries filters.MultiQueries) filters.MultiQueries { diff --git a/pkg/loki/filter.go b/pkg/loki/filter.go index 7b8228cc8..9abe8776e 100644 --- a/pkg/loki/filter.go +++ b/pkg/loki/filter.go @@ -127,10 +127,16 @@ func (f *labelFilter) writeInto(sb *strings.Builder) { sb.WriteString(`ip("`) sb.WriteString(f.value) sb.WriteString(`")`) - case typeRegexContains, typeRegexArrayContains: + case typeRegexContains: + // match any case sb.WriteString("`(?i).*") sb.WriteString(f.value) sb.WriteString(".*`") + case typeRegexArrayContains: + // match any case and ensure we stay inside the array + sb.WriteString("`(?i)[^]]*") + sb.WriteString(f.value) + sb.WriteString("[^]]*`") default: panic(fmt.Sprint("wrong filter value type", int(f.valueType))) } @@ -312,9 +318,9 @@ func (f *lineFilter) writeInto(sb *strings.Builder) { sb.WriteString(`.*"`) // for array, we ensure it starts by [ and ends by ] case typeRegexArrayContains: - sb.WriteString(`\[(?i).*`) + sb.WriteString(`\[(?i)[^]]*`) sb.WriteString(valueReplacer.Replace(v.value)) - sb.WriteString(`.*]`) + sb.WriteString(`[^]]*]`) } } } diff --git a/pkg/loki/flow_query.go b/pkg/loki/flow_query.go index fe2069a48..57e9ccf4f 100644 --- a/pkg/loki/flow_query.go +++ b/pkg/loki/flow_query.go @@ -181,22 +181,12 @@ func (q *FlowQueryBuilder) addLineFilters(key string, values []string, not bool, return } - var isArray bool - if q.config.Deduper.Merge { - switch key { - case "FlowDirection", "Interface": - key = fmt.Sprintf("%ss", key) - isArray = true - default: - isArray = false - } - } - lf := lineFilter{ key: key, not: not, moreThan: moreThan, } + isArray := fields.IsArray(key) isNumeric := fields.IsNumeric(key) emptyMatches := false for _, value := range values { diff --git a/pkg/model/fields/fields.go b/pkg/model/fields/fields.go index 74214d408..fa363370f 100644 --- a/pkg/model/fields/fields.go +++ b/pkg/model/fields/fields.go @@ -41,6 +41,8 @@ const ( DSCP = "Dscp" PktDropBytes = "PktDropBytes" FlowDirection = "FlowDirection" + Interfaces = "Interfaces" + IfDirections = "IfDirections" DNSID = "DnsId" DNSLatency = "DnsLatencyMs" DNSErrNo = "DnsErrno" @@ -80,3 +82,14 @@ func IsIP(f string) bool { return false } } + +func IsArray(v string) bool { + switch v { + case + IfDirections, + Interfaces: + return true + default: + return false + } +} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 7652719cb..cc41df676 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -396,60 +396,6 @@ func TestLokiConfigurationForTopology(t *testing.T) { assert.NotNil(t, qr.Result) } -func TestLokiConfigurationForDeduperMerge(t *testing.T) { - // GIVEN a Loki service - lokiMock := httpMock{} - lokiMock.On("ServeHTTP", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - _, _ = args.Get(0).(http.ResponseWriter).Write([]byte(`{"status":"","data":{"resultType":"matrix","result":[]}}`)) - }) - lokiSvc := httptest.NewServer(&lokiMock) - defer lokiSvc.Close() - authM := &authMock{} - authM.MockGranted() - lokiURL, err := url.Parse(lokiSvc.URL) - require.NoError(t, err) - - // THAT is accessed behind the NOO console plugin backend - backendRoutes := setupRoutes(&Config{ - Loki: loki.Config{ - URL: lokiURL, - Timeout: time.Second, - Labels: utils.GetMapInterface([]string{fields.SrcNamespace, fields.DstNamespace, fields.SrcOwnerName, fields.DstOwnerName}), - Deduper: loki.Deduper{ - Mark: false, - Merge: true, - }, - }, - }, authM) - backendSvc := httptest.NewServer(backendRoutes) - defer backendSvc.Close() - - // WHEN the Loki flows endpoint is queried in the backend - resp, err := backendSvc.Client().Get(backendSvc.URL + "/api/loki/flow/metrics") - require.NoError(t, err) - - // THEN the query has been properly forwarded to Loki - // Two queries for dedup - assert.Len(t, lokiMock.Calls, 1) - req1 := lokiMock.Calls[0].Arguments[1].(*http.Request) - queries := []string{req1.URL.Query().Get("query")} - expected := []string{ - `topk(100,sum by(SrcK8S_Name,SrcK8S_Type,SrcK8S_OwnerName,SrcK8S_OwnerType,SrcK8S_Namespace,SrcAddr,SrcK8S_HostName,DstK8S_Name,DstK8S_Type,DstK8S_OwnerName,DstK8S_OwnerType,DstK8S_Namespace,DstAddr,DstK8S_HostName)(rate({app="netobserv-flowcollector"}|json|unwrap Bytes|__error__=""[1m])))`, - } - assert.Equal(t, expected, queries) - - // without any multi-tenancy header - assert.Empty(t, req1.Header.Get("X-Scope-OrgID")) - - // AND the response is sent back to the client - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - var qr model.AggregatedQueryResponse - err = json.Unmarshal(body, &qr) - require.NoError(t, err) - assert.NotNil(t, qr.Result) -} - func TestLokiConfigurationForTableHistogram(t *testing.T) { // GIVEN a Loki service lokiMock := httpMock{} diff --git a/web/docs/enums/FlowDirection.md b/web/docs/enums/NodeDirection.md similarity index 100% rename from web/docs/enums/FlowDirection.md rename to web/docs/enums/NodeDirection.md diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 9d190c81a..bfaeb032a 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -264,6 +264,7 @@ "Examples": "Examples", "ICMP type provided but protocol is {{proto}}": "ICMP type provided but protocol is {{proto}}", "ICMP code provided but protocol is {{proto}}": "ICMP code provided but protocol is {{proto}}", + "Invalid data provided. Check JSON for details.": "Invalid data provided. Check JSON for details.", "dropped": "dropped", "dropped by": "dropped by", "sent": "sent", diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index ec4c7c20c..32bcdecf7 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -41,7 +41,7 @@ export interface Labels { SrcK8S_Type?: string; /** Kind of the destination matched Kubernetes object, such as Pod name, Service name, etc. */ DstK8S_Type?: string; - /** Flow direction from the node observation point, only when using eBPF deduper "JustMark" */ + /** Flow direction from the node observation point*/ FlowDirection?: FlowDirection; /** Type of record: 'flowLog' for regular flow logs, or 'allConnections', * 'newConnection', 'heartbeat', 'endConnection' for conversation tracking */ @@ -57,7 +57,7 @@ export enum FlowDirection { Inner = '2' } -export const getFlowDirectionDisplayString = (value: FlowDirection, t: TFunction) => { +export const getDirectionDisplayString = (value: FlowDirection, t: TFunction) => { return value === FlowDirection.Ingress ? t('Ingress') : value === FlowDirection.Egress @@ -67,11 +67,7 @@ export const getFlowDirectionDisplayString = (value: FlowDirection, t: TFunction : t('n/a'); }; -export const getFlowDirection = (flow: Record): FlowDirection => { - return String(flow.labels.FlowDirection || flow.fields.FlowDirection!) as FlowDirection; -}; - -export enum InterfaceDirection { +export enum IfDirection { /** Incoming traffic, from the network interface observation point */ Ingress = '0', /** Outgoing traffic, from the network interface observation point */ @@ -115,16 +111,10 @@ export interface Fields { K8S_ClusterName?: string; /** L4 protocol */ Proto: number; - /** Flow direction of the first flow captured, only when using eBPF deduper 'merge' mode */ - FlowDirection?: FlowDirection; - /** Flow direction array, only when using eBPF deduper 'merge' mode */ - FlowDirections?: number[]; - /** Network interface */ - Interface?: string; - /** Network interface array, only when using eBPF deduper 'merge' mode */ + /** Network interface array */ Interfaces?: string[]; - /** Flow direction from the network interface observation point */ - IfDirection?: InterfaceDirection; + /** Flow direction array from the network interface observation point */ + IfDirections?: IfDirection[]; /** Logical OR combination of unique TCP flags comprised in the flow, as per RFC-9293, with additional custom flags to represent the following per-packet combinations: SYN+ACK (0x100), FIN+ACK (0x200) and RST+ACK (0x400). */ Flags?: number; /** Number of packets */ 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 2f64249b6..c614f29b2 100644 --- a/web/src/components/netflow-record/__tests__/record-panel.spec.tsx +++ b/web/src/components/netflow-record/__tests__/record-panel.spec.tsx @@ -16,7 +16,6 @@ describe('', () => { type: 'flowLog', canSwitchTypes: false, allowPktDrops: false, - deduperMerge: false, setFilters: jest.fn(), setRange: jest.fn(), setType: jest.fn(), diff --git a/web/src/components/netflow-record/record-field.tsx b/web/src/components/netflow-record/record-field.tsx index 5b3a389f7..c6d3fa845 100644 --- a/web/src/components/netflow-record/record-field.tsx +++ b/web/src/components/netflow-record/record-field.tsx @@ -4,7 +4,7 @@ import { FilterIcon, GlobeAmericasIcon, TimesIcon, ToggleOffIcon, ToggleOnIcon } import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { FlowDirection, getFlowDirection, getFlowDirectionDisplayString, Record } from '../../api/ipfix'; +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'; @@ -485,21 +485,43 @@ export const RecordField: React.FC<{ } return singleContainer(child); } - case ColumnsId.flowdir: { - return singleContainer(simpleTextWithTooltip(getFlowDirectionDisplayString(String(value) as FlowDirection, t))); + case ColumnsId.nodedir: + case ColumnsId.ifdirs: { + if (Array.isArray(value)) { + return nthContainer( + value.map(dir => simpleTextWithTooltip(getDirectionDisplayString(String(dir) as FlowDirection, t))), + true, + false, + false + ); + } + return singleContainer(simpleTextWithTooltip(getDirectionDisplayString(String(value) as FlowDirection, t))); + } + case ColumnsId.interfaces: { + if (Array.isArray(value)) { + return nthContainer( + value.map(iName => simpleTextWithTooltip(String(iName))), + true, + false, + false + ); + } + return singleContainer(simpleTextWithTooltip(String(value))); } case ColumnsId.flowdirints: { if ( flow.fields.Interfaces && - flow.fields.FlowDirections && - flow.fields.Interfaces.length === flow.fields.FlowDirections.length + flow.fields.IfDirections && + Array.isArray(flow.fields.Interfaces) && + Array.isArray(flow.fields.IfDirections) && + flow.fields.Interfaces.length === flow.fields.IfDirections.length ) { return nthContainer( flow.fields.Interfaces.map((iName, i) => sideBySideContainer( simpleTextWithTooltip(iName), simpleTextWithTooltip( - getFlowDirectionDisplayString(String(flow.fields.FlowDirections![i]) as FlowDirection, t) + getDirectionDisplayString(String(flow.fields.IfDirections![i]) as FlowDirection, t) ) ) ), @@ -508,12 +530,7 @@ export const RecordField: React.FC<{ false ); } else { - return singleContainer( - sideBySideContainer( - simpleTextWithTooltip(flow.fields.Interface), - simpleTextWithTooltip(getFlowDirectionDisplayString(getFlowDirection(flow), t)) - ) - ); + return singleContainer(emptyText(t('Invalid data provided. Check JSON for details.'))); } } case ColumnsId.packets: diff --git a/web/src/components/netflow-record/record-panel.tsx b/web/src/components/netflow-record/record-panel.tsx index d13786fca..bc855f9f4 100644 --- a/web/src/components/netflow-record/record-panel.tsx +++ b/web/src/components/netflow-record/record-panel.tsx @@ -25,7 +25,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { defaultSize, maxSize, minSize } from '../../utils/panel'; import { defaultTimeRange } from '../../utils/router'; -import { FlowDirection, getFlowDirectionDisplayString, Record } from '../../api/ipfix'; +import { FlowDirection, getDirectionDisplayString, Record } from '../../api/ipfix'; import { Column, ColumnGroup, ColumnsId, getColumnGroups, getShortColumnName } from '../../utils/columns'; import { TimeRange } from '../../utils/datetime'; import { doesIncludeFilter, Filter, FilterDefinition, findFromFilters, removeFromFilters } from '../../model/filters'; @@ -44,7 +44,6 @@ export type RecordDrawerProps = { canSwitchTypes: boolean; allowPktDrops: boolean; isDark?: boolean; - deduperMerge: boolean; setFilters: (v: Filter[]) => void; setRange: (r: number | TimeRange) => void; setType: (r: RecordType) => void; @@ -63,7 +62,6 @@ export const RecordPanel: React.FC = ({ canSwitchTypes, allowPktDrops, isDark, - deduperMerge, setFilters, setRange, setType, @@ -75,12 +73,12 @@ export const RecordPanel: React.FC = ({ // hide empty columns const getVisibleColumns = React.useCallback(() => { - const forbiddenColumns = deduperMerge ? [ColumnsId.flowdir, ColumnsId.interface] : [ColumnsId.flowdirints]; + const forbiddenColumns = [ColumnsId.ifdirs, ColumnsId.interfaces]; return columns.filter((c: Column) => { const value = c.value(record); return !forbiddenColumns.includes(c.id) && value !== null && value !== '' && !Number.isNaN(value); }); - }, [columns, deduperMerge, record]); + }, [columns, record]); const toggle = React.useCallback( (id: string) => { @@ -148,22 +146,26 @@ export const RecordPanel: React.FC = ({ }, [canSwitchTypes, setType, type]); const getDirIntsFilter = React.useCallback((): RecordFieldFilter | undefined => { - if (!deduperMerge) { + //get interface filter and values + const interfaceCol = columns.find(c => c.id === ColumnsId.interfaces); + if (!interfaceCol) { + console.error("getDirIntsFilter can't find interfaceCol"); return undefined; } - - //get interface filter and values - const interfaceCol = columns.find(c => c.id === ColumnsId.interface); const interfaceFilterKey = { def: findFilter(filterDefinitions, interfaceCol!.quickFilter!)! }; const interfaceFilterValues = _.uniq(record.fields.Interfaces)!.map(v => ({ v, display: v })); const isDeleteInterface = doesIncludeFilter(filters, interfaceFilterKey, interfaceFilterValues); //get direction filter and values - const directionCol = columns.find(c => c.id === ColumnsId.flowdir); + const directionCol = columns.find(c => c.id === ColumnsId.ifdirs); + if (!directionCol) { + console.error("getDirIntsFilter can't find directionCol"); + return undefined; + } const directionFilterKey = { def: findFilter(filterDefinitions, directionCol!.quickFilter!)! }; - const directionFilterValues = _.uniq(record.fields.FlowDirections)!.map(v => ({ + const directionFilterValues = _.uniq(record.fields.IfDirections)!.map(v => ({ v: String(v), - display: getFlowDirectionDisplayString(String(v) as FlowDirection, t) + display: getDirectionDisplayString(String(v) as FlowDirection, t) })); const isDeleteDirection = doesIncludeFilter(filters, directionFilterKey, directionFilterValues); @@ -195,16 +197,7 @@ export const RecordPanel: React.FC = ({ }, isDelete }; - }, [ - columns, - deduperMerge, - filterDefinitions, - filters, - record.fields.FlowDirections, - record.fields.Interfaces, - setFilters, - t - ]); + }, [columns, filterDefinitions, filters, record.fields.IfDirections, record.fields.Interfaces, setFilters, t]); const getGenericFilter = React.useCallback( (col: Column, value: unknown): RecordFieldFilter | undefined => { diff --git a/web/src/components/netflow-traffic.tsx b/web/src/components/netflow-traffic.tsx index 840e0f2b9..1f160018b 100644 --- a/web/src/components/netflow-traffic.tsx +++ b/web/src/components/netflow-traffic.tsx @@ -1328,7 +1328,6 @@ export const NetflowTraffic: React.FC = ({ forcedFilters, i range={range} type={recordType} isDark={isDarkTheme} - deduperMerge={config.deduper.merge} canSwitchTypes={isFlow() && isConnectionTracking()} allowPktDrops={isPktDrop()} setFilters={setFiltersList} diff --git a/web/src/utils/columns.ts b/web/src/utils/columns.ts index 87424e694..7df00840c 100644 --- a/web/src/utils/columns.ts +++ b/web/src/utils/columns.ts @@ -67,11 +67,11 @@ export enum ColumnsId { hostname = 'K8S_HostName', srchostname = 'SrcK8S_HostName', dsthostname = 'DstK8S_HostName', - flowdir = 'FlowDirection', - flowdirplural = 'FlowDirections', + nodedir = 'FlowDirection', rttTime = 'TimeFlowRttMs', hashid = '_HashId', - interface = 'Interface', + interfaces = 'Interfaces', + ifdirs = 'IfDirections', flowdirints = 'FlowDirInts', recordtype = 'RecordType', bytesab = 'Bytes_AB', diff --git a/web/src/utils/filter-definitions.ts b/web/src/utils/filter-definitions.ts index 1a897e01e..168b91fb7 100644 --- a/web/src/utils/filter-definitions.ts +++ b/web/src/utils/filter-definitions.ts @@ -285,7 +285,7 @@ export const getFilterDefinitions = ( getOptions = getProtocolOptions; validate = protoValidation; } else if (d.id.includes('direction')) { - getOptions = v => getDirectionOptionsAsync(v, t); + getOptions = v => getDirectionOptionsAsync(v, t, d.id === 'nodedirection'); validate = dirValidation; } else if (d.id.includes('drop_state')) { getOptions = getDropStateOptions; diff --git a/web/src/utils/filter-options.ts b/web/src/utils/filter-options.ts index 6bb701d86..d5a4f3fa4 100644 --- a/web/src/utils/filter-options.ts +++ b/web/src/utils/filter-options.ts @@ -29,17 +29,22 @@ export const getProtocolOptions = (value: string): Promise => { return Promise.resolve(opts); }; -export const getDirectionOptions = (t: TFunction): FilterOption[] => { - return [ +export const getDirectionOptions = (t: TFunction, allowInner: boolean): FilterOption[] => { + const directions = [ { name: t('Ingress'), value: String(FlowDirection.Ingress) }, - { name: t('Egress'), value: String(FlowDirection.Egress) }, - { name: t('Inner'), value: String(FlowDirection.Inner) } + { name: t('Egress'), value: String(FlowDirection.Egress) } ]; + if (allowInner) { + return directions.concat({ name: t('Inner'), value: String(FlowDirection.Inner) }); + } + return directions; }; -export const getDirectionOptionsAsync = (value: string, t: TFunction): Promise => { +export const getDirectionOptionsAsync = (value: string, t: TFunction, allowInner: boolean): Promise => { return Promise.resolve( - getDirectionOptions(t).filter(o => o.value === value || o.name.toLowerCase().includes(value.toLowerCase())) + getDirectionOptions(t, allowInner).filter( + o => o.value === value || o.name.toLowerCase().includes(value.toLowerCase()) + ) ); }; @@ -169,5 +174,7 @@ export const findProtocolOption = (nameOrVal: string) => { }; export const findDirectionOption = (nameOrVal: string, t: TFunction) => { - return getDirectionOptions(t).find(o => o.name.toLowerCase() === nameOrVal.toLowerCase() || o.value === nameOrVal); + return getDirectionOptions(t, true).find( + o => o.name.toLowerCase() === nameOrVal.toLowerCase() || o.value === nameOrVal + ); };