diff --git a/config/sample-config.yaml b/config/sample-config.yaml index 1e0a7b2e0..39c35a4b0 100644 --- a/config/sample-config.yaml +++ b/config/sample-config.yaml @@ -47,7 +47,7 @@ frontend: # - pktDrop # - dnsTracking # - flowRTT - # - networkEvents + # - multiNetworks # - packetTranslation # processor features # - multiCluster diff --git a/mocks/loki/flow_metrics_cluster.json b/mocks/loki/flow_metrics_cluster.json index 28a472ae2..7f04bd018 100644 --- a/mocks/loki/flow_metrics_cluster.json +++ b/mocks/loki/flow_metrics_cluster.json @@ -4,35 +4,142 @@ "resultType": "matrix", "result": [ { - "metric": {}, + "metric": { + "K8S_ClusterName": "production-cluster" + }, + "values": [ + [ + 1708009560, + "320.5" + ], + [ + 1708009920, + "385.2" + ], + [ + 1708010280, + "425.8" + ], + [ + 1708010640, + "445.3" + ], + [ + 1708011000, + "438.7" + ], + [ + 1708011360, + "465.1" + ], + [ + 1708011720, + "478.9" + ] + ] + }, + { + "metric": { + "K8S_ClusterName": "staging-cluster" + }, + "values": [ + [ + 1708009560, + "180.3" + ], + [ + 1708009920, + "225.8" + ], + [ + 1708010280, + "245.2" + ], + [ + 1708010640, + "258.7" + ], + [ + 1708011000, + "252.1" + ], + [ + 1708011360, + "268.5" + ], + [ + 1708011720, + "275.2" + ] + ] + }, + { + "metric": { + "K8S_ClusterName": "development-cluster" + }, + "values": [ + [ + 1708009560, + "95.8" + ], + [ + 1708009920, + "112.5" + ], + [ + 1708010280, + "128.3" + ], + [ + 1708010640, + "135.7" + ], + [ + 1708011000, + "132.2" + ], + [ + 1708011360, + "142.8" + ], + [ + 1708011720, + "148.5" + ] + ] + }, + { + "metric": { + "K8S_ClusterName": "edge-cluster" + }, "values": [ [ 1708009560, - "149.26388888888889" + "65.2" ], [ 1708009920, - "374.69722222222225" + "78.5" ], [ 1708010280, - "453.8902777777778" + "85.1" ], [ 1708010640, - "478.52638888888896" + "92.3" ], [ 1708011000, - "467.9861111111112" + "88.7" ], [ 1708011360, - "511.25972222222225" + "95.2" ], [ 1708011720, - "518.5291666666666" + "98.8" ] ] } @@ -46,7 +153,7 @@ "execTime": 0.718415087, "queueTime": 11.360357994, "subqueries": 0, - "totalEntriesReturned": 1, + "totalEntriesReturned": 4, "splits": 6, "shards": 48, "totalPostFilterLines": 199944, diff --git a/mocks/loki/flow_metrics_dropped_owner.json b/mocks/loki/flow_metrics_dropped_owner.json index 73c81a3ec..d18b0f543 100644 --- a/mocks/loki/flow_metrics_dropped_owner.json +++ b/mocks/loki/flow_metrics_dropped_owner.json @@ -184,6 +184,106 @@ "0.001388888888888889" ] ] + }, + { + "metric": { + "DstK8S_Namespace": "production", + "DstK8S_OwnerName": "gateway-api", + "DstK8S_OwnerType": "Gateway", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "web-deployment", + "SrcK8S_OwnerType": "Deployment" + }, + "values": [ + [ + 1708011360, + "0.5" + ], + [ + 1708011720, + "0.8" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "gateway-system", + "DstK8S_OwnerName": "gateway-external", + "DstK8S_OwnerType": "Gateway", + "SrcK8S_Namespace": "gateway-system", + "SrcK8S_OwnerName": "gateway-internal", + "SrcK8S_OwnerType": "Gateway" + }, + "values": [ + [ + 1708011360, + "0.3" + ], + [ + 1708011720, + "0.6" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "vms", + "DstK8S_OwnerName": "vm-ubuntu-server", + "DstK8S_OwnerType": "VirtualMachine", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vm-centos-server", + "SrcK8S_OwnerType": "VirtualMachine" + }, + "values": [ + [ + 1708011360, + "0.2" + ], + [ + 1708011720, + "0.4" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "vms", + "DstK8S_OwnerName": "vmi-windows", + "DstK8S_OwnerType": "VirtualMachineInstance", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vmi-linux", + "SrcK8S_OwnerType": "VirtualMachineInstance" + }, + "values": [ + [ + 1708011360, + "0.15" + ], + [ + 1708011720, + "0.35" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "production", + "DstK8S_OwnerName": "app-deployment", + "DstK8S_OwnerType": "Deployment", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vm-ubuntu-server", + "SrcK8S_OwnerType": "VirtualMachine" + }, + "values": [ + [ + 1708011360, + "0.1" + ], + [ + 1708011720, + "0.25" + ] + ] } ], "stats": { @@ -195,7 +295,7 @@ "execTime": 0.509199253, "queueTime": 5.761458995, "subqueries": 0, - "totalEntriesReturned": 11, + "totalEntriesReturned": 16, "splits": 6, "shards": 48, "totalPostFilterLines": 810, diff --git a/mocks/loki/flow_metrics_dropped_udn.json b/mocks/loki/flow_metrics_dropped_udn.json index c62d99a73..04cdb7bb8 100644 --- a/mocks/loki/flow_metrics_dropped_udn.json +++ b/mocks/loki/flow_metrics_dropped_udn.json @@ -32,6 +32,68 @@ "0.49166666666666664" ] ] + }, + { + "metric": { + "DstK8S_NetworkName": "production-network", + "DstK8S_Type": "UserDefinedNetwork", + "SrcAddr": "10.128.0.90", + "SrcK8S_Name": "app-pod-1", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "app-deployment", + "SrcK8S_OwnerType": "Deployment", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708011360, + "0.15" + ], + [ + 1708011720, + "0.25" + ] + ] + }, + { + "metric": { + "DstK8S_NetworkName": "development-network", + "DstK8S_Type": "UserDefinedNetwork", + "SrcK8S_NetworkName": "production-network", + "SrcK8S_Type": "UserDefinedNetwork" + }, + "values": [ + [ + 1708011360, + "0.08" + ], + [ + 1708011720, + "0.12" + ] + ] + }, + { + "metric": { + "DstK8S_NetworkName": "cluster-network", + "DstK8S_Type": "ClusterUserDefinedNetwork", + "SrcAddr": "10.128.0.100", + "SrcK8S_Name": "cluster-pod", + "SrcK8S_Namespace": "kube-system", + "SrcK8S_OwnerName": "cluster-deployment", + "SrcK8S_OwnerType": "Deployment", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708011360, + "0.1" + ], + [ + 1708011720, + "0.18" + ] + ] } ], "stats": { @@ -43,7 +105,7 @@ "execTime": 0.585506558, "queueTime": 5.295528995, "subqueries": 0, - "totalEntriesReturned": 2, + "totalEntriesReturned": 5, "splits": 6, "shards": 48, "totalPostFilterLines": 810, diff --git a/mocks/loki/flow_metrics_owner.json b/mocks/loki/flow_metrics_owner.json index b8becafe6..30b5c6a24 100644 --- a/mocks/loki/flow_metrics_owner.json +++ b/mocks/loki/flow_metrics_owner.json @@ -1977,6 +1977,206 @@ "2.2222222222222223" ] ] + }, + { + "metric": { + "DstK8S_Namespace": "production", + "DstK8S_OwnerName": "gateway-api", + "DstK8S_OwnerType": "Gateway", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "web-deployment", + "SrcK8S_OwnerType": "Deployment" + }, + "values": [ + [ + 1708009560, + "200.5" + ], + [ + 1708009920, + "225.8" + ], + [ + 1708010280, + "245.3" + ], + [ + 1708010640, + "258.7" + ], + [ + 1708011000, + "252.1" + ], + [ + 1708011360, + "265.4" + ], + [ + 1708011720, + "270.2" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "gateway-system", + "DstK8S_OwnerName": "gateway-external", + "DstK8S_OwnerType": "Gateway", + "SrcK8S_Namespace": "gateway-system", + "SrcK8S_OwnerName": "gateway-internal", + "SrcK8S_OwnerType": "Gateway" + }, + "values": [ + [ + 1708009560, + "150.2" + ], + [ + 1708009920, + "168.5" + ], + [ + 1708010280, + "182.3" + ], + [ + 1708010640, + "195.8" + ], + [ + 1708011000, + "188.1" + ], + [ + 1708011360, + "202.5" + ], + [ + 1708011720, + "210.7" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "vms", + "DstK8S_OwnerName": "vm-ubuntu-server", + "DstK8S_OwnerType": "VirtualMachine", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vm-centos-server", + "SrcK8S_OwnerType": "VirtualMachine" + }, + "values": [ + [ + 1708009560, + "95.3" + ], + [ + 1708009920, + "108.7" + ], + [ + 1708010280, + "118.2" + ], + [ + 1708010640, + "125.8" + ], + [ + 1708011000, + "122.1" + ], + [ + 1708011360, + "132.5" + ], + [ + 1708011720, + "138.9" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "vms", + "DstK8S_OwnerName": "vmi-windows", + "DstK8S_OwnerType": "VirtualMachineInstance", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vmi-linux", + "SrcK8S_OwnerType": "VirtualMachineInstance" + }, + "values": [ + [ + 1708009560, + "88.2" + ], + [ + 1708009920, + "98.5" + ], + [ + 1708010280, + "105.8" + ], + [ + 1708010640, + "112.3" + ], + [ + 1708011000, + "109.1" + ], + [ + 1708011360, + "118.7" + ], + [ + 1708011720, + "125.2" + ] + ] + }, + { + "metric": { + "DstK8S_Namespace": "production", + "DstK8S_OwnerName": "app-deployment", + "DstK8S_OwnerType": "Deployment", + "SrcK8S_Namespace": "vms", + "SrcK8S_OwnerName": "vm-ubuntu-server", + "SrcK8S_OwnerType": "VirtualMachine" + }, + "values": [ + [ + 1708009560, + "65.4" + ], + [ + 1708009920, + "72.8" + ], + [ + 1708010280, + "78.1" + ], + [ + 1708010640, + "82.5" + ], + [ + 1708011000, + "79.9" + ], + [ + 1708011360, + "85.2" + ], + [ + 1708011720, + "88.7" + ] + ] } ], "stats": { @@ -1988,7 +2188,7 @@ "execTime": 0.77872763, "queueTime": 13.016442995, "subqueries": 0, - "totalEntriesReturned": 53, + "totalEntriesReturned": 58, "splits": 6, "shards": 48, "totalPostFilterLines": 199944, diff --git a/mocks/loki/flow_metrics_resource.json b/mocks/loki/flow_metrics_resource.json index 6e00bc686..e2c40fb7b 100644 --- a/mocks/loki/flow_metrics_resource.json +++ b/mocks/loki/flow_metrics_resource.json @@ -2432,6 +2432,263 @@ "5.2555555555555555" ] ] + }, + { + "metric": { + "DstAddr": "10.128.0.70", + "DstK8S_Name": "gateway-api", + "DstK8S_Namespace": "istio-system", + "DstK8S_Type": "Gateway", + "SrcAddr": "10.128.0.71", + "SrcK8S_Name": "web-pod", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "web-deployment", + "SrcK8S_OwnerType": "Deployment", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708009560, + "200.5" + ], + [ + 1708009920, + "225.8" + ], + [ + 1708010280, + "245.3" + ], + [ + 1708010640, + "258.7" + ], + [ + 1708011000, + "252.1" + ], + [ + 1708011360, + "265.4" + ], + [ + 1708011720, + "270.2" + ] + ] + }, + { + "metric": { + "DstAddr": "10.128.0.80", + "DstK8S_Name": "gateway-external", + "DstK8S_Namespace": "gateway-system", + "DstK8S_Type": "Gateway", + "SrcAddr": "10.128.0.81", + "SrcK8S_Name": "gateway-internal", + "SrcK8S_Namespace": "gateway-system", + "SrcK8S_Type": "Gateway" + }, + "values": [ + [ + 1708009560, + "150.2" + ], + [ + 1708009920, + "168.5" + ], + [ + 1708010280, + "182.3" + ], + [ + 1708010640, + "195.8" + ], + [ + 1708011000, + "188.1" + ], + [ + 1708011360, + "202.5" + ], + [ + 1708011720, + "210.7" + ] + ] + }, + { + "metric": { + "DstAddr": "10.128.0.110", + "DstK8S_Name": "vm-ubuntu-server", + "DstK8S_Namespace": "vms", + "DstK8S_Type": "VirtualMachine", + "SrcAddr": "10.128.0.111", + "SrcK8S_Name": "vm-centos-server", + "SrcK8S_Namespace": "vms", + "SrcK8S_Type": "VirtualMachine" + }, + "values": [ + [ + 1708009560, + "95.3" + ], + [ + 1708009920, + "108.7" + ], + [ + 1708010280, + "118.2" + ], + [ + 1708010640, + "125.8" + ], + [ + 1708011000, + "122.1" + ], + [ + 1708011360, + "132.5" + ], + [ + 1708011720, + "138.9" + ] + ] + }, + { + "metric": { + "DstAddr": "10.128.0.120", + "DstK8S_Name": "vmi-windows", + "DstK8S_Namespace": "vms", + "DstK8S_Type": "VirtualMachineInstance", + "SrcAddr": "10.128.0.121", + "SrcK8S_Name": "vmi-linux", + "SrcK8S_Namespace": "vms", + "SrcK8S_Type": "VirtualMachineInstance" + }, + "values": [ + [ + 1708009560, + "88.2" + ], + [ + 1708009920, + "98.5" + ], + [ + 1708010280, + "105.8" + ], + [ + 1708010640, + "112.3" + ], + [ + 1708011000, + "109.1" + ], + [ + 1708011360, + "118.7" + ], + [ + 1708011720, + "125.2" + ] + ] + }, + { + "metric": { + "DstAddr": "10.128.0.130", + "DstK8S_Name": "app-pod", + "DstK8S_Namespace": "production", + "DstK8S_OwnerName": "app-deployment", + "DstK8S_OwnerType": "Deployment", + "DstK8S_Type": "Pod", + "SrcAddr": "10.128.0.110", + "SrcK8S_Name": "vm-ubuntu-server", + "SrcK8S_Namespace": "vms", + "SrcK8S_Type": "VirtualMachine" + }, + "values": [ + [ + 1708009560, + "65.4" + ], + [ + 1708009920, + "72.8" + ], + [ + 1708010280, + "78.1" + ], + [ + 1708010640, + "82.5" + ], + [ + 1708011000, + "79.9" + ], + [ + 1708011360, + "85.2" + ], + [ + 1708011720, + "88.7" + ] + ] + }, + { + "metric": { + "DstAddr": "10.128.0.150", + "DstK8S_Name": "vm-database", + "DstK8S_Namespace": "vms", + "DstK8S_Type": "VirtualMachine", + "SrcK8S_Name": "db-pod-0", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "db-statefulset", + "SrcK8S_OwnerType": "StatefulSet", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708009560, + "42.5" + ], + [ + 1708009920, + "48.2" + ], + [ + 1708010280, + "52.8" + ], + [ + 1708010640, + "55.3" + ], + [ + 1708011000, + "53.9" + ], + [ + 1708011360, + "57.1" + ], + [ + 1708011720, + "59.7" + ] + ] } ], "stats": { @@ -2443,7 +2700,7 @@ "execTime": 0.800734384, "queueTime": 8.669621995, "subqueries": 0, - "totalEntriesReturned": 59, + "totalEntriesReturned": 65, "splits": 6, "shards": 48, "totalPostFilterLines": 199944, diff --git a/mocks/loki/flow_metrics_udn.json b/mocks/loki/flow_metrics_udn.json index 68b3b4b00..aa2a6199f 100644 --- a/mocks/loki/flow_metrics_udn.json +++ b/mocks/loki/flow_metrics_udn.json @@ -5,7 +5,8 @@ "result": [ { "metric": { - "UdnId": "my-udn" + "SrcK8S_NetworkName": "my-udn", + "DstK8S_NetworkName": "my-udn-2" }, "values": [ [ @@ -40,7 +41,8 @@ }, { "metric": { - "UdnId": "my-udn-2" + "SrcK8S_NetworkName": "my-udn", + "DstK8S_NetworkName": "my-udn-2" }, "values": [ [ @@ -245,6 +247,128 @@ "3.863888888888889" ] ] + }, + { + "metric": { + "DstK8S_NetworkName": "production-network", + "DstK8S_Type": "UserDefinedNetwork", + "SrcAddr": "10.128.0.90", + "SrcK8S_Name": "app-pod-1", + "SrcK8S_Namespace": "production", + "SrcK8S_OwnerName": "app-deployment", + "SrcK8S_OwnerType": "Deployment", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708009560, + "75.3" + ], + [ + 1708009920, + "82.7" + ], + [ + 1708010280, + "88.1" + ], + [ + 1708010640, + "92.5" + ], + [ + 1708011000, + "89.8" + ], + [ + 1708011360, + "95.2" + ], + [ + 1708011720, + "98.7" + ] + ] + }, + { + "metric": { + "DstK8S_NetworkName": "development-network", + "DstK8S_Type": "UserDefinedNetwork", + "SrcK8S_NetworkName": "production-network", + "SrcK8S_Type": "UserDefinedNetwork" + }, + "values": [ + [ + 1708009560, + "35.2" + ], + [ + 1708009920, + "38.9" + ], + [ + 1708010280, + "42.1" + ], + [ + 1708010640, + "44.8" + ], + [ + 1708011000, + "43.5" + ], + [ + 1708011360, + "46.2" + ], + [ + 1708011720, + "48.7" + ] + ] + }, + { + "metric": { + "DstK8S_NetworkName": "cluster-network", + "DstK8S_Type": "ClusterUserDefinedNetwork", + "SrcAddr": "10.128.0.100", + "SrcK8S_Name": "cluster-pod", + "SrcK8S_Namespace": "kube-system", + "SrcK8S_OwnerName": "cluster-deployment", + "SrcK8S_OwnerType": "Deployment", + "SrcK8S_Type": "Pod" + }, + "values": [ + [ + 1708009560, + "55.8" + ], + [ + 1708009920, + "62.3" + ], + [ + 1708010280, + "68.1" + ], + [ + 1708010640, + "71.5" + ], + [ + 1708011000, + "69.8" + ], + [ + 1708011360, + "74.2" + ], + [ + 1708011720, + "77.9" + ] + ] } ], "stats": { @@ -256,7 +380,7 @@ "execTime": 0.642710294, "queueTime": 8.042418995, "subqueries": 0, - "totalEntriesReturned": 7, + "totalEntriesReturned": 10, "splits": 6, "shards": 48, "totalPostFilterLines": 199944, diff --git a/mocks/loki/flow_metrics_zone.json b/mocks/loki/flow_metrics_zone.json index 95fe4987d..1aa0b32dd 100644 --- a/mocks/loki/flow_metrics_zone.json +++ b/mocks/loki/flow_metrics_zone.json @@ -4,35 +4,218 @@ "resultType": "matrix", "result": [ { - "metric": {}, + "metric": { + "DstK8S_Zone": "us-east-1a", + "SrcK8S_Zone": "us-east-1a" + }, + "values": [ + [ + 1708009560, + "185.5" + ], + [ + 1708009920, + "225.8" + ], + [ + 1708010280, + "245.3" + ], + [ + 1708010640, + "258.7" + ], + [ + 1708011000, + "252.1" + ], + [ + 1708011360, + "265.4" + ], + [ + 1708011720, + "270.2" + ] + ] + }, + { + "metric": { + "DstK8S_Zone": "us-east-1b", + "SrcK8S_Zone": "us-east-1a" + }, + "values": [ + [ + 1708009560, + "120.3" + ], + [ + 1708009920, + "145.8" + ], + [ + 1708010280, + "165.2" + ], + [ + 1708010640, + "175.5" + ], + [ + 1708011000, + "172.1" + ], + [ + 1708011360, + "182.8" + ], + [ + 1708011720, + "188.5" + ] + ] + }, + { + "metric": { + "DstK8S_Zone": "us-east-1b", + "SrcK8S_Zone": "us-east-1b" + }, + "values": [ + [ + 1708009560, + "150.2" + ], + [ + 1708009920, + "180.5" + ], + [ + 1708010280, + "195.8" + ], + [ + 1708010640, + "205.3" + ], + [ + 1708011000, + "198.7" + ], + [ + 1708011360, + "212.5" + ], + [ + 1708011720, + "218.9" + ] + ] + }, + { + "metric": { + "DstK8S_Zone": "us-east-1c", + "SrcK8S_Zone": "us-east-1a" + }, + "values": [ + [ + 1708009560, + "95.8" + ], + [ + 1708009920, + "115.2" + ], + [ + 1708010280, + "128.5" + ], + [ + 1708010640, + "135.7" + ], + [ + 1708011000, + "132.3" + ], + [ + 1708011360, + "142.1" + ], + [ + 1708011720, + "148.8" + ] + ] + }, + { + "metric": { + "DstK8S_Zone": "us-east-1c", + "SrcK8S_Zone": "us-east-1c" + }, + "values": [ + [ + 1708009560, + "88.5" + ], + [ + 1708009920, + "105.8" + ], + [ + 1708010280, + "118.2" + ], + [ + 1708010640, + "125.3" + ], + [ + 1708011000, + "122.7" + ], + [ + 1708011360, + "132.5" + ], + [ + 1708011720, + "138.1" + ] + ] + }, + { + "metric": { + "DstK8S_Zone": "us-west-2a", + "SrcK8S_Zone": "us-east-1a" + }, "values": [ [ 1708009560, - "149.26388888888889" + "45.2" ], [ 1708009920, - "374.69722222222225" + "52.8" ], [ 1708010280, - "453.8902777777778" + "58.5" ], [ 1708010640, - "478.52638888888896" + "62.3" ], [ 1708011000, - "467.9861111111112" + "60.1" ], [ 1708011360, - "511.25972222222225" + "65.7" ], [ 1708011720, - "518.5291666666666" + "68.9" ] ] } @@ -46,7 +229,7 @@ "execTime": 0.575831062, "queueTime": 7.719558994, "subqueries": 0, - "totalEntriesReturned": 1, + "totalEntriesReturned": 6, "splits": 6, "shards": 48, "totalPostFilterLines": 199944, diff --git a/pkg/handler/lokiclientmock/loki_client_mock.go b/pkg/handler/lokiclientmock/loki_client_mock.go index 0663a7a05..218f05afc 100644 --- a/pkg/handler/lokiclientmock/loki_client_mock.go +++ b/pkg/handler/lokiclientmock/loki_client_mock.go @@ -41,7 +41,7 @@ func (o *LokiClientMock) Get(url string) ([]byte, int, error) { path += "_cause.json" } else if strings.Contains(url, "by(K8S_ClusterName)") { path += "_cluster.json" - } else if strings.Contains(url, "by(UdnId)") { + } else if strings.Contains(url, "by(SrcK8S_NetworkName,DstK8S_NetworkName)") { path += "_udn.json" } else if strings.Contains(url, "by(SrcK8S_Zone,DstK8S_Zone)") { path += "_zone.json" @@ -72,32 +72,42 @@ func (o *LokiClientMock) Get(url string) ([]byte, int, error) { mlog.Debugf("Reading file path: %s", path) file, err := os.ReadFile(path) - mlog.Debugf("here") if err != nil { - // return nil, 500, err - emptyResponse := []byte(`{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [] - } - }`) - - var qr model.QueryResponse - err = json.Unmarshal(emptyResponse, &qr) - if err != nil { - return nil, 500, err - } - for _, s := range qr.Data.Result.(model.Streams) { - for i := range s.Entries { - s.Entries[i].Line = decoders.NetworkEventsToString(s.Entries[i].Line) + // If dropped file doesn't exist, try falling back to non-dropped version + if strings.Contains(path, "_dropped") { + fallbackPath := strings.Replace(path, "_dropped", "", 1) + mlog.Debugf("Dropped file not found, trying fallback: %s", fallbackPath) + file, err = os.ReadFile(fallbackPath) + if err == nil { + mlog.Debugf("Using fallback file: %s", fallbackPath) } } - emptyResponse, err = json.Marshal(qr) + // If still error (or not a dropped file), return empty response if err != nil { - return nil, 500, err + emptyResponse := []byte(`{ + "status": "success", + "data": { + "resultType": "matrix", + "result": [] + } + }`) + + var qr model.QueryResponse + err = json.Unmarshal(emptyResponse, &qr) + if err != nil { + return nil, 500, err + } + for _, s := range qr.Data.Result.(model.Streams) { + for i := range s.Entries { + s.Entries[i].Line = decoders.NetworkEventsToString(s.Entries[i].Line) + } + } + emptyResponse, err = json.Marshal(qr) + if err != nil { + return nil, 500, err + } + return emptyResponse, 200, nil } - return emptyResponse, 200, nil } if parseNetEvents { diff --git a/web/package-lock.json b/web/package-lock.json index 3fadb63db..feae8dd34 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -32,6 +32,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "^11.8.11", + "react-icons": "^5.5.0", "react-modal": "^3.14.4", "react-router": "^5.3.0", "react-router-dom": "^5.3.0" @@ -20573,6 +20574,15 @@ "react": ">= 16.8.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -40004,6 +40014,12 @@ "html-parse-stringify": "^3.0.1" } }, + "react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/web/package.json b/web/package.json index e2392de74..125e6db24 100644 --- a/web/package.json +++ b/web/package.json @@ -119,6 +119,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "^11.8.11", + "react-icons": "^5.5.0", "react-modal": "^3.14.4", "react-router": "^5.3.0", "react-router-dom": "^5.3.0" diff --git a/web/src/components/drawer/record/record-field.tsx b/web/src/components/drawer/record/record-field.tsx index c6418484d..27ae70c16 100644 --- a/web/src/components/drawer/record/record-field.tsx +++ b/web/src/components/drawer/record/record-field.tsx @@ -1,6 +1,6 @@ import { ResourceIcon, ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; import { Button, Flex, FlexItem, Popover, Text, TextContent, TextVariants, Tooltip } from '@patternfly/react-core'; -import { FilterIcon, GlobeAmericasIcon, TimesIcon, ToggleOffIcon, ToggleOnIcon } from '@patternfly/react-icons'; +import { GlobeAmericasIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; @@ -16,6 +16,7 @@ import { formatPort } from '../../../utils/port'; import { formatProtocol, getProtocolDocUrl } from '../../../utils/protocol'; import { getFlagsList, getTCPFlagsDocUrl } from '../../../utils/tcp-flags'; import { Size } from '../../dropdowns/table-display-dropdown'; +import { FilterAddIcon, FilterRemoveIcon, FilterToggleOffIcon, FilterToggleOnIcon } from '../../icons'; import './record-field.css'; export type RecordFieldFilter = { @@ -635,14 +636,14 @@ export const RecordField: React.FC = ({ diff --git a/web/src/components/dropdowns/match-dropdown.tsx b/web/src/components/dropdowns/match-dropdown.tsx index cebf750f2..bd147c45e 100644 --- a/web/src/components/dropdowns/match-dropdown.tsx +++ b/web/src/components/dropdowns/match-dropdown.tsx @@ -1,8 +1,8 @@ import { Dropdown, DropdownItem, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; -import { LongArrowAltDownIcon, LongArrowAltUpIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Match } from '../../model/flow-query'; +import { BackAndForthIcon } from '../icons'; export interface MatchDropdownProps { selected: Match; @@ -26,12 +26,7 @@ export const MatchDropdown: React.FC = ({ allowBidirectional case 'bidirectional': if (allowBidirectional) { if (toggle) { - return ( -
- - -
- ); + return ; } else { return t('Bidirectional'); } diff --git a/web/src/components/icons/index.ts b/web/src/components/icons/index.ts new file mode 100644 index 000000000..9ba02be17 --- /dev/null +++ b/web/src/components/icons/index.ts @@ -0,0 +1,13 @@ +/** + * Icon components for Network Observability + * + * This module exports custom icons and icon wrappers for consistent styling + * across the application. It includes: + * - React Icons wrapper for consistent sizing + * - Kubernetes resource icons + * - Network-specific icons (filters, packets, etc.) + */ + +export * from './kubernetes-icons'; +export * from './react-icons'; +export * from './react-icons-wrapper'; diff --git a/web/src/components/icons/kubernetes-icons.tsx b/web/src/components/icons/kubernetes-icons.tsx new file mode 100644 index 000000000..9f02c2e7f --- /dev/null +++ b/web/src/components/icons/kubernetes-icons.tsx @@ -0,0 +1,533 @@ +/** + * Kubernetes resource icons + * + * This module provides React components for Kubernetes resource icons + * using inline SVGs from the official Kubernetes community icons: + * https://github.com/kubernetes/community/tree/master/icons + * + * react icons: + * https://react-icons.github.io/react-icons/ + * + * and patternfly icons: + * https://www.patternfly.org/v4/icons/ + */ + +import { GlobeRouteIcon } from '@patternfly/react-icons'; +import * as React from 'react'; +import { GrHost, GrMultiple, GrServerCluster, GrVirtualMachine } from 'react-icons/gr'; +import { IoLocationOutline } from 'react-icons/io5'; +import { PiNetwork } from 'react-icons/pi'; +import { TbCloudNetwork } from 'react-icons/tb'; +import { IconWrapper } from './react-icons-wrapper'; + +// All Kubernetes icons return SVG elements directly for use in SVG contexts (like topology) +// Implemented as React.ComponentClass to match PatternFly icon pattern + +// Icon props interface matching PatternFly SVGIconProps pattern +interface SVGIconProps { + size?: string | number; + className?: string; + style?: React.CSSProperties; + title?: string; +} + +// Kubernetes resource icon components using official community icons +// SVG paths from https://github.com/kubernetes/community/tree/master/icons + +// Pod icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/pod.svg +class PodIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + // Center the icon: viewBox center is at (9.017667, 8.750189) + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + ); + } +} + +// Service icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/svc.svg +class ServiceIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + // Center the icon: viewBox center is at (9.017667, 8.750189) + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + + + + + ); + } +} + +// Namespace icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/ns.svg +class NamespaceIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + // Center the icon: viewBox center is at (9.017667, 8.750189) + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + ); + } +} + +// Deployment icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/deploy.svg +class DeploymentIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + // Center the icon: viewBox center is at (9.017667, 8.750189) + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + ); + } +} + +// DaemonSet icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/ds.svg +class DaemonSetIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + + + ); + } +} + +// StatefulSet icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/sts.svg +class StatefulSetIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + + + + ); + } +} + +// Job icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/job.svg +class JobIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + + + + + ); + } +} + +// ReplicaSet icon - actual SVG from https://github.com/kubernetes/community/blob/master/icons/svg/resources/unlabeled/rs.svg +class ReplicaSetIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + const scale = (sizeValue / 18.035334) * 2; + const centerX = 9.017667; + const centerY = 8.750189; + return ( + + + + + + + + + ); + } +} + +// Node icon - using GrHost from react-icons +// Kubernetes icon is too complex and not very readable, so we use the GrHost icon from react-icons instead +class NodeIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +class ClusterIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +class ZoneIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +class NetworkIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +class CloudNetworkIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +class VMIcon extends React.Component { + render() { + const { size, className, style } = this.props; + const sizeValue = typeof size === 'number' ? size : parseFloat(size as string) || 18; + return ; + } +} + +/** + * Get the appropriate icon component for a Kubernetes resource kind + * Uses official Kubernetes community icons as inline SVGs + * + * Returns React.ComponentClass to match PatternFly icon pattern + * All icons are implemented as class components extending React.Component + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getK8sResourceIcon = (resourceKind: string | undefined): React.ComponentClass | null => { + if (!resourceKind) { + return null; + } + // Normalize the resource kind (case-insensitive matching, handle common variations) + const normalizedKind = resourceKind.trim().toLowerCase(); + + // Use a map for more flexible matching - ensure stable references + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const kindMap: { [key: string]: React.ComponentClass } = { + service: ServiceIcon, + svc: ServiceIcon, + ingress: ServiceIcon, + ing: ServiceIcon, + pod: PodIcon, + namespace: NamespaceIcon, + ns: NamespaceIcon, + node: NodeIcon, + cluster: ClusterIcon, + zone: ZoneIcon, + network: NetworkIcon, + udn: NetworkIcon, + userdefinednetwork: NetworkIcon, + clusteruserdefinednetwork: CloudNetworkIcon, + vm: VMIcon, + vmi: VMIcon, + virtualmachine: VMIcon, + virtualmachineinstance: VMIcon, + gateway: GlobeRouteIcon, + daemonset: DaemonSetIcon, + ds: DaemonSetIcon, + deployment: DeploymentIcon, + deploy: DeploymentIcon, + catalogsource: DeploymentIcon, // No specific icon available, using DeploymentIcon + replicationcontroller: DeploymentIcon, // No specific icon available, using DeploymentIcon + statefulset: StatefulSetIcon, + sts: StatefulSetIcon, + job: JobIcon, + cronjob: JobIcon, + cj: JobIcon, + replicaset: ReplicaSetIcon, + rs: ReplicaSetIcon, + rc: DeploymentIcon + }; + + const icon = kindMap[normalizedKind]; + if (icon) { + return icon; + } + + // Debug: log unmatched resourceKind to help diagnose binding issues + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.warn('[getK8sResourceIcon] Unmatched resourceKind:', resourceKind, 'normalized:', normalizedKind); + } + + return null; +}; + +/** + * Enhanced group icon for topology + * Uses a better icon than the default CubesIcon + */ +export const TopologyGroupIcon: React.FC<{ + size?: string | number; + className?: string; + style?: React.CSSProperties; +}> = ({ size, className, style }) => ; diff --git a/web/src/components/icons/react-icons-wrapper.tsx b/web/src/components/icons/react-icons-wrapper.tsx new file mode 100644 index 000000000..13d79c3a5 --- /dev/null +++ b/web/src/components/icons/react-icons-wrapper.tsx @@ -0,0 +1,22 @@ +/** + * Wrapper component for react-icons to ensure consistent sizing with PatternFly icons + */ +import * as React from 'react'; +import { IconBaseProps, IconType } from 'react-icons'; + +export interface IconWrapperProps extends Omit { + icon: IconType; + size?: string | number; + className?: string; + style?: React.CSSProperties; +} + +/** + * Wrapper component that ensures react-icons match PatternFly icon sizing + * PatternFly icons default to 1em, so we maintain that for consistency + */ +export const IconWrapper: React.FC = ({ icon: Icon, size = '1em', className, style, ...props }) => { + // IconType from react-icons is compatible but TypeScript needs explicit casting + const IconComponent = Icon as React.ComponentType; + return ; +}; diff --git a/web/src/components/icons/react-icons.tsx b/web/src/components/icons/react-icons.tsx new file mode 100644 index 000000000..1b409f6a6 --- /dev/null +++ b/web/src/components/icons/react-icons.tsx @@ -0,0 +1,65 @@ +/** + * Various icons using react-icons + * These icons address the missing icons from PatternFly for network observability + */ +import * as React from 'react'; +import { FaBolt, FaNetworkWired } from 'react-icons/fa'; +import { MdDataUsage, MdToggleOff, MdToggleOn } from 'react-icons/md'; +import { RiArrowLeftRightLine } from 'react-icons/ri'; +import { TbDownload, TbFilter, TbFilterOff, TbMapPin, TbUpload } from 'react-icons/tb'; +import { IconWrapper } from './react-icons-wrapper'; + +// Source: upload icon represents data origin/starting point +export const SourceIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Destination: download icon represents data target/endpoint +export const DestinationIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Bidirectional: arrows exchange represents traffic matched in both directions +export const BackAndForthIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Endpoint: location pin represents a network endpoint +export const EndpointIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Filter Toggle On Icon +export const FilterToggleOnIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Filter Toggle Off Icon +export const FilterToggleOffIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Quick Filters Icon +export const QuickFiltersIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Packets Icon +export const PacketsIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Bytes Icon +export const BytesIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Add filter: filter icon represents adding a new filter +export const FilterAddIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); + +// Remove filter: filter icon represents removing a filter +export const FilterRemoveIcon: React.FC<{ size?: string | number; className?: string }> = ({ size, className }) => ( + +); diff --git a/web/src/components/query-summary/flows-query-summary-content.tsx b/web/src/components/query-summary/flows-query-summary-content.tsx index ee3912e30..c86b4db47 100644 --- a/web/src/components/query-summary/flows-query-summary-content.tsx +++ b/web/src/components/query-summary/flows-query-summary-content.tsx @@ -1,5 +1,5 @@ import { Flex, FlexItem, Text, TextVariants, Tooltip } from '@patternfly/react-core'; -import { InfoCircleIcon } from '@patternfly/react-icons'; +import { InfoCircleIcon, TachometerAltIcon } from '@patternfly/react-icons'; import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,6 +8,7 @@ import { RecordType } from '../../model/flow-query'; import { Warning } from '../../model/warnings'; import { rangeToSeconds, TimeRange } from '../../utils/datetime'; import { valueFormat } from '../../utils/format'; +import { BytesIcon, PacketsIcon } from '../icons'; import StatsQuerySummary from './stats-query-summary'; export interface FlowsQuerySummaryContentProps { @@ -57,27 +58,36 @@ export const FlowsQuerySummaryContent: React.FC = {bytes > 0 && ( {t('Filtered sum of bytes')}}> - - {valueFormat(bytes, 0, t('B'), limitReached)} - +
+ + + {valueFormat(bytes, 0, t('B'), limitReached)} + +
)} {packets > 0 && ( {t('Filtered sum of packets')}}> - - {valueFormat(packets, 0, t('Packets'), limitReached, true)} - +
+ + + {valueFormat(packets, 0, t('Packets'), limitReached, true)} + +
)} {bytes > 0 && ( {t('Filtered average speed')}}> - - {valueFormat(bytes / rangeInSeconds, 2, t('Bps'), limitReached)} - +
+ + + {valueFormat(bytes / rangeInSeconds, 2, t('Bps'), limitReached)} + +
)} diff --git a/web/src/components/query-summary/flows-query-summary.tsx b/web/src/components/query-summary/flows-query-summary.tsx index 1b3c734df..dee266150 100644 --- a/web/src/components/query-summary/flows-query-summary.tsx +++ b/web/src/components/query-summary/flows-query-summary.tsx @@ -38,6 +38,7 @@ export const FlowsQuerySummary: React.FC = ({ return ( {textAbs}}> -
+
+ {valAbs} @@ -154,7 +156,8 @@ export const MetricsQuerySummaryContent: React.FC {textAbs}}> -
+
+ {valAbs} diff --git a/web/src/components/query-summary/metrics-query-summary.tsx b/web/src/components/query-summary/metrics-query-summary.tsx index a94973b3b..426430e11 100644 --- a/web/src/components/query-summary/metrics-query-summary.tsx +++ b/web/src/components/query-summary/metrics-query-summary.tsx @@ -31,6 +31,7 @@ export const MetricsQuerySummary: React.FC = ({ return ( = ({ return ( - + ); }; diff --git a/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx b/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx index 7920f1074..d06573b88 100644 --- a/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx +++ b/web/src/components/tabs/netflow-topology/2d/styles/styleNode.tsx @@ -1,16 +1,3 @@ -import { - ClusterIcon, - CubeIcon, - CubesIcon, - ExternalLinkAltIcon, - GlobeRouteIcon, - NetworkIcon, - OutlinedHddIcon, - QuestionCircleIcon, - ServiceIcon, - UsersIcon, - ZoneIcon -} from '@patternfly/react-icons'; import { DEFAULT_LAYER as defaultLayer, Layer, @@ -29,9 +16,13 @@ import { import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel'; import * as _ from 'lodash'; import * as React from 'react'; +import { FaQuestion } from 'react-icons/fa'; +import { TbWorldWww } from 'react-icons/tb'; import { TopologyMetricPeer } from '../../../../../api/loki'; import { Match } from '../../../../../model/flow-query'; import { Decorated, NodeData } from '../../../../../model/topology'; +import { useTheme } from '../../../../../utils/theme-hook'; +import { getK8sResourceIcon } from '../../../../icons/kubernetes-icons'; import DefaultNode from '../components/node'; import { NodeDecorators } from './styleDecorators'; @@ -54,38 +45,21 @@ type StyleNodeProps = { WithSelectionProps; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const getTypeIcon = (peer: TopologyMetricPeer): React.ComponentClass => { - switch (peer.resourceKind) { - case 'Service': - return ServiceIcon; - case 'Pod': - return CubeIcon; - case 'Namespace': - return UsersIcon; - case 'Node': - return OutlinedHddIcon; - case 'Cluster': - return ClusterIcon; - case 'Zone': - return ZoneIcon; - case 'UDN': - return NetworkIcon; - case 'Gateway': - return GlobeRouteIcon; - case 'CatalogSource': - case 'DaemonSet': - case 'Deployment': - case 'StatefulSet': - case 'Job': - return CubesIcon; - } - if (peer.addr || peer.subnetLabel) { - return ExternalLinkAltIcon; +const getTypeIcon = (peer: TopologyMetricPeer): React.ComponentType => { + const icon = getK8sResourceIcon(peer.resourceKind); + if (icon === null) { + if (peer.addr || peer.subnetLabel) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return TbWorldWww as any; + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return FaQuestion as any; + } } - return QuestionCircleIcon; + return icon; }; -const renderIcon = (data: Decorated, element: NodePeer): React.ReactNode => { +const renderIcon = (data: Decorated, element: NodePeer, isDarkTheme: boolean): React.ReactNode => { const { width, height } = element.getDimensions(); const shape = element.getNodeShape(); const iconSize = @@ -95,12 +69,19 @@ const renderIcon = (data: Decorated, element: NodePeer): React.ReactNo return ( - + ); }; const StyleNode: React.FC = ({ element, showLabel, dragging, getShapeDecoratorCenter, ...rest }) => { + const isDarkTheme = useTheme(); + const nodeElement = element as Node; const data = element.getData() as Decorated | undefined; //TODO: check if we can have intelligent pin on view change @@ -167,7 +148,7 @@ const StyleNode: React.FC = ({ element, showLabel, dragging, get ) } > - {(hover || detailsLevel !== ScaleDetailsLevel.low) && renderIcon(passedData, element)} + {(hover || detailsLevel !== ScaleDetailsLevel.low) && renderIcon(passedData, element, isDarkTheme)} diff --git a/web/src/components/toolbar/filters/filters-chips.tsx b/web/src/components/toolbar/filters/filters-chips.tsx index b2e273b86..807f0af8d 100644 --- a/web/src/components/toolbar/filters/filters-chips.tsx +++ b/web/src/components/toolbar/filters/filters-chips.tsx @@ -10,16 +10,7 @@ import { ToolbarItem, Tooltip } from '@patternfly/react-core'; -import { - ArrowLeftIcon, - ArrowRightIcon, - ArrowsAltVIcon, - BanIcon, - CheckIcon, - PencilAltIcon, - TimesCircleIcon, - TimesIcon -} from '@patternfly/react-icons'; +import { ArrowsAltVIcon, BanIcon, CheckIcon, PencilAltIcon, TimesCircleIcon, TimesIcon } from '@patternfly/react-icons'; import * as _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -47,6 +38,7 @@ import { import { getPathWithParams, netflowTrafficPath } from '../../../utils/url'; import { MatchDropdown } from '../../dropdowns/match-dropdown'; import { navigate } from '../../dynamic-loader/dynamic-loader'; +import { DestinationIcon, EndpointIcon, SourceIcon } from '../../icons'; import { Direction } from '../filters-toolbar'; import { LinksOverflow } from '../links-overflow'; import './filters-chips.css'; @@ -286,18 +278,18 @@ export const FiltersChips: React.FC = ({ key="src" onClick={() => swapValue(filter, filterValue, 'src')} > - +  {filters.match === 'bidirectional' ? t('As endpoint A') : t('As source')} )} {(filter.def.category === 'endpoint' || filter.def.id.startsWith('src_')) && ( swapValue(filter, filterValue, 'dst')} > - +  {filters.match === 'bidirectional' ? t('As endpoint B') : t('As destination')} )} @@ -423,7 +415,18 @@ export const FiltersChips: React.FC = ({
{getAndOr(filters.match, index, true)}
- {hasSrcOrDstFilters(gp.filters) && {getGroupName(gp.id)} } + {hasSrcOrDstFilters(gp.filters) && ( + + {filters.match === 'bidirectional' ? ( + + ) : gp.id === 'src' ? ( + + ) : gp.id === 'dst' ? ( + + ) : null} +  {getGroupName(gp.id)}  + + )}
{gp.filters.map(getFilterDisplay)}
diff --git a/web/src/components/toolbar/filters/quick-filters.tsx b/web/src/components/toolbar/filters/quick-filters.tsx index f56a815eb..b4089ce7b 100644 --- a/web/src/components/toolbar/filters/quick-filters.tsx +++ b/web/src/components/toolbar/filters/quick-filters.tsx @@ -1,11 +1,11 @@ import { Badge, MenuToggle, MenuToggleElement, Select, SelectOption } from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons'; 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'; import { useOutsideClickEvent } from '../../../utils/outside-hook'; +import { QuickFiltersIcon } from '../../icons'; export interface QuickFiltersProps { quickFilters: QuickFilter[]; @@ -71,7 +71,7 @@ export const QuickFilters: React.FC = ({ quickFilters, active toggle={(toggleRef: React.Ref) => ( setOpen(!isOpen)} isExpanded={isOpen}> <> - {t('Quick filters')} + {t('Quick filters')} {selectedList.length > 0 && {selectedList.length}}