From 54c70018d948912d36a4949bbaf1633763fb9ae1 Mon Sep 17 00:00:00 2001 From: Alberto Gutierrez Date: Fri, 19 Jan 2024 13:59:53 +0100 Subject: [PATCH] perf(kiali): add context, remove kiali-common and refactor backend (#855) --- .../__fixtures__/data/canaries_status.json | 6 - .../__fixtures__/data/config/auth_info.json | 1 + .../__fixtures__/data/istio_status.json | 1 - .../__fixtures__/data/namespaces/all.json | 84 -- .../namespaces/travel-agency-metrics.json | 546 ----------- .../namespaces/travel-control-metrics.json | 15 - .../namespaces/travel-portal-metrics.json | 786 --------------- .../data/namespaces/validations.json | 7 - .../kiali-backend/__fixtures__/handlers.ts | 151 +-- plugins/kiali-backend/config.d.ts | 9 - .../kiali-backend/dist-dynamic/package.json | 3 +- .../src/clients/{auth.ts => Auth.ts} | 60 +- .../src/clients/KialiAPIConnector.ts | 352 +------ .../kiali-backend/src/clients/auth.test.ts | 191 ---- plugins/kiali-backend/src/clients/fetch.ts | 190 ++-- plugins/kiali-backend/src/dynamic/index.ts | 3 - .../src/filters/byAnnotation.test.ts | 62 -- plugins/kiali-backend/src/plugin.ts | 10 +- .../src/service}/config.ts | 14 +- .../kiali-backend/src/service/router.test.ts | 270 ------ plugins/kiali-backend/src/service/router.ts | 95 +- .../src/service/standaloneServer.ts | 7 +- plugins/kiali-common/.eslintrc.js | 1 - plugins/kiali-common/CHANGELOG.md | 59 -- plugins/kiali-common/README.md | 5 - plugins/kiali-common/package.json | 37 - plugins/kiali-common/src/config/index.ts | 1 - plugins/kiali-common/src/helpers/index.ts | 1 - plugins/kiali-common/src/index.ts | 6 - plugins/kiali-common/src/query/index.ts | 4 - plugins/kiali-common/src/responses/index.ts | 15 - plugins/kiali-common/src/setupTests.ts | 1 - plugins/kiali-common/src/types/Common.ts | 98 -- plugins/kiali-common/src/types/Config.ts | 278 ------ plugins/kiali-common/src/types/Fetch.ts | 47 - plugins/kiali-common/src/types/Health.ts | 17 - .../kiali-common/src/types/HealthStatus.ts | 120 --- .../src/types/IstioConfigList.test.ts | 48 - plugins/kiali-common/src/types/Namespace.ts | 45 - plugins/kiali-common/src/types/Permissions.ts | 28 - plugins/kiali-common/src/types/index.ts | 23 - .../kiali-common/src/utils/RateIntervals.ts | 12 - plugins/kiali-common/src/utils/index.ts | 6 - plugins/kiali-common/tsconfig.json | 9 - plugins/kiali-common/turbo.json | 9 - plugins/kiali/CONTRIBUTING.md | 9 + plugins/kiali/DEVELOPMENT.md | 67 +- plugins/kiali/README.md | 51 +- plugins/kiali/catalog-demo.yaml | 16 + plugins/kiali/config.d.ts | 10 + .../kiali/dev/__fixtures__/1-overview.json | 333 ------- plugins/kiali/dev/__fixtures__/config.json | 195 ---- .../general/auth_info_anonymous.json | 1 + .../dev/__fixtures__/general/config.json} | 35 +- .../__fixtures__/general/istioCertsInfo.json} | 4 +- .../dev/__fixtures__/general/istioConfig.json | 375 +++++++ .../dev/__fixtures__/general/istioStatus.json | 7 + .../general/istioValidations.json | 44 + .../general/meshCanaryStatus.json | 6 + .../general/meshIstioResurceThresholds.json | 1 + .../dev/__fixtures__/general/meshTls.json} | 0 .../dev/__fixtures__/general/namespaces.json | 146 +++ .../general/outbound_traffic_policy_mode.json | 1 + .../dev/__fixtures__/general/status.json | 35 + plugins/kiali/dev/__fixtures__/index.ts | 114 +++ .../kiali/dev/__fixtures__/namespaces.json | 44 - .../namespaces/bookinfo/health/app.json | 120 +++ .../namespaces/bookinfo/health/service.json | 46 + .../namespaces/bookinfo/health/workload.json | 136 +++ .../metrics/inbound/metrics_inbound_120.json | 28 + .../metrics/inbound/metrics_inbound_1800.json | 40 + .../metrics/inbound/metrics_inbound_300.json | 914 ++++++++++++++++++ .../metrics/inbound/metrics_inbound_3600.json | 32 + .../metrics/inbound/metrics_inbound_60.json | 24 + .../metrics/inbound/metrics_inbound_600.json | 40 + .../namespaces/bookinfo/metrics/index.ts | 37 + .../outbound/metrics_outbound_120.json | 28 + .../outbound/metrics_outbound_1800.json | 40 + .../outbound/metrics_outbound_300.json | 40 + .../outbound/metrics_outbound_3600.json | 32 + .../metrics/outbound/metrics_outbound_60.json | 24 + .../outbound/metrics_outbound_600.json | 144 +++ .../__fixtures__/namespaces/bookinfo/tls.json | 1 + .../namespaces/istio-system/health/app.json | 114 +++ .../istio-system/health/service.json | 65 ++ .../istio-system/health/workload.json | 100 ++ .../metrics/inbound/metrics_inbound_120.json | 100 ++ .../metrics/inbound/metrics_inbound_1800.json | 130 +++ .../metrics/inbound/metrics_inbound_300.json | 130 +++ .../metrics/inbound/metrics_inbound_3600.json | 115 +++ .../metrics/inbound/metrics_inbound_60.json | 90 ++ .../metrics/inbound/metrics_inbound_600.json | 141 +++ .../namespaces/istio-system/metrics/index.ts | 37 + .../outbound/metrics_outbound_120.json | 112 +++ .../outbound/metrics_outbound_1800.json | 148 +++ .../outbound/metrics_outbound_300.json | 148 +++ .../outbound/metrics_outbound_3600.json | 124 +++ .../metrics/outbound/metrics_outbound_60.json | 100 ++ .../outbound/metrics_outbound_600.json | 252 +++++ .../namespaces/istio-system/tls.json | 1 + .../namespaces/travel-agency/health/app.json} | 86 +- .../travel-agency/health/service.json | 75 ++ .../travel-agency/health/workload.json | 188 ++++ .../metrics/inbound/metrics_inbound_120.json | 28 + .../metrics/inbound/metrics_inbound_1800.json | 40 + .../metrics/inbound/metrics_inbound_300.json | 40 + .../metrics/inbound/metrics_inbound_3600.json | 34 + .../metrics/inbound/metrics_inbound_60.json | 24 + .../metrics/inbound/metrics_inbound_600.json | 216 +++++ .../namespaces/travel-agency/metrics/index.ts | 37 + .../outbound/metrics_outbound_120.json | 16 + .../outbound/metrics_outbound_1800.json | 22 + .../outbound/metrics_outbound_300.json | 22 + .../outbound/metrics_outbound_3600.json | 19 + .../metrics/outbound/metrics_outbound_60.json | 14 + .../outbound/metrics_outbound_600.json | 198 ++++ .../namespaces/travel-agency/tls.json | 1 + .../travel-control/health/app.json} | 6 +- .../travel-control/health/service.json | 9 + .../travel-control/health/workload.json | 16 + .../metrics/inbound/metrics_inbound_120.json | 1 + .../metrics/inbound/metrics_inbound_1800.json | 1 + .../metrics/inbound/metrics_inbound_300.json | 1 + .../metrics/inbound/metrics_inbound_3600.json | 1 + .../metrics/inbound/metrics_inbound_60.json | 1 + .../metrics/inbound/metrics_inbound_600.json | 1 + .../travel-control/metrics/index.ts | 37 + .../outbound/metrics_outbound_120.json | 1 + .../outbound/metrics_outbound_1800.json | 1 + .../outbound/metrics_outbound_300.json | 1 + .../outbound/metrics_outbound_3600.json | 1 + .../metrics/outbound/metrics_outbound_60.json | 1 + .../outbound/metrics_outbound_600.json | 1 + .../namespaces/travel-control/tls.json | 1 + .../namespaces/travel-portal/health/app.json} | 24 +- .../travel-portal/health/service.json | 23 + .../travel-portal/health/workload.json | 44 + .../metrics/inbound/metrics_inbound_120.json | 1 + .../metrics/inbound/metrics_inbound_1800.json | 1 + .../metrics/inbound/metrics_inbound_300.json | 1 + .../metrics/inbound/metrics_inbound_3600.json | 1 + .../metrics/inbound/metrics_inbound_60.json | 1 + .../metrics/inbound/metrics_inbound_600.json | 1 + .../namespaces/travel-portal/metrics/index.ts | 37 + .../outbound/metrics_outbound_120.json | 28 + .../outbound/metrics_outbound_1800.json | 40 + .../outbound/metrics_outbound_300.json | 40 + .../outbound/metrics_outbound_3600.json | 34 + .../metrics/outbound/metrics_outbound_60.json | 24 + .../outbound/metrics_outbound_600.json | 144 +++ .../namespaces/travel-portal/tls.json | 1 + plugins/kiali/dev/__fixtures__/status.json | 22 - plugins/kiali/dev/index.tsx | 316 ++++-- plugins/kiali/dev/mockEntity.ts | 19 + plugins/kiali/images/overview_tab.png | Bin 116063 -> 173330 bytes plugins/kiali/package.json | 27 +- plugins/kiali/src/Router.tsx | 21 +- plugins/kiali/src/actions/ActionKeys.ts | 106 ++ .../kiali/src/actions/HelpDropdownActions.ts | 19 + .../src/actions/IstioCertsInfoActions.ts | 10 + .../kiali/src/actions/IstioStatusActions.ts | 12 + plugins/kiali/src/actions/KialiAppAction.ts | 18 + plugins/kiali/src/actions/LoginActions.ts | 63 ++ plugins/kiali/src/actions/MeshTlsActions.ts | 10 + .../kiali/src/actions/MessageCenterActions.ts | 59 ++ plugins/kiali/src/actions/NamespaceAction.ts | 30 + .../kiali/src/actions/UserSettingsActions.ts | 33 + plugins/kiali/src/actions/index.ts | 4 + plugins/kiali/src/api/apiClient.ts | 108 --- plugins/kiali/src/api/index.ts | 1 - plugins/kiali/src/app/History.ts | 220 +++++ plugins/kiali/src/assets/img/go-logo.png | Bin 0 -> 5007 bytes plugins/kiali/src/assets/img/graphql-logo.svg | 1 + plugins/kiali/src/assets/img/grpc-logo.svg | 1 + plugins/kiali/src/assets/img/hollow-pin.png | Bin 0 -> 7707 bytes plugins/kiali/src/assets/img/jaeger-icon.svg | 1 + plugins/kiali/src/assets/img/java-logo.png | Bin 0 -> 13841 bytes plugins/kiali/src/assets/img/kiali-title.svg | 1 + .../img/legend-nooptimize/aggregate.svg | 74 ++ .../src/assets/img/legend-nooptimize/app.svg | 75 ++ .../img/legend-nooptimize/edge-danger.svg | 76 ++ .../img/legend-nooptimize/edge-idle.svg | 76 ++ .../img/legend-nooptimize/edge-success.svg | 76 ++ .../assets/img/legend-nooptimize/edge-tcp.svg | 73 ++ .../img/legend-nooptimize/edge-warn.svg | 76 ++ .../legend-nooptimize/external-namespace.svg | 83 ++ .../img/legend-nooptimize/mtls-badge.svg | 64 ++ .../node-badge-circuit-breaker.svg | 75 ++ .../node-badge-fault-injection.svg | 79 ++ .../legend-nooptimize/node-badge-gateways.svg | 81 ++ .../node-badge-mirroring.svg | 80 ++ .../node-badge-missing-sidecar.svg | 119 +++ .../node-badge-request-timeout.svg | 95 ++ .../node-badge-traffic-shifting.svg | 82 ++ .../node-badge-traffic-source.svg | 101 ++ .../node-badge-virtual-services.svg | 75 ++ .../node-badge-workload-entry.svg | 500 ++++++++++ .../legend-nooptimize/node-color-danger.svg | 63 ++ .../img/legend-nooptimize/node-color-idle.svg | 62 ++ .../legend-nooptimize/node-color-normal.svg | 63 ++ .../legend-nooptimize/node-color-warning.svg | 63 ++ .../src/assets/img/legend-nooptimize/node.svg | 67 ++ .../restricted-namespace.svg | 72 ++ .../img/legend-nooptimize/service-entry.svg | 68 ++ .../assets/img/legend-nooptimize/service.svg | 65 ++ .../traffic-failed-request.svg | 78 ++ .../traffic-normal-request.svg | 74 ++ .../img/legend-nooptimize/traffic-tcp.svg | 92 ++ .../img/legend-nooptimize/virtualservice.svg | 66 ++ .../src/assets/img/legend-pf/aggregate.svg | 19 + .../kiali/src/assets/img/legend-pf/app.svg | 1 + .../src/assets/img/legend-pf/edge-danger.svg | 1 + .../src/assets/img/legend-pf/edge-idle.svg | 1 + .../src/assets/img/legend-pf/edge-success.svg | 1 + .../src/assets/img/legend-pf/edge-tcp.svg | 12 + .../src/assets/img/legend-pf/edge-warn.svg | 1 + .../img/legend-pf/external-namespace.svg | 1 + .../src/assets/img/legend-pf/mtls-badge.svg | 1 + .../legend-pf/node-badge-circuit-breaker.svg | 1 + .../legend-pf/node-badge-fault-injection.svg | 1 + .../img/legend-pf/node-badge-gateways.svg | 1 + .../img/legend-pf/node-badge-mirroring.svg | 1 + .../legend-pf/node-badge-missing-sidecar.svg | 1 + .../legend-pf/node-badge-request-timeout.svg | 1 + .../legend-pf/node-badge-traffic-shifting.svg | 1 + .../legend-pf/node-badge-traffic-source.svg | 1 + .../legend-pf/node-badge-virtual-services.svg | 1 + .../legend-pf/node-badge-workload-entry.svg | 1 + .../img/legend-pf/node-color-danger.svg | 1 + .../img/legend-pf/node-color-healthy.svg | 1 + .../assets/img/legend-pf/node-color-idle.svg | 1 + .../img/legend-pf/node-color-warning.svg | 1 + .../kiali/src/assets/img/legend-pf/node.svg | 1 + .../img/legend-pf/restricted-namespace.svg | 1 + .../assets/img/legend-pf/service-entry.svg | 17 + .../src/assets/img/legend-pf/service.svg | 18 + .../img/legend-pf/traffic-failed-request.svg | 22 + .../img/legend-pf/traffic-healthy-request.svg | 21 + .../src/assets/img/legend-pf/traffic-tcp.svg | 21 + .../kiali/src/assets/img/legend/aggregate.svg | 1 + plugins/kiali/src/assets/img/legend/app.svg | 1 + .../src/assets/img/legend/edge-danger.svg | 1 + .../kiali/src/assets/img/legend/edge-idle.svg | 1 + .../src/assets/img/legend/edge-success.svg | 1 + .../kiali/src/assets/img/legend/edge-tcp.svg | 1 + .../kiali/src/assets/img/legend/edge-warn.svg | 1 + .../assets/img/legend/external-namespace.svg | 1 + .../src/assets/img/legend/mtls-badge.svg | 1 + .../img/legend/node-badge-circuit-breaker.svg | 1 + .../img/legend/node-badge-fault-injection.svg | 1 + .../assets/img/legend/node-badge-gateways.svg | 1 + .../img/legend/node-badge-mirroring.svg | 1 + .../img/legend/node-badge-missing-sidecar.svg | 1 + .../img/legend/node-badge-request-timeout.svg | 1 + .../legend/node-badge-traffic-shifting.svg | 1 + .../img/legend/node-badge-traffic-source.svg | 1 + .../legend/node-badge-virtual-services.svg | 1 + .../img/legend/node-badge-workload-entry.svg | 1 + .../assets/img/legend/node-color-danger.svg | 1 + .../src/assets/img/legend/node-color-idle.svg | 1 + .../assets/img/legend/node-color-normal.svg | 1 + .../assets/img/legend/node-color-warning.svg | 1 + plugins/kiali/src/assets/img/legend/node.svg | 1 + .../img/legend/restricted-namespace.svg | 1 + .../src/assets/img/legend/service-entry.svg | 1 + .../kiali/src/assets/img/legend/service.svg | 1 + .../img/legend/traffic-failed-request.svg | 1 + .../img/legend/traffic-normal-request.svg | 1 + .../src/assets/img/legend/traffic-tcp.svg | 1 + .../src/assets/img/legend/virtualservice.svg | 1 + plugins/kiali/src/assets/img/logo-alt.svg | 1 + .../kiali/src/assets/img/logo-lightbkg.svg | 1 + .../src/assets/img/microprofile-logo.png | Bin 0 -> 2538 bytes .../src/assets/img/mtls-status-full-dark.svg | 1 + .../kiali/src/assets/img/mtls-status-full.svg | 1 + .../assets/img/mtls-status-partial-dark.svg | 1 + .../src/assets/img/mtls-status-partial.svg | 1 + .../src/assets/img/node-background-key.png | Bin 0 -> 4286 bytes .../assets/img/node-background-topology.png | Bin 0 -> 6587 bytes plugins/kiali/src/assets/img/nodejs-logo.png | Bin 0 -> 1418 bytes plugins/kiali/src/assets/img/rest-logo.svg | 30 + plugins/kiali/src/assets/img/solid-pin.png | Bin 0 -> 6067 bytes .../kiali/src/assets/img/thorntail-logo.png | Bin 0 -> 825 bytes plugins/kiali/src/assets/img/vertx-logo.png | Bin 0 -> 1345 bytes .../src/components/About/AboutUIModal.tsx | 219 +++++ .../src/components/Ambient/AmbientBadge.tsx | 25 + .../src/components/Ambient/AmbientLabel.tsx | 69 ++ .../CustomTooltip.tsx | 111 ++- .../SparklineChart.tsx | 165 ++-- .../DebugInformation/DebugInformation.tsx | 296 ++++++ .../src/components/FilterList/FitlerHelper.ts | 174 ++++ .../src/components/Filters/CommonFilters.ts | 96 ++ .../src/components/Filters/LabelFilter.tsx | 77 ++ .../components/Filters/StatefulFilters.tsx | 84 ++ .../src/components/Health/HealthStyle.ts | 17 + plugins/kiali/src/components/Health/Helper.ts | 19 + .../src/components/Icons/MTLSStatusFull.tsx | 25 - .../components/Icons/MTLSStatusPartial.tsx | 25 - plugins/kiali/src/components/Icons/index.ts | 69 -- .../IstioStatus/IstioComponentStatus.tsx | 92 ++ .../components/IstioStatus/IstioStatus.tsx | 111 +++ .../IstioStatus/IstioStatusInline.tsx | 30 + .../IstioStatus/IstioStatusList.tsx | 19 +- .../components/IstioWizards/WizardActions.ts} | 0 .../KialiComponent/Header/Header.tsx | 100 -- .../KialiComponent/Header/StatusContent.tsx | 211 ---- .../components/KialiComponent/Header/index.ts | 2 - .../KialiComponent/KialiComponent.tsx | 139 --- .../components/KialiComponent/KialiNoPath.tsx | 27 - .../src/components/KialiComponent/index.ts | 2 - .../components/Link/IstioConfigListLink.tsx | 61 ++ .../components/Link/ValidationSummaryLink.tsx | 39 + .../kiali/src/components/MTls/MTLSIcon.tsx | 53 + .../kiali/src/components/MTls/MTLSStatus.tsx | 67 ++ .../components/MTls/NamespaceMTLSStatus.tsx | 60 ++ .../components/MessageCenter/AlertDrawer.tsx | 91 ++ .../MessageCenter/AlertDrawerGroup.tsx | 80 ++ .../MessageCenter/AlertDrawerMessage.tsx | 90 ++ .../MessageCenter/MessageCenter.tsx | 114 +++ .../MissingSidecar/MissingSidecar.tsx | 103 ++ .../src/components/Overview/Overview.tsx | 224 ----- .../Overview/OverviewCard/OverviewCard.tsx | 115 --- .../ControlPlaneNamespaceStatus.tsx | 103 -- .../OverviewCardBody/IstioConfigStatus.tsx | 133 --- .../OverviewCardBody/NamespaceMTLSStatus.tsx | 82 -- .../OverviewCardBody/NamespaceStatuses.tsx | 88 -- .../OverviewCardBody/OverviewCardBody.tsx | 109 --- .../OverviewCardBody/OverviewStatus.tsx | 45 - .../OverviewCard/OverviewCardBody/TLSInfo.tsx | 117 --- .../OverviewCard/OverviewCardBody/index.tsx | 1 - .../ControlPlaneVersionBadge.tsx | 0 .../IstioStatus/IstioComponentStatus.tsx | 68 -- .../IstioStatus/IstioStatus.tsx | 74 -- .../OverviewCardHeader/IstioStatus/index.tsx | 1 - .../OverviewCardHeader/OverviewCardHeader.tsx | 88 -- .../OverviewCard/OverviewCardHeader/index.tsx | 1 - .../OverviewMetrics/OverviewChart/Charts.css | 3 - .../OverviewChart/ControlPlaneChart.tsx | 211 ---- .../OverviewChart/DataPlaneChart.tsx | 105 -- .../OverviewChart/HookedChartTooltip.tsx | 31 - .../OverviewChart/OverviewChart.tsx | 44 - .../OverviewMetrics/OverviewChart/index.ts | 1 - .../OverviewMetrics/OverviewMetrics.tsx | 77 -- .../OverviewCard/OverviewMetrics/index.tsx | 1 - .../components/Overview/OverviewToolbar.tsx | 111 --- .../kiali/src/components/Overview/TLSInfo.tsx | 109 +++ .../kiali/src/components/Overview/health.ts | 81 -- .../kiali/src/components/Overview/index.ts | 3 - plugins/kiali/src/components/Pf/PfBadges.tsx | 201 ++++ plugins/kiali/src/components/Pf/PfColors.tsx | 233 +++++ plugins/kiali/src/components/Pf/PfTitle.tsx | 79 ++ .../src/components/Validations/Validation.tsx | 115 +++ .../Validations/ValidationSummary.tsx | 136 +++ .../kiali/src/config/AuthenticationConfig.ts | 10 + .../src/config/Config.ts | 53 +- .../src/config}/HealthConfig.ts | 19 +- plugins/kiali/src/config/Icons.ts | 157 +++ plugins/kiali/src/config/KialiIcon.tsx | 202 ++++ plugins/kiali/src/config/KialiLogo.tsx | 31 + plugins/kiali/src/config/Paths.ts | 14 + plugins/kiali/src/config/ServerConfig.ts | 192 ++++ plugins/kiali/src/config/index.ts | 5 + plugins/kiali/src/helper/ErrorRate/index.ts | 3 - plugins/kiali/src/helper/handlerError.ts | 11 - plugins/kiali/src/helper/index.ts | 4 - plugins/kiali/src/helper/utils.ts | 28 - .../kiali/src/pages/Kiali/Header/Header.tsx | 129 +++ plugins/kiali/src/pages/Kiali/KialiHelper.tsx | 74 ++ plugins/kiali/src/pages/Kiali/KialiPage.tsx | 40 + plugins/kiali/src/pages/Kiali/NoPath.tsx | 25 + plugins/kiali/src/pages/Kiali/index.ts | 2 + .../kiali/src/pages/Overview/NamespaceInfo.ts | 28 + .../OverviewCard}/CanaryUpgradeProgress.tsx | 41 +- .../OverviewCard/ControlPlaneBadge.tsx | 44 + .../ControlPlaneNamespaceStatus.tsx | 109 +++ .../OverviewCard/ControlPlaneVersionBadge.tsx | 24 + .../Overview/OverviewCard/NamespaceHeader.tsx | 83 ++ .../Overview/OverviewCard/NamespaceLabels.tsx | 32 + .../Overview/OverviewCard/NamespaceStatus.tsx | 124 +++ .../Overview/OverviewCard/OverviewCard.tsx | 165 ++++ .../OverviewCardControlPlaneNamespace.tsx | 263 +++++ .../OverviewCardDataPlaneNamespace.tsx | 105 ++ .../OverviewCardSparklineCharts.tsx | 54 ++ .../OverviewCard/RemoteClusterBadge.tsx | 15 + .../Overview/OverviewCard/index.ts | 0 .../src/pages/Overview/OverviewHelper.ts | 15 + .../kiali/src/pages/Overview/OverviewPage.tsx | 473 +++++++++ .../src/pages/Overview/OverviewStatus.tsx | 80 ++ .../src/pages/Overview/OverviewToolbar.tsx | 143 +++ plugins/kiali/src/pages/Overview/Sorts.ts | 124 +++ plugins/kiali/src/plugin.ts | 10 +- plugins/kiali/src/reducers/HelpDropdown.ts | 33 + .../kiali/src/reducers/IstioCertsInfoState.ts | 19 + .../kiali/src/reducers/IstioStatusState.ts | 20 + plugins/kiali/src/reducers/Login.ts | 77 ++ plugins/kiali/src/reducers/MeshTlsState.ts | 29 + plugins/kiali/src/reducers/MessageCenter.ts | 198 ++++ plugins/kiali/src/reducers/Namespace.ts | 95 ++ .../kiali/src/reducers/UserSettingsState.ts | 55 ++ plugins/kiali/src/reducers/index.ts | 8 + plugins/kiali/src/services/Api.ts | 510 ++++++++++ .../src/services}/Prometheus.ts | 12 +- plugins/kiali/src/setupTests.ts | 1 + plugins/kiali/src/store/ConfigStore.ts | 27 + plugins/kiali/src/store/Context.ts | 3 + plugins/kiali/src/store/KialiProvider.tsx | 243 +++++ plugins/kiali/src/store/Store.ts | 78 ++ plugins/kiali/src/store/index.ts | 2 + plugins/kiali/src/styles/AceEditorStyle.ts | 46 + plugins/kiali/src/styles/StyleUtils.ts | 15 + plugins/kiali/src/styles/TabStyles.ts | 24 + .../{kiali-common => kiali}/src/types/Auth.ts | 0 .../src/types/CertsInfo.ts | 2 +- plugins/kiali/src/types/Common.ts | 87 ++ .../src/types/Dashboards.ts | 6 +- .../{helper => types}/ErrorRate/ErrorRate.ts | 71 +- plugins/kiali/src/types/ErrorRate/index.ts | 19 + .../src/{helper => types}/ErrorRate/types.ts | 6 +- .../src/{helper => types}/ErrorRate/utils.ts | 131 +-- plugins/kiali/src/types/Filters.ts | 70 ++ .../src/types/Graph.ts | 136 ++- .../{helper/Health.tsx => types/Health.ts} | 338 ++++--- .../src/types/HealthAnnotation.ts | 12 +- plugins/kiali/src/types/IstioConfigDetails.ts | 108 +++ .../src/types/IstioConfigList.ts | 116 +-- .../src/types/IstioObjects.ts | 44 +- .../src/types/IstioStatus.ts | 4 +- plugins/kiali/src/types/Mesh.ts | 18 + plugins/kiali/src/types/MessageCenter.ts | 29 + .../src/types/Metrics.ts | 33 +- .../src/types/MetricsOptions.ts | 5 +- plugins/kiali/src/types/Namespace.ts | 16 + .../src/types/Overlay.ts | 0 plugins/kiali/src/types/Permissions.ts | 23 + plugins/kiali/src/types/ServerConfig.ts | 141 +++ plugins/kiali/src/types/ServiceInfo.ts | 231 +++++ plugins/kiali/src/types/ServiceList.ts | 29 + plugins/kiali/src/types/SortFilters.ts | 10 + .../src/types/StatusState.ts | 14 +- .../src/types/TLSStatus.ts | 0 .../src/types/VictoryChartInfo.ts | 7 +- plugins/kiali/src/types/Workload.ts | 105 ++ plugins/kiali/src/utils/Alertutils.ts | 139 +++ plugins/kiali/src/utils/Callback.ts | 29 + .../src/utils/CancelablePromises.ts | 7 +- plugins/kiali/src/utils/Common.ts | 49 + plugins/kiali/src/utils/Date.ts | 7 + plugins/kiali/src/utils/RateIntervals.ts | 19 + plugins/kiali/src/utils/Reducer.ts | 3 + .../src/utils/TimeSeriesUtils.ts | 38 +- .../src/utils/VictoryChartsUtils.ts | 139 ++- .../src/utils/VictoryEvents.ts | 6 +- .../src/utils/entityFilter.ts} | 23 +- plugins/kiali/tsconfig.json | 8 +- yarn.lock | 129 ++- 455 files changed, 19368 insertions(+), 7778 deletions(-) delete mode 100644 plugins/kiali-backend/__fixtures__/data/canaries_status.json create mode 100644 plugins/kiali-backend/__fixtures__/data/config/auth_info.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/istio_status.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/namespaces/all.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-metrics.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-metrics.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-metrics.json delete mode 100644 plugins/kiali-backend/__fixtures__/data/namespaces/validations.json rename plugins/kiali-backend/src/clients/{auth.ts => Auth.ts} (51%) delete mode 100644 plugins/kiali-backend/src/clients/auth.test.ts delete mode 100644 plugins/kiali-backend/src/filters/byAnnotation.test.ts rename plugins/{kiali-common/src/helpers => kiali-backend/src/service}/config.ts (88%) delete mode 100644 plugins/kiali-backend/src/service/router.test.ts delete mode 100644 plugins/kiali-common/.eslintrc.js delete mode 100644 plugins/kiali-common/CHANGELOG.md delete mode 100644 plugins/kiali-common/README.md delete mode 100644 plugins/kiali-common/package.json delete mode 100644 plugins/kiali-common/src/config/index.ts delete mode 100644 plugins/kiali-common/src/helpers/index.ts delete mode 100644 plugins/kiali-common/src/index.ts delete mode 100644 plugins/kiali-common/src/query/index.ts delete mode 100644 plugins/kiali-common/src/responses/index.ts delete mode 100644 plugins/kiali-common/src/setupTests.ts delete mode 100644 plugins/kiali-common/src/types/Common.ts delete mode 100644 plugins/kiali-common/src/types/Config.ts delete mode 100644 plugins/kiali-common/src/types/Fetch.ts delete mode 100644 plugins/kiali-common/src/types/Health.ts delete mode 100644 plugins/kiali-common/src/types/HealthStatus.ts delete mode 100644 plugins/kiali-common/src/types/IstioConfigList.test.ts delete mode 100644 plugins/kiali-common/src/types/Namespace.ts delete mode 100644 plugins/kiali-common/src/types/Permissions.ts delete mode 100644 plugins/kiali-common/src/types/index.ts delete mode 100644 plugins/kiali-common/src/utils/RateIntervals.ts delete mode 100644 plugins/kiali-common/src/utils/index.ts delete mode 100644 plugins/kiali-common/tsconfig.json delete mode 100644 plugins/kiali-common/turbo.json create mode 100644 plugins/kiali/CONTRIBUTING.md create mode 100644 plugins/kiali/catalog-demo.yaml create mode 100644 plugins/kiali/config.d.ts delete mode 100644 plugins/kiali/dev/__fixtures__/1-overview.json delete mode 100644 plugins/kiali/dev/__fixtures__/config.json create mode 100644 plugins/kiali/dev/__fixtures__/general/auth_info_anonymous.json rename plugins/{kiali-backend/__fixtures__/data/config/anonymous_config.json => kiali/dev/__fixtures__/general/config.json} (86%) rename plugins/{kiali-backend/__fixtures__/data/config/istio_certs.json => kiali/dev/__fixtures__/general/istioCertsInfo.json} (69%) create mode 100644 plugins/kiali/dev/__fixtures__/general/istioConfig.json create mode 100644 plugins/kiali/dev/__fixtures__/general/istioStatus.json create mode 100644 plugins/kiali/dev/__fixtures__/general/istioValidations.json create mode 100644 plugins/kiali/dev/__fixtures__/general/meshCanaryStatus.json create mode 100644 plugins/kiali/dev/__fixtures__/general/meshIstioResurceThresholds.json rename plugins/{kiali-backend/__fixtures__/data/config/mesh_tls.json => kiali/dev/__fixtures__/general/meshTls.json} (100%) create mode 100644 plugins/kiali/dev/__fixtures__/general/namespaces.json create mode 100644 plugins/kiali/dev/__fixtures__/general/outbound_traffic_policy_mode.json create mode 100644 plugins/kiali/dev/__fixtures__/general/status.json create mode 100644 plugins/kiali/dev/__fixtures__/index.ts delete mode 100644 plugins/kiali/dev/__fixtures__/namespaces.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/app.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/service.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/workload.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/index.ts create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/bookinfo/tls.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/app.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/service.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/workload.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/index.ts create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/istio-system/tls.json rename plugins/{kiali-backend/__fixtures__/data/namespaces/travel-agency-health.json => kiali/dev/__fixtures__/namespaces/travel-agency/health/app.json} (56%) create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/service.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/workload.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/index.ts create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-agency/tls.json rename plugins/{kiali-backend/__fixtures__/data/namespaces/travel-control-health.json => kiali/dev/__fixtures__/namespaces/travel-control/health/app.json} (69%) create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/service.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/workload.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/index.ts create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-control/tls.json rename plugins/{kiali-backend/__fixtures__/data/namespaces/travel-portal-health.json => kiali/dev/__fixtures__/namespaces/travel-portal/health/app.json} (69%) create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/service.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/workload.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/index.ts create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_120.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_1800.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_300.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_3600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_60.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_600.json create mode 100644 plugins/kiali/dev/__fixtures__/namespaces/travel-portal/tls.json delete mode 100644 plugins/kiali/dev/__fixtures__/status.json create mode 100644 plugins/kiali/dev/mockEntity.ts create mode 100644 plugins/kiali/src/actions/ActionKeys.ts create mode 100644 plugins/kiali/src/actions/HelpDropdownActions.ts create mode 100644 plugins/kiali/src/actions/IstioCertsInfoActions.ts create mode 100644 plugins/kiali/src/actions/IstioStatusActions.ts create mode 100644 plugins/kiali/src/actions/KialiAppAction.ts create mode 100644 plugins/kiali/src/actions/LoginActions.ts create mode 100644 plugins/kiali/src/actions/MeshTlsActions.ts create mode 100644 plugins/kiali/src/actions/MessageCenterActions.ts create mode 100644 plugins/kiali/src/actions/NamespaceAction.ts create mode 100644 plugins/kiali/src/actions/UserSettingsActions.ts create mode 100644 plugins/kiali/src/actions/index.ts delete mode 100644 plugins/kiali/src/api/apiClient.ts delete mode 100644 plugins/kiali/src/api/index.ts create mode 100644 plugins/kiali/src/app/History.ts create mode 100644 plugins/kiali/src/assets/img/go-logo.png create mode 100644 plugins/kiali/src/assets/img/graphql-logo.svg create mode 100644 plugins/kiali/src/assets/img/grpc-logo.svg create mode 100644 plugins/kiali/src/assets/img/hollow-pin.png create mode 100644 plugins/kiali/src/assets/img/jaeger-icon.svg create mode 100644 plugins/kiali/src/assets/img/java-logo.png create mode 100644 plugins/kiali/src/assets/img/kiali-title.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/aggregate.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/app.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/edge-danger.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/edge-idle.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/edge-success.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/edge-tcp.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/edge-warn.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/external-namespace.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/mtls-badge.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-circuit-breaker.svg create mode 100644 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-fault-injection.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-gateways.svg create mode 100644 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-mirroring.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-missing-sidecar.svg create mode 100644 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-request-timeout.svg create mode 100644 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-shifting.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-source.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-virtual-services.svg create mode 100644 plugins/kiali/src/assets/img/legend-nooptimize/node-badge-workload-entry.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-color-danger.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-color-idle.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-color-normal.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node-color-warning.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/node.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/restricted-namespace.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/service-entry.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/service.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/traffic-failed-request.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/traffic-normal-request.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/traffic-tcp.svg create mode 100755 plugins/kiali/src/assets/img/legend-nooptimize/virtualservice.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/aggregate.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/app.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/edge-danger.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/edge-idle.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/edge-success.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/edge-tcp.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/edge-warn.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/external-namespace.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/mtls-badge.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-circuit-breaker.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-fault-injection.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-gateways.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-mirroring.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-missing-sidecar.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-request-timeout.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-shifting.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-source.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-virtual-services.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-badge-workload-entry.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-color-danger.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-color-healthy.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-color-idle.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node-color-warning.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/node.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/restricted-namespace.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/service-entry.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/service.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/traffic-failed-request.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/traffic-healthy-request.svg create mode 100644 plugins/kiali/src/assets/img/legend-pf/traffic-tcp.svg create mode 100644 plugins/kiali/src/assets/img/legend/aggregate.svg create mode 100644 plugins/kiali/src/assets/img/legend/app.svg create mode 100644 plugins/kiali/src/assets/img/legend/edge-danger.svg create mode 100644 plugins/kiali/src/assets/img/legend/edge-idle.svg create mode 100644 plugins/kiali/src/assets/img/legend/edge-success.svg create mode 100644 plugins/kiali/src/assets/img/legend/edge-tcp.svg create mode 100644 plugins/kiali/src/assets/img/legend/edge-warn.svg create mode 100644 plugins/kiali/src/assets/img/legend/external-namespace.svg create mode 100644 plugins/kiali/src/assets/img/legend/mtls-badge.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-circuit-breaker.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-fault-injection.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-gateways.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-mirroring.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-missing-sidecar.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-request-timeout.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-traffic-shifting.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-traffic-source.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-virtual-services.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-badge-workload-entry.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-color-danger.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-color-idle.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-color-normal.svg create mode 100644 plugins/kiali/src/assets/img/legend/node-color-warning.svg create mode 100644 plugins/kiali/src/assets/img/legend/node.svg create mode 100644 plugins/kiali/src/assets/img/legend/restricted-namespace.svg create mode 100644 plugins/kiali/src/assets/img/legend/service-entry.svg create mode 100644 plugins/kiali/src/assets/img/legend/service.svg create mode 100644 plugins/kiali/src/assets/img/legend/traffic-failed-request.svg create mode 100644 plugins/kiali/src/assets/img/legend/traffic-normal-request.svg create mode 100644 plugins/kiali/src/assets/img/legend/traffic-tcp.svg create mode 100644 plugins/kiali/src/assets/img/legend/virtualservice.svg create mode 100644 plugins/kiali/src/assets/img/logo-alt.svg create mode 100644 plugins/kiali/src/assets/img/logo-lightbkg.svg create mode 100644 plugins/kiali/src/assets/img/microprofile-logo.png create mode 100644 plugins/kiali/src/assets/img/mtls-status-full-dark.svg create mode 100644 plugins/kiali/src/assets/img/mtls-status-full.svg create mode 100644 plugins/kiali/src/assets/img/mtls-status-partial-dark.svg create mode 100644 plugins/kiali/src/assets/img/mtls-status-partial.svg create mode 100644 plugins/kiali/src/assets/img/node-background-key.png create mode 100644 plugins/kiali/src/assets/img/node-background-topology.png create mode 100644 plugins/kiali/src/assets/img/nodejs-logo.png create mode 100644 plugins/kiali/src/assets/img/rest-logo.svg create mode 100644 plugins/kiali/src/assets/img/solid-pin.png create mode 100644 plugins/kiali/src/assets/img/thorntail-logo.png create mode 100644 plugins/kiali/src/assets/img/vertx-logo.png create mode 100644 plugins/kiali/src/components/About/AboutUIModal.tsx create mode 100644 plugins/kiali/src/components/Ambient/AmbientBadge.tsx create mode 100644 plugins/kiali/src/components/Ambient/AmbientLabel.tsx rename plugins/kiali/src/components/{Overview/OverviewCard/OverviewMetrics/OverviewChart => Charts}/CustomTooltip.tsx (58%) rename plugins/kiali/src/components/{Overview/OverviewCard/OverviewMetrics/OverviewChart => Charts}/SparklineChart.tsx (52%) create mode 100644 plugins/kiali/src/components/DebugInformation/DebugInformation.tsx create mode 100644 plugins/kiali/src/components/FilterList/FitlerHelper.ts create mode 100644 plugins/kiali/src/components/Filters/CommonFilters.ts create mode 100644 plugins/kiali/src/components/Filters/LabelFilter.tsx create mode 100644 plugins/kiali/src/components/Filters/StatefulFilters.tsx create mode 100644 plugins/kiali/src/components/Health/HealthStyle.ts create mode 100644 plugins/kiali/src/components/Health/Helper.ts delete mode 100644 plugins/kiali/src/components/Icons/MTLSStatusFull.tsx delete mode 100644 plugins/kiali/src/components/Icons/MTLSStatusPartial.tsx delete mode 100644 plugins/kiali/src/components/Icons/index.ts create mode 100644 plugins/kiali/src/components/IstioStatus/IstioComponentStatus.tsx create mode 100644 plugins/kiali/src/components/IstioStatus/IstioStatus.tsx create mode 100644 plugins/kiali/src/components/IstioStatus/IstioStatusInline.tsx rename plugins/kiali/src/components/{Overview/OverviewCard/OverviewCardHeader => }/IstioStatus/IstioStatusList.tsx (67%) rename plugins/{kiali-common/src/types/IstioWizards.ts => kiali/src/components/IstioWizards/WizardActions.ts} (100%) delete mode 100644 plugins/kiali/src/components/KialiComponent/Header/Header.tsx delete mode 100644 plugins/kiali/src/components/KialiComponent/Header/StatusContent.tsx delete mode 100644 plugins/kiali/src/components/KialiComponent/Header/index.ts delete mode 100644 plugins/kiali/src/components/KialiComponent/KialiComponent.tsx delete mode 100644 plugins/kiali/src/components/KialiComponent/KialiNoPath.tsx delete mode 100644 plugins/kiali/src/components/KialiComponent/index.ts create mode 100644 plugins/kiali/src/components/Link/IstioConfigListLink.tsx create mode 100644 plugins/kiali/src/components/Link/ValidationSummaryLink.tsx create mode 100644 plugins/kiali/src/components/MTls/MTLSIcon.tsx create mode 100644 plugins/kiali/src/components/MTls/MTLSStatus.tsx create mode 100644 plugins/kiali/src/components/MTls/NamespaceMTLSStatus.tsx create mode 100644 plugins/kiali/src/components/MessageCenter/AlertDrawer.tsx create mode 100644 plugins/kiali/src/components/MessageCenter/AlertDrawerGroup.tsx create mode 100644 plugins/kiali/src/components/MessageCenter/AlertDrawerMessage.tsx create mode 100644 plugins/kiali/src/components/MessageCenter/MessageCenter.tsx create mode 100644 plugins/kiali/src/components/MissingSidecar/MissingSidecar.tsx delete mode 100644 plugins/kiali/src/components/Overview/Overview.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCard.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/ControlPlaneNamespaceStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/IstioConfigStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceMTLSStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceStatuses.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewCardBody.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/TLSInfo.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/index.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/ControlPlaneVersionBadge.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioComponentStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatus.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/index.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/OverviewCardHeader.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/index.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/Charts.css delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/ControlPlaneChart.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/DataPlaneChart.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/HookedChartTooltip.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/OverviewChart.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/index.ts delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewMetrics.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/index.tsx delete mode 100644 plugins/kiali/src/components/Overview/OverviewToolbar.tsx create mode 100644 plugins/kiali/src/components/Overview/TLSInfo.tsx delete mode 100644 plugins/kiali/src/components/Overview/health.ts delete mode 100644 plugins/kiali/src/components/Overview/index.ts create mode 100644 plugins/kiali/src/components/Pf/PfBadges.tsx create mode 100644 plugins/kiali/src/components/Pf/PfColors.tsx create mode 100644 plugins/kiali/src/components/Pf/PfTitle.tsx create mode 100644 plugins/kiali/src/components/Validations/Validation.tsx create mode 100644 plugins/kiali/src/components/Validations/ValidationSummary.tsx create mode 100644 plugins/kiali/src/config/AuthenticationConfig.ts rename plugins/{kiali-common => kiali}/src/config/Config.ts (92%) rename plugins/{kiali-common/src/types => kiali/src/config}/HealthConfig.ts (74%) create mode 100644 plugins/kiali/src/config/Icons.ts create mode 100644 plugins/kiali/src/config/KialiIcon.tsx create mode 100644 plugins/kiali/src/config/KialiLogo.tsx create mode 100644 plugins/kiali/src/config/Paths.ts create mode 100644 plugins/kiali/src/config/ServerConfig.ts create mode 100644 plugins/kiali/src/config/index.ts delete mode 100644 plugins/kiali/src/helper/ErrorRate/index.ts delete mode 100644 plugins/kiali/src/helper/handlerError.ts delete mode 100644 plugins/kiali/src/helper/index.ts delete mode 100644 plugins/kiali/src/helper/utils.ts create mode 100644 plugins/kiali/src/pages/Kiali/Header/Header.tsx create mode 100644 plugins/kiali/src/pages/Kiali/KialiHelper.tsx create mode 100644 plugins/kiali/src/pages/Kiali/KialiPage.tsx create mode 100644 plugins/kiali/src/pages/Kiali/NoPath.tsx create mode 100644 plugins/kiali/src/pages/Kiali/index.ts create mode 100644 plugins/kiali/src/pages/Overview/NamespaceInfo.ts rename plugins/kiali/src/{components/Overview/OverviewCard/OverviewMetrics => pages/Overview/OverviewCard}/CanaryUpgradeProgress.tsx (68%) create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneBadge.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneNamespaceStatus.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneVersionBadge.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/NamespaceHeader.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/NamespaceLabels.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/NamespaceStatus.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/OverviewCard.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardControlPlaneNamespace.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardDataPlaneNamespace.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardSparklineCharts.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewCard/RemoteClusterBadge.tsx rename plugins/kiali/src/{components => pages}/Overview/OverviewCard/index.ts (100%) create mode 100644 plugins/kiali/src/pages/Overview/OverviewHelper.ts create mode 100644 plugins/kiali/src/pages/Overview/OverviewPage.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewStatus.tsx create mode 100644 plugins/kiali/src/pages/Overview/OverviewToolbar.tsx create mode 100644 plugins/kiali/src/pages/Overview/Sorts.ts create mode 100644 plugins/kiali/src/reducers/HelpDropdown.ts create mode 100644 plugins/kiali/src/reducers/IstioCertsInfoState.ts create mode 100644 plugins/kiali/src/reducers/IstioStatusState.ts create mode 100644 plugins/kiali/src/reducers/Login.ts create mode 100644 plugins/kiali/src/reducers/MeshTlsState.ts create mode 100644 plugins/kiali/src/reducers/MessageCenter.ts create mode 100644 plugins/kiali/src/reducers/Namespace.ts create mode 100644 plugins/kiali/src/reducers/UserSettingsState.ts create mode 100644 plugins/kiali/src/reducers/index.ts create mode 100644 plugins/kiali/src/services/Api.ts rename plugins/{kiali-common/src/utils => kiali/src/services}/Prometheus.ts (75%) create mode 100644 plugins/kiali/src/store/ConfigStore.ts create mode 100644 plugins/kiali/src/store/Context.ts create mode 100644 plugins/kiali/src/store/KialiProvider.tsx create mode 100644 plugins/kiali/src/store/Store.ts create mode 100644 plugins/kiali/src/store/index.ts create mode 100644 plugins/kiali/src/styles/AceEditorStyle.ts create mode 100644 plugins/kiali/src/styles/StyleUtils.ts create mode 100644 plugins/kiali/src/styles/TabStyles.ts rename plugins/{kiali-common => kiali}/src/types/Auth.ts (100%) rename plugins/{kiali-common => kiali}/src/types/CertsInfo.ts (90%) create mode 100644 plugins/kiali/src/types/Common.ts rename plugins/{kiali-common => kiali}/src/types/Dashboards.ts (88%) rename plugins/kiali/src/{helper => types}/ErrorRate/ErrorRate.ts (71%) create mode 100644 plugins/kiali/src/types/ErrorRate/index.ts rename plugins/kiali/src/{helper => types}/ErrorRate/types.ts (90%) rename plugins/kiali/src/{helper => types}/ErrorRate/utils.ts (52%) create mode 100644 plugins/kiali/src/types/Filters.ts rename plugins/{kiali-common => kiali}/src/types/Graph.ts (76%) rename plugins/kiali/src/{helper/Health.tsx => types/Health.ts} (69%) rename plugins/{kiali-common => kiali}/src/types/HealthAnnotation.ts (87%) create mode 100644 plugins/kiali/src/types/IstioConfigDetails.ts rename plugins/{kiali-common => kiali}/src/types/IstioConfigList.ts (89%) rename plugins/{kiali-common => kiali}/src/types/IstioObjects.ts (96%) rename plugins/{kiali-common => kiali}/src/types/IstioStatus.ts (87%) create mode 100644 plugins/kiali/src/types/Mesh.ts create mode 100644 plugins/kiali/src/types/MessageCenter.ts rename plugins/{kiali-common => kiali}/src/types/Metrics.ts (77%) rename plugins/{kiali-common => kiali}/src/types/MetricsOptions.ts (96%) create mode 100644 plugins/kiali/src/types/Namespace.ts rename plugins/{kiali-common => kiali}/src/types/Overlay.ts (100%) create mode 100644 plugins/kiali/src/types/Permissions.ts create mode 100644 plugins/kiali/src/types/ServerConfig.ts create mode 100644 plugins/kiali/src/types/ServiceInfo.ts create mode 100644 plugins/kiali/src/types/ServiceList.ts create mode 100644 plugins/kiali/src/types/SortFilters.ts rename plugins/{kiali-common => kiali}/src/types/StatusState.ts (70%) rename plugins/{kiali-common => kiali}/src/types/TLSStatus.ts (100%) rename plugins/{kiali-common => kiali}/src/types/VictoryChartInfo.ts (91%) create mode 100644 plugins/kiali/src/types/Workload.ts create mode 100644 plugins/kiali/src/utils/Alertutils.ts create mode 100644 plugins/kiali/src/utils/Callback.ts rename plugins/{kiali-common => kiali}/src/utils/CancelablePromises.ts (94%) create mode 100644 plugins/kiali/src/utils/Common.ts create mode 100644 plugins/kiali/src/utils/Date.ts create mode 100644 plugins/kiali/src/utils/RateIntervals.ts create mode 100644 plugins/kiali/src/utils/Reducer.ts rename plugins/{kiali-common => kiali}/src/utils/TimeSeriesUtils.ts (91%) rename plugins/{kiali-common => kiali}/src/utils/VictoryChartsUtils.ts (67%) rename plugins/{kiali-common => kiali}/src/utils/VictoryEvents.ts (92%) rename plugins/{kiali-backend/src/filters/byAnnotation.ts => kiali/src/utils/entityFilter.ts} (72%) diff --git a/plugins/kiali-backend/__fixtures__/data/canaries_status.json b/plugins/kiali-backend/__fixtures__/data/canaries_status.json deleted file mode 100644 index cb4493b035..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/canaries_status.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "currentVersion": "", - "upgradeVersion": "", - "migratedNamespaces": [], - "pendingNamespaces": [] -} diff --git a/plugins/kiali-backend/__fixtures__/data/config/auth_info.json b/plugins/kiali-backend/__fixtures__/data/config/auth_info.json new file mode 100644 index 0000000000..7bf224fba5 --- /dev/null +++ b/plugins/kiali-backend/__fixtures__/data/config/auth_info.json @@ -0,0 +1 @@ +{ "strategy": "token", "sessionInfo": {} } diff --git a/plugins/kiali-backend/__fixtures__/data/istio_status.json b/plugins/kiali-backend/__fixtures__/data/istio_status.json deleted file mode 100644 index 03fae2f991..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/istio_status.json +++ /dev/null @@ -1 +0,0 @@ -[{ "name": "istiod-7c64f49766-m2gf9", "status": "Healthy", "is_core": true }] diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/all.json b/plugins/kiali-backend/__fixtures__/data/namespaces/all.json deleted file mode 100644 index bd2d528ae0..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/all.json +++ /dev/null @@ -1,84 +0,0 @@ -[ - { - "name": "default", - "cluster": "Kubernetes", - "labels": { "kubernetes.io/metadata.name": "default" }, - "annotations": {} - }, - { - "name": "hostpath-provisioner", - "cluster": "Kubernetes", - "labels": { - "kubernetes.io/metadata.name": "hostpath-provisioner", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": {} - }, - { - "name": "istio-system", - "cluster": "Kubernetes", - "labels": { - "kubernetes.io/metadata.name": "istio-system", - "pod-security.kubernetes.io/audit": "baseline", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "baseline", - "pod-security.kubernetes.io/warn-version": "v1.24", - "topology.istio.io/network": "" - }, - "annotations": {} - }, - { - "name": "kiali", - "cluster": "Kubernetes", - "labels": { - "kubernetes.io/metadata.name": "kiali", - "pod-security.kubernetes.io/audit": "restricted", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "restricted", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": {} - }, - { - "name": "travel-agency", - "cluster": "Kubernetes", - "labels": { - "istio-injection": "enabled", - "kubernetes.io/metadata.name": "travel-agency", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": {} - }, - { - "name": "travel-control", - "cluster": "Kubernetes", - "labels": { - "istio-injection": "enabled", - "kubernetes.io/metadata.name": "travel-control", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": {} - }, - { - "name": "travel-portal", - "cluster": "Kubernetes", - "labels": { - "istio-injection": "enabled", - "kubernetes.io/metadata.name": "travel-portal", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": {} - } -] diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-metrics.json b/plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-metrics.json deleted file mode 100644 index acb553a7d4..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-metrics.json +++ /dev/null @@ -1,546 +0,0 @@ -{ - "grpc_received": null, - "grpc_sent": null, - "request_count": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "3.0666725927242826"], - [1689676185, "3.0666429650697715"], - [1689676200, "3.0666133404434963"], - [1689676215, "3.066666666666667"], - [1689676230, "3.0444681502553315"], - [1689676245, "3.044488895737518"], - [1689676260, "3.044444444444444"], - [1689676275, "3.0666548153415403"], - [1689676290, "3.0666725927242826"], - [1689676305, "3.066666666666667"], - [1689676320, "3.0666785190452908"], - [1689676335, "3.066666666666667"], - [1689676350, "3.066666666666667"], - [1689676365, "3.066666666666667"], - [1689676380, "3.066660740872425"], - [1689676395, "3.066666666666667"], - [1689676410, "3.066666666666667"], - [1689676425, "3.0666725927242826"], - [1689676440, "3.066666666666667"], - [1689676455, "3.066666666666667"], - [1689676470, "3.066660740872425"], - [1689676485, "3"], - [1689676500, "2.9555555555555557"], - [1689676515, "2.888894814946505"], - [1689676530, "2.9555555555555557"], - [1689676545, "2.9555555555555557"], - [1689676560, "3.066666666666667"], - [1689676575, "3.066666666666667"], - [1689676590, "3.066666666666667"], - [1689676605, "3.066666666666667"], - [1689676620, "3.066666666666667"], - [1689676635, "3.066666666666667"], - [1689676650, "3.066666666666667"], - [1689676665, "3.066660740872425"], - [1689676680, "3.066637040328852"], - [1689676695, "3.066666666666667"], - [1689676710, "3.0666488911273873"], - [1689676725, "3.0666962995888434"], - [1689676740, "3.066666666666667"], - [1689676755, "3.0666903724775536"], - [1689676770, "3.066666666666667"] - ], - "name": "request_count" - } - ], - "request_duration_millis": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "4.46484779723388"], - [1689676185, "4.644595060239843"], - [1689676200, "4.405137955019646"], - [1689676215, "4.680797101449263"], - [1689676230, "4.723522907495173"], - [1689676245, "4.94957320133482"], - [1689676260, "4.880797101449319"], - [1689676275, "4.952189009399399"], - [1689676290, "4.610861999776447"], - [1689676305, "4.572463768115895"], - [1689676320, "4.5452769903061725"], - [1689676335, "5.530434782608651"], - [1689676350, "5.552536231884058"], - [1689676365, "5.670652173912998"], - [1689676380, "4.861963573685952"], - [1689676395, "4.9985507246377185"], - [1689676410, "4.561956521739131"], - [1689676425, "4.421007978583803"], - [1689676440, "4.089130434782626"], - [1689676455, "4.4007246376812565"], - [1689676470, "4.39710811020587"], - [1689676485, "4.486956521739092"], - [1689676500, "4.42463768115934"], - [1689676515, "4.871731906315636"], - [1689676530, "5.045652173912979"], - [1689676545, "5.155797101449249"], - [1689676560, "4.983333333333249"], - [1689676575, "4.725000000000014"], - [1689676590, "4.637681159420319"], - [1689676605, "4.659782608695672"], - [1689676620, "4.739130434782638"], - [1689676635, "4.701449275362287"], - [1689676650, "4.689855072463793"], - [1689676665, "4.792037061727915"], - [1689676680, "4.7859057374212775"], - [1689676695, "4.723913043478268"], - [1689676710, "4.634442781402011"], - [1689676725, "4.630035333697192"], - [1689676740, "4.407971014492705"], - [1689676755, "4.498524711184274"], - [1689676770, "4.4829710144927315"] - ], - "stat": "avg", - "name": "request_duration_millis" - } - ], - "request_error_count": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "0"], - [1689676185, "0"], - [1689676200, "0"], - [1689676215, "0"], - [1689676230, "0"], - [1689676245, "0"], - [1689676260, "0"], - [1689676275, "0"], - [1689676290, "0"], - [1689676305, "0"], - [1689676320, "0"], - [1689676335, "0"], - [1689676350, "0"], - [1689676365, "0"], - [1689676380, "0"], - [1689676395, "0"], - [1689676410, "0"], - [1689676425, "0"], - [1689676440, "0"], - [1689676455, "0"], - [1689676470, "0"], - [1689676485, "0"], - [1689676500, "0"], - [1689676515, "0"], - [1689676530, "0"], - [1689676545, "0"], - [1689676560, "0"], - [1689676575, "0"], - [1689676590, "0"], - [1689676605, "0"], - [1689676620, "0"], - [1689676635, "0"], - [1689676650, "0"], - [1689676665, "0"], - [1689676680, "0"], - [1689676695, "0"], - [1689676710, "0"], - [1689676725, "0"], - [1689676740, "0"], - [1689676755, "0"], - [1689676770, "0"] - ], - "name": "request_error_count" - } - ], - "request_size": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "412.898583771587"], - [1689676185, "412.8984185502497"], - [1689676200, "412.9707473529639"], - [1689676215, "412.97101449275357"], - [1689676230, "413.2609989603021"], - [1689676245, "413.2610993809971"], - [1689676260, "413.40579710144925"], - [1689676275, "413.11589610212275"], - [1689676290, "413.04351102775996"], - [1689676305, "413.0434782608695"], - [1689676320, "413.0435244715244"], - [1689676335, "412.97101449275357"], - [1689676350, "412.7536231884058"], - [1689676365, "412.8260869565217"], - [1689676380, "412.9709815871691"], - [1689676395, "413.1159420289855"], - [1689676410, "413.1159420289855"], - [1689676425, "413.11597465584646"], - [1689676440, "413.1159420289855"], - [1689676455, "413.1159420289855"], - [1689676470, "413.1159094034485"], - [1689676485, "413.18840579710144"], - [1689676500, "413.26086956521743"], - [1689676515, "413.26090191201945"], - [1689676530, "413.2608695652173"], - [1689676545, "413.04347826086956"], - [1689676560, "412.89855072463763"], - [1689676575, "413.04347826086956"], - [1689676590, "413.1159420289855"], - [1689676605, "413.18840579710144"], - [1689676620, "412.97101449275357"], - [1689676635, "412.89855072463763"], - [1689676650, "413.1159420289855"], - [1689676665, "413.1159094034485"], - [1689676680, "413.2607561551727"], - [1689676695, "413.26086956521743"], - [1689676710, "413.18834699442306"], - [1689676725, "413.04359379453956"], - [1689676740, "412.97101449275357"], - [1689676755, "413.1160338938941"], - [1689676770, "413.3333333333333"] - ], - "stat": "avg", - "name": "request_size" - } - ], - "request_throughput": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "1266.224770426997"], - [1689676185, "1266.2120305355572"], - [1689676200, "1266.4216030455198"], - [1689676215, "1266.4444444444443"], - [1689676230, "1267.3435268320147"], - [1689676245, "1267.353632732586"], - [1689676260, "1267.7777777777778"], - [1689676275, "1266.8838520757101"], - [1689676290, "1266.6692148714415"], - [1689676305, "1266.6666666666665"], - [1689676320, "1266.6717039275818"], - [1689676335, "1266.4444444444443"], - [1689676350, "1265.7777777777778"], - [1689676365, "1266"], - [1689676380, "1266.4418963529206"], - [1689676395, "1266.888888888889"], - [1689676410, "1266.888888888889"], - [1689676425, "1266.8914370936636"], - [1689676440, "1266.888888888889"], - [1689676455, "1266.888888888889"], - [1689676470, "1266.886340797365"], - [1689676485, "1267.111111111111"], - [1689676500, "1267.3333333333335"], - [1689676515, "1267.3358815381082"], - [1689676530, "1267.3333333333333"], - [1689676545, "1266.6666666666667"], - [1689676560, "1266.2222222222222"], - [1689676575, "1266.6666666666667"], - [1689676590, "1266.888888888889"], - [1689676605, "1267.111111111111"], - [1689676620, "1266.4444444444443"], - [1689676635, "1266.2222222222222"], - [1689676650, "1266.888888888889"], - [1689676665, "1266.886340797365"], - [1689676680, "1267.3207421397622"], - [1689676695, "1267.3333333333335"], - [1689676710, "1267.1035861372056"], - [1689676725, "1266.6792606585918"], - [1689676740, "1266.4444444444443"], - [1689676755, "1266.8989638585158"], - [1689676770, "1267.5555555555557"] - ], - "name": "request_throughput" - } - ], - "response_size": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "512.8979267534253"], - [1689676185, "513.0459750126561"], - [1689676200, "512.7592356441766"], - [1689676215, "512.6811594202899"], - [1689676230, "512.6786650639601"], - [1689676245, "512.7486330751949"], - [1689676260, "513.0434782608695"], - [1689676275, "513.0447266874194"], - [1689676290, "512.9703903815117"], - [1689676305, "512.9710144927536"], - [1689676320, "512.9697662449425"], - [1689676335, "513.1159420289855"], - [1689676350, "512.9710144927536"], - [1689676365, "512.9710144927536"], - [1689676380, "512.8267107623903"], - [1689676395, "512.8260869565217"], - [1689676410, "512.7536231884058"], - [1689676425, "512.9703903815118"], - [1689676440, "512.8985507246376"], - [1689676455, "512.8260869565217"], - [1689676470, "512.8267107623903"], - [1689676485, "512.7536231884058"], - [1689676500, "512.9710144927536"], - [1689676515, "513.2602448938577"], - [1689676530, "513.695652173913"], - [1689676545, "513.623188405797"], - [1689676560, "513.0434782608695"], - [1689676575, "513.0434782608695"], - [1689676590, "513.0434782608695"], - [1689676605, "513.1159420289855"], - [1689676620, "512.8260869565216"], - [1689676635, "512.6086956521739"], - [1689676650, "513.3333333333334"], - [1689676665, "513.406422027508"], - [1689676680, "513.5538504145313"], - [1689676695, "513.4782608695652"], - [1689676710, "513.6975284450428"], - [1689676725, "513.9099135661105"], - [1689676740, "513.5507246376811"], - [1689676755, "513.6206867674929"], - [1689676770, "513.3333333333333"] - ], - "stat": "avg", - "name": "response_size" - } - ], - "response_throughput": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "1572.8900148398359"], - [1689676185, "1573.3288300299234"], - [1689676200, "1572.4343124620423"], - [1689676215, "1572.2222222222224"], - [1689676230, "1572.2267263262909"], - [1689676245, "1572.4534532281634"], - [1689676260, "1573.3333333333333"], - [1689676275, "1573.3310815815591"], - [1689676290, "1573.112237062058"], - [1689676305, "1573.111111111111"], - [1689676320, "1573.1133630630495"], - [1689676335, "1573.5555555555557"], - [1689676350, "1573.111111111111"], - [1689676365, "1573.111111111111"], - [1689676380, "1572.6655407657606"], - [1689676395, "1572.6666666666667"], - [1689676410, "1572.4444444444446"], - [1689676425, "1573.1122370620583"], - [1689676440, "1572.888888888889"], - [1689676455, "1572.6666666666667"], - [1689676470, "1572.6655407657609"], - [1689676485, "1572.4444444444446"], - [1689676500, "1573.111111111111"], - [1689676515, "1574.001125950947"], - [1689676530, "1575.3333333333333"], - [1689676545, "1575.111111111111"], - [1689676560, "1573.3333333333333"], - [1689676575, "1573.3333333333333"], - [1689676590, "1573.3333333333333"], - [1689676605, "1573.5555555555557"], - [1689676620, "1572.6666666666665"], - [1689676635, "1572"], - [1689676650, "1574.2222222222224"], - [1689676665, "1574.4433185435387"], - [1689676680, "1574.883259884704"], - [1689676695, "1574.6666666666667"], - [1689676710, "1575.3299559808702"], - [1689676725, "1576.0056302552134"], - [1689676740, "1574.888888888889"], - [1689676755, "1575.1156152151796"], - [1689676770, "1574.2222222222222"] - ], - "name": "response_throughput" - } - ], - "tcp_closed": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "1.7333392593909496"], - [1689676185, "1.7333096317364385"], - [1689676200, "1.7332266808869927"], - [1689676215, "1.7333333333333334"], - [1689676230, "1.711134816921998"], - [1689676245, "1.688983717269278"], - [1689676260, "1.6888888888888887"], - [1689676275, "1.7110992597859846"], - [1689676290, "1.7333392593909496"], - [1689676305, "1.7333333333333334"], - [1689676320, "1.7333451857119577"], - [1689676335, "1.7333333333333334"], - [1689676350, "1.7333333333333334"], - [1689676365, "1.7333333333333334"], - [1689676380, "1.7333274075390919"], - [1689676395, "1.7333333333333334"], - [1689676410, "1.7333333333333334"], - [1689676425, "1.7333392593909496"], - [1689676440, "1.7333333333333334"], - [1689676455, "1.7333333333333334"], - [1689676470, "1.7333274075390919"], - [1689676485, "1.7333333333333334"], - [1689676500, "1.7333333333333334"], - [1689676515, "1.7333392593909496"], - [1689676530, "1.7333333333333334"], - [1689676545, "1.7333333333333334"], - [1689676560, "1.7333333333333334"], - [1689676575, "1.7333333333333334"], - [1689676590, "1.7333333333333334"], - [1689676605, "1.7333333333333334"], - [1689676620, "1.7333333333333334"], - [1689676635, "1.7333333333333334"], - [1689676650, "1.7333333333333334"], - [1689676665, "1.7333274075390919"], - [1689676680, "1.7333037069955188"], - [1689676695, "1.7333333333333334"], - [1689676710, "1.7333155577940542"], - [1689676725, "1.73336296625551"], - [1689676740, "1.7333333333333334"], - [1689676755, "1.7333570391442201"], - [1689676770, "1.7333333333333334"] - ], - "name": "tcp_closed" - } - ], - "tcp_opened": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "1.7333392593909496"], - [1689676185, "1.7333096317364385"], - [1689676200, "1.7332266808869927"], - [1689676215, "1.7333333333333334"], - [1689676230, "1.711134816921998"], - [1689676245, "1.688983717269278"], - [1689676260, "1.6888888888888887"], - [1689676275, "1.7110992597859846"], - [1689676290, "1.7333392593909496"], - [1689676305, "1.7333333333333334"], - [1689676320, "1.7333451857119577"], - [1689676335, "1.7333333333333334"], - [1689676350, "1.7333333333333334"], - [1689676365, "1.7333333333333334"], - [1689676380, "1.7333274075390919"], - [1689676395, "1.7333333333333334"], - [1689676410, "1.7333333333333334"], - [1689676425, "1.7333392593909496"], - [1689676440, "1.7333333333333334"], - [1689676455, "1.7333333333333334"], - [1689676470, "1.7333274075390919"], - [1689676485, "1.7333333333333334"], - [1689676500, "1.7333333333333334"], - [1689676515, "1.7333392593909496"], - [1689676530, "1.7333333333333334"], - [1689676545, "1.7333333333333334"], - [1689676560, "1.7333333333333334"], - [1689676575, "1.7333333333333334"], - [1689676590, "1.7333333333333334"], - [1689676605, "1.7333333333333334"], - [1689676620, "1.7333333333333334"], - [1689676635, "1.7333333333333334"], - [1689676650, "1.7333333333333334"], - [1689676665, "1.7333274075390919"], - [1689676680, "1.7333037069955188"], - [1689676695, "1.7333333333333334"], - [1689676710, "1.7333155577940542"], - [1689676725, "1.73336296625551"], - [1689676740, "1.7333333333333334"], - [1689676755, "1.7333570391442201"], - [1689676770, "1.7333333333333334"] - ], - "name": "tcp_opened" - } - ], - "tcp_received": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "325.17898965656025"], - [1689676185, "324.9284922821675"], - [1689676200, "325.0711016308936"], - [1689676215, "325.66666666666663"], - [1689676230, "321.6270996928122"], - [1689676245, "318.7267400510643"], - [1689676260, "318.2"], - [1689676275, "322.06426677332854"], - [1689676290, "325.0678790392873"], - [1689676305, "325.35555555555555"], - [1689676320, "325.5135171439718"], - [1689676335, "325.0222222222222"], - [1689676350, "324.55555555555554"], - [1689676365, "324.7111111111111"], - [1689676380, "324.88767706396646"], - [1689676395, "324.97777777777776"], - [1689676410, "325.1555555555555"], - [1689676425, "325.4901022491858"], - [1689676440, "325.8666666666667"], - [1689676455, "326.06666666666666"], - [1689676470, "326.1543397801036"], - [1689676485, "326.4222222222222"], - [1689676500, "326.4222222222222"], - [1689676515, "326.4678859530212"], - [1689676530, "326.0222222222222"], - [1689676545, "325.08888888888885"], - [1689676560, "324.8666666666667"], - [1689676575, "325.1555555555555"], - [1689676590, "325.7777777777777"], - [1689676605, "325.68888888888887"], - [1689676620, "325.2444444444444"], - [1689676635, "325.6222222222222"], - [1689676650, "325.64444444444445"], - [1689676665, "325.576564965223"], - [1689676680, "325.4828796800355"], - [1689676695, "325.5777777777777"], - [1689676710, "325.55195847935903"], - [1689676725, "325.16155869170643"], - [1689676740, "325.1111111111111"], - [1689676755, "325.6492626505072"], - [1689676770, "326"] - ], - "name": "tcp_received" - } - ], - "tcp_sent": [ - { - "labels": {}, - "datapoints": [ - [1689676170, "957.8461096666346"], - [1689676185, "957.6600065179392"], - [1689676200, "957.4833399991112"], - [1689676215, "957.7111111111111"], - [1689676230, "952.2066613328592"], - [1689676245, "918.998749721949"], - [1689676260, "918.7777777777777"], - [1689676275, "924.2859260576074"], - [1689676290, "957.4905541110791"], - [1689676305, "957.7777777777777"], - [1689676320, "957.8918519835449"], - [1689676335, "957.9111111111112"], - [1689676350, "957.3333333333334"], - [1689676365, "957.4888888888889"], - [1689676380, "957.4872237407069"], - [1689676395, "957.6888888888889"], - [1689676410, "957.5777777777778"], - [1689676425, "957.7572207777456"], - [1689676440, "958"], - [1689676455, "958.1111111111111"], - [1689676470, "958.1983348518181"], - [1689676485, "958.2222222222223"], - [1689676500, "958.4666666666667"], - [1689676515, "958.4238874444123"], - [1689676530, "958.4000000000001"], - [1689676545, "957.8222222222222"], - [1689676560, "957.3555555555555"], - [1689676575, "957.4222222222222"], - [1689676590, "957.8444444444444"], - [1689676605, "957.9555555555555"], - [1689676620, "957.8222222222222"], - [1689676635, "957.5111111111111"], - [1689676650, "958.0222222222222"], - [1689676665, "957.8205570740403"], - [1689676680, "957.9481489711019"], - [1689676695, "957.9111111111112"], - [1689676710, "957.9735176007441"], - [1689676725, "957.8740748972108"], - [1689676740, "957.6"], - [1689676755, "957.7392597860551"], - [1689676770, "958.0444444444445"] - ], - "name": "tcp_sent" - } - ] -} diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-metrics.json b/plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-metrics.json deleted file mode 100644 index 951e5063ef..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-metrics.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "grpc_received": null, - "grpc_sent": null, - "request_count": null, - "request_duration_millis": null, - "request_error_count": null, - "request_size": null, - "request_throughput": null, - "response_size": null, - "response_throughput": null, - "tcp_closed": null, - "tcp_opened": null, - "tcp_received": null, - "tcp_sent": null -} diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-metrics.json b/plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-metrics.json deleted file mode 100644 index a546d9d1c1..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-metrics.json +++ /dev/null @@ -1,786 +0,0 @@ -{ - "grpc_received": null, - "grpc_sent": null, - "request_count": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "0.8"], - [1689675165, "0.8001363659944711"], - [1689675180, "0.7999466773312003"], - [1689675195, "0.8"], - [1689675210, "0.8"], - [1689675225, "0.8000533440021338"], - [1689675240, "0.8"], - [1689675255, "0.8"], - [1689675270, "0.8"], - [1689675285, "0.8"], - [1689675300, "0.7999585249701897"], - [1689675315, "0.8"], - [1689675330, "0.8"], - [1689675345, "0.8000414879351603"], - [1689675360, "0.8"], - [1689675375, "0.8"], - [1689675390, "0.8"], - [1689675405, "0.8"], - [1689675420, "0.8"], - [1689675435, "0.8"], - [1689675450, "0.8"], - [1689675465, "0.8"], - [1689675480, "0.8"], - [1689675495, "0.8"], - [1689675510, "0.8"], - [1689675525, "0.8"], - [1689675540, "0.7999111407308674"], - [1689675555, "0.8"], - [1689675570, "0.8"], - [1689675585, "0.777866696306176"], - [1689675600, "0.7555377789628839"], - [1689675615, "0.7555555555555555"], - [1689675630, "0.7777777777777777"], - [1689675645, "0.800005926057616"], - [1689675660, "0.7999822234073284"], - [1689675675, "0.8"], - [1689675690, "0.8000118523786242"], - [1689675705, "0.800017778963042"], - [1689675720, "0.7999170628397092"], - [1689675735, "0.8"], - [1689675750, "0.8"], - [1689675765, "0.8000829887816951"], - [1689675780, "0.8"], - [1689675795, "0.8"], - [1689675810, "0.8"], - [1689675825, "0.8"], - [1689675840, "0.8"], - [1689675855, "0.8"], - [1689675870, "0.8"], - [1689675885, "0.8"], - [1689675900, "0.8"], - [1689675915, "0.7555555555555555"], - [1689675930, "0.7555555555555555"], - [1689675945, "0.7555555555555555"], - [1689675960, "0.8"], - [1689675975, "0.8"], - [1689675990, "0.8"], - [1689676005, "0.8"], - [1689676020, "0.7999466773312003"], - [1689676035, "0.8"], - [1689676050, "0.8"], - [1689676065, "0.8000533440021338"], - [1689676080, "0.8"], - [1689676095, "0.8"], - [1689676110, "0.8"], - [1689676125, "0.8"], - [1689676140, "0.8"], - [1689676155, "0.8"], - [1689676170, "0.7999881486748737"], - [1689676185, "0.8"], - [1689676200, "0.8"], - [1689676215, "0.7999348307450771"], - [1689676230, "0.8"], - [1689676245, "0.8"], - [1689676260, "0.8000770592986122"], - [1689676275, "0.8"], - [1689676290, "0.8"], - [1689676305, "0.8"], - [1689676320, "0.7999881486748737"], - [1689676335, "0.8"], - [1689676350, "0.8"], - [1689676365, "0.8000000010534979"], - [1689676380, "0.8"], - [1689676395, "0.8"], - [1689676410, "0.7999763015631773"], - [1689676425, "0.8"], - [1689676440, "0.8"], - [1689676455, "0.8000355602969285"], - [1689676470, "0.8"], - [1689676485, "0.8"], - [1689676500, "0.7999822234073284"], - [1689676515, "0.8"], - [1689676530, "0.8"], - [1689676545, "0.800017778963042"], - [1689676560, "0.7998696933351102"], - [1689676575, "0.8"], - [1689676590, "0.8"], - [1689676605, "0.777908211915949"], - [1689676620, "0.7555555555555555"], - [1689676635, "0.7555555555555555"], - [1689676650, "0.7777777777777778"], - [1689676665, "0.8"], - [1689676680, "0.8"], - [1689676695, "0.8"], - [1689676710, "0.8"], - [1689676725, "0.8"], - [1689676740, "0.8"], - [1689676755, "0.8"], - [1689676770, "0.8"], - [1689676785, "0.8"], - [1689676800, "0.7995916850857555"], - [1689676815, "0.8"], - [1689676830, "0.7998815341329779"], - [1689676845, "0.800409464546342"], - [1689676860, "0.7999822234073284"], - [1689676875, "0.800118571216837"], - [1689676890, "0.8"], - [1689676905, "0.800017778963042"], - [1689676920, "0.7999822234073284"], - [1689676935, "0.8"], - [1689676950, "0.8"] - ], - "name": "request_count" - } - ], - "request_duration_millis": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "9.320833333333413"], - [1689675165, "8.669435449609951"], - [1689675180, "9.194463793716393"], - [1689675195, "8.629166666666604"], - [1689675210, "13.779166666666542"], - [1689675225, "13.834451537982922"], - [1689675240, "14.20833333333333"], - [1689675255, "8.684722222222463"], - [1689675270, "8.199999999999918"], - [1689675285, "8.447222222222383"], - [1689675300, "8.740275545498825"], - [1689675315, "8.947222222222283"], - [1689675330, "9.005555555555475"], - [1689675345, "8.452812205216324"], - [1689675360, "8.866666666666786"], - [1689675375, "8.437499999999899"], - [1689675390, "8.409722222222323"], - [1689675405, "7.6097222222220395"], - [1689675420, "8.011111111111152"], - [1689675435, "8.437499999999998"], - [1689675450, "8.493055555555555"], - [1689675465, "11.919444444444402"], - [1689675480, "11.752777777777839"], - [1689675495, "11.709722222222302"], - [1689675510, "7.770833333333534"], - [1689675525, "7.952777777777757"], - [1689675540, "7.884746908094457"], - [1689675555, "7.88472222222208"], - [1689675570, "7.347222222222221"], - [1689675585, "7.776316496258347"], - [1689675600, "8.813897808245539"], - [1689675615, "9.252777777777737"], - [1689675630, "8.98750000000008"], - [1689675645, "14.248569947949767"], - [1689675660, "19.13037198346732"], - [1689675675, "19.65833333333325"], - [1689675690, "14.341764385611507"], - [1689675705, "9.722279786509029"], - [1689675720, "11.983216129781866"], - [1689675735, "17.348529411764662"], - [1689675750, "17.070588235294164"], - [1689675765, "14.342929919640607"], - [1689675780, "9.055555555555554"], - [1689675795, "8.740277777777816"], - [1689675810, "9.129166666666706"], - [1689675825, "8.147222222222403"], - [1689675840, "9.737499999999878"], - [1689675855, "9.391666666666524"], - [1689675870, "10.598611111110886"], - [1689675885, "10.37638888888907"], - [1689675900, "10.722222222222321"], - [1689675915, "11.593055555555715"], - [1689675930, "10.76249999999992"], - [1689675945, "10.12638888888897"], - [1689675960, "7.995833333333393"], - [1689675975, "8.134722222222281"], - [1689675990, "8.05138888888895"], - [1689676005, "12.030555555555413"], - [1689676020, "11.282941996622592"], - [1689676035, "11.780555555555514"], - [1689676050, "8.03611111111109"], - [1689676065, "8.298585366937914"], - [1689676080, "9.230555555555535"], - [1689676095, "9.588888888888807"], - [1689676110, "9.712499999999839"], - [1689676125, "8.474999999999856"], - [1689676140, "8.243055555555555"], - [1689676155, "8.369444444444424"], - [1689676170, "8.06666086436949"], - [1689676185, "7.980555555555535"], - [1689676200, "7.952777777777857"], - [1689676215, "8.866670400626091"], - [1689676230, "9.033333333333452"], - [1689676245, "9.376388888888968"], - [1689676260, "8.684775462928702"], - [1689676275, "9.098611111110987"], - [1689676290, "8.712499999999736"], - [1689676305, "9.027777777777878"], - [1689676320, "9.388879465299995"], - [1689676335, "9.997222222222463"], - [1689676350, "10.024999999999837"], - [1689676365, "9.305587963117535"], - [1689676380, "8.57361111111115"], - [1689676395, "8.480555555555535"], - [1689676410, "7.968030042773679"], - [1689676425, "7.816666666666707"], - [1689676440, "7.702777777777758"], - [1689676455, "8.03611469167648"], - [1689676470, "8.255555555555373"], - [1689676485, "8.573611111111253"], - [1689676500, "8.187477500999957"], - [1689676515, "9.141666666666827"], - [1689676530, "9.543055555555434"], - [1689676545, "10.052791142569136"], - [1689676560, "8.962420581440064"], - [1689676575, "8.1875000000001"], - [1689676590, "8.063888888888867"], - [1689676605, "8.106893049092605"], - [1689676620, "8.190277777777736"], - [1689676635, "8.755555555555574"], - [1689676650, "8.904166666666544"], - [1689676665, "9.330555555555694"], - [1689676680, "8.626388888888968"], - [1689676695, "9.151388888888807"], - [1689676710, "8.243055555555454"], - [1689676725, "8.727777777777797"], - [1689676740, "7.983333333333473"], - [1689676755, "8.631944444444342"], - [1689676770, "8.264285714285714"], - [1689676785, "8.038235294117625"], - [1689676800, "7.861692213768087"], - [1689676815, "9.64571428571435"], - [1689676830, "10.48187080371254"], - [1689676845, "9.916995605460045"], - [1689676860, "7.909721327200171"], - [1689676875, "7.344491577751271"], - [1689676890, "8.93472222222226"], - [1689676905, "9.694420492762742"], - [1689676920, "10.497259473653065"], - [1689676935, "9.555555555555554"], - [1689676950, "9.530555555555413"] - ], - "stat": "avg", - "name": "request_duration_millis" - } - ], - "request_error_count": null, - "request_size": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "179.7222222222222"], - [1689675165, "179.7215594449467"], - [1689675180, "178.88914811358484"], - [1689675195, "178.61111111111106"], - [1689675210, "179.16666666666663"], - [1689675225, "179.16644441481088"], - [1689675240, "179.16666666666663"], - [1689675255, "178.88888888888886"], - [1689675270, "179.44444444444443"], - [1689675285, "179.16666666666663"], - [1689675300, "179.1668826936466"], - [1689675315, "178.88888888888886"], - [1689675330, "179.16666666666663"], - [1689675345, "178.88868722188468"], - [1689675360, "179.44444444444443"], - [1689675375, "179.44444444444443"], - [1689675390, "180.27777777777777"], - [1689675405, "180"], - [1689675420, "179.7222222222222"], - [1689675435, "179.16666666666663"], - [1689675450, "179.44444444444443"], - [1689675465, "179.44444444444443"], - [1689675480, "179.44444444444443"], - [1689675495, "178.88888888888886"], - [1689675510, "179.44444444444443"], - [1689675525, "179.16666666666663"], - [1689675540, "178.8893208916537"], - [1689675555, "178.6111111111111"], - [1689675570, "178.6111111111111"], - [1689675585, "178.88845669408016"], - [1689675600, "179.1667592551442"], - [1689675615, "179.16666666666663"], - [1689675630, "179.16666666666663"], - [1689675645, "179.16663580201188"], - [1689675660, "179.722179014266"], - [1689675675, "179.7222222222222"], - [1689675690, "179.4443785988721"], - [1689675705, "179.16670370534985"], - [1689675720, "179.5718272200858"], - [1689675735, "179.70588235294116"], - [1689675750, "179.41176470588235"], - [1689675765, "178.71397834985947"], - [1689675780, "178.88888888888889"], - [1689675795, "179.16666666666663"], - [1689675810, "179.16666666666663"], - [1689675825, "178.88888888888886"], - [1689675840, "179.7222222222222"], - [1689675855, "179.7222222222222"], - [1689675870, "179.44444444444443"], - [1689675885, "178.33333333333331"], - [1689675900, "178.6111111111111"], - [1689675915, "178.6111111111111"], - [1689675930, "179.16666666666663"], - [1689675945, "179.44444444444443"], - [1689675960, "180"], - [1689675975, "180"], - [1689675990, "179.7222222222222"], - [1689676005, "179.16666666666663"], - [1689676020, "178.61135181975737"], - [1689676035, "178.33333333333331"], - [1689676050, "178.88888888888886"], - [1689676065, "179.16638885184688"], - [1689676080, "179.16666666666663"], - [1689676095, "178.6111111111111"], - [1689676110, "178.88888888888886"], - [1689676125, "179.16666666666663"], - [1689676140, "179.44444444444443"], - [1689676155, "178.88888888888886"], - [1689676170, "178.61116460746842"], - [1689676185, "178.33333333333331"], - [1689676200, "178.61111111111106"], - [1689676215, "179.1669382273506"], - [1689676230, "179.7222222222222"], - [1689676245, "179.7222222222222"], - [1689676260, "179.72184766448845"], - [1689676275, "179.16666666666663"], - [1689676290, "179.16666666666663"], - [1689676305, "179.16666666666663"], - [1689676320, "179.44449794080177"], - [1689676335, "179.16666666666663"], - [1689676350, "178.88888888888889"], - [1689676365, "178.88880246785544"], - [1689676380, "179.44444444444443"], - [1689676395, "179.7222222222222"], - [1689676410, "179.72238681932737"], - [1689676425, "179.16666666666663"], - [1689676440, "179.16666666666663"], - [1689676455, "179.16648146501907"], - [1689676470, "179.16666666666663"], - [1689676485, "179.44444444444443"], - [1689676500, "179.44454320548715"], - [1689676515, "179.7222222222222"], - [1689676530, "179.44444444444443"], - [1689676545, "179.16659258930022"], - [1689676560, "179.16720969955472"], - [1689676575, "179.16666666666663"], - [1689676590, "179.44444444444443"], - [1689676605, "179.44385577521564"], - [1689676620, "178.88888888888886"], - [1689676635, "179.16666666666663"], - [1689676650, "178.88888888888886"], - [1689676665, "178.88888888888886"], - [1689676680, "178.88888888888886"], - [1689676695, "179.16666666666663"], - [1689676710, "179.7222222222222"], - [1689676725, "179.16666666666663"], - [1689676740, "179.16666666666663"], - [1689676755, "179.44444444444443"], - [1689676770, "179.85714285714283"], - [1689676785, "180"], - [1689676800, "179.11930521401027"], - [1689676815, "179.00000000000003"], - [1689676830, "179.44497926540285"], - [1689676845, "180.27567073097228"], - [1689676860, "180.00009258847757"], - [1689676875, "179.1660491997629"], - [1689676890, "178.33333333333331"], - [1689676905, "178.3332592559669"], - [1689676920, "178.6111913544583"], - [1689676935, "179.44444444444443"], - [1689676950, "179.44444444444443"] - ], - "stat": "avg", - "name": "request_size" - } - ], - "request_throughput": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "143.77777777777777"], - [1689675165, "143.80175546513897"], - [1689675180, "143.10177964407114"], - [1689675195, "142.88888888888886"], - [1689675210, "143.33333333333331"], - [1689675225, "143.34271298704186"], - [1689675240, "143.33333333333331"], - [1689675255, "143.1111111111111"], - [1689675270, "143.55555555555554"], - [1689675285, "143.33333333333331"], - [1689675300, "143.32607520311655"], - [1689675315, "143.1111111111111"], - [1689675330, "143.33333333333331"], - [1689675345, "143.11837149976412"], - [1689675360, "143.55555555555554"], - [1689675375, "143.55555555555554"], - [1689675390, "144.22222222222223"], - [1689675405, "144"], - [1689675420, "143.77777777777777"], - [1689675435, "143.33333333333331"], - [1689675450, "143.55555555555554"], - [1689675465, "143.55555555555554"], - [1689675480, "143.55555555555554"], - [1689675495, "143.1111111111111"], - [1689675510, "143.55555555555554"], - [1689675525, "143.33333333333331"], - [1689675540, "143.0955607390129"], - [1689675555, "142.88888888888889"], - [1689675570, "142.88888888888889"], - [1689675585, "143.12667185358083"], - [1689675600, "143.3302224296158"], - [1689675615, "143.33333333333331"], - [1689675630, "143.33333333333331"], - [1689675645, "143.33437039341612"], - [1689675660, "143.77454836344242"], - [1689675675, "143.77777777777777"], - [1689675690, "143.5576297218148"], - [1689675705, "143.33654836248343"], - [1689675720, "139.65208354931553"], - [1689675735, "135.77777777777777"], - [1689675750, "135.55555555555554"], - [1689675765, "139.01459219411473"], - [1689675780, "143.11111111111111"], - [1689675795, "143.33333333333331"], - [1689675810, "143.33333333333331"], - [1689675825, "143.1111111111111"], - [1689675840, "143.77777777777777"], - [1689675855, "143.77777777777777"], - [1689675870, "143.55555555555554"], - [1689675885, "142.66666666666666"], - [1689675900, "142.88888888888889"], - [1689675915, "142.88888888888889"], - [1689675930, "143.33333333333331"], - [1689675945, "143.55555555555554"], - [1689675960, "144"], - [1689675975, "144"], - [1689675990, "143.77777777777777"], - [1689676005, "143.33333333333331"], - [1689676020, "142.87955742184894"], - [1689676035, "142.66666666666666"], - [1689676050, "143.1111111111111"], - [1689676065, "143.34266853370673"], - [1689676080, "143.33333333333331"], - [1689676095, "142.88888888888889"], - [1689676110, "143.1111111111111"], - [1689676125, "143.33333333333331"], - [1689676140, "143.55555555555554"], - [1689676155, "143.1111111111111"], - [1689676170, "142.8868149069918"], - [1689676185, "142.66666666666666"], - [1689676200, "142.88888888888886"], - [1689676215, "143.32187440600939"], - [1689676230, "143.77777777777777"], - [1689676245, "143.77777777777777"], - [1689676260, "143.79132737111706"], - [1689676275, "143.33333333333331"], - [1689676290, "143.33333333333331"], - [1689676305, "143.33333333333331"], - [1689676320, "143.5534716975542"], - [1689676335, "143.33333333333331"], - [1689676350, "143.11111111111111"], - [1689676365, "143.11104216274333"], - [1689676380, "143.55555555555554"], - [1689676395, "143.77777777777777"], - [1689676410, "143.77365031583224"], - [1689676425, "143.33333333333331"], - [1689676440, "143.33333333333331"], - [1689676455, "143.3395563852958"], - [1689676470, "143.33333333333331"], - [1689676485, "143.55555555555554"], - [1689676500, "143.55244465183802"], - [1689676515, "143.77777777777777"], - [1689676530, "143.55555555555554"], - [1689676545, "143.3364594676682"], - [1689676560, "143.3104210780902"], - [1689676575, "143.33333333333331"], - [1689676590, "143.55555555555554"], - [1689676605, "143.57849022485067"], - [1689676620, "143.1111111111111"], - [1689676635, "143.33333333333331"], - [1689676650, "143.1111111111111"], - [1689676665, "143.1111111111111"], - [1689676680, "143.1111111111111"], - [1689676695, "143.33333333333331"], - [1689676710, "143.77777777777777"], - [1689676725, "143.33333333333331"], - [1689676740, "143.33333333333331"], - [1689676755, "143.55555555555554"], - [1689676770, "139.88888888888886"], - [1689676785, "136"], - [1689676800, "135.26144907794867"], - [1689676815, "139.22222222222223"], - [1689676830, "143.53472530727083"], - [1689676845, "144.29435308051018"], - [1689676860, "143.99687428245525"], - [1689676875, "143.35408329627984"], - [1689676890, "142.66666666666666"], - [1689676905, "142.669777985199"], - [1689676920, "142.88577798517133"], - [1689676935, "143.55555555555554"], - [1689676950, "143.55555555555554"] - ], - "name": "request_throughput" - } - ], - "response_size": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "1535.2777777777776"], - [1689675165, "1534.1668086903687"], - [1689675180, "1532.7777407456786"], - [1689675195, "1532.2222222222222"], - [1689675210, "1534.1666666666665"], - [1689675225, "1534.444648175312"], - [1689675240, "1536.9444444444446"], - [1689675255, "1541.111111111111"], - [1689675270, "1542.222222222222"], - [1689675285, "1540"], - [1689675300, "1536.111082307514"], - [1689675315, "1534.4444444444441"], - [1689675330, "1535.2777777777776"], - [1689675345, "1533.3333765476914"], - [1689675360, "1533.8888888888885"], - [1689675375, "1533.0555555555554"], - [1689675390, "1534.4444444444446"], - [1689675405, "1534.7222222222222"], - [1689675420, "1535"], - [1689675435, "1534.1666666666665"], - [1689675450, "1534.1666666666665"], - [1689675465, "1533.333333333333"], - [1689675480, "1534.1666666666665"], - [1689675495, "1533.611111111111"], - [1689675510, "1533.8888888888887"], - [1689675525, "1532.5"], - [1689675540, "1532.5"], - [1689675555, "1533.6111111111109"], - [1689675570, "1533.611111111111"], - [1689675585, "1535.5555864266132"], - [1689675600, "1535.277820985734"], - [1689675615, "1535.5555555555554"], - [1689675630, "1535.2777777777776"], - [1689675645, "1535.5555390944062"], - [1689675660, "1536.9444135816184"], - [1689675675, "1535.2777777777776"], - [1689675690, "1535.5555226327695"], - [1689675705, "1534.1666666666663"], - [1689675720, "1562.7173301712282"], - [1689675735, "1534.8529411764705"], - [1689675750, "1533.0882352941176"], - [1689675765, "1508.0027027747894"], - [1689675780, "1533.3333333333333"], - [1689675795, "1532.7777777777776"], - [1689675810, "1533.8888888888887"], - [1689675825, "1534.1666666666665"], - [1689675840, "1535.833333333333"], - [1689675855, "1535.5555555555554"], - [1689675870, "1535"], - [1689675885, "1532.5"], - [1689675900, "1532.2222222222222"], - [1689675915, "1531.6666666666665"], - [1689675930, "1533.0555555555554"], - [1689675945, "1531.9444444444446"], - [1689675960, "1533.611111111111"], - [1689675975, "1534.1666666666665"], - [1689675990, "1535.5555555555554"], - [1689676005, "1545.5555555555554"], - [1689676020, "1545.2777407456783"], - [1689676035, "1544.4444444444441"], - [1689676050, "1533.3333333333333"], - [1689676065, "1532.2221296172825"], - [1689676080, "1532.7777777777776"], - [1689676095, "1533.8888888888887"], - [1689676110, "1535"], - [1689676125, "1535.2777777777776"], - [1689676140, "1535"], - [1689676155, "1534.4444444444446"], - [1689676170, "1533.6111275715289"], - [1689676185, "1532.5"], - [1689676200, "1533.333333333333"], - [1689676215, "1533.3333333333333"], - [1689676230, "1534.4444444444441"], - [1689676245, "1533.8888888888887"], - [1689676260, "1534.9999197376285"], - [1689676275, "1533.8888888888887"], - [1689676290, "1533.8888888888887"], - [1689676305, "1533.333333333333"], - [1689676320, "1534.1666543213537"], - [1689676335, "1533.8888888888887"], - [1689676350, "1533.0555555555554"], - [1689676365, "1532.5"], - [1689676380, "1532.2222222222222"], - [1689676395, "1532.5"], - [1689676410, "1533.055448567108"], - [1689676425, "1533.8888888888887"], - [1689676440, "1533.8888888888887"], - [1689676455, "1534.72212344801"], - [1689676470, "1534.4444444444446"], - [1689676485, "1535"], - [1689676500, "1534.1667037020575"], - [1689676515, "1534.7222222222222"], - [1689676530, "1535.2777777777776"], - [1689676545, "1534.7222469146777"], - [1689676560, "1533.6111563638517"], - [1689676575, "1532.7777777777776"], - [1689676590, "1533.3333333333333"], - [1689676605, "1533.0554649910587"], - [1689676620, "1531.9444444444441"], - [1689676635, "1533.333333333333"], - [1689676650, "1534.1666666666665"], - [1689676665, "1535.5555555555554"], - [1689676680, "1534.7222222222222"], - [1689676695, "1535.833333333333"], - [1689676710, "1535.833333333333"], - [1689676725, "1534.4444444444441"], - [1689676740, "1533.333333333333"], - [1689676755, "1533.611111111111"], - [1689676770, "1562.4285714285716"], - [1689676785, "1534.8529411764707"], - [1689676800, "1531.6154460678363"], - [1689676815, "1508.2857142857144"], - [1689676830, "1534.4441153238547"], - [1689676845, "1535.2776726958782"], - [1689676860, "1534.1667037020575"], - [1689676875, "1534.1662961865245"], - [1689676890, "1533.0555555555554"], - [1689676905, "1532.7777716046637"], - [1689676920, "1534.1666481489713"], - [1689676935, "1534.7222222222222"], - [1689676950, "1535.2777777777776"] - ], - "stat": "avg", - "name": "response_size" - } - ], - "response_throughput": [ - { - "labels": {}, - "datapoints": [ - [1689675150, "1228.2222222222222"], - [1689675165, "1227.5426551348467"], - [1689675180, "1226.1404607967295"], - [1689675195, "1225.7777777777778"], - [1689675210, "1227.3333333333333"], - [1689675225, "1227.6375719588361"], - [1689675240, "1229.5555555555557"], - [1689675255, "1232.888888888889"], - [1689675270, "1233.7777777777776"], - [1689675285, "1232"], - [1689675300, "1228.8251555930806"], - [1689675315, "1227.5555555555554"], - [1689675330, "1228.2222222222222"], - [1689675345, "1226.7303160738584"], - [1689675360, "1227.1111111111109"], - [1689675375, "1226.4444444444443"], - [1689675390, "1227.5555555555557"], - [1689675405, "1227.7777777777778"], - [1689675420, "1228"], - [1689675435, "1227.3333333333333"], - [1689675450, "1227.3333333333333"], - [1689675465, "1226.6666666666665"], - [1689675480, "1227.3333333333333"], - [1689675495, "1226.888888888889"], - [1689675510, "1227.111111111111"], - [1689675525, "1226"], - [1689675540, "1225.8638231700543"], - [1689675555, "1226.8888888888887"], - [1689675570, "1226.888888888889"], - [1689675585, "1228.5810084843095"], - [1689675600, "1228.1949647801257"], - [1689675615, "1228.4444444444443"], - [1689675630, "1228.2222222222222"], - [1689675645, "1228.4535310661222"], - [1689675660, "1229.5282092304956"], - [1689675675, "1228.2222222222222"], - [1689675690, "1228.4626180916682"], - [1689675705, "1227.3606092258"], - [1689675720, "1215.3172050176981"], - [1689675735, "1159.6666666666665"], - [1689675750, "1158.3333333333333"], - [1689675765, "1173.0161383541547"], - [1689675780, "1226.6666666666667"], - [1689675795, "1226.2222222222222"], - [1689675810, "1227.111111111111"], - [1689675825, "1227.3333333333333"], - [1689675840, "1228.6666666666665"], - [1689675855, "1228.4444444444443"], - [1689675870, "1228"], - [1689675885, "1226"], - [1689675900, "1225.7777777777778"], - [1689675915, "1225.3333333333333"], - [1689675930, "1226.4444444444443"], - [1689675945, "1225.5555555555557"], - [1689675960, "1226.888888888889"], - [1689675975, "1227.3333333333333"], - [1689675990, "1228.4444444444443"], - [1689676005, "1236.4444444444443"], - [1689676020, "1236.1397942633694"], - [1689676035, "1235.5555555555554"], - [1689676050, "1226.6666666666667"], - [1689676065, "1225.8594385543777"], - [1689676080, "1226.2222222222222"], - [1689676095, "1227.111111111111"], - [1689676110, "1228"], - [1689676125, "1228.2222222222222"], - [1689676140, "1228"], - [1689676155, "1227.5555555555557"], - [1689676170, "1226.8707267331329"], - [1689676185, "1226"], - [1689676200, "1226.6666666666665"], - [1689676215, "1226.5667404757849"], - [1689676230, "1227.5555555555554"], - [1689676245, "1227.111111111111"], - [1689676260, "1228.1182218072875"], - [1689676275, "1227.111111111111"], - [1689676290, "1227.111111111111"], - [1689676305, "1226.6666666666665"], - [1689676320, "1227.3151415492646"], - [1689676335, "1227.111111111111"], - [1689676350, "1226.4444444444443"], - [1689676365, "1226.0000016144854"], - [1689676380, "1225.7777777777778"], - [1689676395, "1226"], - [1689676410, "1226.408027835993"], - [1689676425, "1227.111111111111"], - [1689676440, "1227.111111111111"], - [1689676455, "1227.8322739328205"], - [1689676470, "1227.5555555555557"], - [1689676485, "1228"], - [1689676500, "1227.306090705064"], - [1689676515, "1227.7777777777778"], - [1689676530, "1228.2222222222222"], - [1689676545, "1227.80508330185"], - [1689676560, "1226.6890853360578"], - [1689676575, "1226.2222222222222"], - [1689676590, "1226.6666666666667"], - [1689676605, "1226.6443347611917"], - [1689676620, "1225.5555555555554"], - [1689676635, "1226.6666666666665"], - [1689676650, "1227.3333333333333"], - [1689676665, "1228.4444444444443"], - [1689676680, "1227.7777777777778"], - [1689676695, "1228.6666666666665"], - [1689676710, "1228.6666666666665"], - [1689676725, "1227.5555555555554"], - [1689676740, "1226.6666666666665"], - [1689676755, "1226.888888888889"], - [1689676770, "1215.2222222222222"], - [1689676785, "1159.6666666666667"], - [1689676800, "1156.5951778217375"], - [1689676815, "1173.111111111111"], - [1689676830, "1227.373513006565"], - [1689676845, "1228.850779932462"], - [1689676860, "1227.306090705064"], - [1689676875, "1227.514944913789"], - [1689676890, "1226.4444444444443"], - [1689676905, "1226.249468483084"], - [1689676920, "1227.3060462635824"], - [1689676935, "1227.7777777777778"], - [1689676950, "1228.2222222222222"] - ], - "name": "response_throughput" - } - ], - "tcp_closed": null, - "tcp_opened": null, - "tcp_received": null, - "tcp_sent": null -} diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/validations.json b/plugins/kiali-backend/__fixtures__/data/namespaces/validations.json deleted file mode 100644 index 6a2b4f7a43..0000000000 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/validations.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Kubernetes": { - "travel-agency": { "errors": 0, "objectCount": 0, "warnings": 0 }, - "travel-control": { "errors": 0, "objectCount": 0, "warnings": 0 }, - "travel-portal": { "errors": 0, "objectCount": 0, "warnings": 0 } - } -} diff --git a/plugins/kiali-backend/__fixtures__/handlers.ts b/plugins/kiali-backend/__fixtures__/handlers.ts index 289903fea5..9231283f72 100644 --- a/plugins/kiali-backend/__fixtures__/handlers.ts +++ b/plugins/kiali-backend/__fixtures__/handlers.ts @@ -3,157 +3,10 @@ import { rest } from 'msw'; const LOCAL_ADDR = 'https://localhost:4000'; export const handlers = [ - rest.get(`${LOCAL_ADDR}/api/config`, (_, res, ctx) => { + rest.post(`${LOCAL_ADDR}/api/auth/info`, (_, res, ctx) => { return res( ctx.status(200), - ctx.json(require(`${__dirname}/data/config/anonymous_config.json`)), + ctx.json(require(`${__dirname}/data/config/auth_info.json`)), ); }), - rest.get(`${LOCAL_ADDR}/api/istio/certs`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/config/istio_certs.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/status`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/config/status.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/mesh/tls`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/config/mesh_tls.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/namespaces`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/namespaces/all.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/mesh/canaries/status`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/canaries_status.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/istio/status`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/istio_status.json`)), - ); - }), - rest.get( - `${LOCAL_ADDR}/api/mesh/outbound_traffic_policy/mode`, - (_, res, ctx) => { - return res(ctx.status(200), ctx.json({ mode: 'ALLOW_ANY' })); - }, - ), - rest.get(`${LOCAL_ADDR}/api/mesh/resources/thresholds`, (_, res, ctx) => { - return res(ctx.status(200), ctx.json({ memory: 0, cpu: 0 })); - }), - rest.get(`${LOCAL_ADDR}/api/istio/validations`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json(require(`${__dirname}/data/namespaces/validations.json`)), - ); - }), - rest.get(`${LOCAL_ADDR}/api/namespaces/travel-portal/tls`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - status: 'MTLS_NOT_ENABLED', - autoMTLSEnabled: true, - minTLS: '', - }), - ); - }), - rest.get(`${LOCAL_ADDR}/api/namespaces/travel-control/tls`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - status: 'MTLS_NOT_ENABLED', - autoMTLSEnabled: true, - minTLS: '', - }), - ); - }), - rest.get(`${LOCAL_ADDR}/api/namespaces/travel-agency/tls`, (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - status: 'MTLS_NOT_ENABLED', - autoMTLSEnabled: true, - minTLS: '', - }), - ); - }), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-agency/metrics`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-agency-metrics.json`), - ), - ); - }, - ), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-portal/metrics`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-portal-metrics.json`), - ), - ); - }, - ), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-control/metrics`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-control-metrics.json`), - ), - ); - }, - ), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-agency/health`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-agency-health.json`), - ), - ); - }, - ), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-portal/health`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-portal-health.json`), - ), - ); - }, - ), - rest.get( - `${LOCAL_ADDR}/api/namespaces/travel-control/health`, - (_, res, ctx) => { - return res( - ctx.status(200), - ctx.json( - require(`${__dirname}/data/namespaces/travel-control-health.json`), - ), - ); - }, - ), ]; diff --git a/plugins/kiali-backend/config.d.ts b/plugins/kiali-backend/config.d.ts index 59872dc852..88f3fc1bff 100644 --- a/plugins/kiali-backend/config.d.ts +++ b/plugins/kiali-backend/config.d.ts @@ -6,15 +6,6 @@ export interface Config { * Url of the hub cluster API endpoint */ url: string; - /** - * Strategy used to auth with Kiali - * - anonymous - * - token - * - * More info in https://kiali.io/docs/configuration/authentication/ - * - */ - strategy: string; /** * Service Account Token which is used for querying data from Kiali * @visibility secret diff --git a/plugins/kiali-backend/dist-dynamic/package.json b/plugins/kiali-backend/dist-dynamic/package.json index 67fa1889dd..fba3ea1888 100644 --- a/plugins/kiali-backend/dist-dynamic/package.json +++ b/plugins/kiali-backend/dist-dynamic/package.json @@ -30,8 +30,7 @@ "express-promise-router": "^4.1.1", "moment": "^2.29.4", "winston": "^3.11.0", - "yn": "^4.0.0", - "deep-freeze": "^0.0.1" + "yn": "^4.0.0" }, "devDependencies": {}, "files": [ diff --git a/plugins/kiali-backend/src/clients/auth.ts b/plugins/kiali-backend/src/clients/Auth.ts similarity index 51% rename from plugins/kiali-backend/src/clients/auth.ts rename to plugins/kiali-backend/src/clients/Auth.ts index 9485a8181c..d6837fbe9d 100644 --- a/plugins/kiali-backend/src/clients/auth.ts +++ b/plugins/kiali-backend/src/clients/Auth.ts @@ -1,39 +1,65 @@ import moment from 'moment'; -import { config, KialiDetails } from '@janus-idp/backstage-plugin-kiali-common'; - -export type Session = { - expiresOn: string; - username: string; -}; +import { KialiDetails } from '../service/config'; export const MILLISECONDS = 1000; export const AUTH_KIALI_TOKEN = 'kiali-token-aes'; + +const timeOutforWarningUser = 60 * MILLISECONDS; + +export enum AuthStrategy { + anonymous = 'anonymous', + openshift = 'openshift', + token = 'token', + openid = 'openid', + header = 'header', +} + +export interface SessionInfo { + username?: string; + expiresOn?: string; +} + +export interface AuthConfig { + authorizationEndpoint?: string; + logoutEndpoint?: string; + logoutRedirect?: string; + strategy?: AuthStrategy; +} + +export type AuthInfo = { + sessionInfo: SessionInfo; +} & AuthConfig; + export class KialiAuthentication { - protected session: Session; protected cookie: string; - private KialiDetails: KialiDetails; + protected auth: AuthInfo; private readonly sessionSeconds: number; constructor(KD: KialiDetails) { - this.KialiDetails = KD; this.sessionSeconds = KD.sessionTime ? KD.sessionTime * MILLISECONDS - : config.session.timeOutforWarningUser; - this.session = { expiresOn: '', username: 'anonymous' }; + : timeOutforWarningUser; + this.auth = { + sessionInfo: { expiresOn: '', username: 'anonymous' }, + }; this.cookie = ''; } + setAuthInfo = (auth: AuthInfo) => { + this.auth = auth; + }; + getSession = () => { - return this.session; + return this.auth; }; getCookie = () => { return this.cookie; }; - setSession = (session: Session) => { - this.session = session; + setSession = (session: SessionInfo) => { + this.auth.sessionInfo = session; }; checkIfExtendSession = () => { @@ -52,7 +78,7 @@ export class KialiAuthentication { }; private timeLeft = (): number => { - const expiresOn = moment(this.session.expiresOn); + const expiresOn = moment(this.auth.sessionInfo.expiresOn); if (expiresOn <= moment()) { return -1; @@ -62,12 +88,12 @@ export class KialiAuthentication { }; shouldRelogin = (): boolean => { - if (this.KialiDetails.strategy === 'anonymous') { + if (this.auth.strategy === 'anonymous') { return false; } if (this.cookie === '') { return true; } - return moment(this.session.expiresOn).diff(moment()) <= 0; + return moment(this.auth.sessionInfo.expiresOn).diff(moment()) <= 0; }; } diff --git a/plugins/kiali-backend/src/clients/KialiAPIConnector.ts b/plugins/kiali-backend/src/clients/KialiAPIConnector.ts index daa925cb8e..bdc2836141 100644 --- a/plugins/kiali-backend/src/clients/KialiAPIConnector.ts +++ b/plugins/kiali-backend/src/clients/KialiAPIConnector.ts @@ -1,40 +1,6 @@ -import { Entity } from '@backstage/catalog-model'; - -import { AxiosResponse } from 'axios'; import { Logger } from 'winston'; -import { - AuthInfo, - AuthStrategy, - CanaryUpgradeStatus, - CertsInfo, - ComponentStatus, - ComputedServerConfig, - computePrometheusRateParams, - config, - defaultMetricsDuration, - defaultServerConfig, - Direction, - FetchResponseWrapper, - HealthNamespace, - INITIAL_STATUS_STATE, - IstiodResourceThresholds, - IstioMetricsOptions, - KialiConfigT, - KialiDetails, - KialiInfo, - Namespace, - NsMetrics, - nsWideMTLSStatus, - OutboundTrafficPolicy, - OverviewData, - StatusState, - TLSStatus, - ValidationStatus, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { filterNsByAnnotation } from '../filters/byAnnotation'; -import { OverviewQuery } from '../service/router'; +import { KialiDetails } from '../service/config'; import { KialiFetcher } from './fetch'; export type Options = { @@ -43,308 +9,46 @@ export type Options = { }; export interface KialiApi { - fetchConfig(): Promise; - fetchOverviewNamespaces( - entity: Entity, - query: OverviewQuery, - ): Promise; + proxy(endpoint: string): Promise; } - export class KialiApiImpl implements KialiApi { - private readonly logger: Logger; - private kialiDetails: KialiDetails; - private kialiconfig: KialiConfigT; private kialiFetcher: KialiFetcher; + private logger: Logger; constructor(options: Options) { - options.logger.debug(`creating kiali client with url=${options.kiali.url}`); this.logger = options.logger; - this.kialiDetails = options.kiali; + options.logger.debug(`creating kiali client with url=${options.kiali.url}`); this.kialiFetcher = new KialiFetcher(options.kiali, options.logger); - this.kialiconfig = { - server: defaultServerConfig, - kialiConsole: options.kiali.url, - meshTLSStatus: { status: '', autoMTLSEnabled: false, minTLS: '' }, - username: '', - }; - } - - private queryMetrics = (options: IstioMetricsOptions) => { - const filters = options.filters?.map(f => `filters[]=${f}`).join('&'); - return ( - `?${filters || ''}&duration=${options.duration}` + - `&step=${options.step}&rateInterval=${options.rateInterval}` + - `&direction=${options.direction}&reporter=${options.reporter}` - ); - }; - - private queryOptions = (query: OverviewQuery): IstioMetricsOptions => { - const direction = query.direction; - const duration = query.duration || defaultMetricsDuration; - const globalScrapeInterval = 15; - const rateParams = computePrometheusRateParams( - duration, - globalScrapeInterval, - 10, - ); - return { - filters: ['request_count', 'request_error_count'], - duration: duration, - step: rateParams.step, - rateInterval: rateParams.rateInterval, - direction: (direction as Direction) || 'inbound', - reporter: direction === 'inbound' ? 'destination' : 'source', - }; - }; - - async fetchInfo(): Promise { - const response: FetchResponseWrapper = { errors: [], warnings: [] }; - const info: KialiInfo = { - status: INITIAL_STATUS_STATE, - auth: { sessionInfo: {}, strategy: AuthStrategy.anonymous }, - }; - await Promise.all([ - this.kialiFetcher - .newRequest('api') - .then(resp => { - info.status = resp.data; - return info; - }) - .catch(err => - response.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err), - ), - ), - this.kialiFetcher - .newRequest(config.api.urls.authInfo) - .then(resp => { - info.auth = resp.data; - // Check if strategy is the same - if (this.kialiDetails.strategy !== info.auth.strategy) { - response.errors.push({ - errorType: 'Bad configuration', - message: `Strategy in app-config is ${this.kialiDetails.strategy} and kiali server is configured for ${info.auth.strategy}`, - }); - } - return info; - }) - .catch(err => - response.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err), - ), - ), - ]); - response.response = info; - return response; - } - - async fetchConfig(): Promise { - let response: FetchResponseWrapper = { errors: [], warnings: [] }; - await this.kialiFetcher.checkSession().then(resp => { - response = resp; - return response; - }); - if (response.errors.length === 0) { - this.logger.info( - `Authenticated user ${this.kialiFetcher.getSession().username}`, - ); - this.logger.debug(`Fetching configuration`); - await Promise.all([ - this.kialiFetcher - .newRequest(config.api.urls.serverConfig) - .then(resp => { - this.kialiconfig.server = resp.data; - return this.kialiconfig; - }) - .catch(err => - response.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err), - ), - ), - this.kialiFetcher - .newRequest(config.api.urls.meshTls()) - .then(resp => { - this.kialiconfig.meshTLSStatus = resp.data; - return this.kialiconfig; - }) - .catch(err => - response.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err), - ), - ), - this.kialiFetcher - .newRequest(config.api.urls.istioCertsInfo()) - .then(resp => { - this.kialiconfig.istioCerts = resp.data; - return this.kialiconfig; - }) - .catch(err => - response.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err), - ), - ), - ]); - this.kialiconfig.username = this.kialiFetcher.getSession().username; - response.response = this.kialiconfig; - } - return response; } - async fetchNamespaces(entity: Entity): Promise { - const result: FetchResponseWrapper = { - errors: [], - warnings: [], - }; - - await this.kialiFetcher.checkSession().then(resp => { - result.errors = resp.errors; - result.warnings = resp.warnings; - return result; - }); - if (result.errors.length === 0) { - await this.handlePromise( - result, - config.api.urls.namespaces, - resp => (result.response = filterNsByAnnotation(resp.data, entity)), + async proxy(endpoint: string): Promise { + const authValid = await this.kialiFetcher.checkSession(); + if (authValid.verify) { + this.logger.debug( + `Authenticated user : ${ + this.kialiFetcher.getAuthData().sessionInfo.username + }`, ); + return this.kialiFetcher + .newRequest(endpoint) + .then(resp => resp.data); } - - return result; - } - - handlePromise( - result: FetchResponseWrapper, - endpoint: string, - handlerThen: (resp: AxiosResponse) => void, - handlerError?: (err: any) => void, - ): Promise { - return this.kialiFetcher - .newRequest(endpoint) - .then(resp => handlerThen(resp)) - .catch(err => - handlerError - ? handlerError(err) - : result.errors.push( - this.kialiFetcher.handleUnsuccessfulResponse(err, endpoint), - ), - ); + this.logger.debug( + `Authentication failed : ${ + authValid.missingAttributes && + `Missing attributes: [${authValid.missingAttributes?.join(',')}] .` + } ${authValid.message}`, + ); + return Promise.resolve(authValid); } - async fetchOverviewNamespaces( - entity: Entity, - query: OverviewQuery, - ): Promise { - this.logger.debug(`Fetching namespaces`); - const result: FetchResponseWrapper = { - errors: [], - warnings: [], - }; - - const response: OverviewData = { - namespaces: [], - }; - await this.kialiFetcher.checkSession().then(resp => { - result.errors = resp.errors; - result.warnings = resp.warnings; - return result; - }); - if (result.errors.length === 0) { - await this.handlePromise( - result, - config.api.urls.namespaces, - resp => (response.namespaces = filterNsByAnnotation(resp.data, entity)), - ); - - if (result.errors.length === 0) { - const meshStatus = 'MTLS_NOT_ENABLED'; - const duration = query.duration || defaultMetricsDuration; - const overviewType = query.overviewType; - const queryTime = undefined; - const options = this.queryOptions(query); - - await Promise.all([ - this.handlePromise( - result, - config.api.urls.canaryUpgradeStatus(), - resp => (response.canaryUpgrade = resp.data), - ), - this.handlePromise( - result, - config.api.urls.istioStatus(), - resp => (response.istioStatus = resp.data), - ), - this.handlePromise( - result, - config.api.urls.outboundTrafficPolicyMode(), - resp => (response.outboundTraffic = resp.data), - ), - this.handlePromise( - result, - config.api.urls.istiodResourceThresholds(), - resp => (response.istiodResourceThresholds = resp.data), - ), - this.handlePromise<{ - [key: string]: { [key: string]: ValidationStatus }; - }>( - result, - `${config.api.urls.configValidations()}?namespaces=${response.namespaces - .map(n => n.name) - .join(',')}`, - resp => - response.namespaces.forEach( - (n, index) => - (response.namespaces[index].validations = - resp.data[n.cluster][n.name]), - ), - ), - response.namespaces.map(ns => - this.handlePromise( - result, - config.api.urls.namespaceTls(ns.name), - resp => - (ns.tlsStatus = { - status: nsWideMTLSStatus(resp.data.status, meshStatus), - autoMTLSEnabled: resp.data.autoMTLSEnabled, - minTLS: resp.data.minTLS, - }), - ), - ), - response.namespaces.map(ns => - this.handlePromise( - result, - `${config.api.urls.namespaceMetrics(ns.name)}${this.queryMetrics( - options, - )}`, - resp => { - const metricsNs: NsMetrics = resp.data; - ns.metrics = metricsNs.request_count; - ns.errorMetrics = metricsNs.request_error_count; - if (ns.name === this.kialiconfig.server.istioNamespace) { - ns.controlPlaneMetrics = { - istiod_proxy_time: metricsNs.pilot_proxy_convergence_time, - istiod_cpu: metricsNs.process_cpu_seconds_total, - istiod_mem: metricsNs.process_virtual_memory_bytes, - }; - } - }, - ), - ), - response.namespaces.map(ns => - this.handlePromise( - result, - `${config.api.urls.namespaceHealth( - ns.name, - )}?type=${overviewType}&rateInterval=${duration}s${ - queryTime ? `&queryTime=${queryTime}` : '' - }`, - resp => (ns.nsHealth = resp.data), - ), - ), - ]); - } - result.response = response; + async status(): Promise { + const authValid = await this.kialiFetcher.checkSession(); + if (authValid.verify) { + return this.kialiFetcher + .newRequest('api/status') + .then(resp => resp.data); } - - return result; + return Promise.resolve(authValid); } } diff --git a/plugins/kiali-backend/src/clients/auth.test.ts b/plugins/kiali-backend/src/clients/auth.test.ts deleted file mode 100644 index 6e69f6e763..0000000000 --- a/plugins/kiali-backend/src/clients/auth.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import moment from 'moment'; - -import { config } from '@janus-idp/backstage-plugin-kiali-common'; - -import { - AUTH_KIALI_TOKEN, - KialiAuthentication, - MILLISECONDS, - Session, -} from './auth'; - -const kialiURL = 'https://localhost:4000'; -const tomorrow = moment().add(1, 'days'); -const yesterday = moment().subtract(1, 'days'); - -describe('createRouter', () => { - describe('KialiAuthentication constructor', () => { - it('returns values for KialiAuthentication', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }); - - expect(kialiAuth.getSession()).toEqual({ - expiresOn: '', - username: 'anonymous', - }); - expect(kialiAuth.getCookie()).toEqual(''); - // eslint-disable-next-line - expect(kialiAuth['sessionSeconds']).toEqual( - config.session.timeOutforWarningUser, - ); - }); - - it('with a specific sessionTime', () => { - const sessionSeconds = 500; - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - sessionTime: sessionSeconds, - }); - // eslint-disable-next-line - expect(kialiAuth['sessionSeconds']).toEqual(500 * MILLISECONDS); - }); - }); - describe('KialiAuthentication set', () => { - it('session set', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }); - expect(kialiAuth.getSession()).toEqual({ - expiresOn: '', - username: 'anonymous', - }); - const session: Session = { - expiresOn: tomorrow.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.getSession()).toEqual(session); - }); - - it('Cookie set', async () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }); - expect(kialiAuth.getCookie()).toEqual(''); - - const cookieKiali = `${AUTH_KIALI_TOKEN}=kiali-cookie-token`; - const cookie = `d5b5278d6ecca213a6cda3f6cfaa8cef=d0f2b7c7d1dd95460bc1764814f35468; ${cookieKiali} ; date=today`; - kialiAuth.setKialiCookie(cookie); - expect(kialiAuth.getCookie()).toEqual(cookieKiali); - }); - - it('Cookie set to empty', async () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }); - expect(kialiAuth.getCookie()).toEqual(''); - - const wrongCookieKiali = `${AUTH_KIALI_TOKEN}error=kiali-cookie-token`; - const cookie = `d5b5278d6ecca213a6cda3f6cfaa8cef=d0f2b7c7d1dd95460bc1764814f35468; ${wrongCookieKiali} ; date=today`; - kialiAuth.setKialiCookie(cookie); - expect(kialiAuth.getCookie()).toEqual(''); - }); - }); - - describe('Should relogin', () => { - it('should be false if strategy is anonymous', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }); - expect(kialiAuth.shouldRelogin()).toBeFalsy(); - }); - - it('should be true if strategy is not anonymous and cookie is empty', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'token', - skipTLSVerify: true, - }); - expect(kialiAuth.shouldRelogin()).toBeTruthy(); - }); - - describe('Strategy is not anonymous', () => { - it('should check the expire time if cookie is set but expire date is out', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'token', - skipTLSVerify: true, - }); - const cookieKiali = `${AUTH_KIALI_TOKEN}=kiali-cookie-token`; - const cookie = `d5b5278d6ecca213a6cda3f6cfaa8cef=d0f2b7c7d1dd95460bc1764814f35468; ${cookieKiali} ; date=today`; - kialiAuth.setKialiCookie(cookie); - const session: Session = { - expiresOn: yesterday.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.shouldRelogin()).toBeTruthy(); - }); - - it('should check the expire time if cookie is set and expire date is fine', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'token', - skipTLSVerify: true, - }); - const cookieKiali = `${AUTH_KIALI_TOKEN}=kiali-cookie-token`; - const cookie = `d5b5278d6ecca213a6cda3f6cfaa8cef=d0f2b7c7d1dd95460bc1764814f35468; ${cookieKiali} ; date=today`; - kialiAuth.setKialiCookie(cookie); - const session: Session = { - expiresOn: tomorrow.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.getSession()).toBe(session); - expect(kialiAuth.shouldRelogin()).toBeFalsy(); - }); - - it('should check if we need extend session', () => { - const kialiAuth = new KialiAuthentication({ - url: kialiURL, - strategy: 'token', - skipTLSVerify: true, - sessionTime: 60 * 60, // 1 hour of session - }); - const cookieKiali = `${AUTH_KIALI_TOKEN}=kiali-cookie-token`; - const cookie = `d5b5278d6ecca213a6cda3f6cfaa8cef=d0f2b7c7d1dd95460bc1764814f35468; ${cookieKiali} ; date=today`; - kialiAuth.setKialiCookie(cookie); - let session: Session = { - expiresOn: tomorrow.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.checkIfExtendSession()).toBeFalsy(); - session = { expiresOn: yesterday.toISOString(), username: 'kiali' }; - kialiAuth.setSession(session); - expect(kialiAuth.checkIfExtendSession()).toBeTruthy(); - - // Check with sessionTime less than option - const today_less_sessiontime = moment().add(50, 'minutes'); - session = { - expiresOn: today_less_sessiontime.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.checkIfExtendSession()).toBeTruthy(); - - // Check with sessionTime less than option - const today_more_sessiontime = moment().add(70, 'minutes'); - session = { - expiresOn: today_more_sessiontime.toISOString(), - username: 'kiali', - }; - kialiAuth.setSession(session); - expect(kialiAuth.checkIfExtendSession()).toBeFalsy(); - }); - }); - }); -}); diff --git a/plugins/kiali-backend/src/clients/fetch.ts b/plugins/kiali-backend/src/clients/fetch.ts index 0e60208b0f..a750df62d3 100644 --- a/plugins/kiali-backend/src/clients/fetch.ts +++ b/plugins/kiali-backend/src/clients/fetch.ts @@ -1,20 +1,26 @@ import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import { Logger } from 'winston'; -import { - config, - FetchResponseWrapper, - KialiDetails, - KialiFetchError, -} from '@janus-idp/backstage-plugin-kiali-common'; - import fs from 'fs'; import https from 'https'; -import { KialiAuthentication, Session } from './auth'; +import { KialiDetails } from '../service/config'; +import { + AuthInfo, + AuthStrategy, + KialiAuthentication, + SessionInfo, +} from './Auth'; + +export type AuthValid = { + verify: boolean; + missingAttributes?: string[]; + message?: string; + helper?: string; + authData?: AuthInfo; +}; const TIMEOUT_FETCH = 8000; - export class KialiFetcher { private readonly logger: Logger; private kialiAuth: KialiAuthentication; @@ -26,91 +32,86 @@ export class KialiFetcher { this.kialiAuth = new KialiAuthentication(KD); } - getSession = () => { - return this.kialiAuth.getSession(); - }; - newRequest = async

(endpoint: string, auth: boolean = false) => { + this.logger.info(`Query to ${endpoint}`); return axios.request

(this.getRequestInit(endpoint, auth)); }; - async checkSession(): Promise { - const response: FetchResponseWrapper = { - errors: [], - warnings: [], + private async getAuthInfo(): Promise { + return this.newRequest('api/auth/info').then(resp => resp.data); + } + + getAuthData(): AuthInfo { + return this.kialiAuth.getSession(); + } + + private validateConfiguration = (auth: AuthInfo): AuthValid => { + const result: AuthValid = { + verify: true, + authData: auth, }; + switch (auth.strategy) { + case AuthStrategy.anonymous: + break; + case AuthStrategy.token: { + if (this.KialiDetails.serviceAccountToken === '') { + result.verify = false; + result.message = `Attribute 'serviceAccountToken' is not in the backstage configuration`; + result.helper = `For more information follow the steps in https://janus-idp.io/plugins/kiali`; + result.missingAttributes = ['serviceAccountToken']; + } + break; + } + default: + result.verify = false; + result.message = `Strategy ${auth.strategy} is not supported in Kiali backstage plugin yet`; + break; + } + + return result; + }; + + async checkSession(): Promise { + let checkAuth: AuthValid = { verify: true }; /* * Check if the actual cookie/session is valid */ if (this.kialiAuth.shouldRelogin()) { + this.logger.info(`User must relogin`); + /* + * Get/Update AuthInformation from /api/auth/info + */ + const auth = await this.getAuthInfo(); + this.kialiAuth.setAuthInfo(auth); + this.logger.info(`AuthInfo: ${JSON.stringify(auth)}`); + /* + * Check Configuration + */ + checkAuth = this.validateConfiguration(auth); + if (!checkAuth.verify) { + return checkAuth; + } /* * Verify that SA token is in the config file */ - if (this.KialiDetails.serviceAccountToken) { - this.logger.debug( - `Query to ${ - new URL(config.api.urls.authenticate, this.KialiDetails.url).href - }`, - ); - const params = new URLSearchParams(); - params.append('token', this.KialiDetails.serviceAccountToken || ''); - await axios - .request( - this.getRequestInit(config.api.urls.authenticate, true), - ) + if (this.kialiAuth.checkIfExtendSession()) { + this.logger.info(`User need extend the session`); + await this.newRequest('api/authenticate', true) .then(resp => { - const session = resp.data; - /* - * Store the session and cookie - */ - this.logger.debug(`Logged username ${session.username}`); + const session = resp.data as SessionInfo; this.kialiAuth.setSession(session); this.kialiAuth.setKialiCookie( resp.headers['set-cookie']?.join(';') || '', ); + this.logger.info(`User ${session.username} logged in kiali plugin`); }) - .catch(err => - response.errors.push(this.handleUnsuccessfulResponse(err)), - ); - } else { - response.errors.push({ - errorType: 'NOT_FOUND', - message: 'No service account token for Kiali', - }); + .catch(err => { + checkAuth.verify = false; + checkAuth.message = this.handleUnsuccessfulResponse(err); + }); } - return response; } - /* - * Check if we need to extend the session - */ - if (this.kialiAuth.checkIfExtendSession()) { - this.logger.debug( - `Query to ${ - new URL(config.api.urls.authenticate, this.KialiDetails.url).href - } to extend session`, - ); - await axios - .request(this.getRequestInit(config.api.urls.authenticate)) - .then(resp => { - const session = resp.data; - /* - * Extend the session and store it - */ - this.logger.debug( - `Extended session for username ${session.username}`, - ); - this.kialiAuth.setSession(session); - return {}; - }) - .catch(err => - response.errors.push(this.handleUnsuccessfulResponse(err)), - ); - } - /* - * Login again is not needed - */ - this.logger.debug(`Not need relogin`); - return response; + return checkAuth; } private bufferFromFileOrString(file?: string, data?: string): Buffer | null { @@ -123,45 +124,26 @@ export class KialiFetcher { return null; } - handleUnsuccessfulResponse( - res: AxiosError, - endpoint?: string, - ): KialiFetchError { - const message = res.message; - const codeMessage = res.code ? `status (${res.code}) ` : ''; - const url = endpoint || res.config?.url || ''; - const urlMessage = url ? `when fetching "${url}" in "Kiali";` : ''; - this.logger.warn(`Error response axios: ${JSON.stringify(res)}`); - this.logger.warn( - `Received ${res.status} ${codeMessage} ${urlMessage} body=[${message}]`, - ); - - return { - errorType: res.code || 'UNKNOWN_ERROR', - message, - statusCode: res.status, - resourcePath: url, - }; - } - private getRequestInit = ( endpoint: string, auth: boolean = false, ): AxiosRequestConfig => { const requestInit: AxiosRequestConfig = { timeout: TIMEOUT_FETCH }; + const headers = { 'X-Auth-Type-Kiali-UI': '1' }; + if (auth) { const params = new URLSearchParams(); params.append('token', this.KialiDetails.serviceAccountToken || ''); - requestInit.headers = config.login.headers; + requestInit.headers = headers; requestInit.data = params; requestInit.method = 'post'; } else { requestInit.method = 'get'; requestInit.headers = { - ...config.login.headers, - cookie: this.kialiAuth.getCookie(), + ...headers, Accept: 'application/json', 'Content-Type': 'application/json', + cookie: this.kialiAuth.getCookie(), }; } @@ -179,4 +161,16 @@ export class KialiFetcher { } return requestInit; }; + + private handleUnsuccessfulResponse( + res: AxiosError, + endpoint?: string, + ): string { + const message = res.message; + const url = endpoint || res.config?.url || ''; + const urlMessage = url ? `when fetching "${url}" in "Kiali";` : ''; + return `[${ + res.code || 'UNKNOWN_ERROR' + }] Fetching ${urlMessage} body=[${message}]`; + } } diff --git a/plugins/kiali-backend/src/dynamic/index.ts b/plugins/kiali-backend/src/dynamic/index.ts index 3800583047..aab94ac418 100644 --- a/plugins/kiali-backend/src/dynamic/index.ts +++ b/plugins/kiali-backend/src/dynamic/index.ts @@ -1,5 +1,4 @@ import { BackendDynamicPluginInstaller } from '@backstage/backend-plugin-manager'; -import { CatalogClient } from '@backstage/catalog-client'; import { createRouter } from '../service/router'; @@ -8,10 +7,8 @@ export const dynamicPluginInstaller: BackendDynamicPluginInstaller = { router: { pluginID: 'kiali', createPlugin: async env => { - const catalogApi = new CatalogClient({ discoveryApi: env.discovery }); return await createRouter({ logger: env.logger, - catalogApi, config: env.config, }); }, diff --git a/plugins/kiali-backend/src/filters/byAnnotation.test.ts b/plugins/kiali-backend/src/filters/byAnnotation.test.ts deleted file mode 100644 index 78cbfd9a68..0000000000 --- a/plugins/kiali-backend/src/filters/byAnnotation.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Namespace } from '@janus-idp/backstage-plugin-kiali-common'; - -import { exportedForTesting } from './byAnnotation'; - -const namespaceMock: Namespace[] = [ - { - name: 'bookinfo', - cluster: 'kubernetes', - labels: { - app: 'bookinfo', - version: 'v1', - }, - }, - { - name: 'travel-agency', - cluster: 'kubernetes', - labels: { - 'backstage.io/kubernetes-id': 'travels', - app: 'travels', - }, - }, - { - name: 'travel-control', - cluster: 'kubernetes', - labels: { - app: 'travels', - }, - }, -]; - -describe('filterNsByAnnotation', () => { - describe('filterByNs', () => { - it('returns bookinfo', async () => { - const result = exportedForTesting.filterByNs( - namespaceMock, - 'bookinfo,istio-system', - ); - expect(result.length).toEqual(1); - expect(result[0].name).toEqual('bookinfo'); - }); - }); - - describe('filterById', () => { - it('returns travel-agency', async () => { - const result = exportedForTesting.filterById(namespaceMock, 'travels'); - expect(result.length).toEqual(1); - expect(result[0].name).toEqual('travel-agency'); - }); - }); - - describe('filterBySelector', () => { - it('returns travel-agency and travel-control', async () => { - const result = exportedForTesting.filterBySelector( - namespaceMock, - 'app=travels', - ); - expect(result.length).toEqual(2); - expect(result[0].name).toEqual('travel-agency'); - expect(result[1].name).toEqual('travel-control'); - }); - }); -}); diff --git a/plugins/kiali-backend/src/plugin.ts b/plugins/kiali-backend/src/plugin.ts index 9700d40795..42ef1f159f 100644 --- a/plugins/kiali-backend/src/plugin.ts +++ b/plugins/kiali-backend/src/plugin.ts @@ -1,12 +1,8 @@ -import { - HostDiscovery, - loggerToWinstonLogger, -} from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { createRouter } from './service/router'; @@ -27,13 +23,9 @@ export const kialiPlugin = createBackendPlugin({ }, async init({ http, logger, config }) { const winstonLogger = loggerToWinstonLogger(logger); - const catalogApi = new CatalogClient({ - discoveryApi: HostDiscovery.fromConfig(config), - }); const router = await createRouter({ logger: winstonLogger, config, - catalogApi, }); http.use(router); }, diff --git a/plugins/kiali-common/src/helpers/config.ts b/plugins/kiali-backend/src/service/config.ts similarity index 88% rename from plugins/kiali-common/src/helpers/config.ts rename to plugins/kiali-backend/src/service/config.ts index b116961d85..85e27299b4 100644 --- a/plugins/kiali-common/src/helpers/config.ts +++ b/plugins/kiali-backend/src/service/config.ts @@ -1,9 +1,16 @@ import { Config } from '@backstage/config'; -import { KialiDetails } from '../types'; - const KIALI_PREFIX = 'catalog.providers.kiali'; +export type KialiDetails = { + url: string; + skipTLSVerify?: boolean; + sessionTime?: number; + serviceAccountToken?: string; + caData?: string; + caFile?: string; +}; + const isValidUrl = (url: string): boolean => { try { // eslint-disable-next-line no-new @@ -26,6 +33,7 @@ export const getFromKialiConfig = (config: Config): Config => { }); return config; }; + export const getHubClusterFromConfig = (config: Config): KialiDetails => { const hub = getFromKialiConfig(config); @@ -36,12 +44,10 @@ export const getHubClusterFromConfig = (config: Config): KialiDetails => { return { url, - strategy: hub.getString('strategy'), serviceAccountToken: hub.getOptionalString('serviceAccountToken'), skipTLSVerify: hub.getOptionalBoolean('skipTLSVerify') || false, caData: hub.getOptionalString('caData'), caFile: hub.getOptionalString('caFile'), - sessionTime: hub.getOptionalNumber('sessionTime'), }; }; diff --git a/plugins/kiali-backend/src/service/router.test.ts b/plugins/kiali-backend/src/service/router.test.ts deleted file mode 100644 index 1e97c27310..0000000000 --- a/plugins/kiali-backend/src/service/router.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { HostDiscovery } from '@backstage/backend-common'; -import { CatalogClient } from '@backstage/catalog-client'; -import { ConfigReader } from '@backstage/config'; - -import express from 'express'; -import { setupServer } from 'msw/node'; -import request from 'supertest'; -import { createLogger, transports } from 'winston'; - -import { readKialiConfigs } from '@janus-idp/backstage-plugin-kiali-common'; - -import { handlers } from '../../__fixtures__/handlers'; -import { KialiApiImpl } from '../clients/KialiAPIConnector'; -import { makeRouter } from './router'; - -const server = setupServer(...handlers); - -const kialiURL = 'https://localhost:4000'; -const MockConfig = { - backend: { - baseUrl: 'http://localhost:7007', - }, - catalog: { - providers: { - kiali: { - url: kialiURL, - strategy: 'anonymous', - skipTLSVerify: true, - }, - }, - }, -}; - -afterEach(() => server.restoreHandlers()); -afterAll(() => server.close()); - -beforeAll(() => - server.listen({ - /* - * This is required so that msw doesn't throw - * warnings when the express app is requesting an endpoint - */ - onUnhandledRequest: 'bypass', - }), -); - -describe('createRouter', () => { - let app: express.Express; - - beforeAll(async () => { - const mockConfig = new ConfigReader(MockConfig, 'kiali'); - const kiali = readKialiConfigs(mockConfig); - const mockCatalog = new CatalogClient({ - discoveryApi: HostDiscovery.fromConfig(mockConfig), - }); - const logger = createLogger({ - transports: [new transports.Console({ silent: true })], - }); - const kialiAPI = new KialiApiImpl({ logger, kiali }); - app = express(); - app.use(express.json()); - const router = makeRouter(logger, kialiAPI, mockCatalog); - app.use('/', router); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('POST /config', () => { - it('returns config', async () => { - const response = await request(app) - .post('/config') - .set('Content-Type', 'application/json'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ - warnings: [], - errors: [], - response: { - server: { - accessibleNamespaces: ['**'], - authStrategy: 'anonymous', - clusterInfo: {}, - deployment: {}, - healthConfig: { - rate: [ - { - tolerance: [ - { - code: '5XX', - degraded: 0, - failure: 10, - protocol: 'http', - direction: '.*', - }, - { - code: '4XX', - degraded: 10, - failure: 20, - protocol: 'http', - direction: '.*', - }, - { - code: '^[1-9]$|^1[0-6]$', - degraded: 0, - failure: 10, - protocol: 'grpc', - direction: '.*', - }, - { - code: '^-$', - degraded: 0, - failure: 10, - protocol: 'http|grpc', - direction: '.*', - }, - ], - }, - { - tolerance: [ - { - code: '5XX', - degraded: 0, - failure: 10, - protocol: 'http', - direction: '.*', - }, - { - code: '4XX', - degraded: 10, - failure: 20, - protocol: 'http', - direction: '.*', - }, - { - code: '^[1-9]$|^1[0-6]$', - degraded: 0, - failure: 10, - protocol: 'grpc', - direction: '.*', - }, - { - code: '^-$', - degraded: 0, - failure: 10, - protocol: 'http|grpc', - direction: '.*', - }, - ], - }, - ], - }, - istioAnnotations: { - istioInjectionAnnotation: 'sidecar.istio.io/inject', - }, - istioCanaryRevision: {}, - istioStatusEnabled: true, - istioIdentityDomain: 'svc.cluster.local', - istioNamespace: 'istio-system', - istioLabels: { - appLabelName: 'app', - injectionLabelName: 'istio-injection', - injectionLabelRev: 'istio.io/rev', - versionLabelName: 'version', - }, - istioConfigMap: 'istio', - kialiFeatureFlags: { - certificatesInformationIndicators: { - enabled: true, - secrets: ['cacerts', 'istio-ca-secret'], - }, - clustering: { - enable_exec_provider: false, - }, - istioAnnotationAction: true, - istioInjectionAction: true, - istioUpgradeAction: false, - uiDefaults: { - graph: { - findOptions: [ - { - description: 'Find: slow edges (\u003e 1s)', - expression: 'rt \u003e 1000', - }, - { - description: 'Find: unhealthy nodes', - expression: '! healthy', - }, - { - description: 'Find: unknown nodes', - expression: 'name = unknown', - }, - { - description: 'Find: nodes with the 2 top rankings', - expression: 'rank \u003c= 2', - }, - ], - hideOptions: [ - { - description: 'Hide: healthy nodes', - expression: 'healthy', - }, - { - description: 'Hide: unknown nodes', - expression: 'name = unknown', - }, - { - description: - 'Hide: nodes ranked lower than the 2 top rankings', - expression: 'rank \u003e 2', - }, - ], - settings: { - fontLabel: 13, - minFontBadge: 7, - minFontLabel: 10, - }, - traffic: { - grpc: 'requests', - http: 'requests', - tcp: 'sent', - }, - }, - list: { - includeHealth: true, - includeIstioResources: true, - includeValidations: true, - showIncludeToggles: false, - }, - metricsPerRefresh: '1m', - metricsInbound: {}, - metricsOutbound: {}, - refreshInterval: '60s', - }, - validations: { - ignore: ['KIA1301'], - SkipWildcardGatewayHosts: false, - }, - }, - logLevel: 'trace', - prometheus: { - globalScrapeInterval: 15, - storageTsdbRetention: 1296000, - }, - }, - meshTLSStatus: { - status: 'MTLS_NOT_ENABLED', - autoMTLSEnabled: true, - minTLS: 'N/A', - }, - username: 'anonymous', - istioCerts: [ - { - secretName: 'istio-ca-secret', - secretNamespace: 'istio-system', - dnsNames: null, - issuer: 'O=cluster.local', - notBefore: '2023-07-10T10:43:24Z', - notAfter: '2033-07-07T10:43:24Z', - error: '', - accessible: true, - }, - ], - kialiConsole: kialiURL, - }, - }); - }); - }); -}); diff --git a/plugins/kiali-backend/src/service/router.ts b/plugins/kiali-backend/src/service/router.ts index bace4ce1d1..550040aeb1 100644 --- a/plugins/kiali-backend/src/service/router.ts +++ b/plugins/kiali-backend/src/service/router.ts @@ -1,106 +1,34 @@ import { errorHandler } from '@backstage/backend-common'; -import { CatalogApi } from '@backstage/catalog-client'; -import { - CompoundEntityRef, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; -import express, { Request } from 'express'; +import express from 'express'; import { Logger } from 'winston'; -import { readKialiConfigs } from '@janus-idp/backstage-plugin-kiali-common'; - import { KialiApiImpl } from '../clients/KialiAPIConnector'; +import { readKialiConfigs } from './config'; export interface RouterOptions { logger: Logger; config: Config; - catalogApi: CatalogApi; } -export type OverviewQuery = { - ns?: string; - nss?: string[]; - overviewType?: string; - duration?: number; - direction?: string; -}; - export const makeRouter = ( logger: Logger, kialiAPI: KialiApiImpl, - catalogApi: CatalogApi, ): express.Router => { - const getEntityByReq = async (req: Request) => { - const rawEntityRef = req.body.entityRef; - if (rawEntityRef && typeof rawEntityRef !== 'string') { - throw new InputError(`entity query must be a string`); - } else if (!rawEntityRef) { - throw new InputError('entity is a required field'); - } - let entityRef: CompoundEntityRef | undefined = undefined; - - try { - entityRef = parseEntityRef(rawEntityRef); - } catch (error) { - throw new InputError(`Invalid entity ref, ${error}`); - } - - logger.info(`entityRef: ${JSON.stringify(entityRef)}`); - - const token = - getBearerTokenFromAuthorizationHeader(req.headers.authorization) || - (req.cookies?.token as string | undefined); - - logger.info(`Token: ${JSON.stringify(token)}`); - const entity = await catalogApi.getEntityByRef(entityRef, { - token: token, - }); - - if (!entity) { - throw new InputError( - `Entity ref missing, ${stringifyEntityRef(entityRef)}`, - ); - } - return entity; - }; - const router = express.Router(); router.use(express.json()); - router.post('/info', async (_, res) => { - logger.debug('Call to Kiali information'); - const response = await kialiAPI.fetchInfo(); - res.json(response); - }); - - router.post('/config', async (_, res) => { - logger.debug('Call to Configuration'); - const response = await kialiAPI.fetchConfig(); - res.json(response); - }); - - router.post('/namespaces', async (req, res) => { - const entity = await getEntityByReq(req); - logger.debug('Call to Namespaces'); - const response = await kialiAPI.fetchNamespaces(entity); - res.json(response); + // curl -H "Content-type: application/json" -H "Accept: application/json" -X GET localhost:7007/api/kiali/proxy --data '{"endpoint": "api/namespaces"}' + router.post('/proxy', async (req, res) => { + const endpoint = req.body.endpoint; + logger.info(`Call to Kiali ${endpoint}`); + res.json(await kialiAPI.proxy(endpoint)); }); - router.post('/overview', async (req, res) => { - const entity = await getEntityByReq(req); - const query: OverviewQuery = { - duration: Number(req.body.query.duration) || 600, - direction: (req.body.query.direction as string) || 'inbound', - overviewType: (req.body.query.overviewType as string) || 'app', - }; - logger.debug('Call to Overview'); - const response = await kialiAPI.fetchOverviewNamespaces(entity, query); - res.json(response); + router.post('/status', async (_, res) => { + logger.info(`Call to Kiali Status`); + res.json(await kialiAPI.status()); }); router.use(errorHandler()); @@ -113,7 +41,6 @@ export async function createRouter( ): Promise { const { logger } = options; const { config } = options; - const { catalogApi } = options; logger.info('Initializing Kiali backend'); @@ -121,5 +48,5 @@ export async function createRouter( const kialiAPI = new KialiApiImpl({ logger, kiali }); - return makeRouter(logger, kialiAPI, catalogApi); + return makeRouter(logger, kialiAPI); } diff --git a/plugins/kiali-backend/src/service/standaloneServer.ts b/plugins/kiali-backend/src/service/standaloneServer.ts index b877b68b4d..4ea2081eaf 100644 --- a/plugins/kiali-backend/src/service/standaloneServer.ts +++ b/plugins/kiali-backend/src/service/standaloneServer.ts @@ -1,5 +1,4 @@ -import { createServiceBuilder, HostDiscovery } from '@backstage/backend-common'; -import { CatalogClient } from '@backstage/catalog-client'; +import { createServiceBuilder } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Logger } from 'winston'; @@ -19,14 +18,10 @@ export async function startStandaloneServer( ): Promise { const logger = options.logger.child({ service: 'kiali-backend' }); const config = new ConfigReader({}); - const catalogApi = new CatalogClient({ - discoveryApi: HostDiscovery.fromConfig(config), - }); logger.debug('Starting application server...'); const router = await createRouter({ logger, config, - catalogApi, }); let service = createServiceBuilder(module) diff --git a/plugins/kiali-common/.eslintrc.js b/plugins/kiali-common/.eslintrc.js deleted file mode 100644 index e2a53a6ad2..0000000000 --- a/plugins/kiali-common/.eslintrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/kiali-common/CHANGELOG.md b/plugins/kiali-common/CHANGELOG.md deleted file mode 100644 index ecbcf83b07..0000000000 --- a/plugins/kiali-common/CHANGELOG.md +++ /dev/null @@ -1,59 +0,0 @@ -## @janus-idp/backstage-plugin-kiali-common 1.0.0 (2023-10-27) - - -### Features - -* **kiali:** add namespace selector ([#675](https://github.com/janus-idp/backstage-plugins/issues/675)) ([e3cfc26](https://github.com/janus-idp/backstage-plugins/commit/e3cfc26bdf550916da3ee801601196d8614471b5)) -* **kiali:** kiali plugin ([#371](https://github.com/janus-idp/backstage-plugins/issues/371)) ([08d5583](https://github.com/janus-idp/backstage-plugins/commit/08d5583f839a8233d7b08a7ec1eb043bf4978e91)) -* **kiali:** move from node-fetch to axios ([#573](https://github.com/janus-idp/backstage-plugins/issues/573)) ([c0ed797](https://github.com/janus-idp/backstage-plugins/commit/c0ed7972ef8fa143d51b590ca5f874900e5d8bef)) -* **kiali:** show kiali information in header ([#630](https://github.com/janus-idp/backstage-plugins/issues/630)) ([b9a83b3](https://github.com/janus-idp/backstage-plugins/commit/b9a83b332ec518e60a9780961fdce070eda02d02)) -* **ts:** transpile each plugin separately ([#634](https://github.com/janus-idp/backstage-plugins/issues/634)) ([b94c4dc](https://github.com/janus-idp/backstage-plugins/commit/b94c4dc50ada328e5ce1bed5fb7c76f64607e1ee)) - - -### Bug Fixes - -* **kiali:** fix code smells ([#607](https://github.com/janus-idp/backstage-plugins/issues/607)) ([ef2eecf](https://github.com/janus-idp/backstage-plugins/commit/ef2eecfa71e2a60b4442ce3105a526b3332eaa1b)) - -## @janus-idp/backstage-plugin-kiali-common [1.4.1](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.4.0...@janus-idp/backstage-plugin-kiali-common@1.4.1) (2023-10-19) - -## @janus-idp/backstage-plugin-kiali-common [1.4.0](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.3.0...@janus-idp/backstage-plugin-kiali-common@1.4.0) (2023-08-30) - - -### Features - -* **kiali:** add namespace selector ([#675](https://github.com/janus-idp/backstage-plugins/issues/675)) ([e3cfc26](https://github.com/janus-idp/backstage-plugins/commit/e3cfc26bdf550916da3ee801601196d8614471b5)) - -## @janus-idp/backstage-plugin-kiali-common [1.3.0](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.2.1...@janus-idp/backstage-plugin-kiali-common@1.3.0) (2023-08-28) - - -### Features - -* **kiali:** show kiali information in header ([#630](https://github.com/janus-idp/backstage-plugins/issues/630)) ([b9a83b3](https://github.com/janus-idp/backstage-plugins/commit/b9a83b332ec518e60a9780961fdce070eda02d02)) - -## @janus-idp/backstage-plugin-kiali-common [1.2.1](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.2.0...@janus-idp/backstage-plugin-kiali-common@1.2.1) (2023-08-22) - - -### Bug Fixes - -* **kiali:** fix code smells ([#607](https://github.com/janus-idp/backstage-plugins/issues/607)) ([ef2eecf](https://github.com/janus-idp/backstage-plugins/commit/ef2eecfa71e2a60b4442ce3105a526b3332eaa1b)) - -## @janus-idp/backstage-plugin-kiali-common [1.2.0](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.1.0...@janus-idp/backstage-plugin-kiali-common@1.2.0) (2023-08-14) - - -### Features - -* **ts:** transpile each plugin separately ([#634](https://github.com/janus-idp/backstage-plugins/issues/634)) ([b94c4dc](https://github.com/janus-idp/backstage-plugins/commit/b94c4dc50ada328e5ce1bed5fb7c76f64607e1ee)) - -## @janus-idp/backstage-plugin-kiali-common [1.1.0](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-kiali-common@1.0.0...@janus-idp/backstage-plugin-kiali-common@1.1.0) (2023-07-27) - - -### Features - -* **kiali:** move from node-fetch to axios ([#573](https://github.com/janus-idp/backstage-plugins/issues/573)) ([c0ed797](https://github.com/janus-idp/backstage-plugins/commit/c0ed7972ef8fa143d51b590ca5f874900e5d8bef)) - -## @janus-idp/backstage-plugin-kiali-common 1.0.0 (2023-07-25) - - -### Features - -* **kiali:** kiali plugin ([#371](https://github.com/janus-idp/backstage-plugins/issues/371)) ([08d5583](https://github.com/janus-idp/backstage-plugins/commit/08d5583f839a8233d7b08a7ec1eb043bf4978e91)) diff --git a/plugins/kiali-common/README.md b/plugins/kiali-common/README.md deleted file mode 100644 index 0cfb61b886..0000000000 --- a/plugins/kiali-common/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Kiali Common plugin for Kiali Plugin - -Welcome to the common package for the kiali plugin! - -For more information about Kiali plugin, see the [Kiali Plugin documentation](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/kiali) on GitHub. diff --git a/plugins/kiali-common/package.json b/plugins/kiali-common/package.json deleted file mode 100644 index 56dab11dec..0000000000 --- a/plugins/kiali-common/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@janus-idp/backstage-plugin-kiali-common", - "description": "Common functionalities for the kiali plugin", - "version": "1.0.0", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", - "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "backstage": { - "role": "common-library" - }, - "scripts": { - "build": "backstage-cli package build", - "tsc": "tsc", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test --passWithNoTests --coverage", - "clean": "backstage-cli package clean", - "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" - }, - "devDependencies": { - "@backstage/cli": "0.23.0", - "supertest": "6.3.3" - }, - "files": [ - "dist" - ], - "dependencies": { - "@backstage/config": "^1.1.1", - "deep-freeze": "^0.0.1" - } -} diff --git a/plugins/kiali-common/src/config/index.ts b/plugins/kiali-common/src/config/index.ts deleted file mode 100644 index bd37f302df..0000000000 --- a/plugins/kiali-common/src/config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Config'; diff --git a/plugins/kiali-common/src/helpers/index.ts b/plugins/kiali-common/src/helpers/index.ts deleted file mode 100644 index f03c2281a9..0000000000 --- a/plugins/kiali-common/src/helpers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './config'; diff --git a/plugins/kiali-common/src/index.ts b/plugins/kiali-common/src/index.ts deleted file mode 100644 index 12cee53565..0000000000 --- a/plugins/kiali-common/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './config'; -export * from './helpers'; -export * from './types'; -export * from './query'; -export * from './responses'; -export * from './utils'; diff --git a/plugins/kiali-common/src/query/index.ts b/plugins/kiali-common/src/query/index.ts deleted file mode 100644 index 5448ad2d1d..0000000000 --- a/plugins/kiali-common/src/query/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id'; -export const KUBERNETES_LABEL_SELECTOR = - 'backstage.io/kubernetes-label-selector'; -export const KUBERNETES_NAMESPACE = 'backstage.io/kubernetes-namespace'; diff --git a/plugins/kiali-common/src/responses/index.ts b/plugins/kiali-common/src/responses/index.ts deleted file mode 100644 index 6368110b36..0000000000 --- a/plugins/kiali-common/src/responses/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - CanaryUpgradeStatus, - ComponentStatus, - IstiodResourceThresholds, - NamespaceInfo, - OutboundTrafficPolicy, -} from '../types'; - -export type OverviewData = { - namespaces: NamespaceInfo[]; - canaryUpgrade?: CanaryUpgradeStatus; - istioStatus?: ComponentStatus[]; - outboundTraffic?: OutboundTrafficPolicy; - istiodResourceThresholds?: IstiodResourceThresholds; -}; diff --git a/plugins/kiali-common/src/setupTests.ts b/plugins/kiali-common/src/setupTests.ts deleted file mode 100644 index cb0ff5c3b5..0000000000 --- a/plugins/kiali-common/src/setupTests.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/plugins/kiali-common/src/types/Common.ts b/plugins/kiali-common/src/types/Common.ts deleted file mode 100644 index da24f43bc5..0000000000 --- a/plugins/kiali-common/src/types/Common.ts +++ /dev/null @@ -1,98 +0,0 @@ -export const defaultMetricsDuration: number = 600; - -// cluster name to denote the cluster where Kiali is deployed -export const HomeClusterName = ''; - -export enum HTTP_VERBS { - DELETE = 'DELETE', - GET = 'get', - PATCH = 'patch', - POST = 'post', - PUT = 'put', -} - -export type TargetKind = 'app' | 'service' | 'workload'; - -export const MILLISECONDS = 1000; - -export const UNIT_TIME = { - SECOND: 1, - MINUTE: 60, - HOUR: 3600, - DAY: 24 * 3600, -}; - -export type BoundsInMilliseconds = { - from?: number; - to?: number; -}; - -// Redux doesn't work well with type composition -export type TimeRange = { - from?: number; - to?: number; - rangeDuration?: number; -}; - -// Type-guarding TimeRange: executes first callback when range is a duration, or second callback when it's a bounded range, mapping to a value -export function guardTimeRange( - range: TimeRange, - ifDuration: (d: number) => T, - ifBounded: (b: BoundsInMilliseconds) => T, -): T { - if (range.from) { - const b: BoundsInMilliseconds = { - from: range.from, - }; - if (range.to) { - b.to = range.to; - } - return ifBounded(b); - } - - if (range.rangeDuration) { - return ifDuration(range.rangeDuration); - } - - // It shouldn't reach here a TimeRange should have DurationInSeconds or BoundsInMilliseconds - return ifDuration(defaultMetricsDuration); -} - -export const durationToBounds = (duration: number): BoundsInMilliseconds => { - return { - from: new Date().getTime() - duration * 1000, - }; -}; - -export const evalTimeRange = (range: TimeRange): [Date, Date] => { - const bounds = guardTimeRange(range, durationToBounds, b => b); - return [ - bounds.from ? new Date(bounds.from) : new Date(), - bounds.to ? new Date(bounds.to) : new Date(), - ]; -}; - -export const boundsToDuration = (bounds: BoundsInMilliseconds): number => { - return Math.floor( - ((bounds.to || new Date().getTime()) - - (bounds.from || new Date().getTime())) / - 1000, - ); -}; - -export const isEqualTimeRange = (t1: TimeRange, t2: TimeRange): boolean => { - if (t1.from && t2.from && t1.from !== t2.from) { - return false; - } - if (t1.to && t2.to && t1.to !== t2.to) { - return false; - } - if ( - t1.rangeDuration && - t2.rangeDuration && - t1.rangeDuration !== t2.rangeDuration - ) { - return false; - } - return true; -}; diff --git a/plugins/kiali-common/src/types/Config.ts b/plugins/kiali-common/src/types/Config.ts deleted file mode 100644 index cf8f3f169e..0000000000 --- a/plugins/kiali-common/src/types/Config.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { parseHealthConfig } from './HealthConfig'; - -export type KialiDetails = { - url: string; - strategy: string; - skipTLSVerify?: boolean; - serviceAccountToken?: string; - caData?: string; - caFile?: string; - sessionTime?: number; -}; - -export interface MeshCluster { - apiEndpoint: string; - isKialiHome: boolean; - kialiInstances: KialiInstance[]; - name: string; - network: string; - secretName: string; -} - -export interface KialiInstance { - serviceName: string; - namespace: string; - operatorResource: string; - url: string; - version: string; -} - -export type IstioLabelKey = - | 'appLabelName' - | 'versionLabelName' - | 'injectionLabelName' - | 'injectionLabelRev'; - -interface ClusterInfo { - name: string; - network: string; -} - -interface DeploymentConfig { - viewOnlyMode: boolean; -} - -interface IstioAnnotations { - // this could also be the name of the pod label, both label and annotation are supported - istioInjectionAnnotation: string; -} - -interface GraphFindOption { - description: string; - expression: string; -} - -interface GraphTraffic { - grpc: string; - http: string; - tcp: string; -} - -interface GraphSettings { - fontLabel: number; - minFontBadge: number; - minFontLabel: number; -} - -interface GraphUIDefaults { - findOptions: GraphFindOption[]; - hideOptions: GraphFindOption[]; - settings: GraphSettings; - traffic: GraphTraffic; -} - -interface UIDefaults { - graph: GraphUIDefaults; - metricsPerRefresh?: string; - namespaces?: string[]; - refreshInterval?: string; -} - -interface CertificatesInformationIndicators { - enabled: boolean; -} - -interface KialiFeatureFlags { - certificatesInformationIndicators: CertificatesInformationIndicators; - disabledFeatures: string[]; - istioInjectionAction: boolean; - istioAnnotationAction: boolean; - istioUpgradeAction: boolean; - uiDefaults: UIDefaults; -} - -// Not based exactly on Kiali configuration but rather whether things like prometheus config -// allow for certain Kiali features. True means the feature is crippled, false means supported. -export interface KialiCrippledFeatures { - requestSize: boolean; - requestSizeAverage: boolean; - requestSizePercentiles: boolean; - responseSize: boolean; - responseSizeAverage: boolean; - responseSizePercentiles: boolean; - responseTime: boolean; - responseTimeAverage: boolean; - responseTimePercentiles: boolean; -} - -interface IstioCanaryRevision { - current: string; - upgrade: string; -} - -/* - Health Config -*/ -export type RegexConfig = string | RegExp; - -export interface HealthConfig { - rate: RateHealthConfig[]; -} - -// rateHealthConfig -export interface RateHealthConfig { - namespace?: RegexConfig; - kind?: RegexConfig; - name?: RegexConfig; - tolerance: ToleranceConfig[]; -} -// toleranceConfig -export interface ToleranceConfig { - code: RegexConfig; - degraded: number; - failure: number; - protocol?: RegexConfig; - direction?: RegexConfig; -} - -/* - End Health Config -*/ - -export interface ServerConfig { - accessibleNamespaces: Array; - authStrategy: string; - clusterInfo?: ClusterInfo; - clusters: { [key: string]: MeshCluster }; - deployment: DeploymentConfig; - gatewayAPIEnabled: boolean; - healthConfig: HealthConfig; - installationTag?: string; - istioAnnotations: IstioAnnotations; - istioCanaryRevision: IstioCanaryRevision; - istioIdentityDomain: string; - istioNamespace: string; - istioLabels: { [key in IstioLabelKey]: string }; - kialiFeatureFlags: KialiFeatureFlags; - logLevel: string; - prometheus: { - globalScrapeInterval?: number; - storageTsdbRetention?: number; - }; -} - -export type Durations = { [key: number]: string }; - -export type ComputedServerConfig = ServerConfig & { - durations: Durations; -}; - -export const defaultServerConfig: ComputedServerConfig = { - accessibleNamespaces: [], - authStrategy: '', - clusters: {}, - durations: {}, - gatewayAPIEnabled: false, - logLevel: '', - healthConfig: { - rate: [], - }, - deployment: { - viewOnlyMode: false, - }, - installationTag: 'Kiali Console', - istioAnnotations: { - istioInjectionAnnotation: 'sidecar.istio.io/inject', - }, - istioCanaryRevision: { - current: '', - upgrade: '', - }, - istioIdentityDomain: 'svc.cluster.local', - istioNamespace: 'istio-system', - istioLabels: { - appLabelName: 'app', - injectionLabelName: 'istio-injection', - injectionLabelRev: 'istio.io/rev', - versionLabelName: 'version', - }, - kialiFeatureFlags: { - certificatesInformationIndicators: { - enabled: true, - }, - disabledFeatures: [], - istioInjectionAction: true, - istioAnnotationAction: true, - istioUpgradeAction: false, - uiDefaults: { - graph: { - findOptions: [], - hideOptions: [], - settings: { - fontLabel: 13, - minFontBadge: 7, - minFontLabel: 10, - }, - traffic: { - grpc: 'requests', - http: 'requests', - tcp: 'sent', - }, - }, - }, - }, - prometheus: { - globalScrapeInterval: 15, - storageTsdbRetention: 21600, - }, -}; - -const toDurations = (tupleArray: [number, string][]): Durations => { - const obj: { [key: number]: string } = {}; - tupleArray.forEach(tuple => { - obj[tuple[0]] = tuple[1]; - }); - return obj; -}; - -const durationsTuples: [number, string][] = [ - [60, '1m'], - [120, '2m'], - [300, '5m'], - [600, '10m'], - [1800, '30m'], - [3600, '1h'], - [10800, '3h'], - [21600, '6h'], - [43200, '12h'], - [86400, '1d'], - [604800, '7d'], - [2592000, '30d'], -]; -export const computeValidDurations = (cfg: ComputedServerConfig) => { - const tsdbRetention = cfg.prometheus.storageTsdbRetention; - const scrapeInterval = cfg.prometheus.globalScrapeInterval; - let filtered = durationsTuples.filter( - d => - (!tsdbRetention || d[0] <= tsdbRetention) && - (!scrapeInterval || d[0] >= scrapeInterval * 2), - ); - // Make sure we keep at least one item, even if it's silly - if (filtered.length === 0) { - filtered = [durationsTuples[0]]; - } - cfg.durations = toDurations(filtered); -}; - -export const setServerConfig = ( - serverConfig: ComputedServerConfig, - cfg: ServerConfig, -) => { - const config = { ...defaultServerConfig, ...cfg }; - config.healthConfig = cfg.healthConfig - ? parseHealthConfig(cfg.healthConfig) - : serverConfig.healthConfig; - computeValidDurations(config); - - return config; -}; diff --git a/plugins/kiali-common/src/types/Fetch.ts b/plugins/kiali-common/src/types/Fetch.ts deleted file mode 100644 index 26b84c1fc4..0000000000 --- a/plugins/kiali-common/src/types/Fetch.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - AuthInfo, - ControlPlaneMetricsMap, - KialiConfigT, - Metric, - Namespace, - NamespaceHealth, - NamespaceInfo, - OverviewData, - StatusState, - TLSStatus, -} from '../'; - -export type NsMetrics = { [key: string]: Metric[] }; -export type HealthNamespace = { [key: string]: NamespaceHealth }; - -export interface KialiInfo { - status: StatusState; - auth: AuthInfo; -} -export type FetchResponse = - | KialiConfigT - | KialiInfo - | OverviewData - | NamespaceInfo - | TLSStatus - | NsMetrics - | ControlPlaneMetricsMap - | HealthNamespace - | Namespace[]; - -export interface StatusError { - errorType: string; - message?: string; - resourcePath?: string; - statusCode?: number; -} - -export type KialiFetchError = StatusError; - -export interface FetchResponseWrapper { - errors: KialiFetchError[]; - warnings: KialiFetchError[]; - response?: FetchResponse; -} - -export type FetchResult = FetchResponse | KialiFetchError | number; diff --git a/plugins/kiali-common/src/types/Health.ts b/plugins/kiali-common/src/types/Health.ts deleted file mode 100644 index 119afe8373..0000000000 --- a/plugins/kiali-common/src/types/Health.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { RequestHealth } from './'; - -export type NamespaceHealth = { - workloadStatuses?: WorkloadStatus[]; - WorkloadStatus?: WorkloadStatus; - requests: RequestHealth; -}; - -export interface WorkloadStatus { - name: string; - desiredReplicas: number; - currentReplicas: number; - availableReplicas: number; - syncedProxies: number; -} - -export type HealthAnnotationType = { [key: string]: string }; diff --git a/plugins/kiali-common/src/types/HealthStatus.ts b/plugins/kiali-common/src/types/HealthStatus.ts deleted file mode 100644 index 71a6bf8355..0000000000 --- a/plugins/kiali-common/src/types/HealthStatus.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { HealthAnnotationType, ToleranceConfig } from './'; - -/* -RequestType interface -- where the structure is type {: {:value ...} ...} - -Example: { "http": {"200": 2, "404": 1 ...} ... } -*/ -export interface RequestType { - [key: string]: { [key: string]: number }; -} -export interface RequestHealth { - inbound: RequestType; - outbound: RequestType; - healthAnnotations: HealthAnnotationType; -} - -export interface Status { - name: string; - color?: string; - htmlColor?: string; - priority: number; -} - -export interface ProxyStatus { - CDS: string; - EDS: string; - LDS: string; - RDS: string; -} - -export const FAILURE: Status = { - name: 'Failure', - color: 'error', - htmlColor: '#c9190b', - priority: 4, -}; -export const DEGRADED: Status = { - name: 'Degraded', - htmlColor: '#f0ab00', - priority: 3, -}; -export const NOT_READY: Status = { - name: 'Not Ready', - htmlColor: '#6a6e73', - priority: 2, -}; -export const HEALTHY: Status = { - name: 'Healthy', - htmlColor: '#3e8635', - priority: 1, -}; -export const NA: Status = { - name: 'No health information', - htmlColor: '#6a6e73', - priority: 0, -}; - -export interface Thresholds { - degraded: number; - failure: number; - unit: string; -} - -export interface ThresholdStatus { - value: number; - status: Status; - violation?: string; -} - -export const POD_STATUS = 'Pod Status'; - -// Use -1 rather than NaN to allow straigthforward comparison -export const RATIO_NA = -1; - -export const ascendingThresholdCheck = ( - value: number, - thresholds: Thresholds, -): ThresholdStatus => { - if (value > 0) { - if (value >= thresholds.failure) { - return { - value: value, - status: FAILURE, - violation: `${value.toFixed(2)}${thresholds.unit}>=${ - thresholds.failure - }${thresholds.unit}`, - }; - } else if (value >= thresholds.degraded) { - return { - value: value, - status: DEGRADED, - violation: `${value.toFixed(2)}${thresholds.unit}>=${ - thresholds.degraded - }${thresholds.unit}`, - }; - } - } - - return { value: value, status: HEALTHY }; -}; - -export const getRequestErrorsStatus = ( - ratio: number, - tolerance?: ToleranceConfig, -): ThresholdStatus => { - if (tolerance && ratio >= 0) { - const thresholds = { - degraded: tolerance.degraded, - failure: tolerance.failure, - unit: '%', - }; - return ascendingThresholdCheck(100 * ratio, thresholds); - } - - return { - value: RATIO_NA, - status: NA, - }; -}; diff --git a/plugins/kiali-common/src/types/IstioConfigList.test.ts b/plugins/kiali-common/src/types/IstioConfigList.test.ts deleted file mode 100644 index 3e5a2715b1..0000000000 --- a/plugins/kiali-common/src/types/IstioConfigList.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { filterConfigValidations, IstioConfigItem } from './IstioConfigList'; - -const items: IstioConfigItem[] = [ - { - namespace: 'bookinfo', - type: 'service', - name: 'productpage', - validation: { - name: 'Check', - objectType: 'validation', - valid: true, - checks: [], - }, - }, - { - namespace: 'bookinfo', - type: 'service', - name: 'details', - validation: { - name: 'Check', - objectType: 'validation', - valid: false, - checks: [], - }, - }, - { - namespace: 'bookinfo', - type: 'service', - name: 'reviews', - }, -]; -describe('IstioConfigList', () => { - describe('filterConfigValidations', () => { - it('returns filtered correctly the istiovalidations', () => { - const filterByValid = true; - const filterByWarning = false; - const filterByNotValidated = true; - const result = filterConfigValidations(items, { - filterByValid, - filterByWarning, - filterByNotValidated, - }); - expect(result.length).toBe(2); - expect(result[0].name).toBe('productpage'); - expect(result[1].name).toBe('reviews'); - }); - }); -}); diff --git a/plugins/kiali-common/src/types/Namespace.ts b/plugins/kiali-common/src/types/Namespace.ts deleted file mode 100644 index 7eebb309c3..0000000000 --- a/plugins/kiali-common/src/types/Namespace.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ControlPlaneMetricsMap, - IstioConfigList, - Metric, - NamespaceHealth, - TLSStatus, - ValidationStatus, -} from './'; - -export interface Namespace { - name: string; - cluster: string; - labels?: { [key: string]: string }; -} -export type NamespaceInfo = { - name: string; - cluster: string; - outboundPolicyMode?: string; - status?: NamespaceStatus; - tlsStatus?: TLSStatus; - istioConfig?: IstioConfigList; - validations?: ValidationStatus; - metrics?: Metric[]; - errorMetrics?: Metric[]; - labels?: { [key: string]: string }; - controlPlaneMetrics?: ControlPlaneMetricsMap; - nsHealth?: { [key: string]: NamespaceHealth }; -}; - -export type NamespaceStatus = { - inNotReady: string[]; - inError: string[]; - inWarning: string[]; - inSuccess: string[]; - notAvailable: string[]; -}; - -export const namespaceFromString = (namespace: string) => ({ name: namespace }); - -export const namespacesFromString = (namespaces: string) => { - return namespaces.split(',').map(name => namespaceFromString(name)); -}; - -export const namespacesToString = (namespaces: Namespace[]) => - namespaces.map(namespace => namespace.name).join(','); diff --git a/plugins/kiali-common/src/types/Permissions.ts b/plugins/kiali-common/src/types/Permissions.ts deleted file mode 100644 index a52792bade..0000000000 --- a/plugins/kiali-common/src/types/Permissions.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ComputedServerConfig } from './'; - -export interface ResourcePermissions { - create: boolean; - update: boolean; - delete: boolean; -} - -export function canCreate( - serverConfig: ComputedServerConfig, - privs?: ResourcePermissions, -) { - return privs?.create && !serverConfig.deployment.viewOnlyMode; -} - -export function canUpdate( - serverConfig: ComputedServerConfig, - privs?: ResourcePermissions, -) { - return privs?.update && !serverConfig.deployment.viewOnlyMode; -} - -export function canDelete( - serverConfig: ComputedServerConfig, - privs?: ResourcePermissions, -) { - return privs?.delete && !serverConfig.deployment.viewOnlyMode; -} diff --git a/plugins/kiali-common/src/types/index.ts b/plugins/kiali-common/src/types/index.ts deleted file mode 100644 index 5e437c31ea..0000000000 --- a/plugins/kiali-common/src/types/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export * from './Auth'; -export * from './CertsInfo'; -export * from './Common'; -export * from './Config'; -export * from './Dashboards'; -export * from './Fetch'; -export * from './Graph'; -export * from './Health'; -export * from './HealthAnnotation'; -export * from './HealthConfig'; -export * from './HealthStatus'; -export * from './MetricsOptions'; -export * from './Namespace'; -export * from './Overlay'; -export * from './IstioConfigList'; -export * from './IstioObjects'; -export * from './IstioStatus'; -export * from './IstioWizards'; -export * from './Permissions'; -export * from './StatusState'; -export * from './TLSStatus'; -export * from './Metrics'; -export * from './VictoryChartInfo'; diff --git a/plugins/kiali-common/src/utils/RateIntervals.ts b/plugins/kiali-common/src/utils/RateIntervals.ts deleted file mode 100644 index 9cf48c9030..0000000000 --- a/plugins/kiali-common/src/utils/RateIntervals.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ComputedServerConfig } from '../types'; - -export const getName = ( - serverConfig: ComputedServerConfig, - durationSeconds: number, -): string => { - const name = serverConfig.durations[durationSeconds]; - if (name) { - return name; - } - return `${durationSeconds} seconds`; -}; diff --git a/plugins/kiali-common/src/utils/index.ts b/plugins/kiali-common/src/utils/index.ts deleted file mode 100644 index 4ea14cd8f6..0000000000 --- a/plugins/kiali-common/src/utils/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './CancelablePromises'; -export * from './Prometheus'; -export * from './RateIntervals'; -export * from './TimeSeriesUtils'; -export * from './VictoryChartsUtils'; -export * from './VictoryEvents'; diff --git a/plugins/kiali-common/tsconfig.json b/plugins/kiali-common/tsconfig.json deleted file mode 100644 index 4bd72a66db..0000000000 --- a/plugins/kiali-common/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src", "dev", "migrations"], - "exclude": ["node_modules"], - "compilerOptions": { - "outDir": "../../dist-types/plugins/kiali-common", - "rootDir": "." - } -} diff --git a/plugins/kiali-common/turbo.json b/plugins/kiali-common/turbo.json deleted file mode 100644 index 1f3d52c6ec..0000000000 --- a/plugins/kiali-common/turbo.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": ["//"], - "pipeline": { - "tsc": { - "outputs": ["../../dist-types/plugins/kiali-common/**"], - "dependsOn": ["^tsc"] - } - } -} diff --git a/plugins/kiali/CONTRIBUTING.md b/plugins/kiali/CONTRIBUTING.md new file mode 100644 index 0000000000..0e82e6f828 --- /dev/null +++ b/plugins/kiali/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Setting up the development environment for Quay plugin + +In [Backstage plugin terminology](https://backstage.io/docs/local-dev/cli-build-system#package-roles), the Kiali plugin is a front-end plugin. + +You can run the following commands concurrently from the root repository to start a live development session: + +```console +yarn workspace @janus-idp/backstage-plugin-kiali run start +``` diff --git a/plugins/kiali/DEVELOPMENT.md b/plugins/kiali/DEVELOPMENT.md index 43d93d594e..7b8bd57bf5 100644 --- a/plugins/kiali/DEVELOPMENT.md +++ b/plugins/kiali/DEVELOPMENT.md @@ -18,51 +18,13 @@ "@janus-idp/backstage-plugin-kiali": "link:../../plugins/kiali", ``` - or launch - - ```bash - yarn workspace add @janus-idp/backstage-plugin-kiali@* - ``` - - Add to packages/backend/package.json ```yaml title="packages/backend/package.json" '@janus-idp/backstage-plugin-kiali-backend': 'link:../../plugins/kiali-backend' ``` - or launch - - ```bash - yarn workspace backend @janus-idp/backstage-plugin-kiali-backend@* - ``` - -2. If you are going to modify `kiali-common` then you need to link this too. - - - Replace in plugin/kiali/package.json - - ```yaml title="plugin/kiali/package.json" - "@janus-idp/backstage-plugin-kiali-common": "link:../kiali-common", - ``` - - or launch - - ```bash - yarn upgrade @janus-idp/backstage-plugin-kiali-common@link:../kiali-common - ``` - - - Replace in plugin/kiali-backend/package.json - - ```yaml title="plugin/kiali-backend/package.json" - "@janus-idp/backstage-plugin-kiali-common": "link:../kiali-common", - ``` - - or launch - - ```bash - yarn upgrade @janus-idp/backstage-plugin-kiali-common@link:../kiali-common - ``` - -3. Enable the **Kiali** tab on the entity view page using the `packages/app/src/components/catalog/EntityPage.tsx` file: +2. Enable the **Kiali** tab on the entity view page using the `packages/app/src/components/catalog/EntityPage.tsx` file: ```tsx title="packages/app/src/components/catalog/EntityPage.tsx" /* highlight-add-next-line */ @@ -80,12 +42,10 @@ ); ``` -4. Create a file called `kiali.ts` inside `packages/backend/src/plugins/` and add the following: +3. Create a file called `kiali.ts` inside `packages/backend/src/plugins/` and add the following: ```ts /* highlight-add-start */ -import { CatalogClient } from '@backstage/catalog-client'; - import { Router } from 'express'; import { createRouter } from '@janus-idp/backstage-plugin-kiali-backend'; @@ -95,10 +55,8 @@ import { PluginEnvironment } from '../types'; export default async function createPlugin( env: PluginEnvironment, ): Promise { - const catalogApi = new CatalogClient({ discoveryApi: env.discovery }); return await createRouter({ logger: env.logger, - catalogApi, config: env.config, }); } @@ -130,8 +88,6 @@ catalog: kiali: # Required. Kiali endpoint url: ${KIALI_ENDPOINT} - # Required. Kiali authentication. Supported anonymous and token - strategy: ${KIALI_AUTH_STRATEGY} # Optional. Required by token authentication serviceAccountToken: ${KIALI_SERVICE_ACCOUNT_TOKEN} # Optional. defaults false @@ -145,6 +101,17 @@ catalog: # highlight-add-end ``` +7. Add catalog + +Add to locations in `app-config.yaml` + +```yaml +locations: + # Local example data for Kiali plugin + - type: file + target: ../../plugins/kiali/catalog-demo.yaml +``` + ## Configure auth ### Token authentication @@ -158,8 +125,6 @@ catalog: kiali: # Required. Kiali endpoint url: ${KIALI_ENDPOINT} - # Required. Kiali authentication. Supported anonymous and token - strategy: token # Optional. Required by token authentication serviceAccountToken: ${KIALI_SERVICE_ACCOUNT_TOKEN} # Optional. defaults false @@ -172,3 +137,9 @@ catalog: ```bash kubectl create token $KIALI_SERVICE_ACCOUNT ``` + +or if you installed kiali with the operator then execute + +```bash +export KIALI_SERVICE_ACCOUNT_TOKEN=$(kubectl describe secret $(kubectl get secret -n istio-system | grep kiali-service-account-token | cut -d" " -f1) -n istio-system | grep token: | cut -d ":" -f2 | sed 's/^ *//') +``` diff --git a/plugins/kiali/README.md b/plugins/kiali/README.md index c9c6bb095f..b1148c5795 100644 --- a/plugins/kiali/README.md +++ b/plugins/kiali/README.md @@ -78,7 +78,6 @@ The Kiali plugin has the following capabilities: ```console yarn workspace app add @janus-idp/backstage-plugin-kiali - yarn workspace app add @janus-idp/backstage-plugin-kiali-backend ``` 2. Enable the **Kiali** tab on the entity view page using the `packages/app/src/components/catalog/EntityPage.tsx` file: @@ -99,48 +98,7 @@ The Kiali plugin has the following capabilities: ); ``` -3. Create a file called `kiali.ts` inside `packages/backend/src/plugins/` and add the following: - -```ts -/* highlight-add-start */ -import { CatalogClient } from '@backstage/catalog-client'; - -import { Router } from 'express'; - -import { createRouter } from '@janus-idp/backstage-plugin-kiali-backend'; - -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const catalogApi = new CatalogClient({ discoveryApi: env.discovery }); - return await createRouter({ - logger: env.logger, - catalogApi, - config: env.config, - }); -} -/* highlight-add-end */ -``` - -4. import the plugin to `packages/backend/src/index.ts`. There are three lines of code you'll need to add, and they should be added near similar code in your existing Backstage backend. - -```typescript title="packages/backend/src/index.ts" -// .. -/* highlight-add-next-line */ -import kiali from './plugins/kiali'; - -async function main() { - // ... - /* highlight-add-next-line */ - const kialiEnv = useHotMemoize(module, () => createEnv('kiali')); - // ... - /* highlight-add-next-line */ - apiRouter.use('/kiali', await kiali(kialiEnv)); -``` - -5. Configure you `app-config.yaml` with kiali configuration +3. Configure you `app-config.yaml` with kiali configuration ```yaml catalog: @@ -149,8 +107,6 @@ catalog: kiali: # Required. Kiali endpoint url: ${KIALI_ENDPOINT} - # Required. Kiali authentication. Supported anonymous and token - strategy: ${KIALI_AUTH_STRATEGY} # Optional. Required by token authentication serviceAccountToken: ${KIALI_SERVICE_ACCOUNT_TOKEN} # Optional. defaults false @@ -172,9 +128,8 @@ Authentication methods: The following table describes the parameters that you can configure to enable the plugin under `catalog.providers.keycloakOrg.` object in the `app-config.yaml` file: | Name | Description | Default Value | Required | -| --------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------- | --------------------------------------- | -| `url` | Location of the kIALI server, such as `https://localhost:4000` | "" | Yes | -| `strategy` | Authentication strategy. [Methods](https://kiali.io/docs/configuration/authentication/) | `anonymous` | Yes | +| --------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------- | --------------------------------------- | --- | +| `url` | Location of the kIALI server, such as `https://localhost:4000` | "" | Yes | | | `serviceAccountToken` | Service Account Token which is used for querying data from Kiali | "" | Yes if using token based authentication | | `skipTLSVerify` | Skip TLS certificate verification presented by the API server | false | No | | `caData` | Base64-encoded certificate authority bundle in PEM format | "" | No | diff --git a/plugins/kiali/catalog-demo.yaml b/plugins/kiali/catalog-demo.yaml new file mode 100644 index 0000000000..cc5ab01117 --- /dev/null +++ b/plugins/kiali/catalog-demo.yaml @@ -0,0 +1,16 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: istio + description: An example of a Kiali demo istio-system. + tags: + - istio + - kiali + - core + - servicemesh + annotations: + 'backstage.io/kubernetes-namespace': istio-system,bookinfo +spec: + type: service + lifecycle: production + owner: user:guest diff --git a/plugins/kiali/config.d.ts b/plugins/kiali/config.d.ts new file mode 100644 index 0000000000..24bd3590c7 --- /dev/null +++ b/plugins/kiali/config.d.ts @@ -0,0 +1,10 @@ +export interface Config { + /** Configurations for the Kiali plugin */ + kiali?: { + /** + * The token secret for the Kiali instance. + * @visibility frontend + */ + token?: string; + }; +} diff --git a/plugins/kiali/dev/__fixtures__/1-overview.json b/plugins/kiali/dev/__fixtures__/1-overview.json deleted file mode 100644 index c84e5c5862..0000000000 --- a/plugins/kiali/dev/__fixtures__/1-overview.json +++ /dev/null @@ -1,333 +0,0 @@ -{ - "namespaces": [ - { - "name": "bookinfo", - "cluster": "Kubernetes", - "isAmbient": false, - "labels": { - "istio-injection": "enabled", - "kubernetes.io/metadata.name": "bookinfo", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": { - "openshift.io/description": "", - "openshift.io/display-name": "", - "openshift.io/requester": "kubeadmin", - "openshift.io/sa.scc.mcs": "s0:c26,c10", - "openshift.io/sa.scc.supplemental-groups": "1000670000/10000", - "openshift.io/sa.scc.uid-range": "1000670000/10000" - }, - "tlsStatus": { - "status": "MTLS_NOT_ENABLED", - "autoMTLSEnabled": true, - "minTLS": "" - }, - "validations": { "errors": 0, "objectCount": 2, "warnings": 0 }, - "metrics": [ - { - "labels": {}, - "datapoints": [ - [1693301460, "3.644444444444444"], - [1693301520, "3.866711113086507"], - [1693301580, "3.822222222222222"], - [1693301640, "3.666666666666666"], - [1693301700, "3.555347210480139"], - [1693301760, "3.733333333333333"], - [1693301820, "3.7110933341234214"], - [1693301880, "3.488888888888888"], - [1693301940, "3.6889170389137043"], - [1693302000, "3.6888888888888887"], - [1693302060, "3.688644504182928"] - ], - "name": "request_count" - } - ], - "errorMetrics": [ - { - "labels": {}, - "datapoints": [ - [1693301460, "0"], - [1693301520, "0"], - [1693301580, "0"], - [1693301640, "0"], - [1693301700, "0"], - [1693301760, "0"], - [1693301820, "0"], - [1693301880, "0"], - [1693301940, "0.02222222222222222"], - [1693302000, "0"], - [1693302060, "0"] - ], - "name": "request_error_count" - } - ], - "nsHealth": { - "details": { - "workloadStatuses": [ - { - "name": "details-v1", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - } - ], - "requests": { - "inbound": { "http": { "200": 0.9999999999999999 } }, - "outbound": {}, - "healthAnnotations": {} - } - }, - "kiali-traffic-generator": { - "workloadStatuses": [ - { - "name": "kiali-traffic-generator", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "productpage": { - "workloadStatuses": [ - { - "name": "productpage-v1", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - } - ], - "requests": { - "inbound": { "http": { "200": 0.9999999999999999 } }, - "outbound": { "http": { "200": 2 } }, - "healthAnnotations": {} - } - }, - "ratings": { - "workloadStatuses": [ - { - "name": "ratings-v1", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - } - ], - "requests": { - "inbound": { - "http": { - "200": 0.6615384615384614, - "503": 0.0017094017094017092 - } - }, - "outbound": {}, - "healthAnnotations": {} - } - }, - "reviews": { - "workloadStatuses": [ - { - "name": "reviews-v1", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - }, - { - "name": "reviews-v2", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - }, - { - "name": "reviews-v3", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": 1 - } - ], - "requests": { - "inbound": { "http": { "200": 1.0017094017094017 } }, - "outbound": { "http": { "200": 0.6615384615384614 } }, - "healthAnnotations": {} - } - } - } - }, - { - "name": "istio-system", - "cluster": "Kubernetes", - "isAmbient": false, - "labels": { - "kubernetes.io/metadata.name": "istio-system", - "pod-security.kubernetes.io/audit": "baseline", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "baseline", - "pod-security.kubernetes.io/warn-version": "v1.24", - "topology.istio.io/network": "" - }, - "annotations": { - "openshift.io/description": "", - "openshift.io/display-name": "", - "openshift.io/requester": "kubeadmin", - "openshift.io/sa.scc.mcs": "s0:c26,c0", - "openshift.io/sa.scc.supplemental-groups": "1000650000/10000", - "openshift.io/sa.scc.uid-range": "1000650000/10000" - }, - "tlsStatus": { - "status": "MTLS_NOT_ENABLED", - "autoMTLSEnabled": true, - "minTLS": "" - }, - "validations": { "errors": 0, "objectCount": 0, "warnings": 0 }, - "nsHealth": { - "grafana": { - "workloadStatuses": [ - { - "name": "grafana", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "istio-egressgateway": { - "workloadStatuses": [ - { - "name": "istio-egressgateway", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "istio-ingressgateway": { - "workloadStatuses": [ - { - "name": "istio-ingressgateway", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "istiod": { - "workloadStatuses": [ - { - "name": "istiod", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "jaeger": { - "workloadStatuses": [ - { - "name": "jaeger", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "kiali": { - "workloadStatuses": [ - { - "name": "kiali", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - }, - "prometheus": { - "workloadStatuses": [ - { - "name": "prometheus", - "desiredReplicas": 1, - "currentReplicas": 1, - "availableReplicas": 1, - "syncedProxies": -1 - } - ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } - } - }, - "metrics": null, - "errorMetrics": null, - "controlPlaneMetrics": { - "istiod_proxy_time": [ - { - "labels": {}, - "datapoints": [ - [1693301460, "NaN"], - [1693301520, "NaN"], - [1693301580, "NaN"], - [1693301640, "NaN"], - [1693301700, "NaN"], - [1693301760, "NaN"], - [1693301820, "NaN"], - [1693301880, "NaN"], - [1693301940, "NaN"], - [1693302000, "NaN"], - [1693302060, "NaN"] - ], - "stat": "avg", - "name": "pilot_proxy_convergence_time" - } - ], - "istiod_cpu": [ - { - "labels": {}, - "datapoints": [ - [1693301460, "0.0011111111111110873"], - [1693301520, "0.0013333333333333047"], - [1693301580, "0.0011111111111111267"], - [1693301640, "0.0013333333333333441"], - [1693301700, "0.004444444444444428"], - [1693301760, "0.0013333333333333047"], - [1693301820, "0.0013333333333333047"], - [1693301880, "0.0013333333333333047"], - [1693301940, "0.0008888888888888698"], - [1693302000, "0.0015555555555555618"], - [1693302060, "0.0011107902161597918"] - ], - "name": "process_cpu_seconds_total" - } - ] - } - } - ], - "istiodResourceThresholds": { "memory": 0, "cpu": 0 }, - "outboundTraffic": { "mode": "ALLOW_ANY" }, - "canaryUpgrade": { - "currentVersion": "", - "upgradeVersion": "", - "migratedNamespaces": [], - "pendingNamespaces": [] - }, - "istioStatus": [ - { "name": "istiod-69744664c5-wtv8x", "status": "Healthy", "is_core": true } - ] -} diff --git a/plugins/kiali/dev/__fixtures__/config.json b/plugins/kiali/dev/__fixtures__/config.json deleted file mode 100644 index 48abef325b..0000000000 --- a/plugins/kiali/dev/__fixtures__/config.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "server": { - "accessibleNamespaces": ["**"], - "authStrategy": "anonymous", - "clusters": { - "Kubernetes": { - "apiEndpoint": "https://10.217.4.1:443", - "isKialiHome": true, - "kialiInstances": [ - { - "namespace": "istio-system", - "operatorResource": "kiali-operator/kiali", - "serviceName": "kiali", - "url": "https://kiali-istio-system.apps-crc.testing", - "version": "dev" - } - ], - "name": "Kubernetes", - "network": "", - "secretName": "" - } - }, - "deployment": {}, - "healthConfig": { - "rate": [ - { - "tolerance": [ - { - "code": "5XX", - "degraded": 0, - "failure": 10, - "protocol": "http", - "direction": ".*" - }, - { - "code": "4XX", - "degraded": 10, - "failure": 20, - "protocol": "http", - "direction": ".*" - }, - { - "code": "^[1-9]$|^1[0-6]$", - "degraded": 0, - "failure": 10, - "protocol": "grpc", - "direction": ".*" - }, - { - "code": "^-$", - "degraded": 0, - "failure": 10, - "protocol": "http|grpc", - "direction": ".*" - } - ] - }, - { - "tolerance": [ - { - "code": "5XX", - "degraded": 0, - "failure": 10, - "protocol": "http", - "direction": ".*" - }, - { - "code": "4XX", - "degraded": 10, - "failure": 20, - "protocol": "http", - "direction": ".*" - }, - { - "code": "^[1-9]$|^1[0-6]$", - "degraded": 0, - "failure": 10, - "protocol": "grpc", - "direction": ".*" - }, - { - "code": "^-$", - "degraded": 0, - "failure": 10, - "protocol": "http|grpc", - "direction": ".*" - } - ] - } - ] - }, - "istioAnnotations": { - "istioInjectionAnnotation": "sidecar.istio.io/inject" - }, - "istioCanaryRevision": {}, - "istioConfigMap": "istio", - "istioIdentityDomain": "svc.cluster.local", - "istioLabels": { - "appLabelName": "app", - "injectionLabelName": "istio-injection", - "injectionLabelRev": "istio.io/rev", - "versionLabelName": "version" - }, - "istioNamespace": "istio-system", - "istioStatusEnabled": true, - "kialiFeatureFlags": { - "certificatesInformationIndicators": { - "enabled": true, - "secrets": ["cacerts", "istio-ca-secret"] - }, - "clustering": { "enable_exec_provider": false }, - "istioAnnotationAction": true, - "istioInjectionAction": true, - "istioUpgradeAction": false, - "uiDefaults": { - "graph": { - "findOptions": [ - { - "description": "Find: slow edges (> 1s)", - "expression": "rt > 1000" - }, - { - "description": "Find: unhealthy nodes", - "expression": "! healthy" - }, - { - "description": "Find: unknown nodes", - "expression": "name = unknown" - }, - { - "description": "Find: nodes with the 2 top rankings", - "expression": "rank <= 2" - } - ], - "hideOptions": [ - { "description": "Hide: healthy nodes", "expression": "healthy" }, - { - "description": "Hide: unknown nodes", - "expression": "name = unknown" - }, - { - "description": "Hide: nodes ranked lower than the 2 top rankings", - "expression": "rank > 2" - } - ], - "impl": "cy", - "settings": { - "fontLabel": 13, - "minFontBadge": 7, - "minFontLabel": 10 - }, - "traffic": { "grpc": "requests", "http": "requests", "tcp": "sent" } - }, - "list": { - "includeHealth": true, - "includeIstioResources": true, - "includeValidations": true, - "showIncludeToggles": false - }, - "metricsPerRefresh": "1m", - "metricsInbound": {}, - "metricsOutbound": {}, - "refreshInterval": "60s" - }, - "validations": { - "ignore": ["KIA1301"], - "SkipWildcardGatewayHosts": false - } - }, - "logLevel": "trace", - "prometheus": { - "globalScrapeInterval": 15, - "storageTsdbRetention": 1296000 - } - }, - "kialiConsole": "https://kiali-istio-system.apps-crc.testing/", - "meshTLSStatus": { - "status": "MTLS_NOT_ENABLED", - "autoMTLSEnabled": true, - "minTLS": "N/A" - }, - "username": "anonymous", - "istioCerts": [ - { - "secretName": "istio-ca-secret", - "secretNamespace": "istio-system", - "dnsNames": null, - "issuer": "O=cluster.local", - "notBefore": "2023-08-28T06:39:18Z", - "notAfter": "2033-08-25T06:39:18Z", - "error": "", - "accessible": true - } - ] -} diff --git a/plugins/kiali/dev/__fixtures__/general/auth_info_anonymous.json b/plugins/kiali/dev/__fixtures__/general/auth_info_anonymous.json new file mode 100644 index 0000000000..b66d1554cf --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/auth_info_anonymous.json @@ -0,0 +1 @@ +{ "strategy": "anonymous", "sessionInfo": {} } diff --git a/plugins/kiali-backend/__fixtures__/data/config/anonymous_config.json b/plugins/kiali/dev/__fixtures__/general/config.json similarity index 86% rename from plugins/kiali-backend/__fixtures__/data/config/anonymous_config.json rename to plugins/kiali/dev/__fixtures__/general/config.json index ad45c6964d..48d358f71d 100644 --- a/plugins/kiali-backend/__fixtures__/data/config/anonymous_config.json +++ b/plugins/kiali/dev/__fixtures__/general/config.json @@ -1,8 +1,32 @@ { "accessibleNamespaces": ["**"], "authStrategy": "anonymous", - "clusterInfo": {}, + "ambientEnabled": true, + "clusters": { + "Kubernetes": { + "apiEndpoint": "https://10.217.4.1:443", + "isKialiHome": true, + "kialiInstances": [ + { + "namespace": "istio-system", + "operatorResource": "kiali-operator/kiali", + "serviceName": "kiali", + "url": "https://kiali-istio-system.apps-crc.testing", + "version": "dev" + } + ], + "name": "Kubernetes", + "network": "", + "secretName": "" + } + }, "deployment": {}, + "gatewayAPIClasses": [ + { + "name": "Istio", + "className": "istio" + } + ], "healthConfig": { "rate": [ { @@ -75,16 +99,16 @@ "istioInjectionAnnotation": "sidecar.istio.io/inject" }, "istioCanaryRevision": {}, - "istioStatusEnabled": true, + "istioConfigMap": "istio", "istioIdentityDomain": "svc.cluster.local", - "istioNamespace": "istio-system", "istioLabels": { "appLabelName": "app", "injectionLabelName": "istio-injection", "injectionLabelRev": "istio.io/rev", "versionLabelName": "version" }, - "istioConfigMap": "istio", + "istioNamespace": "istio-system", + "istioStatusEnabled": true, "kialiFeatureFlags": { "certificatesInformationIndicators": { "enabled": true, @@ -130,6 +154,7 @@ "expression": "rank \u003e 2" } ], + "impl": "cy", "settings": { "fontLabel": 13, "minFontBadge": 7, @@ -153,7 +178,7 @@ "refreshInterval": "60s" }, "validations": { - "ignore": ["KIA1301"], + "ignore": ["KIA1201"], "SkipWildcardGatewayHosts": false } }, diff --git a/plugins/kiali-backend/__fixtures__/data/config/istio_certs.json b/plugins/kiali/dev/__fixtures__/general/istioCertsInfo.json similarity index 69% rename from plugins/kiali-backend/__fixtures__/data/config/istio_certs.json rename to plugins/kiali/dev/__fixtures__/general/istioCertsInfo.json index 55fc76c280..eeda6f935b 100644 --- a/plugins/kiali-backend/__fixtures__/data/config/istio_certs.json +++ b/plugins/kiali/dev/__fixtures__/general/istioCertsInfo.json @@ -4,8 +4,8 @@ "secretNamespace": "istio-system", "dnsNames": null, "issuer": "O=cluster.local", - "notBefore": "2023-07-10T10:43:24Z", - "notAfter": "2033-07-07T10:43:24Z", + "notBefore": "2023-10-09T07:01:47Z", + "notAfter": "2033-10-06T07:01:47Z", "error": "", "accessible": true } diff --git a/plugins/kiali/dev/__fixtures__/general/istioConfig.json b/plugins/kiali/dev/__fixtures__/general/istioConfig.json new file mode 100644 index 0000000000..bdc9f0bd98 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/istioConfig.json @@ -0,0 +1,375 @@ +{ + "bookinfo": { + "namespace": { + "name": "bookinfo", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [ + { + "kind": "Gateway", + "apiVersion": "networking.istio.io/v1alpha3", + "metadata": { + "name": "bookinfo-gateway", + "namespace": "bookinfo", + "resourceVersion": "30111", + "creationTimestamp": "2023-10-09T07:02:07Z", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"networking.istio.io/v1alpha3\",\"kind\":\"Gateway\",\"metadata\":{\"annotations\":{},\"name\":\"bookinfo-gateway\",\"namespace\":\"bookinfo\"},\"spec\":{\"selector\":{\"istio\":\"ingressgateway\"},\"servers\":[{\"hosts\":[\"*\"],\"port\":{\"name\":\"http\",\"number\":80,\"protocol\":\"HTTP\"}}]}}\n" + } + }, + "spec": { + "servers": [ + { + "port": { + "number": 80, + "protocol": "HTTP", + "name": "http" + }, + "hosts": ["*"] + } + ], + "selector": { + "istio": "ingressgateway" + } + }, + "status": {} + } + ], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [ + { + "kind": "VirtualService", + "apiVersion": "networking.istio.io/v1alpha3", + "metadata": { + "name": "bookinfo", + "namespace": "bookinfo", + "resourceVersion": "30205", + "creationTimestamp": "2023-10-09T07:02:07Z", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"networking.istio.io/v1alpha3\",\"kind\":\"VirtualService\",\"metadata\":{\"annotations\":{},\"name\":\"bookinfo\",\"namespace\":\"bookinfo\"},\"spec\":{\"gateways\":[\"bookinfo-gateway\"],\"hosts\":[\"*\"],\"http\":[{\"match\":[{\"uri\":{\"exact\":\"/productpage\"}},{\"uri\":{\"prefix\":\"/static\"}},{\"uri\":{\"exact\":\"/login\"}},{\"uri\":{\"exact\":\"/logout\"}},{\"uri\":{\"prefix\":\"/api/v1/products\"}}],\"route\":[{\"destination\":{\"host\":\"productpage\",\"port\":{\"number\":9080}}}]}]}}\n" + } + }, + "spec": { + "hosts": ["istio-ingressgateway-istio-system.apps-crc.testing"], + "gateways": ["bookinfo-gateway"], + "http": [ + { + "match": [ + { + "uri": { + "exact": "/productpage" + } + }, + { + "uri": { + "prefix": "/static" + } + }, + { + "uri": { + "exact": "/login" + } + }, + { + "uri": { + "exact": "/logout" + } + }, + { + "uri": { + "prefix": "/api/v1/products" + } + } + ], + "route": [ + { + "destination": { + "host": "productpage", + "port": { + "number": 9080 + } + } + } + ] + } + ] + }, + "status": {} + } + ], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "default": { + "namespace": { + "name": "default", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "hostpath-provisioner": { + "namespace": { + "name": "hostpath-provisioner", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "istio-system": { + "namespace": { + "name": "istio-system", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "kiali": { + "namespace": { + "name": "kiali", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "travel-agency": { + "namespace": { + "name": "travel-agency", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [ + { + "kind": "DestinationRule", + "apiVersion": "networking.istio.io/v1alpha3", + "metadata": { + "name": "travels", + "namespace": "travel-agency", + "resourceVersion": "79940", + "creationTimestamp": "2023-10-09T10:34:13Z", + "labels": { + "kiali_wizard": "request_routing" + } + }, + "spec": { + "host": "travels.travel-agency.svc.cluster.local", + "subsets": [ + { + "name": "v1", + "labels": { + "version": "v1" + } + }, + { + "name": "v2", + "labels": { + "version": "v2" + } + }, + { + "name": "v3", + "labels": { + "version": "v3" + } + } + ] + }, + "status": {} + } + ], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [ + { + "kind": "VirtualService", + "apiVersion": "networking.istio.io/v1alpha3", + "metadata": { + "name": "travels", + "namespace": "travel-agency", + "resourceVersion": "79939", + "creationTimestamp": "2023-10-09T10:34:13Z", + "labels": { + "kiali_wizard": "request_routing" + } + }, + "spec": { + "hosts": ["travels.travel-agency.svc.cluster.local"], + "http": [ + { + "route": [ + { + "destination": { + "host": "travels.travel-agency.svc.cluster.local", + "subset": "v1" + }, + "weight": 52 + }, + { + "destination": { + "host": "travels.travel-agency.svc.cluster.local", + "subset": "v2" + }, + "weight": 24 + }, + { + "destination": { + "host": "travels.travel-agency.svc.cluster.local", + "subset": "v3" + }, + "weight": 24 + } + ] + } + ] + }, + "status": {} + } + ], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "travel-control": { + "namespace": { + "name": "travel-control", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + }, + "travel-portal": { + "namespace": { + "name": "travel-portal", + "cluster": "", + "isAmbient": false, + "labels": null, + "annotations": null + }, + "destinationRules": [], + "envoyFilters": [], + "gateways": [], + "serviceEntries": [], + "sidecars": [], + "virtualServices": [], + "workloadEntries": [], + "workloadGroups": [], + "wasmPlugins": [], + "telemetries": [], + "k8sGateways": [], + "k8sHTTPRoutes": [], + "authorizationPolicies": [], + "peerAuthentications": [], + "requestAuthentications": [], + "validations": {} + } +} diff --git a/plugins/kiali/dev/__fixtures__/general/istioStatus.json b/plugins/kiali/dev/__fixtures__/general/istioStatus.json new file mode 100644 index 0000000000..accf21fb5c --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/istioStatus.json @@ -0,0 +1,7 @@ +[ + { "name": "istiod-7548d4ff85-fj8tb", "status": "Healthy", "is_core": true }, + { "name": "istiod-2", "status": "NotFound", "is_core": true }, + { "name": "istiod-3", "status": "Unhealthy", "is_core": true }, + { "name": "istiod-4", "status": "Unreachable", "is_core": true }, + { "name": "istiod-5", "status": "NotReady", "is_core": true } +] diff --git a/plugins/kiali/dev/__fixtures__/general/istioValidations.json b/plugins/kiali/dev/__fixtures__/general/istioValidations.json new file mode 100644 index 0000000000..2e03262db2 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/istioValidations.json @@ -0,0 +1,44 @@ +{ + "Kubernetes": { + "bookinfo": { + "errors": 1, + "objectCount": 2, + "warnings": 1 + }, + "default": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + }, + "hostpath-provisioner": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + }, + "istio-system": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + }, + "kiali": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + }, + "travel-agency": { + "errors": 0, + "objectCount": 2, + "warnings": 0 + }, + "travel-control": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + }, + "travel-portal": { + "errors": 0, + "objectCount": 0, + "warnings": 0 + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/general/meshCanaryStatus.json b/plugins/kiali/dev/__fixtures__/general/meshCanaryStatus.json new file mode 100644 index 0000000000..013e5deacf --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/meshCanaryStatus.json @@ -0,0 +1,6 @@ +{ + "currentVersion": "1.3", + "upgradeVersion": "1.4", + "migratedNamespaces": ["bookinfo"], + "pendingNamespaces": ["travel-agency"] +} diff --git a/plugins/kiali/dev/__fixtures__/general/meshIstioResurceThresholds.json b/plugins/kiali/dev/__fixtures__/general/meshIstioResurceThresholds.json new file mode 100644 index 0000000000..87399806fe --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/meshIstioResurceThresholds.json @@ -0,0 +1 @@ +{ "memory": 64, "cpu": 8 } diff --git a/plugins/kiali-backend/__fixtures__/data/config/mesh_tls.json b/plugins/kiali/dev/__fixtures__/general/meshTls.json similarity index 100% rename from plugins/kiali-backend/__fixtures__/data/config/mesh_tls.json rename to plugins/kiali/dev/__fixtures__/general/meshTls.json diff --git a/plugins/kiali/dev/__fixtures__/general/namespaces.json b/plugins/kiali/dev/__fixtures__/general/namespaces.json new file mode 100644 index 0000000000..a1615edc99 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/namespaces.json @@ -0,0 +1,146 @@ +[ + { + "name": "bookinfo", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "istio-injection": "enabled", + "kubernetes.io/metadata.name": "bookinfo", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "openshift.io/description": "", + "openshift.io/display-name": "", + "openshift.io/requester": "kubeadmin", + "openshift.io/sa.scc.mcs": "s0:c26,c10", + "openshift.io/sa.scc.supplemental-groups": "1000670000/10000", + "openshift.io/sa.scc.uid-range": "1000670000/10000" + } + }, + { + "name": "default", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "kubernetes.io/metadata.name": "default" + }, + "annotations": { + "openshift.io/sa.scc.mcs": "s0:c1,c0", + "openshift.io/sa.scc.supplemental-groups": "1000000000/10000", + "openshift.io/sa.scc.uid-range": "1000000000/10000" + } + }, + { + "name": "hostpath-provisioner", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "kubernetes.io/metadata.name": "hostpath-provisioner", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Namespace\",\"metadata\":{\"annotations\":{},\"name\":\"hostpath-provisioner\"}}\n", + "openshift.io/sa.scc.mcs": "s0:c25,c20", + "openshift.io/sa.scc.supplemental-groups": "1000640000/10000", + "openshift.io/sa.scc.uid-range": "1000640000/10000" + } + }, + { + "name": "istio-system", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "kubernetes.io/metadata.name": "istio-system", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24", + "topology.istio.io/network": "" + }, + "annotations": { + "openshift.io/description": "", + "openshift.io/display-name": "", + "openshift.io/requester": "kubeadmin", + "openshift.io/sa.scc.mcs": "s0:c26,c0", + "openshift.io/sa.scc.supplemental-groups": "1000650000/10000", + "openshift.io/sa.scc.uid-range": "1000650000/10000" + } + }, + { + "name": "kiali", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "kubernetes.io/metadata.name": "kiali", + "pod-security.kubernetes.io/audit": "restricted", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "restricted", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "openshift.io/sa.scc.mcs": "s0:c27,c4", + "openshift.io/sa.scc.supplemental-groups": "1000710000/10000", + "openshift.io/sa.scc.uid-range": "1000710000/10000" + } + }, + { + "name": "travel-agency", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "istio-injection": "enabled", + "kubernetes.io/metadata.name": "travel-agency", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "openshift.io/sa.scc.mcs": "s0:c27,c14", + "openshift.io/sa.scc.supplemental-groups": "1000730000/10000", + "openshift.io/sa.scc.uid-range": "1000730000/10000" + } + }, + { + "name": "travel-control", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "istio-injection": "enabled", + "kubernetes.io/metadata.name": "travel-control", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "openshift.io/sa.scc.mcs": "s0:c27,c24", + "openshift.io/sa.scc.supplemental-groups": "1000750000/10000", + "openshift.io/sa.scc.uid-range": "1000750000/10000" + } + }, + { + "name": "travel-portal", + "cluster": "Kubernetes", + "isAmbient": false, + "labels": { + "istio-injection": "enabled", + "kubernetes.io/metadata.name": "travel-portal", + "pod-security.kubernetes.io/audit": "privileged", + "pod-security.kubernetes.io/audit-version": "v1.24", + "pod-security.kubernetes.io/warn": "privileged", + "pod-security.kubernetes.io/warn-version": "v1.24" + }, + "annotations": { + "openshift.io/sa.scc.mcs": "s0:c27,c19", + "openshift.io/sa.scc.supplemental-groups": "1000740000/10000", + "openshift.io/sa.scc.uid-range": "1000740000/10000" + } + } +] diff --git a/plugins/kiali/dev/__fixtures__/general/outbound_traffic_policy_mode.json b/plugins/kiali/dev/__fixtures__/general/outbound_traffic_policy_mode.json new file mode 100644 index 0000000000..b6f01c27d1 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/outbound_traffic_policy_mode.json @@ -0,0 +1 @@ +{ "mode": "ALLOW_ANY" } diff --git a/plugins/kiali/dev/__fixtures__/general/status.json b/plugins/kiali/dev/__fixtures__/general/status.json new file mode 100644 index 0000000000..56bb026b74 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/general/status.json @@ -0,0 +1,35 @@ +{ + "status": { + "Kiali commit hash": "c17d0550cfb033900c392ff5813368c1185954f1", + "Kiali container version": "v1.74.0-SNAPSHOT", + "Kiali state": "running", + "Kiali version": "v1.74.0-SNAPSHOT", + "Mesh name": "Istio", + "Mesh version": "1.18.2" + }, + "externalServices": [ + { + "name": "Istio", + "version": "1.18.2" + }, + { + "name": "Prometheus", + "version": "2.41.0" + }, + { + "name": "Kubernetes", + "version": "v1.26.3+b404935" + }, + { + "name": "Grafana" + }, + { + "name": "Jaeger" + } + ], + "warningMessages": [], + "istioEnvironment": { + "isMaistra": false, + "istioAPIEnabled": true + } +} diff --git a/plugins/kiali/dev/__fixtures__/index.ts b/plugins/kiali/dev/__fixtures__/index.ts new file mode 100644 index 0000000000..1d64a37ba2 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/index.ts @@ -0,0 +1,114 @@ +/* Config Data */ +import anonymousAuth from './general/auth_info_anonymous.json'; +import configData from './general/config.json'; +import istioCertsInfo from './general/istioCertsInfo.json'; +import istioConfig from './general/istioConfig.json'; +import istioStatus from './general/istioStatus.json'; +import istioValidations from './general/istioValidations.json'; +import meshCanaryStatus from './general/meshCanaryStatus.json'; +import meshIstioResourceThresholds from './general/meshIstioResurceThresholds.json'; +import meshTls from './general/meshTls.json'; +import namespacesData from './general/namespaces.json'; +import outboundTrafficPolicy from './general/outbound_traffic_policy_mode.json'; +import status from './general/status.json'; +/** health **/ + +import bookinfoHealthApp from './namespaces/bookinfo/health/app.json'; +import bookinfoHealthService from './namespaces/bookinfo/health/service.json'; +import bookinfoHealthWorkload from './namespaces/bookinfo/health/workload.json'; +import bookInfoMetrics from './namespaces/bookinfo/metrics'; +/* bookinfo */ +import bookinfoTls from './namespaces/bookinfo/tls.json'; +/** health **/ +import istioSystemHealthApp from './namespaces/istio-system/health/app.json'; +import istioSystemHealthService from './namespaces/istio-system/health/service.json'; +import istioSystemHealthWorkload from './namespaces/istio-system/health/workload.json'; +import istioSystemMetrics from './namespaces/istio-system/metrics'; +/* istio-system */ +import istioSystemTls from './namespaces/istio-system/tls.json'; +/** health **/ + +import travelAgencyHealthApp from './namespaces/travel-agency/health/app.json'; +import travelAgencyHealthService from './namespaces/travel-agency/health/service.json'; +import travelAgencyHealthWorkload from './namespaces/travel-agency/health/workload.json'; +import travelAgencyMetrics from './namespaces/travel-agency/metrics'; +/* Travel agency */ +import travelAgencyTls from './namespaces/travel-agency/tls.json'; +/** health **/ + +import travelControlHealthApp from './namespaces/travel-control/health/app.json'; +import travelControlHealthService from './namespaces/travel-control/health/service.json'; +import travelControlHealthWorkload from './namespaces/travel-control/health/workload.json'; +import travelControlMetrics from './namespaces/travel-control/metrics'; +/* Travel control */ +import travelControlTls from './namespaces/travel-control/tls.json'; +/** health **/ + +import travelPortalHealthApp from './namespaces/travel-portal/health/app.json'; +import travelPortalHealthService from './namespaces/travel-portal/health/service.json'; +import travelPortalHealthWorkload from './namespaces/travel-portal/health/workload.json'; +import travelPortalMetrics from './namespaces/travel-portal/metrics'; +/* Travel portal */ +import travelPortalTls from './namespaces/travel-portal/tls.json'; + +export const kialiData: { [index: string]: any } = { + auth: anonymousAuth, + config: configData, + namespaces: namespacesData, + meshTls: meshTls, + meshCanaryStatus: meshCanaryStatus, + meshIstioResourceThresholds: meshIstioResourceThresholds, + outboundTrafficPolicy: outboundTrafficPolicy, + istioValidations: istioValidations, + istioConfig: istioConfig, + istioStatus: istioStatus, + istioCertsInfo: istioCertsInfo, + namespacesData: { + 'istio-system': { + tls: istioSystemTls, + metrics: istioSystemMetrics, + health: { + app: istioSystemHealthApp, + service: istioSystemHealthService, + workload: istioSystemHealthWorkload, + }, + }, + bookinfo: { + tls: bookinfoTls, + metrics: bookInfoMetrics, + health: { + app: bookinfoHealthApp, + service: bookinfoHealthService, + workload: bookinfoHealthWorkload, + }, + }, + 'travel-control': { + tls: travelControlTls, + metrics: travelControlMetrics, + health: { + app: travelControlHealthApp, + service: travelControlHealthService, + workload: travelControlHealthWorkload, + }, + }, + 'travel-portal': { + tls: travelPortalTls, + metrics: travelPortalMetrics, + health: { + app: travelPortalHealthApp, + service: travelPortalHealthService, + workload: travelPortalHealthWorkload, + }, + }, + 'travel-agency': { + tls: travelAgencyTls, + metrics: travelAgencyMetrics, + health: { + app: travelAgencyHealthApp, + service: travelAgencyHealthService, + workload: travelAgencyHealthWorkload, + }, + }, + }, + status: status, +}; diff --git a/plugins/kiali/dev/__fixtures__/namespaces.json b/plugins/kiali/dev/__fixtures__/namespaces.json deleted file mode 100644 index fa2feaf36d..0000000000 --- a/plugins/kiali/dev/__fixtures__/namespaces.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "name": "bookinfo", - "cluster": "Kubernetes", - "isAmbient": false, - "labels": { - "istio-injection": "enabled", - "kubernetes.io/metadata.name": "bookinfo", - "pod-security.kubernetes.io/audit": "privileged", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "privileged", - "pod-security.kubernetes.io/warn-version": "v1.24" - }, - "annotations": { - "openshift.io/description": "", - "openshift.io/display-name": "", - "openshift.io/requester": "kubeadmin", - "openshift.io/sa.scc.mcs": "s0:c26,c10", - "openshift.io/sa.scc.supplemental-groups": "1000670000/10000", - "openshift.io/sa.scc.uid-range": "1000670000/10000" - } - }, - { - "name": "istio-system", - "cluster": "Kubernetes", - "isAmbient": false, - "labels": { - "kubernetes.io/metadata.name": "istio-system", - "pod-security.kubernetes.io/audit": "baseline", - "pod-security.kubernetes.io/audit-version": "v1.24", - "pod-security.kubernetes.io/warn": "baseline", - "pod-security.kubernetes.io/warn-version": "v1.24", - "topology.istio.io/network": "" - }, - "annotations": { - "openshift.io/description": "", - "openshift.io/display-name": "", - "openshift.io/requester": "kubeadmin", - "openshift.io/sa.scc.mcs": "s0:c26,c0", - "openshift.io/sa.scc.supplemental-groups": "1000650000/10000", - "openshift.io/sa.scc.uid-range": "1000650000/10000" - } - } -] diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/app.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/app.json new file mode 100644 index 0000000000..9bd9fa35fb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/app.json @@ -0,0 +1,120 @@ +{ + "details": { + "workloadStatuses": [ + { + "name": "details-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + } + ], + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "kiali-traffic-generator": { + "workloadStatuses": [ + { + "name": "kiali-traffic-generator", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "productpage": { + "workloadStatuses": [ + { + "name": "productpage-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + } + ], + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": { + "http": { + "200": 1.9999999999999998 + } + }, + "healthAnnotations": {} + } + }, + "ratings": { + "workloadStatuses": [ + { + "name": "ratings-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + } + ], + "requests": { + "inbound": { + "http": { + "200": 0.711111111111111 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "reviews": { + "workloadStatuses": [ + { + "name": "reviews-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + { + "name": "reviews-v2", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + { + "name": "reviews-v3", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + } + ], + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": { + "http": { + "200": 0.711111111111111 + } + }, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/service.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/service.json new file mode 100644 index 0000000000..6da4f68dbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/service.json @@ -0,0 +1,46 @@ +{ + "details": { + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "productpage": { + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "ratings": { + "requests": { + "inbound": { + "http": { + "200": 0.7555555555555555 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "reviews": { + "requests": { + "inbound": { + "http": { + "200": 1 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/workload.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/workload.json new file mode 100644 index 0000000000..12af3ccd76 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/health/workload.json @@ -0,0 +1,136 @@ +{ + "details-v1": { + "workloadStatus": { + "name": "details-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "kiali-traffic-generator": { + "workloadStatus": { + "name": "kiali-traffic-generator", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "productpage-v1": { + "workloadStatus": { + "name": "productpage-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.9999999999999999 + } + }, + "outbound": { + "http": { + "200": 2 + } + }, + "healthAnnotations": {} + } + }, + "ratings-v1": { + "workloadStatus": { + "name": "ratings-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.6666666666666665 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "reviews-v1": { + "workloadStatus": { + "name": "reviews-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.3333333333333333 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "reviews-v2": { + "workloadStatus": { + "name": "reviews-v2", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.3777777777777777 + } + }, + "outbound": { + "http": { + "200": 0.3777777777777777 + } + }, + "healthAnnotations": {} + } + }, + "reviews-v3": { + "workloadStatus": { + "name": "reviews-v3", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.3555555555555555 + } + }, + "outbound": { + "http": { + "200": 0.2444444444444444 + } + }, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_120.json new file mode 100644 index 0000000000..56ae0787f5 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_120.json @@ -0,0 +1,28 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023800, "3.466666666666667"], + [1697023830, "3.399800039992002"], + [1697023860, "3.2664801740597667"], + [1697023890, "3.533333333333333"], + [1697023920, "3.866666666666667"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023800, "0"], + [1697023830, "0"], + [1697023860, "0"], + [1697023890, "0"], + [1697023920, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_1800.json new file mode 100644 index 0000000000..64876a240a --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_1800.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "3.6605924340032585"], + [1697022360, "3.672727272727272"], + [1697022540, "3.6606060606060598"], + [1697022720, "3.6666666666666656"], + [1697022900, "3.6606060606060598"], + [1697023080, "3.7515151515151506"], + [1697023260, "3.642218224270407"], + [1697023440, "3.6424242424242417"], + [1697023620, "3.7272727272727266"], + [1697023800, "3.636363636363636"], + [1697023980, "3.6363636363636362"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "0"], + [1697022360, "0"], + [1697022540, "0"], + [1697022720, "0"], + [1697022900, "0"], + [1697023080, "0"], + [1697023260, "0"], + [1697023440, "0"], + [1697023620, "0"], + [1697023800, "0"], + [1697023980, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_300.json new file mode 100644 index 0000000000..3b20582b5c --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_300.json @@ -0,0 +1,914 @@ +{ + "grpc_received": null, + "grpc_sent": null, + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "3.577777777777777"], + [1696410810, "3.622222222222221"], + [1696410825, "3.599999999999999"], + [1696410840, "3.5999999999999996"], + [1696410855, "3.5111111111111106"], + [1696410870, "3.4666666666666663"], + [1696410885, "3.5333333333333328"], + [1696410900, "3.5111111111111106"], + [1696410915, "3.555555555555555"], + [1696410930, "3.621711372212227"], + [1696410945, "3.7333333333333325"], + [1696410960, "3.6888888888888887"], + [1696410975, "3.622733594701489"], + [1696410990, "3.5554084154912404"], + [1696411005, "3.577777777777777"], + [1696411020, "3.6222222222222222"], + [1696411035, "3.622397558974338"], + [1696411050, "3.666666666666666"], + [1696411065, "3.0355833333333333"], + [1696411080, "1.9189166666666662"], + [1696411095, "0.5111111111111111"], + [1696411110, "0"], + [1696411125, "0"], + [1696411140, "0"], + [1696411155, "0"], + [1696411170, "0"], + [1696411185, "0"], + [1696411200, "0"], + [1696411215, "0"], + [1696411230, "0"], + [1696411245, "0"], + [1696411260, "0"], + [1696411275, "0"], + [1696411290, "0"], + [1696411305, "0"], + [1696411320, "0"], + [1696411335, "0"], + [1696411350, "0"], + [1696411365, "0"], + [1696411380, "0"], + [1696411395, "0.5777777777777777"], + [1696411410, "1.8667499999999997"], + [1696411425, "3.0945277777777775"], + [1696411440, "3.7223055555555553"], + [1696411455, "3.844444444444444"], + [1696411470, "3.7777777777777772"], + [1696411485, "3.7111111111111104"], + [1696411500, "3.733333333333333"], + [1696411515, "3.688888888888888"], + [1696411530, "3.666666666666666"], + [1696411545, "3.6222222222222222"], + [1696411560, "3.6887333575271"], + [1696411575, "3.733333333333333"], + [1696411590, "3.7777777777777777"], + [1696411605, "3.644600024201295"], + [1696411620, "3.64440000197522"], + [1696411635, "3.577777777777777"], + [1696411650, "3.7111111111111112"], + [1696411665, "3.6667111130865067"], + [1696411680, "3.7555555555555555"], + [1696411695, "3.7111111111111104"], + [1696411710, "3.7111111111111112"], + [1696411725, "3.622133337283775"], + [1696411740, "3.6"], + [1696411755, "3.644444444444444"], + [1696411770, "3.6889777817285703"], + [1696411785, "3.7777284005480873"], + [1696411800, "3.8222222222222215"], + [1696411815, "3.7555555555555546"], + [1696411830, "3.6661952130599573"], + [1696411845, "3.622222222222222"], + [1696411860, "3.666666666666666"], + [1696411875, "3.711600148202358"], + [1696411890, "3.644444444444444"], + [1696411905, "3.644444444444444"], + [1696411920, "3.622222222222222"], + [1696411935, "3.7111111111111112"], + [1696411950, "3.533333333333333"], + [1696411965, "3.533333333333333"], + [1696411980, "3.5331555713566205"], + [1696411995, "3.733333333333333"], + [1696412010, "3.622222222222222"], + [1696412025, "3.622400015803874"], + [1696412040, "3.622222222222222"], + [1696412055, "3.7111111111111112"], + [1696412070, "3.666666666666666"], + [1696412085, "3.666666666666666"], + [1696412100, "3.644311119999408"], + [1696412115, "3.6666666666666665"], + [1696412130, "3.5777185290516256"], + [1696412145, "3.577911120000592"], + [1696412160, "3.599999999999999"], + [1696412175, "3.6222814920183337"], + [1696412190, "3.6444128423152504"], + [1696412205, "3.5111111111111106"], + [1696412220, "3.621333728219557"], + [1696412235, "3.64446419928685"], + [1696412250, "3.688888888888888"], + [1696412265, "3.6008892841262776"], + [1696412280, "3.555555555555555"], + [1696412295, "3.555555555555555"], + [1696412310, "3.5555555555555554"], + [1696412325, "3.644444444444444"], + [1696412340, "3.6888888888888887"], + [1696412355, "3.711111111111111"], + [1696412370, "3.7111111111111112"], + [1696412385, "3.666666666666666"], + [1696412400, "3.555555555555555"], + [1696412415, "3.5333333333333328"], + [1696412430, "3.5555111130863324"], + [1696412445, "3.6666666666666665"], + [1696412460, "3.7111111111111112"], + [1696412475, "3.755600001975396"], + [1696412490, "3.8000000000000003"], + [1696412505, "3.7777777777777772"], + [1696412520, "3.7999999999999994"], + [1696412535, "3.8444444444444446"], + [1696412550, "3.688888888888888"], + [1696412565, "3.644444444444444"], + [1696412580, "3.5555555555555554"], + [1696412595, "3.6"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "7.5645962732919285"], + [1696410810, "7.019631901840506"], + [1696410825, "6.782716049382715"], + [1696410840, "6.498765432098765"], + [1696410855, "6.617088607594929"], + [1696410870, "6.762025316455705"], + [1696410885, "7.118867924528306"], + [1696410900, "7.021698113207562"], + [1696410915, "7.2767080745341595"], + [1696410930, "8.127719843059305"], + [1696410945, "8.459821428571418"], + [1696410960, "8.162195121951203"], + [1696410975, "7.567245817974092"], + [1696410990, "7.407068616263373"], + [1696411005, "8.43788819875777"], + [1696411020, "8.077914110429436"], + [1696411035, "8.08794526421048"], + [1696411050, "7.014759036144582"], + [1696411065, "6.607978236324356"], + [1696411080, "6.655912886939783"], + [1696411095, "2.7642857142856845"], + [1696411110, "NaN"], + [1696411125, "NaN"], + [1696411140, "NaN"], + [1696411155, "NaN"], + [1696411170, "NaN"], + [1696411185, "NaN"], + [1696411200, "NaN"], + [1696411215, "NaN"], + [1696411230, "NaN"], + [1696411245, "NaN"], + [1696411260, "NaN"], + [1696411275, "NaN"], + [1696411290, "NaN"], + [1696411305, "NaN"], + [1696411320, "NaN"], + [1696411335, "NaN"], + [1696411350, "NaN"], + [1696411365, "NaN"], + [1696411380, "NaN"], + [1696411395, "3.8761904761905015"], + [1696411410, "6.452448459725035"], + [1696411425, "5.9819901137018965"], + [1696411440, "6.213269220663208"], + [1696411455, "6.192774566473981"], + [1696411470, "6.533235294117629"], + [1696411485, "6.437425149700596"], + [1696411500, "6.775449101796406"], + [1696411515, "6.425449101796422"], + [1696411530, "7.216265060240964"], + [1696411545, "7.5265243902439165"], + [1696411560, "7.541374007531274"], + [1696411575, "6.799999999999992"], + [1696411590, "6.236011904761901"], + [1696411605, "6.2989043693660625"], + [1696411620, "6.499256365445137"], + [1696411635, "6.71073619631903"], + [1696411650, "7.01190476190475"], + [1696411665, "6.77995647248545"], + [1696411680, "7.267857142857117"], + [1696411695, "7.066265060240969"], + [1696411710, "7.394910179640704"], + [1696411725, "6.774941057751279"], + [1696411740, "6.828703703703713"], + [1696411755, "6.501552795031084"], + [1696411770, "7.861713569971116"], + [1696411785, "8.253635670380952"], + [1696411800, "9.162865497076009"], + [1696411815, "8.881764705882352"], + [1696411830, "8.529558572626945"], + [1696411845, "8.622256097560962"], + [1696411860, "7.677743902439009"], + [1696411875, "7.6418207924871995"], + [1696411890, "6.469090909090909"], + [1696411905, "6.387575757575752"], + [1696411920, "6.352147239263806"], + [1696411935, "6.634226190476188"], + [1696411950, "7.04748427672957"], + [1696411965, "7.104088050314491"], + [1696411980, "7.197714394440217"], + [1696411995, "6.841964285714277"], + [1696412010, "6.800304878048771"], + [1696412025, "6.63219087137394"], + [1696412040, "7.066564417177924"], + [1696412055, "7.158035714285704"], + [1696412070, "7.208928571428565"], + [1696412085, "6.9175757575757615"], + [1696412100, "6.680204190222556"], + [1696412115, "6.60304878048782"], + [1696412130, "7.147589279961249"], + [1696412145, "7.331397269920375"], + [1696412160, "7.555864197530868"], + [1696412175, "7.3379350122773825"], + [1696412190, "7.111091974580902"], + [1696412205, "7.103124999999996"], + [1696412220, "6.397247694068532"], + [1696412235, "6.805354054492854"], + [1696412250, "6.502727272727262"], + [1696412265, "6.650858726071548"], + [1696412280, "6.441509433962276"], + [1696412295, "6.476562500000013"], + [1696412310, "7.179754601226991"], + [1696412325, "6.864329268292677"], + [1696412340, "6.934848484848462"], + [1696412355, "6.306707317073169"], + [1696412370, "6.648181818181811"], + [1696412385, "6.394879518072293"], + [1696412400, "6.37331288343559"], + [1696412415, "6.716250000000013"], + [1696412430, "7.006688978351573"], + [1696412445, "7.872392638036815"], + [1696412460, "7.588757396449705"], + [1696412475, "7.960641995561795"], + [1696412490, "7.024418604651141"], + [1696412505, "6.798214285714275"], + [1696412520, "6.709826589595367"], + [1696412535, "6.770175438596499"], + [1696412550, "6.709281437125758"], + [1696412565, "6.748765432098778"], + [1696412580, "6.945652173913059"], + [1696412595, "6.937037037037037"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "0"], + [1696410810, "0"], + [1696410825, "0"], + [1696410840, "0"], + [1696410855, "0"], + [1696410870, "0"], + [1696410885, "0"], + [1696410900, "0"], + [1696410915, "0"], + [1696410930, "0"], + [1696410945, "0"], + [1696410960, "0"], + [1696410975, "0"], + [1696410990, "0"], + [1696411005, "0"], + [1696411020, "0"], + [1696411035, "0"], + [1696411050, "0"], + [1696411065, "0"], + [1696411080, "0"], + [1696411095, "0"], + [1696411110, "0"], + [1696411125, "0"], + [1696411140, "0"], + [1696411155, "0"], + [1696411170, "0"], + [1696411185, "0"], + [1696411200, "0"], + [1696411215, "0"], + [1696411230, "0"], + [1696411245, "0"], + [1696411260, "0"], + [1696411275, "0"], + [1696411290, "0"], + [1696411305, "0"], + [1696411320, "0"], + [1696411335, "0"], + [1696411350, "0"], + [1696411365, "0"], + [1696411380, "0"], + [1696411395, "0"], + [1696411410, "0"], + [1696411425, "0"], + [1696411440, "0"], + [1696411455, "0"], + [1696411470, "0"], + [1696411485, "0"], + [1696411500, "0"], + [1696411515, "0"], + [1696411530, "0"], + [1696411545, "0"], + [1696411560, "0"], + [1696411575, "0"], + [1696411590, "0"], + [1696411605, "0"], + [1696411620, "0"], + [1696411635, "0"], + [1696411650, "0"], + [1696411665, "0"], + [1696411680, "0"], + [1696411695, "0"], + [1696411710, "0"], + [1696411725, "0"], + [1696411740, "0"], + [1696411755, "0"], + [1696411770, "0"], + [1696411785, "0"], + [1696411800, "0"], + [1696411815, "0"], + [1696411830, "0"], + [1696411845, "0"], + [1696411860, "0"], + [1696411875, "0"], + [1696411890, "0"], + [1696411905, "0"], + [1696411920, "0"], + [1696411935, "0"], + [1696411950, "0"], + [1696411965, "0"], + [1696411980, "0"], + [1696411995, "0"], + [1696412010, "0"], + [1696412025, "0"], + [1696412040, "0"], + [1696412055, "0"], + [1696412070, "0"], + [1696412085, "0"], + [1696412100, "0"], + [1696412115, "0"], + [1696412130, "0"], + [1696412145, "0"], + [1696412160, "0"], + [1696412175, "0"], + [1696412190, "0"], + [1696412205, "0"], + [1696412220, "0"], + [1696412235, "0"], + [1696412250, "0"], + [1696412265, "0"], + [1696412280, "0"], + [1696412295, "0"], + [1696412310, "0"], + [1696412325, "0"], + [1696412340, "0"], + [1696412355, "0"], + [1696412370, "0"], + [1696412385, "0"], + [1696412400, "0"], + [1696412415, "0"], + [1696412430, "0"], + [1696412445, "0"], + [1696412460, "0"], + [1696412475, "0"], + [1696412490, "0"], + [1696412505, "0"], + [1696412520, "0"], + [1696412535, "0"], + [1696412550, "0"], + [1696412565, "0"], + [1696412580, "0"], + [1696412595, "0"] + ], + "name": "request_error_count" + } + ], + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "279.4099378881988"], + [1696410810, "280.33742331288346"], + [1696410825, "279.99999999999994"], + [1696410840, "280.00000000000006"], + [1696410855, "277.9746835443038"], + [1696410870, "277.9746835443038"], + [1696410885, "278.45911949685535"], + [1696410900, "278.5849056603774"], + [1696410915, "279.53416149068323"], + [1696410930, "280.13979544724987"], + [1696410945, "282.79761904761904"], + [1696410960, "281.0365853658537"], + [1696410975, "281.2262147276294"], + [1696410990, "279.4102849337094"], + [1696411005, "279.9068322981367"], + [1696411020, "280.8282208588957"], + [1696411035, "279.9067354332076"], + [1696411050, "281.6867469879518"], + [1696411065, "285.3403241436331"], + [1696411080, "285.0850015565572"], + [1696411095, "325.71428571428567"], + [1696411110, "NaN"], + [1696411125, "NaN"], + [1696411140, "NaN"], + [1696411155, "NaN"], + [1696411170, "NaN"], + [1696411185, "NaN"], + [1696411200, "NaN"], + [1696411215, "NaN"], + [1696411230, "NaN"], + [1696411245, "NaN"], + [1696411260, "NaN"], + [1696411275, "NaN"], + [1696411290, "NaN"], + [1696411305, "NaN"], + [1696411320, "NaN"], + [1696411335, "NaN"], + [1696411350, "NaN"], + [1696411365, "NaN"], + [1696411380, "NaN"], + [1696411395, "324.52380952380946"], + [1696411410, "287.3358068446118"], + [1696411425, "289.0796102189919"], + [1696411440, "286.4486910955459"], + [1696411455, "284.53757225433526"], + [1696411470, "283.52941176470586"], + [1696411485, "282.00598802395206"], + [1696411500, "281.8862275449102"], + [1696411515, "281.76646706586826"], + [1696411530, "281.4457831325301"], + [1696411545, "280.67073170731703"], + [1696411560, "282.60971940114854"], + [1696411575, "282.7245508982036"], + [1696411590, "282.79761904761904"], + [1696411605, "279.6246759409596"], + [1696411620, "280.30628416048376"], + [1696411635, "280.21472392638043"], + [1696411650, "282.3214285714285"], + [1696411665, "281.68534107798405"], + [1696411680, "282.55952380952374"], + [1696411695, "281.92771084337346"], + [1696411710, "282.2455089820359"], + [1696411725, "280.70465809405835"], + [1696411740, "280.37037037037044"], + [1696411755, "280.27950310559004"], + [1696411770, "281.7281009043954"], + [1696411785, "283.1056240344476"], + [1696411800, "283.4795321637427"], + [1696411815, "283.17647058823525"], + [1696411830, "282.0170789560339"], + [1696411845, "280.6707317073171"], + [1696411860, "280.4268292682927"], + [1696411875, "281.3145869262265"], + [1696411890, "280.8787878787879"], + [1696411905, "281"], + [1696411920, "279.9693251533742"], + [1696411935, "282.32142857142856"], + [1696411950, "278.5849056603774"], + [1696411965, "278.58490566037733"], + [1696411980, "278.9356747237607"], + [1696411995, "282.32142857142856"], + [1696412010, "280.67073170731703"], + [1696412025, "278.58673793706083"], + [1696412040, "280.7055214723926"], + [1696412055, "282.9166666666667"], + [1696412070, "283.0357142857143"], + [1696412085, "281.6060606060606"], + [1696412100, "280.5815225148697"], + [1696412115, "280.9146341463414"], + [1696412130, "278.8113150533358"], + [1696412145, "279.0638643627106"], + [1696412160, "280.00000000000006"], + [1696412175, "280.4613205458113"], + [1696412190, "280.58222431127575"], + [1696412205, "278.93749999999994"], + [1696412220, "281.23413451827906"], + [1696412235, "281.886673285088"], + [1696412250, "281.24242424242425"], + [1696412265, "278.59406845764806"], + [1696412280, "278.45911949685535"], + [1696412295, "278.9375"], + [1696412310, "280.2147239263804"], + [1696412325, "280.5487804878049"], + [1696412340, "280.75757575757575"], + [1696412355, "280.3048780487805"], + [1696412370, "280.8787878787878"], + [1696412385, "281.4457831325301"], + [1696412400, "280.0920245398773"], + [1696412415, "279.06250000000006"], + [1696412430, "279.53558422973896"], + [1696412445, "280.58282208588963"], + [1696412460, "282.7514792899408"], + [1696412475, "281.7650686196597"], + [1696412490, "283.7790697674418"], + [1696412505, "282.2023809523809"], + [1696412520, "284.42196531791905"], + [1696412535, "283.59649122807014"], + [1696412550, "282.4850299401198"], + [1696412565, "280.37037037037044"], + [1696412580, "280.03105590062114"], + [1696412595, "279.99999999999994"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "999.6666666666665"], + [1696410810, "1015.4444444444443"], + [1696410825, "1007.9999999999999"], + [1696410840, "1007.9999999999999"], + [1696410855, "975.9999999999999"], + [1696410870, "975.9999999999999"], + [1696410885, "983.8888888888887"], + [1696410900, "984.3333333333333"], + [1696410915, "1000.111111111111"], + [1696410930, "1008.3601541927952"], + [1696410945, "1055.7777777777776"], + [1696410960, "1024.2222222222222"], + [1696410975, "1031.3065986813012"], + [1696410990, "999.6252784577467"], + [1696411005, "1001.4444444444443"], + [1696411020, "1017.2222222222221"], + [1696411035, "1001.4901341155481"], + [1696411050, "1039.1111111111109"], + [1696411065, "891.5379166666665"], + [1696411080, "585.0656944444444"], + [1696411095, "202.66666666666663"], + [1696411110, "0"], + [1696411125, "0"], + [1696411140, "0"], + [1696411155, "0"], + [1696411170, "0"], + [1696411185, "0"], + [1696411200, "0"], + [1696411215, "0"], + [1696411230, "0"], + [1696411245, "0"], + [1696411260, "0"], + [1696411275, "0"], + [1696411290, "0"], + [1696411305, "0"], + [1696411320, "0"], + [1696411335, "0"], + [1696411350, "0"], + [1696411365, "0"], + [1696411380, "0"], + [1696411395, "151.44444444444443"], + [1696411410, "504.4579166666666"], + [1696411425, "849.5969444444444"], + [1696411440, "1059.8840277777776"], + [1696411455, "1093.8888888888887"], + [1696411470, "1071.1111111111109"], + [1696411485, "1046.5555555555554"], + [1696411500, "1046.111111111111"], + [1696411515, "1045.6666666666665"], + [1696411530, "1038.2222222222222"], + [1696411545, "1022.8888888888888"], + [1696411560, "1048.7521151030826"], + [1696411575, "1049.2222222222222"], + [1696411590, "1055.7777777777776"], + [1696411605, "1006.692337326547"], + [1696411620, "1021.5482225481336"], + [1696411635, "1014.9999999999999"], + [1696411650, "1053.9999999999998"], + [1696411665, "1039.1184447703847"], + [1696411680, "1054.8888888888887"], + [1696411695, "1039.9999999999998"], + [1696411710, "1047.4444444444443"], + [1696411725, "1016.7497000133326"], + [1696411740, "1009.3333333333333"], + [1696411755, "1002.7777777777776"], + [1696411770, "1033.0280802603572"], + [1696411785, "1063.2056191287388"], + [1696411800, "1077.2222222222222"], + [1696411815, "1069.7777777777776"], + [1696411830, "1046.4651500995017"], + [1696411845, "1022.8888888888888"], + [1696411860, "1021.9999999999999"], + [1696411875, "1037.8754104717032"], + [1696411890, "1029.8888888888887"], + [1696411905, "1030.3333333333333"], + [1696411920, "1014.111111111111"], + [1696411935, "1054"], + [1696411950, "984.3333333333333"], + [1696411965, "984.3333333333333"], + [1696411980, "991.7217037497902"], + [1696411995, "1054"], + [1696412010, "1022.8888888888887"], + [1696412025, "984.3893383115535"], + [1696412040, "1016.7777777777776"], + [1696412055, "1056.2222222222222"], + [1696412070, "1056.6666666666665"], + [1696412085, "1032.5555555555554"], + [1696412100, "1016.2912176225288"], + [1696412115, "1023.7777777777776"], + [1696412130, "991.3137022554014"], + [1696412145, "992.264284285619"], + [1696412160, "1007.9999999999999"], + [1696412175, "1015.9098506401136"], + [1696412190, "1016.3228552523725"], + [1696412205, "991.7777777777776"], + [1696412220, "1030.941951725159"], + [1696412235, "1046.1194476546061"], + [1696412250, "1031.2222222222222"], + [1696412265, "984.613457833111"], + [1696412280, "983.8888888888887"], + [1696412295, "991.7777777777776"], + [1696412310, "1014.9999999999999"], + [1696412325, "1022.4444444444443"], + [1696412340, "1029.4444444444443"], + [1696412355, "1021.5555555555554"], + [1696412370, "1029.8888888888887"], + [1696412385, "1038.2222222222222"], + [1696412400, "1014.5555555555554"], + [1696412415, "992.2222222222222"], + [1696412430, "1000.1037781036893"], + [1696412445, "1016.3333333333333"], + [1696412460, "1061.8888888888887"], + [1696412475, "1045.6740003259401"], + [1696412490, "1084.6666666666665"], + [1696412505, "1053.5555555555554"], + [1696412520, "1093.4444444444443"], + [1696412535, "1077.6666666666665"], + [1696412550, "1048.3333333333333"], + [1696412565, "1009.3333333333333"], + [1696412580, "1001.8888888888888"], + [1696412595, "1007.9999999999999"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "1741.1180124223602"], + [1696410810, "1730.1226993865032"], + [1696410825, "1732.3148148148143"], + [1696410840, "1737.9320987654323"], + [1696410855, "1755.7278481012659"], + [1696410870, "1736.6139240506334"], + [1696410885, "1734.4025157232707"], + [1696410900, "1740.1257861635222"], + [1696410915, "1720.8695652173915"], + [1696410930, "1731.7680297986026"], + [1696410945, "1698.6607142857142"], + [1696410960, "1746.25"], + [1696410975, "1730.4287021168816"], + [1696410990, "1733.7618095304606"], + [1696411005, "1734.8447204968948"], + [1696411020, "1717.484662576687"], + [1696411035, "1761.7615043666383"], + [1696411050, "1715.2710843373495"], + [1696411065, "1589.3159733643906"], + [1696411080, "1608.4669942204355"], + [1696411095, "413.21428571428567"], + [1696411110, "NaN"], + [1696411125, "NaN"], + [1696411140, "NaN"], + [1696411155, "NaN"], + [1696411170, "NaN"], + [1696411185, "NaN"], + [1696411200, "NaN"], + [1696411215, "NaN"], + [1696411230, "NaN"], + [1696411245, "NaN"], + [1696411260, "NaN"], + [1696411275, "NaN"], + [1696411290, "NaN"], + [1696411305, "NaN"], + [1696411320, "NaN"], + [1696411335, "NaN"], + [1696411350, "NaN"], + [1696411365, "NaN"], + [1696411380, "NaN"], + [1696411395, "421.1904761904761"], + [1696411410, "1518.7230036548897"], + [1696411425, "1486.5537839191707"], + [1696411440, "1606.3605924791486"], + [1696411455, "1686.7630057803472"], + [1696411470, "1712.8529411764707"], + [1696411485, "1726.3473053892217"], + [1696411500, "1738.6826347305391"], + [1696411515, "1712.6946107784431"], + [1696411530, "1715.5722891566263"], + [1696411545, "1707.9573170731705"], + [1696411560, "1713.6845952569065"], + [1696411575, "1720.3592814371257"], + [1696411590, "1717.7678571428576"], + [1696411605, "1744.7478495869427"], + [1696411620, "1713.7690722169127"], + [1696411635, "1741.9631901840492"], + [1696411650, "1717.1130952380952"], + [1696411665, "1728.747379767859"], + [1696411680, "1704.7321428571427"], + [1696411695, "1716.6566265060244"], + [1696411710, "1725.02994011976"], + [1696411725, "1749.1722864139313"], + [1696411740, "1745.2160493827162"], + [1696411755, "1742.2360248447203"], + [1696411770, "1718.0911398883477"], + [1696411785, "1708.4208953752093"], + [1696411800, "1697.719298245614"], + [1696411815, "1718.6176470588232"], + [1696411830, "1719.549525159515"], + [1696411845, "1732.9573170731708"], + [1696411860, "1714.7865853658536"], + [1696411875, "1715.8324995142395"], + [1696411890, "1724.1818181818182"], + [1696411905, "1723.6363636363637"], + [1696411920, "1741.4723926380368"], + [1696411935, "1705.267857142857"], + [1696411950, "1760.6289308176104"], + [1696411965, "1734.2767295597484"], + [1696411980, "1743.7822556900564"], + [1696411995, "1698.8392857142856"], + [1696412010, "1739.4817073170734"], + [1696412025, "1754.401064247901"], + [1696412040, "1735.644171779141"], + [1696412055, "1692.2321428571427"], + [1696412070, "1710.267857142857"], + [1696412085, "1717.6969696969695"], + [1696412100, "1736.5493995475795"], + [1696412115, "1726.4939024390244"], + [1696412130, "1751.554992190156"], + [1696412145, "1744.1711320746856"], + [1696412160, "1726.018518518519"], + [1696412175, "1728.9937953174717"], + [1696412190, "1729.5215082845393"], + [1696412205, "1744.40625"], + [1696412220, "1705.8735608967884"], + [1696412235, "1713.9425847508378"], + [1696412250, "1730.8484848484852"], + [1696412265, "1753.5145842244906"], + [1696412280, "1752.7044025157236"], + [1696412295, "1731.7187500000005"], + [1696412310, "1729.20245398773"], + [1696412325, "1708.2012195121954"], + [1696412340, "1730.5454545454545"], + [1696412355, "1721.3719512195123"], + [1696412370, "1731.151515151515"], + [1696412385, "1709.5481927710841"], + [1696412400, "1741.963190184049"], + [1696412415, "1749.9062500000002"], + [1696412430, "1733.560322153935"], + [1696412445, "1717.300613496933"], + [1696412460, "1690.059171597633"], + [1696412475, "1727.168339198239"], + [1696412490, "1707.5290697674416"], + [1696412505, "1736.3392857142853"], + [1696412520, "1686.300578034682"], + [1696412535, "1709.7660818713448"], + [1696412550, "1730.8982035928145"], + [1696412565, "1751.2654320987658"], + [1696412580, "1740.6832298136644"], + [1696412595, "1713.9197530864194"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1696410795, "6229.333333333332"], + [1696410810, "6266.888888888888"], + [1696410825, "6236.333333333332"], + [1696410840, "6256.555555555555"], + [1696410855, "6164.555555555555"], + [1696410870, "6097.444444444445"], + [1696410885, "6128.222222222222"], + [1696410900, "6148.444444444444"], + [1696410915, "6156.888888888889"], + [1696410930, "6233.480233559637"], + [1696410945, "6341.666666666666"], + [1696410960, "6364.11111111111"], + [1696410975, "6345.790134710828"], + [1696410990, "6202.749952609931"], + [1696411005, "6206.888888888889"], + [1696411020, "6221.11111111111"], + [1696411035, "6303.480916802658"], + [1696411050, "6327.444444444443"], + [1696411065, "4965.773611111112"], + [1696411080, "3300.976388888888"], + [1696411095, "257.1111111111111"], + [1696411110, "0"], + [1696411125, "0"], + [1696411140, "0"], + [1696411155, "0"], + [1696411170, "0"], + [1696411185, "0"], + [1696411200, "0"], + [1696411215, "0"], + [1696411230, "0"], + [1696411245, "0"], + [1696411260, "0"], + [1696411275, "0"], + [1696411290, "0"], + [1696411305, "0"], + [1696411320, "0"], + [1696411335, "0"], + [1696411350, "0"], + [1696411365, "0"], + [1696411380, "0"], + [1696411395, "196.55555555555551"], + [1696411410, "2666.3291666666664"], + [1696411425, "4368.940277777778"], + [1696411440, "5943.668055555556"], + [1696411455, "6484.666666666667"], + [1696411470, "6470.777777777777"], + [1696411485, "6406.666666666667"], + [1696411500, "6452.444444444444"], + [1696411515, "6355.999999999999"], + [1696411530, "6328.555555555555"], + [1696411545, "6224.555555555555"], + [1696411560, "6359.4074106990765"], + [1696411575, "6384.444444444444"], + [1696411590, "6413"], + [1696411605, "6281.363705959198"], + [1696411620, "6245.660010172388"], + [1696411635, "6309.7777777777765"], + [1696411650, "6410.555555555556"], + [1696411665, "6377.2338375532745"], + [1696411680, "6364.333333333333"], + [1696411695, "6332.555555555556"], + [1696411710, "6401.7777777777765"], + [1696411725, "6335.735251272784"], + [1696411740, "6282.7777777777765"], + [1696411755, "6233.333333333332"], + [1696411770, "6299.820238924198"], + [1696411785, "6415.989445617153"], + [1696411800, "6451.333333333332"], + [1696411815, "6492.555555555555"], + [1696411830, "6380.637153645969"], + [1696411845, "6315.666666666666"], + [1696411860, "6249.444444444443"], + [1696411875, "6330.352006243607"], + [1696411890, "6321.999999999999"], + [1696411905, "6319.999999999999"], + [1696411920, "6307.999999999999"], + [1696411935, "6366.333333333333"], + [1696411950, "6220.888888888889"], + [1696411965, "6127.777777777777"], + [1696411980, "6199.80470871725"], + [1696411995, "6342.333333333333"], + [1696412010, "6339.444444444444"], + [1696412025, "6199.19568159145"], + [1696412040, "6286.888888888888"], + [1696412055, "6317.666666666665"], + [1696412070, "6384.999999999999"], + [1696412085, "6298.222222222221"], + [1696412100, "6289.936300542926"], + [1696412115, "6292.11111111111"], + [1696412130, "6227.6542244341745"], + [1696412145, "6201.7299301434905"], + [1696412160, "6213.666666666667"], + [1696412175, "6262.902224593014"], + [1696412190, "6264.660000592539"], + [1696412205, "6202.333333333333"], + [1696412220, "6253.354064860062"], + [1696412235, "6360.672000474116"], + [1696412250, "6346.444444444444"], + [1696412265, "6197.310903611481"], + [1696412280, "6192.888888888889"], + [1696412295, "6157.222222222223"], + [1696412310, "6263.555555555555"], + [1696412325, "6225.444444444444"], + [1696412340, "6345.333333333332"], + [1696412355, "6273.444444444444"], + [1696412370, "6347.555555555555"], + [1696412385, "6306.333333333332"], + [1696412400, "6309.7777777777765"], + [1696412415, "6221.888888888889"], + [1696412430, "6202.21655333837"], + [1696412445, "6220.444444444444"], + [1696412460, "6347.11111111111"], + [1696412475, "6409.790380807049"], + [1696412490, "6526.555555555555"], + [1696412505, "6482.333333333332"], + [1696412520, "6482.888888888889"], + [1696412535, "6497.1111111111095"], + [1696412550, "6423.555555555555"], + [1696412565, "6304.555555555555"], + [1696412580, "6227.7777777777765"], + [1696412595, "6170.11111111111"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": null, + "tcp_opened": null, + "tcp_received": null, + "tcp_sent": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_3600.json new file mode 100644 index 0000000000..9e2895fe2f --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_3600.json @@ -0,0 +1,32 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "2.6002743899782135"], + [1697022000, "3.634782608695652"], + [1697022360, "3.678260869565217"], + [1697022720, "3.6753623188405795"], + [1697023080, "3.7101449275362324"], + [1697023440, "3.6318840579710145"], + [1697023800, "3.6811594202898554"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0"], + [1697022000, "0"], + [1697022360, "0"], + [1697022720, "0"], + [1697023080, "0"], + [1697023440, "0"], + [1697023800, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_60.json new file mode 100644 index 0000000000..5a7ba4ce06 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_60.json @@ -0,0 +1,24 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023800, "3.466666666666667"], + [1697023830, "3.399800039992002"], + [1697023860, "3.2664801740597667"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023800, "0"], + [1697023830, "0"], + [1697023860, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_600.json new file mode 100644 index 0000000000..c53996c283 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/inbound/metrics_inbound_600.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "3.399866684442075"], + [1697023710, "3.7333333333333334"], + [1697023740, "3.9327959496369327"], + [1697023770, "3.6666666666666665"], + [1697023800, "3.466666666666667"], + [1697023830, "3.399800039992002"], + [1697023860, "3.2664801740597667"], + [1697023890, "3.533333333333333"], + [1697023920, "3.866666666666667"], + [1697023950, "3.733333333333334"], + [1697023980, "3.466666666666667"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "0"], + [1697023710, "0"], + [1697023740, "0"], + [1697023770, "0"], + [1697023800, "0"], + [1697023830, "0"], + [1697023860, "0"], + [1697023890, "0"], + [1697023920, "0"], + [1697023950, "0"], + [1697023980, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/index.ts b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/index.ts new file mode 100644 index 0000000000..e975e594e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/index.ts @@ -0,0 +1,37 @@ +/* Inbound Metrics */ + +import inbound60 from './inbound/metrics_inbound_60.json'; +import inbound120 from './inbound/metrics_inbound_120.json'; +import inbound300 from './inbound/metrics_inbound_300.json'; +import inbound600 from './inbound/metrics_inbound_600.json'; +import inbound1800 from './inbound/metrics_inbound_1800.json'; +import inbound3600 from './inbound/metrics_inbound_3600.json'; +/* Outbound Metrics */ + +import outbound60 from './outbound/metrics_outbound_60.json'; +import outbound120 from './outbound/metrics_outbound_120.json'; +import outbound300 from './outbound/metrics_outbound_300.json'; +import outbound600 from './outbound/metrics_outbound_600.json'; +import outbound1800 from './outbound/metrics_outbound_1800.json'; +import outbound3600 from './outbound/metrics_outbound_3600.json'; + +export const bookInfoMetrics = { + inbound: { + 60: inbound60, + 120: inbound120, + 300: inbound300, + 600: inbound600, + 1800: inbound1800, + 3600: inbound3600, + }, + outbound: { + 60: outbound60, + 120: outbound120, + 300: outbound300, + 600: outbound600, + 1800: outbound1800, + 3600: outbound3600, + }, +}; + +export default bookInfoMetrics; diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_120.json new file mode 100644 index 0000000000..3a2f963923 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_120.json @@ -0,0 +1,28 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "2.4"], + [1697023860, "2.2664801740597667"], + [1697023890, "2.533333333333333"], + [1697023920, "2.8666666666666667"], + [1697023950, "2.733333333333334"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0"], + [1697023860, "0"], + [1697023890, "0"], + [1697023920, "0"], + [1697023950, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_1800.json new file mode 100644 index 0000000000..1f6372ba41 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_1800.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "2.660592434003259"], + [1697022360, "2.6727272727272724"], + [1697022540, "2.66060606060606"], + [1697022720, "2.6666666666666665"], + [1697022900, "2.66060606060606"], + [1697023080, "2.751515151515151"], + [1697023260, "2.642218224270407"], + [1697023440, "2.6424242424242417"], + [1697023620, "2.7272727272727266"], + [1697023800, "2.636363636363636"], + [1697023980, "2.6363636363636362"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "0"], + [1697022360, "0"], + [1697022540, "0"], + [1697022720, "0"], + [1697022900, "0"], + [1697023080, "0"], + [1697023260, "0"], + [1697023440, "0"], + [1697023620, "0"], + [1697023800, "0"], + [1697023980, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_300.json new file mode 100644 index 0000000000..e5384d2042 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_300.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "2.4"], + [1697023710, "2.7333333333333334"], + [1697023740, "2.9327959496369327"], + [1697023770, "2.6666666666666665"], + [1697023800, "2.466666666666667"], + [1697023830, "2.4"], + [1697023860, "2.2664801740597667"], + [1697023890, "2.533333333333333"], + [1697023920, "2.8666666666666667"], + [1697023950, "2.733333333333334"], + [1697023980, "2.466666666666667"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "0"], + [1697023710, "0"], + [1697023740, "0"], + [1697023770, "0"], + [1697023800, "0"], + [1697023830, "0"], + [1697023860, "0"], + [1697023890, "0"], + [1697023920, "0"], + [1697023950, "0"], + [1697023980, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_3600.json new file mode 100644 index 0000000000..b15fb136cb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_3600.json @@ -0,0 +1,32 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "1.8706855010893246"], + [1697022000, "2.634782608695652"], + [1697022360, "2.678260869565217"], + [1697022720, "2.6753623188405795"], + [1697023080, "2.7101449275362324"], + [1697023440, "2.631884057971014"], + [1697023800, "2.6811594202898554"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0"], + [1697022000, "0"], + [1697022360, "0"], + [1697022720, "0"], + [1697023080, "0"], + [1697023440, "0"], + [1697023800, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_60.json new file mode 100644 index 0000000000..d10d586b91 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_60.json @@ -0,0 +1,24 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "2.4"], + [1697023860, "2.2664801740597667"], + [1697023890, "2.533333333333333"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0"], + [1697023860, "0"], + [1697023890, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_600.json new file mode 100644 index 0000000000..2ce8ed7f51 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/metrics/outbound/metrics_outbound_600.json @@ -0,0 +1,144 @@ +{ + "grpc_received": null, + "grpc_sent": null, + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "2.5770227926554257"], + [1697023320, "2.688875062342908"], + [1697023380, "2.5334024768184116"], + [1697023440, "2.644444444444444"], + [1697023500, "2.888888888888889"], + [1697023560, "2.8222222222222224"], + [1697023620, "2.577792593580313"], + [1697023680, "2.511111111111111"], + [1697023740, "2.799815353777718"], + [1697023800, "2.5777777777777775"], + [1697023860, "2.422111639341834"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "0.8984028601338787"], + [1697023320, "0.7599175779653324"], + [1697023380, "1.1891073830431969"], + [1697023440, "0.8486956521739024"], + [1697023500, "1.3678294573643095"], + [1697023560, "1.3087301587301081"], + [1697023620, "1.0917565417025843"], + [1697023680, "1.2628205128205447"], + [1697023740, "1.460967924929601"], + [1697023800, "1.292016806722755"], + [1697023860, "1.284750805346726"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "0"], + [1697023320, "0"], + [1697023380, "0"], + [1697023440, "0"], + [1697023500, "0"], + [1697023560, "0"], + [1697023620, "0"], + [1697023680, "0"], + [1697023740, "0"], + [1697023800, "0"], + [1697023860, "0"] + ], + "name": "request_error_count" + } + ], + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "1321.2978379025685"], + [1697023320, "1318.96540098226"], + [1697023380, "1321.3024875544975"], + [1697023440, "1319.5652173913045"], + [1697023500, "1320.5426356589146"], + [1697023560, "1319.047619047619"], + [1697023620, "1321.30451418782"], + [1697023680, "1321.7948717948716"], + [1697023740, "1321.778861892338"], + [1697023800, "1321.4285714285716"], + [1697023860, "1327.7766258130607"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "3375.6524699856404"], + [1697023320, "3399.9827661980453"], + [1697023380, "3376.750009260288"], + [1697023440, "3372.222222222222"], + [1697023500, "3785.555555555555"], + [1697023560, "3693.333333333333"], + [1697023620, "3376.686668000089"], + [1697023680, "3436.666666666666"], + [1697023740, "3641.998202908425"], + [1697023800, "3494.4444444444443"], + [1697023860, "3186.4987189170774"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "1238.7218631817639"], + [1697023320, "1226.7230258729253"], + [1697023380, "1238.6907292600722"], + [1697023440, "1224.782608695652"], + [1697023500, "1233.720930232558"], + [1697023560, "1231.7460317460318"], + [1697023620, "1235.218636496714"], + [1697023680, "1249.1452991452988"], + [1697023740, "1234.6894311611843"], + [1697023800, "1239.075630252101"], + [1697023860, "1236.10002345071"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023260, "3164.687322664851"], + [1697023320, "3162.203605765669"], + [1697023380, "3165.625563334197"], + [1697023440, "3129.9999999999995"], + [1697023500, "3536.666666666666"], + [1697023560, "3448.8888888888887"], + [1697023620, "3156.688149580342"], + [1697023680, "3247.777777777777"], + [1697023740, "3402.0340459986323"], + [1697023800, "3276.6666666666665"], + [1697023860, "2966.4862783430335"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": null, + "tcp_opened": null, + "tcp_received": null, + "tcp_sent": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/tls.json b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/tls.json new file mode 100644 index 0000000000..ea7db32cbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/bookinfo/tls.json @@ -0,0 +1 @@ +{ "status": "MTLS_NOT_ENABLED", "autoMTLSEnabled": true, "minTLS": "" } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/app.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/app.json new file mode 100644 index 0000000000..55a1b7dec6 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/app.json @@ -0,0 +1,114 @@ +{ + "grafana": { + "workloadStatuses": [ + { + "name": "grafana", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-egressgateway": { + "workloadStatuses": [ + { + "name": "istio-egressgateway", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-ingressgateway": { + "workloadStatuses": [ + { + "name": "istio-ingressgateway", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istiod": { + "workloadStatuses": [ + { + "name": "istiod", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "jaeger": { + "workloadStatuses": [ + { + "name": "jaeger", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "kiali": { + "workloadStatuses": [ + { + "name": "kiali", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "prometheus": { + "workloadStatuses": [ + { + "name": "prometheus", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + } + ], + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/service.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/service.json new file mode 100644 index 0000000000..af1aa51acc --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/service.json @@ -0,0 +1,65 @@ +{ + "grafana": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-egressgateway": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-ingressgateway": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istiod": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "jaeger-collector": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "kiali": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "prometheus": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "tracing": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "zipkin": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/workload.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/workload.json new file mode 100644 index 0000000000..3855649f3b --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/health/workload.json @@ -0,0 +1,100 @@ +{ + "grafana": { + "workloadStatus": { + "name": "grafana", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-egressgateway": { + "workloadStatus": { + "name": "istio-egressgateway", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istio-ingressgateway": { + "workloadStatus": { + "name": "istio-ingressgateway", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "istiod": { + "workloadStatus": { + "name": "istiod", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "jaeger": { + "workloadStatus": { + "name": "jaeger", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "kiali": { + "workloadStatus": { + "name": "kiali", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "prometheus": { + "workloadStatus": { + "name": "prometheus", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": -1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_120.json new file mode 100644 index 0000000000..680a665149 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_120.json @@ -0,0 +1,100 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024040, "0.002755125239989775"], + [1697024070, "0.0028308143301925487"], + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"], + [1697024160, "0.0016924311084703827"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697024040, "145.055744"], + [1697024070, "143.683584"], + [1697024100, "143.683584"], + [1697024130, "143.679488"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697024040, "NaN"], + [1697024070, "NaN"], + [1697024100, "NaN"], + [1697024130, "NaN"], + [1697024160, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024040, "0.0013333333333333049"], + [1697024070, "0.0013333333333333049"], + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"], + [1697024160, "0.0013333333333333049"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697024040, "165.003264"], + [1697024070, "163.6352"], + [1697024100, "163.6352"], + [1697024130, "163.6352"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_1800.json new file mode 100644 index 0000000000..12112e1412 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_1800.json @@ -0,0 +1,130 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "0.004423229563643965"], + [1697022540, "0.002818351158753509"], + [1697022720, "0.0033681828956744772"], + [1697022900, "0.004075735098607482"], + [1697023080, "0.004858861411539032"], + [1697023260, "0.003327461760086153"], + [1697023440, "0.0029723897808957888"], + [1697023620, "0.005595762889521851"], + [1697023800, "0.0034129233521214435"], + [1697023980, "0.0034297933448419722"], + [1697024160, "0.005304226914689294"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697022360, "145.154048"], + [1697022540, "143.589376"], + [1697022720, "146.763776"], + [1697022900, "155.20972799999998"], + [1697023080, "152.662016"], + [1697023260, "148.979712"], + [1697023440, "146.354176"], + [1697023620, "144.191488"], + [1697023800, "146.18624"], + [1697023980, "144.396288"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "NaN"], + [1697022540, "NaN"], + [1697022720, "NaN"], + [1697022900, "NaN"], + [1697023080, "NaN"], + [1697023260, "NaN"], + [1697023440, "NaN"], + [1697023620, "NaN"], + [1697023800, "NaN"], + [1697023980, "0.00026259236363636437"], + [1697024160, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "0.002242424242424248"], + [1697022540, "0.0013333333333333478"], + [1697022720, "0.0017575757575757738"], + [1697022900, "0.001999999999999989"], + [1697023080, "0.002363636363636367"], + [1697023260, "0.0016363636363636335"], + [1697023440, "0.0015757575757575635"], + [1697023620, "0.002727272727272744"], + [1697023800, "0.001757575757575752"], + [1697023980, "0.0017575757575757738"], + [1697024160, "0.002666666666666674"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697022360, "165.09747199999998"], + [1697022540, "163.688448"], + [1697022720, "165.851136"], + [1697022900, "175.038464"], + [1697023080, "172.531712"], + [1697023260, "168.40704"], + [1697023440, "166.641664"], + [1697023620, "164.35609599999998"], + [1697023800, "166.244352"], + [1697023980, "164.462592"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_300.json new file mode 100644 index 0000000000..1b3825ca9e --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_300.json @@ -0,0 +1,130 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "0.00277104088632311"], + [1697023890, "0.002440995810258295"], + [1697023920, "0.0030462017153864134"], + [1697023950, "0.0011749084878141453"], + [1697023980, "0.003215558196758648"], + [1697024010, "0.0020194318109019596"], + [1697024040, "0.002755125239989775"], + [1697024070, "0.0028308143301925487"], + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"], + [1697024160, "0.0016924311084703827"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697023860, "144.982016"], + [1697023890, "144.846848"], + [1697023920, "145.51449599999998"], + [1697023950, "144.52326399999998"], + [1697023980, "144.396288"], + [1697024010, "144.392192"], + [1697024040, "145.055744"], + [1697024070, "143.683584"], + [1697024100, "143.683584"], + [1697024130, "143.679488"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "NaN"], + [1697023890, "NaN"], + [1697023920, "NaN"], + [1697023950, "NaN"], + [1697023980, "NaN"], + [1697024010, "NaN"], + [1697024040, "NaN"], + [1697024070, "NaN"], + [1697024100, "NaN"], + [1697024130, "NaN"], + [1697024160, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "0.0019999999999998387"], + [1697023890, "0.002000000000000076"], + [1697023920, "0.0013333333333333049"], + [1697023950, "0.0013333333333333049"], + [1697023980, "0.0013333333333335417"], + [1697024010, "0.0013333333333333049"], + [1697024040, "0.0013333333333333049"], + [1697024070, "0.0013333333333333049"], + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"], + [1697024160, "0.0013333333333333049"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697023860, "164.769792"], + [1697023890, "164.769792"], + [1697023920, "165.58079999999998"], + [1697023950, "164.462592"], + [1697023980, "164.462592"], + [1697024010, "164.462592"], + [1697024040, "165.003264"], + [1697024070, "163.6352"], + [1697024100, "163.6352"], + [1697024130, "163.6352"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_3600.json new file mode 100644 index 0000000000..6178363f65 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_3600.json @@ -0,0 +1,115 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.0022837365589533235"], + [1697022000, "0.003630157034545756"], + [1697022360, "0.0034882143205133696"], + [1697022720, "0.003109542147831563"], + [1697023080, "0.004421874876878809"], + [1697023440, "0.0031209359563211837"], + [1697023800, "0.004399331006493985"], + [1697024160, "0.004336317402418327"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697021640, "143.09376"], + [1697022000, "145.137664"], + [1697022360, "145.154048"], + [1697022720, "146.763776"], + [1697023080, "152.662016"], + [1697023440, "146.354176"], + [1697023800, "146.18624"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "NaN"], + [1697022000, "NaN"], + [1697022360, "NaN"], + [1697022720, "NaN"], + [1697023080, "0.00021478099999999977"], + [1697023440, "NaN"], + [1697023800, "NaN"], + [1697024160, "0.0002625923636363643"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.001045284722222216"], + [1697022000, "0.0018260869565217364"], + [1697022360, "0.0017971014492753651"], + [1697022720, "0.0015652173913043557"], + [1697023080, "0.002173913043478261"], + [1697023440, "0.0015942028985507267"], + [1697023800, "0.002202898550724642"], + [1697024160, "0.002202898550724642"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697021640, "163.19692799999999"], + [1697022000, "165.09747199999998"], + [1697022360, "165.09747199999998"], + [1697022720, "165.851136"], + [1697023080, "172.531712"], + [1697023440, "166.641664"], + [1697023800, "166.244352"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_60.json new file mode 100644 index 0000000000..74a54ee770 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_60.json @@ -0,0 +1,90 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"], + [1697024160, "0.0016924311084703827"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697024100, "143.683584"], + [1697024130, "143.679488"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697024100, "NaN"], + [1697024130, "NaN"], + [1697024160, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"], + [1697024160, "0.0013333333333333049"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697024100, "163.6352"], + [1697024130, "163.6352"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_600.json new file mode 100644 index 0000000000..9e292c84ec --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/inbound/metrics_inbound_600.json @@ -0,0 +1,141 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023560, "0.012649111781990258"], + [1697023620, "0.0028395890688735514"], + [1697023680, "0.0029086472614529356"], + [1697023740, "0.003109156035267781"], + [1697023800, "0.002981445175995798"], + [1697023860, "0.0030650943556152"], + [1697023920, "0.002949315857020238"], + [1697023980, "0.0030294256967151605"], + [1697024040, "0.003211951468553024"], + [1697024100, "0.0031811090223253153"], + [1697024160, "0.009876169457103496"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697023560, "144.203776"], + [1697023620, "144.191488"], + [1697023680, "144.859136"], + [1697023740, "145.522688"], + [1697023800, "146.18624"], + [1697023860, "144.982016"], + [1697023920, "145.51449599999998"], + [1697023980, "144.396288"], + [1697024040, "145.055744"], + [1697024100, "143.683584"], + [1697024160, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "grpc_received": null, + "grpc_sent": null, + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697023560, "NaN"], + [1697023620, "NaN"], + [1697023680, "NaN"], + [1697023740, "NaN"], + [1697023800, "NaN"], + [1697023860, "0.0002625923636363643"], + [1697023920, "NaN"], + [1697023980, "NaN"], + [1697024040, "NaN"], + [1697024100, "NaN"], + [1697024160, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023560, "0.004888888888888863"], + [1697023620, "0.0015555555555555618"], + [1697023680, "0.0015555555555555618"], + [1697023740, "0.0015555555555555618"], + [1697023800, "0.0015555555555555618"], + [1697023860, "0.0019999999999999966"], + [1697023920, "0.0015555555555555618"], + [1697023980, "0.0013333333333333836"], + [1697024040, "0.0015555555555555618"], + [1697024100, "0.0017777777777778186"], + [1697024160, "0.00511111111111112"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697023560, "164.35609599999998"], + [1697023620, "164.35609599999998"], + [1697023680, "164.892672"], + [1697023740, "165.70368"], + [1697023800, "166.244352"], + [1697023860, "164.769792"], + [1697023920, "165.58079999999998"], + [1697023980, "164.462592"], + [1697024040, "165.003264"], + [1697024100, "163.6352"], + [1697024160, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": null, + "request_duration_millis": null, + "request_error_count": null, + "request_size": null, + "request_throughput": null, + "response_size": null, + "response_throughput": null, + "tcp_closed": null, + "tcp_opened": null, + "tcp_received": null, + "tcp_sent": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/index.ts b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/index.ts new file mode 100644 index 0000000000..c1b81f13ea --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/index.ts @@ -0,0 +1,37 @@ +/* Inbound Metrics */ + +import inbound60 from './inbound/metrics_inbound_60.json'; +import inbound120 from './inbound/metrics_inbound_120.json'; +import inbound300 from './inbound/metrics_inbound_300.json'; +import inbound600 from './inbound/metrics_inbound_600.json'; +import inbound1800 from './inbound/metrics_inbound_1800.json'; +import inbound3600 from './inbound/metrics_inbound_3600.json'; +/* Outbound Metrics */ + +import outbound60 from './outbound/metrics_outbound_60.json'; +import outbound120 from './outbound/metrics_outbound_120.json'; +import outbound300 from './outbound/metrics_outbound_300.json'; +import outbound600 from './outbound/metrics_outbound_600.json'; +import outbound1800 from './outbound/metrics_outbound_1800.json'; +import outbound3600 from './outbound/metrics_outbound_3600.json'; + +export const istioSystemMetrics = { + inbound: { + 60: inbound60, + 120: inbound120, + 300: inbound300, + 600: inbound600, + 1800: inbound1800, + 3600: inbound3600, + }, + outbound: { + 60: outbound60, + 120: outbound120, + 300: outbound300, + 600: outbound600, + 1800: outbound1800, + 3600: outbound3600, + }, +}; + +export default istioSystemMetrics; diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_120.json new file mode 100644 index 0000000000..1ed12e8c7e --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_120.json @@ -0,0 +1,112 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024010, "0.0020194318109019596"], + [1697024040, "0.002755125239989775"], + [1697024070, "0.0028308143301925487"], + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697024010, "144.392192"], + [1697024040, "145.055744"], + [1697024070, "143.683584"], + [1697024100, "143.683584"], + [1697024130, "143.679488"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697024010, "NaN"], + [1697024040, "NaN"], + [1697024070, "NaN"], + [1697024100, "NaN"], + [1697024130, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024010, "0.0013333333333333049"], + [1697024040, "0.0013333333333333049"], + [1697024070, "0.0013333333333333049"], + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697024010, "164.462592"], + [1697024040, "165.003264"], + [1697024070, "163.6352"], + [1697024100, "163.6352"], + [1697024130, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024010, "1"], + [1697024040, "1"], + [1697024070, "0.9996667777407532"], + [1697024100, "1"], + [1697024130, "1"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_1800.json new file mode 100644 index 0000000000..b23b5c5d65 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_1800.json @@ -0,0 +1,148 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "0.0026138895855586384"], + [1697022360, "0.004423229563643965"], + [1697022540, "0.002818351158753509"], + [1697022720, "0.0033681828956744772"], + [1697022900, "0.004075735098607482"], + [1697023080, "0.004858861411539032"], + [1697023260, "0.003327461760086153"], + [1697023440, "0.0029723897808957888"], + [1697023620, "0.005595762889521851"], + [1697023800, "0.0034129233521214435"], + [1697023980, "0.0034297933448419722"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697022180, "145.154048"], + [1697022360, "145.154048"], + [1697022540, "143.589376"], + [1697022720, "146.763776"], + [1697022900, "155.20972799999998"], + [1697023080, "152.662016"], + [1697023260, "148.979712"], + [1697023440, "146.354176"], + [1697023620, "144.191488"], + [1697023800, "146.18624"], + [1697023980, "144.396288"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "NaN"], + [1697022360, "NaN"], + [1697022540, "NaN"], + [1697022720, "NaN"], + [1697022900, "NaN"], + [1697023080, "NaN"], + [1697023260, "NaN"], + [1697023440, "NaN"], + [1697023620, "NaN"], + [1697023800, "NaN"], + [1697023980, "0.00026259236363636437"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "0.0013939393939393964"], + [1697022360, "0.002242424242424248"], + [1697022540, "0.0013333333333333478"], + [1697022720, "0.0017575757575757738"], + [1697022900, "0.001999999999999989"], + [1697023080, "0.002363636363636367"], + [1697023260, "0.0016363636363636335"], + [1697023440, "0.0015757575757575635"], + [1697023620, "0.002727272727272744"], + [1697023800, "0.001757575757575752"], + [1697023980, "0.0017575757575757738"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697022180, "165.09747199999998"], + [1697022360, "165.09747199999998"], + [1697022540, "163.688448"], + [1697022720, "165.851136"], + [1697022900, "175.038464"], + [1697023080, "172.531712"], + [1697023260, "168.40704"], + [1697023440, "166.641664"], + [1697023620, "164.35609599999998"], + [1697023800, "166.244352"], + [1697023980, "164.462592"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022180, "0.9999999999999999"], + [1697022360, "0.9999999999999999"], + [1697022540, "0.9999999999999999"], + [1697022720, "0.9999999999999999"], + [1697022900, "0.9999999999999999"], + [1697023080, "0.9999999999999999"], + [1697023260, "0.9999999999999999"], + [1697023440, "0.9999999999999999"], + [1697023620, "0.9999999999999999"], + [1697023800, "0.9999999999999999"], + [1697023980, "0.9999999999999999"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_300.json new file mode 100644 index 0000000000..341f41eb51 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_300.json @@ -0,0 +1,148 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0.004117320729759894"], + [1697023860, "0.00277104088632311"], + [1697023890, "0.002440995810258295"], + [1697023920, "0.0030462017153864134"], + [1697023950, "0.0011749084878141453"], + [1697023980, "0.003215558196758648"], + [1697024010, "0.0020194318109019596"], + [1697024040, "0.002755125239989775"], + [1697024070, "0.0028308143301925487"], + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697023830, "144.850944"], + [1697023860, "144.982016"], + [1697023890, "144.846848"], + [1697023920, "145.51449599999998"], + [1697023950, "144.52326399999998"], + [1697023980, "144.396288"], + [1697024010, "144.392192"], + [1697024040, "145.055744"], + [1697024070, "143.683584"], + [1697024100, "143.683584"], + [1697024130, "143.679488"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0.00026259236363636437"], + [1697023860, "NaN"], + [1697023890, "NaN"], + [1697023920, "NaN"], + [1697023950, "NaN"], + [1697023980, "NaN"], + [1697024010, "NaN"], + [1697024040, "NaN"], + [1697024070, "NaN"], + [1697024100, "NaN"], + [1697024130, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0.0026666666666668466"], + [1697023860, "0.0019999999999998387"], + [1697023890, "0.002000000000000076"], + [1697023920, "0.0013333333333333049"], + [1697023950, "0.0013333333333333049"], + [1697023980, "0.0013333333333335417"], + [1697024010, "0.0013333333333333049"], + [1697024040, "0.0013333333333333049"], + [1697024070, "0.0013333333333333049"], + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697023830, "164.22912"], + [1697023860, "164.769792"], + [1697023890, "164.769792"], + [1697023920, "165.58079999999998"], + [1697023950, "164.462592"], + [1697023980, "164.462592"], + [1697024010, "164.462592"], + [1697024040, "165.003264"], + [1697024070, "163.6352"], + [1697024100, "163.6352"], + [1697024130, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023830, "0.9998000399920017"], + [1697023860, "1"], + [1697023890, "1"], + [1697023920, "1"], + [1697023950, "1"], + [1697023980, "1"], + [1697024010, "1"], + [1697024040, "1"], + [1697024070, "0.9996667777407532"], + [1697024100, "1"], + [1697024130, "1"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_3600.json new file mode 100644 index 0000000000..cd77133738 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_3600.json @@ -0,0 +1,124 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.0022837365589533235"], + [1697022000, "0.003630157034545756"], + [1697022360, "0.0034882143205133696"], + [1697022720, "0.003109542147831563"], + [1697023080, "0.004421874876878809"], + [1697023440, "0.0031209359563211837"], + [1697023800, "0.004399331006493985"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697021640, "143.09376"], + [1697022000, "145.137664"], + [1697022360, "145.154048"], + [1697022720, "146.763776"], + [1697023080, "152.662016"], + [1697023440, "146.354176"], + [1697023800, "146.18624"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "NaN"], + [1697022000, "NaN"], + [1697022360, "NaN"], + [1697022720, "NaN"], + [1697023080, "0.00021478099999999977"], + [1697023440, "NaN"], + [1697023800, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.001045284722222216"], + [1697022000, "0.0018260869565217364"], + [1697022360, "0.0017971014492753651"], + [1697022720, "0.0015652173913043557"], + [1697023080, "0.002173913043478261"], + [1697023440, "0.0015942028985507267"], + [1697023800, "0.002202898550724642"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697021640, "163.19692799999999"], + [1697022000, "165.09747199999998"], + [1697022360, "165.09747199999998"], + [1697022720, "165.851136"], + [1697023080, "172.531712"], + [1697023440, "166.641664"], + [1697023800, "166.244352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.7295888888888888"], + [1697022000, "1"], + [1697022360, "1"], + [1697022720, "1"], + [1697023080, "1"], + [1697023440, "1"], + [1697023800, "1"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_60.json new file mode 100644 index 0000000000..d9aa2a50c6 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_60.json @@ -0,0 +1,100 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024070, "0.0028308143301925487"], + [1697024100, "0.0027977164583027226"], + [1697024130, "0.017605061010355652"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697024070, "143.683584"], + [1697024100, "143.683584"], + [1697024130, "143.679488"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697024070, "NaN"], + [1697024100, "NaN"], + [1697024130, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697024070, "0.0013333333333333049"], + [1697024100, "0.002000000000000076"], + [1697024130, "0.002000000000000076"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697024070, "163.6352"], + [1697024100, "163.6352"], + [1697024130, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024070, "0.9996667777407532"], + [1697024100, "1"], + [1697024130, "1"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_600.json new file mode 100644 index 0000000000..4fcd086822 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/metrics/outbound/metrics_outbound_600.json @@ -0,0 +1,252 @@ +{ + "container_cpu_usage_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "0.004063051366539051"], + [1697023560, "0.012649111781990258"], + [1697023620, "0.0028395890688735514"], + [1697023680, "0.0029086472614529356"], + [1697023740, "0.003109156035267781"], + [1697023800, "0.002981445175995798"], + [1697023860, "0.0030650943556152"], + [1697023920, "0.002949315857020238"], + [1697023980, "0.0030294256967151605"], + [1697024040, "0.003211951468553024"], + [1697024100, "0.0031811090223253153"] + ], + "name": "container_cpu_usage_seconds_total" + } + ], + "container_memory_working_set_bytes": [ + { + "labels": { + "__name__": "container_memory_working_set_bytes", + "beta_kubernetes_io_arch": "amd64", + "beta_kubernetes_io_os": "linux", + "container": "discovery", + "id": "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod86ae28e5_296a_4c6f_81ae_e3102580203a.slice/crio-311ddafa211336c162b5aeb91d70fbd048f94c9f18142285c0473eae845da2de.scope", + "image": "gcr.io/istio-release/pilot:1.18.2", + "instance": "crc-8cf2w-master-0", + "job": "kubernetes-nodes-cadvisor", + "kubernetes_io_arch": "amd64", + "kubernetes_io_hostname": "crc-8cf2w-master-0", + "kubernetes_io_os": "linux", + "name": "k8s_discovery_istiod-7548d4ff85-9czcn_istio-system_86ae28e5-296a-4c6f-81ae-e3102580203a_2", + "namespace": "istio-system", + "node_openshift_io_os_id": "rhcos", + "pod": "istiod-7548d4ff85-9czcn", + "topology_hostpath_csi_node": "crc-8cf2w-master-0" + }, + "datapoints": [ + [1697023500, "144.195584"], + [1697023560, "144.203776"], + [1697023620, "144.191488"], + [1697023680, "144.859136"], + [1697023740, "145.522688"], + [1697023800, "146.18624"], + [1697023860, "144.982016"], + [1697023920, "145.51449599999998"], + [1697023980, "144.396288"], + [1697024040, "145.055744"], + [1697024100, "143.683584"] + ], + "name": "container_memory_working_set_bytes" + } + ], + "grpc_received": null, + "grpc_sent": null, + "pilot_proxy_convergence_time": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "NaN"], + [1697023560, "NaN"], + [1697023620, "NaN"], + [1697023680, "NaN"], + [1697023740, "NaN"], + [1697023800, "NaN"], + [1697023860, "0.0002625923636363643"], + [1697023920, "NaN"], + [1697023980, "NaN"], + [1697024040, "NaN"], + [1697024100, "NaN"] + ], + "stat": "avg", + "name": "pilot_proxy_convergence_time" + } + ], + "process_cpu_seconds_total": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "0.0019999999999999966"], + [1697023560, "0.004888888888888863"], + [1697023620, "0.0015555555555555618"], + [1697023680, "0.0015555555555555618"], + [1697023740, "0.0015555555555555618"], + [1697023800, "0.0015555555555555618"], + [1697023860, "0.0019999999999999966"], + [1697023920, "0.0015555555555555618"], + [1697023980, "0.0013333333333333836"], + [1697024040, "0.0015555555555555618"], + [1697024100, "0.0017777777777778186"] + ], + "name": "process_cpu_seconds_total" + } + ], + "process_resident_memory_bytes": [ + { + "labels": { + "__name__": "process_resident_memory_bytes", + "app": "istiod", + "install_operator_istio_io_owning_resource": "unknown", + "instance": "10.217.0.34:15014", + "istio": "pilot", + "istio_io_rev": "default", + "job": "kubernetes-pods", + "namespace": "istio-system", + "operator_istio_io_component": "Pilot", + "pod": "istiod-7548d4ff85-9czcn", + "pod_template_hash": "7548d4ff85", + "sidecar_istio_io_inject": "false" + }, + "datapoints": [ + [1697023500, "163.81951999999998"], + [1697023560, "164.35609599999998"], + [1697023620, "164.35609599999998"], + [1697023680, "164.892672"], + [1697023740, "165.70368"], + [1697023800, "166.244352"], + [1697023860, "164.769792"], + [1697023920, "165.58079999999998"], + [1697023980, "164.462592"], + [1697024040, "165.003264"], + [1697024100, "163.6352"] + ], + "name": "process_resident_memory_bytes" + } + ], + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "0.9999999999999999"], + [1697023560, "0.9999999999999999"], + [1697023620, "0.9999999999999999"], + [1697023680, "0.9999555575307764"], + [1697023740, "0.9999999999999999"], + [1697023800, "0.9999999999999999"], + [1697023860, "0.9999999999999999"], + [1697023920, "0.9999999999999999"], + [1697023980, "0.9999999999999999"], + [1697024040, "0.9999999999999999"], + [1697024100, "0.9999999999999999"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "17.144444444444446"], + [1697023560, "17.98888888888889"], + [1697023620, "15.245555555555297"], + [1697023680, "15.845555555555297"], + [1697023740, "16.65555555555556"], + [1697023800, "15.968888888889017"], + [1697023860, "15.96888888888837"], + [1697023920, "15.522222222222222"], + [1697023980, "13.882222222222481"], + [1697024040, "15.981111111110982"], + [1697024100, "16.5466666666668"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": null, + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "1950"], + [1697023560, "1950"], + [1697023620, "1950"], + [1697023680, "1950"], + [1697023740, "1950"], + [1697023800, "1950"], + [1697023860, "1950"], + [1697023920, "1950"], + [1697023980, "1950"], + [1697024040, "1950"], + [1697024100, "1950"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "1949.9999999999998"], + [1697023560, "1949.9999999999998"], + [1697023620, "1949.9999999999998"], + [1697023680, "1949.913337185014"], + [1697023740, "1949.9999999999998"], + [1697023800, "1949.9999999999998"], + [1697023860, "1949.9999999999998"], + [1697023920, "1949.9999999999998"], + [1697023980, "1949.9999999999998"], + [1697024040, "1949.9999999999998"], + [1697024100, "1949.9999999999998"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "6127.777777777778"], + [1697023560, "6083.333333333334"], + [1697023620, "5972.222222222223"], + [1697023680, "5883.333333333334"], + [1697023740, "6083.333333333334"], + [1697023800, "6038.888888888889"], + [1697023860, "5972.222222222223"], + [1697023920, "5994.444444444444"], + [1697023980, "5905.555555555556"], + [1697024040, "6127.777777777778"], + [1697024100, "6016.666666666667"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023500, "6127.777777777777"], + [1697023560, "6083.333333333333"], + [1697023620, "5972.222222222222"], + [1697023680, "5883.071863472735"], + [1697023740, "6083.333333333333"], + [1697023800, "6038.888888888888"], + [1697023860, "5972.222222222222"], + [1697023920, "5994.444444444443"], + [1697023980, "5905.555555555555"], + [1697024040, "6127.777777777777"], + [1697024100, "6016.666666666666"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": null, + "tcp_opened": null, + "tcp_received": null, + "tcp_sent": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/istio-system/tls.json b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/tls.json new file mode 100644 index 0000000000..ea7db32cbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/istio-system/tls.json @@ -0,0 +1 @@ +{ "status": "MTLS_NOT_ENABLED", "autoMTLSEnabled": true, "minTLS": "" } diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-health.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/app.json similarity index 56% rename from plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-health.json rename to plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/app.json index 64899ff352..84a00bf3c8 100644 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-agency-health.json +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/app.json @@ -10,8 +10,16 @@ } ], "requests": { - "inbound": { "http": { "200": 0.26666666666666666 } }, - "outbound": { "http": { "200": 0.26666666666666666 } }, + "inbound": { + "http": { + "200": 0.26322191455252564 + } + }, + "outbound": { + "http": { + "200": 0.2649572649572649 + } + }, "healthAnnotations": {} } }, @@ -26,7 +34,11 @@ } ], "requests": { - "inbound": { "http": { "200": 1.3333333333333333 } }, + "inbound": { + "http": { + "200": 1.3299145299145296 + } + }, "outbound": {}, "healthAnnotations": {} } @@ -42,8 +54,16 @@ } ], "requests": { - "inbound": { "http": { "200": 0.26666666666666666 } }, - "outbound": { "http": { "200": 0.26666666666666666 } }, + "inbound": { + "http": { + "200": 0.2653657058650162 + } + }, + "outbound": { + "http": { + "200": 0.2666666666666666 + } + }, "healthAnnotations": {} } }, @@ -58,8 +78,16 @@ } ], "requests": { - "inbound": { "http": { "200": 0.7999999999999999 } }, - "outbound": { "http": { "200": 0.39999999999999997 } }, + "inbound": { + "http": { + "200": 0.7991927774830533 + } + }, + "outbound": { + "http": { + "200": 0.39829059829059826 + } + }, "healthAnnotations": {} } }, @@ -74,8 +102,16 @@ } ], "requests": { - "inbound": { "http": { "200": 0.39658119658119656 } }, - "outbound": { "http": { "200": 0.39658119658119656 } }, + "inbound": { + "http": { + "200": 0.39883943943412903 + } + }, + "outbound": { + "http": { + "200": 0.39999999999999997 + } + }, "healthAnnotations": {} } }, @@ -89,7 +125,11 @@ "syncedProxies": 1 } ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } }, "travels": { "workloadStatuses": [ @@ -99,11 +139,33 @@ "currentReplicas": 1, "availableReplicas": 1, "syncedProxies": 1 + }, + { + "name": "travels-v2", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + { + "name": "travels-v3", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 } ], "requests": { - "inbound": { "http": { "200": 0.7965611411095852 } }, - "outbound": { "http": { "200": 1.986324786324786 } }, + "inbound": { + "http": { + "200": 0.7942120752085292 + } + }, + "outbound": { + "http": { + "200": 1.9840915760326658 + } + }, "healthAnnotations": {} } } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/service.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/service.json new file mode 100644 index 0000000000..338f0ad2c2 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/service.json @@ -0,0 +1,75 @@ +{ + "cars": { + "requests": { + "inbound": { + "http": { + "200": 0.260085535042735 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "discounts": { + "requests": { + "inbound": { + "http": { + "200": 1.3299145299145296 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "flights": { + "requests": { + "inbound": { + "http": { + "200": 0.26561828319088315 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "hotels": { + "requests": { + "inbound": { + "http": { + "200": 0.7984701908831908 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "insurances": { + "requests": { + "inbound": { + "http": { + "200": 0.3991372467236467 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "mysqldb": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "travels": { + "requests": { + "inbound": { + "http": { + "200": 0.793200482030932 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/workload.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/workload.json new file mode 100644 index 0000000000..1b21415624 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/health/workload.json @@ -0,0 +1,188 @@ +{ + "cars-v1": { + "workloadStatus": { + "name": "cars-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.2600759354226021 + } + }, + "outbound": { + "http": { + "200": 0.2649572649572649 + } + }, + "healthAnnotations": {} + } + }, + "discounts-v1": { + "workloadStatus": { + "name": "discounts-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 1.3299145299145296 + } + }, + "outbound": {}, + "healthAnnotations": {} + } + }, + "flights-v1": { + "workloadStatus": { + "name": "flights-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.2656132557771446 + } + }, + "outbound": { + "http": { + "200": 0.2666666666666666 + } + }, + "healthAnnotations": {} + } + }, + "hotels-v1": { + "workloadStatus": { + "name": "hotels-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.7984947065527065 + } + }, + "outbound": { + "http": { + "200": 0.39829059829059826 + } + }, + "healthAnnotations": {} + } + }, + "insurances-v1": { + "workloadStatus": { + "name": "insurances-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.3991459420702754 + } + }, + "outbound": { + "http": { + "200": 0.39999999999999997 + } + }, + "healthAnnotations": {} + } + }, + "mysqldb-v1": { + "workloadStatus": { + "name": "mysqldb-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "travels-v1": { + "workloadStatus": { + "name": "travels-v1", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.49743589743589733 + } + }, + "outbound": { + "http": { + "200": 1.1008547008547007 + } + }, + "healthAnnotations": {} + } + }, + "travels-v2": { + "workloadStatus": { + "name": "travels-v2", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.1514719596391263 + } + }, + "outbound": { + "http": { + "200": 0.32564116856600195 + } + }, + "healthAnnotations": {} + } + }, + "travels-v3": { + "workloadStatus": { + "name": "travels-v3", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": { + "http": { + "200": 0.1476955061728395 + } + }, + "outbound": { + "http": { + "200": 0.29358963325102877 + } + }, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_120.json new file mode 100644 index 0000000000..ddf90f9504 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_120.json @@ -0,0 +1,28 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024130, "3.933244474064202"], + [1697024160, "3.800053774557242"], + [1697024190, "3.8666666666666685"], + [1697024220, "3.9334756314478847"], + [1697024250, "3.800000000000001"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697024130, "0"], + [1697024160, "0"], + [1697024190, "0"], + [1697024220, "0"], + [1697024250, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_1800.json new file mode 100644 index 0000000000..8909e1ecf8 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_1800.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "3.872727272727272"], + [1697022540, "3.8545359783060222"], + [1697022720, "3.8545454545454536"], + [1697022900, "3.8424242424242423"], + [1697023080, "3.8849503289542557"], + [1697023260, "3.872730798962184"], + [1697023440, "3.866666666666666"], + [1697023620, "3.8787878787878785"], + [1697023800, "3.7999692964648504"], + [1697023980, "3.866666666666666"], + [1697024160, "3.8545352074830523"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "0"], + [1697022540, "0"], + [1697022720, "0"], + [1697022900, "0"], + [1697023080, "0"], + [1697023260, "0"], + [1697023440, "0"], + [1697023620, "0"], + [1697023800, "0"], + [1697023980, "0"], + [1697024160, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_300.json new file mode 100644 index 0000000000..67504e1639 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_300.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023950, "3.8000000000000007"], + [1697023980, "3.8000000000000016"], + [1697024010, "3.799946691548685"], + [1697024040, "3.933386677335468"], + [1697024070, "3.8000000000000016"], + [1697024100, "3.866729028197065"], + [1697024130, "3.933244474064202"], + [1697024160, "3.800053774557242"], + [1697024190, "3.8666666666666685"], + [1697024220, "3.9334756314478847"], + [1697024250, "3.800000000000001"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023950, "0"], + [1697023980, "0"], + [1697024010, "0"], + [1697024040, "0"], + [1697024070, "0"], + [1697024100, "0"], + [1697024130, "0"], + [1697024160, "0"], + [1697024190, "0"], + [1697024220, "0"], + [1697024250, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_3600.json new file mode 100644 index 0000000000..936ede0342 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_3600.json @@ -0,0 +1,34 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "2.741467670887799"], + [1697022000, "3.8637681159420296"], + [1697022360, "3.886961159447176"], + [1697022720, "3.852173913043479"], + [1697023080, "3.87536231884058"], + [1697023440, "3.8695668557168306"], + [1697023800, "3.831869372789426"], + [1697024160, "3.8637611596017596"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0"], + [1697022000, "0"], + [1697022360, "0"], + [1697022720, "0"], + [1697023080, "0"], + [1697023440, "0"], + [1697023800, "0"], + [1697024160, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_60.json new file mode 100644 index 0000000000..ef4e583f44 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_60.json @@ -0,0 +1,24 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024190, "3.8666666666666685"], + [1697024220, "3.9334756314478847"], + [1697024250, "3.800000000000001"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697024190, "0"], + [1697024220, "0"], + [1697024250, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_600.json new file mode 100644 index 0000000000..f626ff6190 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/inbound/metrics_inbound_600.json @@ -0,0 +1,216 @@ +{ + "grpc_received": null, + "grpc_sent": null, + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "3.9333333333333322"], + [1697023680, "3.8444444444444428"], + [1697023740, "3.844444444444443"], + [1697023800, "3.6443941298905074"], + [1697023860, "3.911016329996248"], + [1697023920, "3.844444444444443"], + [1697023980, "3.8444444444444446"], + [1697024040, "3.844454815506219"], + [1697024100, "3.8666222273573743"], + [1697024160, "3.8666133439978667"], + [1697024220, "3.86663111585122"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "6.566379310344839"], + [1697023680, "6.0295977011493616"], + [1697023740, "5.244848484848487"], + [1697023800, "5.478065206312723"], + [1697023860, "5.412254385266055"], + [1697023920, "5.532183908045853"], + [1697023980, "4.92413793103454"], + [1697024040, "6.368985326269782"], + [1697024100, "7.1145999315657855"], + [1697024160, "5.6739383767877145"], + [1697024220, "5.092213150551359"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "0"], + [1697023680, "0"], + [1697023740, "0"], + [1697023800, "0"], + [1697023860, "0"], + [1697023920, "0"], + [1697023980, "0"], + [1697024040, "0"], + [1697024100, "0"], + [1697024160, "0"], + [1697024220, "0"] + ], + "name": "request_error_count" + } + ], + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "1366.0919540229886"], + [1697023680, "1366.666666666667"], + [1697023740, "1360.3030303030305"], + [1697023800, "1365.3831644528366"], + [1697023860, "1364.942895021214"], + [1697023920, "1364.9425287356325"], + [1697023980, "1364.367816091954"], + [1697024040, "1365.5168872809536"], + [1697024100, "1364.3682110995646"], + [1697024160, "1365.607152888307"], + [1697024220, "1364.9426661211412"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "5282.222222222221"], + [1697023680, "5284.444444444443"], + [1697023740, "4987.7777777777765"], + [1697023800, "5127.703630078002"], + [1697023860, "5277.649823272713"], + [1697023920, "5277.777777777778"], + [1697023980, "5275.555555555556"], + [1697024040, "5280.014815802535"], + [1697024100, "5275.496451337396"], + [1697024160, "5249.928014397122"], + [1697024220, "5277.729784176925"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "1598.275862068966"], + [1697023680, "1598.2758620689665"], + [1697023740, "1628.1818181818187"], + [1697023800, "1572.5156425114612"], + [1697023860, "1598.289301932234"], + [1697023920, "1598.2758620689658"], + [1697023980, "1598.2758620689654"], + [1697024040, "1598.2777065514515"], + [1697024100, "1598.278817440177"], + [1697024160, "1600.8746926872575"], + [1697024220, "1598.2809030603103"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "6180"], + [1697023680, "6180.000000000001"], + [1697023740, "5969.999999999999"], + [1697023800, "5905.59073700882"], + [1697023860, "6179.900479829393"], + [1697023920, "6180"], + [1697023980, "6180"], + [1697024040, "6180.026075812462"], + [1697024100, "6179.940401028648"], + [1697024160, "6154.388455642205"], + [1697024220, "6179.962671643781"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "1.733333333333333"], + [1697023680, "1.733333333333333"], + [1697023740, "1.733333333333333"], + [1697023800, "1.733333333333333"], + [1697023860, "1.733333333333333"], + [1697023920, "1.733333333333333"], + [1697023980, "1.733333333333333"], + [1697024040, "1.733333333333333"], + [1697024100, "1.733333333333333"], + [1697024160, "1.733333333333333"], + [1697024220, "1.733333333333333"] + ], + "name": "tcp_closed" + } + ], + "tcp_opened": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "1.733333333333333"], + [1697023680, "1.733333333333333"], + [1697023740, "1.733333333333333"], + [1697023800, "1.733333333333333"], + [1697023860, "1.733333333333333"], + [1697023920, "1.733333333333333"], + [1697023980, "1.733333333333333"], + [1697024040, "1.733333333333333"], + [1697024100, "1.733333333333333"], + [1697024160, "1.733333333333333"], + [1697024220, "1.733333333333333"] + ], + "name": "tcp_opened" + } + ], + "tcp_received": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "957.5999999999998"], + [1697023680, "958.2888888888888"], + [1697023740, "957.6222222222221"], + [1697023800, "958.1555555555556"], + [1697023860, "957.3999999999999"], + [1697023920, "957.4444444444443"], + [1697023980, "957.2444444444443"], + [1697024040, "958.2444444444443"], + [1697024100, "957.511111111111"], + [1697024160, "958.1333333333333"], + [1697024220, "957.3333333333333"] + ], + "name": "tcp_received" + } + ], + "tcp_sent": [ + { + "labels": {}, + "datapoints": [ + [1697023620, "324.9555555555555"], + [1697023680, "326.1333333333333"], + [1697023740, "325.17777777777775"], + [1697023800, "326.1111111111111"], + [1697023860, "324.75555555555553"], + [1697023920, "324.66666666666663"], + [1697023980, "324.3555555555555"], + [1697024040, "326.0222222222222"], + [1697024100, "324.66666666666663"], + [1697024160, "325.99999999999994"], + [1697024220, "324.7777777777777"] + ], + "name": "tcp_sent" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/index.ts b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/index.ts new file mode 100644 index 0000000000..3988cf5a5d --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/index.ts @@ -0,0 +1,37 @@ +/* Inbound Metrics */ + +import inbound60 from './inbound/metrics_inbound_60.json'; +import inbound120 from './inbound/metrics_inbound_120.json'; +import inbound300 from './inbound/metrics_inbound_300.json'; +import inbound600 from './inbound/metrics_inbound_600.json'; +import inbound1800 from './inbound/metrics_inbound_1800.json'; +import inbound3600 from './inbound/metrics_inbound_3600.json'; +/* Outbound Metrics */ + +import outbound60 from './outbound/metrics_outbound_60.json'; +import outbound120 from './outbound/metrics_outbound_120.json'; +import outbound300 from './outbound/metrics_outbound_300.json'; +import outbound600 from './outbound/metrics_outbound_600.json'; +import outbound1800 from './outbound/metrics_outbound_1800.json'; +import outbound3600 from './outbound/metrics_outbound_3600.json'; + +export const travelAgencyMetrics = { + inbound: { + 60: inbound60, + 120: inbound120, + 300: inbound300, + 600: inbound600, + 1800: inbound1800, + 3600: inbound3600, + }, + outbound: { + 60: outbound60, + 120: outbound120, + 300: outbound300, + 600: outbound600, + 1800: outbound1800, + 3600: outbound3600, + }, +}; + +export default travelAgencyMetrics; diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_120.json new file mode 100644 index 0000000000..1084dd865d --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_120.json @@ -0,0 +1,16 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024190, "3.0666666666666673"], + [1697024220, "3.1334756314478835"], + [1697024250, "3.0000000000000004"], + [1697024280, "3.0666666666666673"], + [1697024310, "3.13326224118013"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_1800.json new file mode 100644 index 0000000000..240a8b90d2 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_1800.json @@ -0,0 +1,22 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022360, "3.0727272727272723"], + [1697022540, "3.0545359783060224"], + [1697022720, "3.072727272727272"], + [1697022900, "3.0484848484848484"], + [1697023080, "3.0728291168330446"], + [1697023260, "3.072727272727272"], + [1697023440, "3.0727272727272723"], + [1697023620, "3.0606060606060606"], + [1697023800, "2.9999692964648506"], + [1697023980, "3.0606060606060597"], + [1697024160, "3.0605915159448593"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_300.json new file mode 100644 index 0000000000..e4e25a85d0 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_300.json @@ -0,0 +1,22 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024010, "3.0666000222148173"], + [1697024040, "3.1333866773354675"], + [1697024070, "3.0000000000000004"], + [1697024100, "3.0666756171822707"], + [1697024130, "3.133244474064201"], + [1697024160, "3.000053774557241"], + [1697024190, "3.0666666666666673"], + [1697024220, "3.1334756314478835"], + [1697024250, "3.0000000000000004"], + [1697024280, "3.0666666666666673"], + [1697024310, "3.13326224118013"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_3600.json new file mode 100644 index 0000000000..2c501bbf59 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_3600.json @@ -0,0 +1,19 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "2.1670810770697164"], + [1697022000, "3.069565217391305"], + [1697022360, "3.0869611594471746"], + [1697022720, "3.0608695652173914"], + [1697023080, "3.069565217391305"], + [1697023440, "3.0695652173913044"], + [1697023800, "3.0318693727894255"], + [1697024160, "3.0637611596017598"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_60.json new file mode 100644 index 0000000000..0f2af383be --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_60.json @@ -0,0 +1,14 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024250, "3.0000000000000004"], + [1697024280, "3.0666666666666673"], + [1697024310, "3.13326224118013"] + ], + "name": "request_count" + } + ], + "request_error_count": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_600.json new file mode 100644 index 0000000000..f768e774be --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/metrics/outbound/metrics_outbound_600.json @@ -0,0 +1,198 @@ +{ + "grpc_received": null, + "grpc_sent": null, + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "3.066666666666666"], + [1697023740, "3.0666666666666664"], + [1697023800, "2.822109677148661"], + [1697023860, "3.0665718855518036"], + [1697023920, "3.066666666666666"], + [1697023980, "3.0666666666666664"], + [1697024040, "3.0666666666666664"], + [1697024100, "3.0666311158512194"], + [1697024160, "3.066613343997867"], + [1697024220, "3.0666311158512203"], + [1697024280, "3.066666666666667"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "4.850362318840522"], + [1697023740, "4.438372093023219"], + [1697023800, "4.504722864182922"], + [1697023860, "4.575570249658066"], + [1697023920, "4.5568840579709216"], + [1697023980, "4.083333333333391"], + [1697024040, "5.183333333333363"], + [1697024100, "6.056815740306035"], + [1697024160, "4.519259624156209"], + [1697024220, "4.197780125318008"], + [1697024280, "3.9851449275362336"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": null, + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "1397.1014492753623"], + [1697023740, "1391.0852713178294"], + [1697023800, "1396.6183165397285"], + [1697023860, "1394.9289248450104"], + [1697023920, "1394.927536231884"], + [1697023980, "1394.2028985507245"], + [1697024040, "1395.6521739130435"], + [1697024100, "1394.2034109857157"], + [1697024160, "1395.9862068965522"], + [1697024220, "1394.9280570674496"], + [1697024280, "1395.6521739130435"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "4284.444444444443"], + [1697023740, "3987.7777777777774"], + [1697023800, "4127.6258419284695"], + [1697023860, "4277.649823272714"], + [1697023920, "4277.777777777777"], + [1697023980, "4275.555555555556"], + [1697024040, "4280"], + [1697024100, "4275.507561954702"], + [1697024160, "4249.928014397121"], + [1697024220, "4277.729784176925"], + [1697024280, "4279.999999999999"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "1384.7826086956525"], + [1697023740, "1408.139534883721"], + [1697023800, "1343.996157532804"], + [1697023860, "1384.7929561031394"], + [1697023920, "1384.782608695652"], + [1697023980, "1384.7826086956522"], + [1697024040, "1384.7826086956527"], + [1697024100, "1384.7864897606667"], + [1697024160, "1386.5022441160374"], + [1697024220, "1384.7864897606673"], + [1697024280, "1384.7826086956522"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "4246.666666666667"], + [1697023740, "4036.666666666666"], + [1697023800, "3972.1040498949824"], + [1697023860, "4246.567146496061"], + [1697023920, "4246.666666666666"], + [1697023980, "4246.666666666667"], + [1697024040, "4246.666666666668"], + [1697024100, "4246.629338310448"], + [1697024160, "4221.055122308871"], + [1697024220, "4246.629338310448"], + [1697024280, "4246.666666666666"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "1.733333333333333"], + [1697023740, "1.733333333333333"], + [1697023800, "1.733333333333333"], + [1697023860, "1.733333333333333"], + [1697023920, "1.733333333333333"], + [1697023980, "1.733333333333333"], + [1697024040, "1.733333333333333"], + [1697024100, "1.733333333333333"], + [1697024160, "1.733333333333333"], + [1697024220, "1.733333333333333"], + [1697024280, "1.733333333333333"] + ], + "name": "tcp_closed" + } + ], + "tcp_opened": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "1.733333333333333"], + [1697023740, "1.733333333333333"], + [1697023800, "1.733333333333333"], + [1697023860, "1.733333333333333"], + [1697023920, "1.733333333333333"], + [1697023980, "1.733333333333333"], + [1697024040, "1.733333333333333"], + [1697024100, "1.733333333333333"], + [1697024160, "1.733333333333333"], + [1697024220, "1.733333333333333"], + [1697024280, "1.733333333333333"] + ], + "name": "tcp_opened" + } + ], + "tcp_received": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "958.2888888888888"], + [1697023740, "957.6222222222221"], + [1697023800, "958.1555555555556"], + [1697023860, "957.3999999999999"], + [1697023920, "957.4444444444443"], + [1697023980, "957.2444444444443"], + [1697024040, "958.2444444444443"], + [1697024100, "957.511111111111"], + [1697024160, "958.1333333333333"], + [1697024220, "957.3333333333333"], + [1697024280, "957.7555555555554"] + ], + "name": "tcp_received" + } + ], + "tcp_sent": [ + { + "labels": {}, + "datapoints": [ + [1697023680, "326.1333333333333"], + [1697023740, "325.17777777777775"], + [1697023800, "326.1111111111111"], + [1697023860, "324.75555555555553"], + [1697023920, "324.66666666666663"], + [1697023980, "324.3555555555555"], + [1697024040, "326.0222222222222"], + [1697024100, "324.66666666666663"], + [1697024160, "325.99999999999994"], + [1697024220, "324.7777777777777"], + [1697024280, "325.26666666666665"] + ], + "name": "tcp_sent" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/tls.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/tls.json new file mode 100644 index 0000000000..ea7db32cbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-agency/tls.json @@ -0,0 +1 @@ +{ "status": "MTLS_NOT_ENABLED", "autoMTLSEnabled": true, "minTLS": "" } diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-health.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/app.json similarity index 69% rename from plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-health.json rename to plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/app.json index 2c1939bc20..2800f2b95a 100644 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-control-health.json +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/app.json @@ -9,6 +9,10 @@ "syncedProxies": 1 } ], - "requests": { "inbound": {}, "outbound": {}, "healthAnnotations": {} } + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } } } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/service.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/service.json new file mode 100644 index 0000000000..c3873c7c04 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/service.json @@ -0,0 +1,9 @@ +{ + "control": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/workload.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/workload.json new file mode 100644 index 0000000000..6b1c459332 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/health/workload.json @@ -0,0 +1,16 @@ +{ + "control": { + "workloadStatus": { + "name": "control", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_120.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_120.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_1800.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_1800.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_300.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_300.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_3600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_3600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_60.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_60.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/inbound/metrics_inbound_600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/index.ts b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/index.ts new file mode 100644 index 0000000000..3e1973c9ba --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/index.ts @@ -0,0 +1,37 @@ +/* Inbound Metrics */ + +import inbound60 from './inbound/metrics_inbound_60.json'; +import inbound120 from './inbound/metrics_inbound_120.json'; +import inbound300 from './inbound/metrics_inbound_300.json'; +import inbound600 from './inbound/metrics_inbound_600.json'; +import inbound1800 from './inbound/metrics_inbound_1800.json'; +import inbound3600 from './inbound/metrics_inbound_3600.json'; +/* Outbound Metrics */ + +import outbound60 from './outbound/metrics_outbound_60.json'; +import outbound120 from './outbound/metrics_outbound_120.json'; +import outbound300 from './outbound/metrics_outbound_300.json'; +import outbound600 from './outbound/metrics_outbound_600.json'; +import outbound1800 from './outbound/metrics_outbound_1800.json'; +import outbound3600 from './outbound/metrics_outbound_3600.json'; + +export const travelControlMetrics = { + inbound: { + 60: inbound60, + 120: inbound120, + 300: inbound300, + 600: inbound600, + 1800: inbound1800, + 3600: inbound3600, + }, + outbound: { + 60: outbound60, + 120: outbound120, + 300: outbound300, + 600: outbound600, + 1800: outbound1800, + 3600: outbound3600, + }, +}; + +export default travelControlMetrics; diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_120.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_120.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_1800.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_1800.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_300.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_300.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_3600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_3600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_60.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_60.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/metrics/outbound/metrics_outbound_600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-control/tls.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/tls.json new file mode 100644 index 0000000000..ea7db32cbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-control/tls.json @@ -0,0 +1 @@ +{ "status": "MTLS_NOT_ENABLED", "autoMTLSEnabled": true, "minTLS": "" } diff --git a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-health.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/app.json similarity index 69% rename from plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-health.json rename to plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/app.json index 0a99412007..bd0b97a47f 100644 --- a/plugins/kiali-backend/__fixtures__/data/namespaces/travel-portal-health.json +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/app.json @@ -10,8 +10,16 @@ } ], "requests": { - "inbound": { "http": { "200": 0.7965811965811965 } }, - "outbound": { "http": { "200": 0.26666666666666666 } }, + "inbound": { + "http": { + "200": 0.7885940361045931 + } + }, + "outbound": { + "http": { + "200": 0.2620244359339096 + } + }, "healthAnnotations": {} } }, @@ -27,7 +35,11 @@ ], "requests": { "inbound": {}, - "outbound": { "http": { "200": 0.26666666666666666 } }, + "outbound": { + "http": { + "200": 0.26284883186391517 + } + }, "healthAnnotations": {} } }, @@ -43,7 +55,11 @@ ], "requests": { "inbound": {}, - "outbound": { "http": { "200": 0.2632478632478632 } }, + "outbound": { + "http": { + "200": 0.26372076830676827 + } + }, "healthAnnotations": {} } } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/service.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/service.json new file mode 100644 index 0000000000..bf448926fc --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/service.json @@ -0,0 +1,23 @@ +{ + "travels": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "viaggi": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "voyages": { + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/workload.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/workload.json new file mode 100644 index 0000000000..db0f480e3d --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/health/workload.json @@ -0,0 +1,44 @@ +{ + "travels": { + "workloadStatus": { + "name": "travels", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "viaggi": { + "workloadStatus": { + "name": "viaggi", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + }, + "voyages": { + "workloadStatus": { + "name": "voyages", + "desiredReplicas": 1, + "currentReplicas": 1, + "availableReplicas": 1, + "syncedProxies": 1 + }, + "requests": { + "inbound": {}, + "outbound": {}, + "healthAnnotations": {} + } + } +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_120.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_120.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_1800.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_1800.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_300.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_300.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_3600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_3600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_60.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_60.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_600.json new file mode 100644 index 0000000000..c7215c95e7 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/inbound/metrics_inbound_600.json @@ -0,0 +1 @@ +{ "request_count": null, "request_error_count": null } diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/index.ts b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/index.ts new file mode 100644 index 0000000000..c2fb5a67a9 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/index.ts @@ -0,0 +1,37 @@ +/* Inbound Metrics */ + +import inbound60 from './inbound/metrics_inbound_60.json'; +import inbound120 from './inbound/metrics_inbound_120.json'; +import inbound300 from './inbound/metrics_inbound_300.json'; +import inbound600 from './inbound/metrics_inbound_600.json'; +import inbound1800 from './inbound/metrics_inbound_1800.json'; +import inbound3600 from './inbound/metrics_inbound_3600.json'; +/* Outbound Metrics */ + +import outbound60 from './outbound/metrics_outbound_60.json'; +import outbound120 from './outbound/metrics_outbound_120.json'; +import outbound300 from './outbound/metrics_outbound_300.json'; +import outbound600 from './outbound/metrics_outbound_600.json'; +import outbound1800 from './outbound/metrics_outbound_1800.json'; +import outbound3600 from './outbound/metrics_outbound_3600.json'; + +export const travelPortalMetrics = { + inbound: { + 60: inbound60, + 120: inbound120, + 300: inbound300, + 600: inbound600, + 1800: inbound1800, + 3600: inbound3600, + }, + outbound: { + 60: outbound60, + 120: outbound120, + 300: outbound300, + 600: outbound600, + 1800: outbound1800, + 3600: outbound3600, + }, +}; + +export default travelPortalMetrics; diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_120.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_120.json new file mode 100644 index 0000000000..7b786affa9 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_120.json @@ -0,0 +1,28 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024340, "0.8"], + [1697024370, "0.7999999999999999"], + [1697024400, "0.7999999999999999"], + [1697024430, "0.7999999999999999"], + [1697024460, "0.7998933901918976"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697024340, "0"], + [1697024370, "0"], + [1697024400, "0"], + [1697024430, "0"], + [1697024460, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_1800.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_1800.json new file mode 100644 index 0000000000..7138de3b07 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_1800.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697022540, "0.7999999999999998"], + [1697022720, "0.7818181818181817"], + [1697022900, "0.793939393939394"], + [1697023080, "0.812121212121212"], + [1697023260, "0.800003526234912"], + [1697023440, "0.7939393939393938"], + [1697023620, "0.8181818181818182"], + [1697023800, "0.7999999999999998"], + [1697023980, "0.8060606060606059"], + [1697024160, "0.7939436915381931"], + [1697024340, "0.7999999999999998"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697022540, "0"], + [1697022720, "0"], + [1697022900, "0"], + [1697023080, "0"], + [1697023260, "0"], + [1697023440, "0"], + [1697023620, "0"], + [1697023800, "0"], + [1697023980, "0"], + [1697024160, "0"], + [1697024340, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_300.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_300.json new file mode 100644 index 0000000000..a738918bed --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_300.json @@ -0,0 +1,40 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024160, "0.8"], + [1697024190, "0.7999999999999999"], + [1697024220, "0.7999999999999999"], + [1697024250, "0.7999999999999999"], + [1697024280, "0.7999999999999999"], + [1697024310, "0.7999999999999999"], + [1697024340, "0.8"], + [1697024370, "0.7999999999999999"], + [1697024400, "0.7999999999999999"], + [1697024430, "0.7999999999999999"], + [1697024460, "0.7998933901918976"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697024160, "0"], + [1697024190, "0"], + [1697024220, "0"], + [1697024250, "0"], + [1697024280, "0"], + [1697024310, "0"], + [1697024340, "0"], + [1697024370, "0"], + [1697024400, "0"], + [1697024430, "0"], + [1697024460, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_3600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_3600.json new file mode 100644 index 0000000000..4e6646498e --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_3600.json @@ -0,0 +1,34 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0.5743865938180828"], + [1697022000, "0.7942028985507248"], + [1697022360, "0.8"], + [1697022720, "0.791304347826087"], + [1697023080, "0.8057971014492753"], + [1697023440, "0.8000016383255255"], + [1697023800, "0.8"], + [1697024160, "0.8000000000000002"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697021640, "0"], + [1697022000, "0"], + [1697022360, "0"], + [1697022720, "0"], + [1697023080, "0"], + [1697023440, "0"], + [1697023800, "0"], + [1697024160, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_60.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_60.json new file mode 100644 index 0000000000..f4d2cc631f --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_60.json @@ -0,0 +1,24 @@ +{ + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697024400, "0.7999999999999999"], + [1697024430, "0.7999999999999999"], + [1697024460, "0.7998933901918976"] + ], + "name": "request_count" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697024400, "0"], + [1697024430, "0"], + [1697024460, "0"] + ], + "name": "request_error_count" + } + ] +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_600.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_600.json new file mode 100644 index 0000000000..0e48ec8977 --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/metrics/outbound/metrics_outbound_600.json @@ -0,0 +1,144 @@ +{ + "grpc_received": null, + "grpc_sent": null, + "request_count": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "0.8444444444444442"], + [1697023920, "0.7777777777777777"], + [1697023980, "0.7777777777777778"], + [1697024040, "0.7777881488395523"], + [1697024100, "0.7999911115061552"], + [1697024160, "0.7999999999999999"], + [1697024220, "0.7999999999999998"], + [1697024280, "0.7999999999999998"], + [1697024340, "0.8"], + [1697024400, "0.8"], + [1697024460, "0.799976300509539"] + ], + "name": "request_count" + } + ], + "request_duration_millis": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "8.619444444444401"], + [1697023920, "9.27083333333308"], + [1697023980, "8.147222222222279"], + [1697024040, "10.913917295711464"], + [1697024100, "11.169437376778708"], + [1697024160, "10.068055555555624"], + [1697024220, "8.520833333333282"], + [1697024280, "8.076388888889193"], + [1697024340, "9.351388888888751"], + [1697024400, "8.795833333333498"], + [1697024460, "9.931840961833021"] + ], + "stat": "avg", + "name": "request_duration_millis" + } + ], + "request_error_count": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "0"], + [1697023920, "0"], + [1697023980, "0"], + [1697024040, "0"], + [1697024100, "0"], + [1697024160, "0"], + [1697024220, "0"], + [1697024280, "0"], + [1697024340, "0"], + [1697024400, "0"], + [1697024460, "0"] + ], + "name": "request_error_count" + } + ], + "request_size": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "1250.0000000000005"], + [1697023920, "1250.0000000000002"], + [1697023980, "1250"], + [1697024040, "1250"], + [1697024100, "1250.0000000000002"], + [1697024160, "1250"], + [1697024220, "1250.0000000000002"], + [1697024280, "1250.0000000000002"], + [1697024340, "1250"], + [1697024400, "1249.9999999999998"], + [1697024460, "1250"] + ], + "stat": "avg", + "name": "request_size" + } + ], + "request_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "1000.0000000000001"], + [1697023920, "1000"], + [1697023980, "1000"], + [1697024040, "1000.014815802535"], + [1697024100, "999.9888893826942"], + [1697024160, "1000"], + [1697024220, "1000"], + [1697024280, "1000"], + [1697024340, "1000"], + [1697024400, "999.9999999999999"], + [1697024460, "999.9703756369238"] + ], + "name": "request_throughput" + } + ], + "response_size": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "2416.666666666667"], + [1697023920, "2416.666666666667"], + [1697023980, "2416.6666666666665"], + [1697024040, "2416.663456623676"], + [1697024100, "2416.665679045266"], + [1697024160, "2416.6666666666665"], + [1697024220, "2416.666666666667"], + [1697024280, "2416.666666666667"], + [1697024340, "2416.666666666666"], + [1697024400, "2419.444444444444"], + [1697024460, "2416.666172912655"] + ], + "stat": "avg", + "name": "response_size" + } + ], + "response_throughput": [ + { + "labels": {}, + "datapoints": [ + [1697023860, "1933.333333333333"], + [1697023920, "1933.3333333333333"], + [1697023980, "1933.333333333333"], + [1697024040, "1933.3594091457946"], + [1697024100, "1933.3110627182"], + [1697024160, "1933.333333333333"], + [1697024220, "1933.333333333333"], + [1697024280, "1933.333333333333"], + [1697024340, "1933.333333333333"], + [1697024400, "1935.5555555555552"], + [1697024460, "1933.2756645732115"] + ], + "name": "response_throughput" + } + ], + "tcp_closed": null, + "tcp_opened": null, + "tcp_received": null, + "tcp_sent": null +} diff --git a/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/tls.json b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/tls.json new file mode 100644 index 0000000000..ea7db32cbb --- /dev/null +++ b/plugins/kiali/dev/__fixtures__/namespaces/travel-portal/tls.json @@ -0,0 +1 @@ +{ "status": "MTLS_NOT_ENABLED", "autoMTLSEnabled": true, "minTLS": "" } diff --git a/plugins/kiali/dev/__fixtures__/status.json b/plugins/kiali/dev/__fixtures__/status.json deleted file mode 100644 index 68f9a26bbd..0000000000 --- a/plugins/kiali/dev/__fixtures__/status.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "status": { - "status": { - "Kiali commit hash": "3987a086369a9e1a4c5705ca84d4bd8db64642e9", - "Kiali container version": "v1.74.0-SNAPSHOT", - "Kiali state": "running", - "Kiali version": "v1.74.0-SNAPSHOT", - "Mesh name": "Istio", - "Mesh version": "1.18.2" - }, - "externalServices": [ - { "name": "Istio", "version": "1.18.2" }, - { "name": "Prometheus", "version": "2.41.0" }, - { "name": "Kubernetes", "version": "v1.26.3+b404935" }, - { "name": "Grafana" }, - { "name": "Jaeger" } - ], - "warningMessages": [], - "istioEnvironment": { "isMaistra": false, "istioAPIEnabled": true } - }, - "auth": { "strategy": "anonymous", "sessionInfo": {} } -} diff --git a/plugins/kiali/dev/index.tsx b/plugins/kiali/dev/index.tsx index 289cd2beba..e12f04e029 100644 --- a/plugins/kiali/dev/index.tsx +++ b/plugins/kiali/dev/index.tsx @@ -5,112 +5,254 @@ import { createDevApp } from '@backstage/dev-utils'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { TestApiProvider } from '@backstage/test-utils'; -import { - FetchResponse, - FetchResponseWrapper, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { KialiApi, kialiApiRef } from '../src/api'; import { KialiPage, kialiPlugin } from '../src/plugin'; -import overviewJson from './__fixtures__/1-overview.json'; -import configJson from './__fixtures__/config.json'; -import namespacesJson from './__fixtures__/namespaces.json'; -import statusJson from './__fixtures__/status.json'; - -const mockEntity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'backstage', - description: 'backstage.io', - annotations: { - 'backstage.io/kubernetes-namespace': 'istio-system,bookinfo', - }, - }, - spec: { - lifecycle: 'production', - type: 'service', - owner: 'user:guest', - }, -}; +import { KialiApi, kialiApiRef } from '../src/services/Api'; +import { KialiProvider } from '../src/store/KialiProvider'; +import { AuthInfo } from '../src/types/Auth'; +import { CertsInfo } from '../src/types/CertsInfo'; +import { DurationInSeconds, TimeInSeconds } from '../src/types/Common'; +import { + AppHealth, + NamespaceAppHealth, + NamespaceServiceHealth, + NamespaceWorkloadHealth, + ServiceHealth, + WorkloadHealth, +} from '../src/types/Health'; +import { IstioConfigsMap } from '../src/types/IstioConfigList'; +import { + CanaryUpgradeStatus, + OutboundTrafficPolicy, + ValidationStatus, +} from '../src/types/IstioObjects'; +import { + ComponentStatus, + IstiodResourceThresholds, +} from '../src/types/IstioStatus'; +import { IstioMetricsMap } from '../src/types/Metrics'; +import { IstioMetricsOptions } from '../src/types/MetricsOptions'; +import { Namespace } from '../src/types/Namespace'; +import { ServerConfig } from '../src/types/ServerConfig'; +import { StatusState } from '../src/types/StatusState'; +import { TLSStatus } from '../src/types/TLSStatus'; +import { filterNsByAnnotation } from '../src/utils/entityFilter'; +import { kialiData } from './__fixtures__'; +import { mockEntity } from './mockEntity'; class MockKialiClient implements KialiApi { - readonly resource: FetchResponse; - readonly status: FetchResponse; - readonly config: FetchResponse; - readonly namespaces: FetchResponse; - - constructor( - fixtureData: any, - status: any = statusJson, - config: any = configJson, - namespaces: any = namespacesJson, - ) { - this.resource = fixtureData; - this.status = status; - this.config = config; - this.namespaces = namespaces; - } - - setEntity(_: Entity): void {} - - async getConfig(): Promise { - return { - errors: [], - warnings: [], - response: this.config, - }; + private entity?: Entity; + + constructor() { + this.entity = undefined; + } + + setEntity(entity?: Entity): void { + this.entity = entity; + } + + async status(): Promise { + return kialiData.status; } - async getNamespaces(): Promise { - return { - errors: [], - warnings: [], - response: this.namespaces, + async getAuthInfo(): Promise { + return kialiData.auth; + } + async getStatus(): Promise { + return kialiData.status; + } + + async getNamespaces(): Promise { + return filterNsByAnnotation( + kialiData.namespaces as Namespace[], + this.entity, + ); + } + + async getServerConfig(): Promise { + return kialiData.config; + } + + async getNamespaceAppHealth( + namespace: string, + duration: DurationInSeconds, + cluster?: string, + queryTime?: TimeInSeconds, + ): Promise { + const ret: NamespaceAppHealth = {}; + const params: any = { + type: 'app', + rateInterval: `${String(duration)}s`, + queryTime: String(queryTime), + clusterName: cluster, }; + const data = kialiData.namespacesData[namespace].health[params.type]; + Object.keys(data).forEach(k => { + ret[k] = AppHealth.fromJson(namespace, k, data[k], { + rateInterval: duration, + hasSidecar: true, + hasAmbient: false, + }); + }); + return ret; } - async getInfo(): Promise { - return { - errors: [], - warnings: [], - response: this.status, + async getNamespaceServiceHealth( + namespace: string, + duration: DurationInSeconds, + cluster?: string, + queryTime?: TimeInSeconds, + ): Promise { + const ret: NamespaceServiceHealth = {}; + const params: any = { + type: 'service', + rateInterval: `${String(duration)}s`, + queryTime: String(queryTime), + clusterName: cluster, }; + const data = kialiData.namespacesData[namespace].health[params.type]; + Object.keys(data).forEach(k => { + ret[k] = ServiceHealth.fromJson(namespace, k, data[k], { + rateInterval: duration, + hasSidecar: true, + hasAmbient: false, + }); + }); + return ret; } - async getOverview(): Promise { - return { - errors: [], - warnings: [], - response: this.resource, + async getNamespaceWorkloadHealth( + namespace: string, + duration: DurationInSeconds, + cluster?: string, + queryTime?: TimeInSeconds, + ): Promise { + const ret: NamespaceWorkloadHealth = {}; + const params: any = { + type: 'workload', + rateInterval: `${String(duration)}s`, + queryTime: String(queryTime), + clusterName: cluster, }; + const data = kialiData.namespacesData[namespace].health[params.type]; + Object.keys(data).forEach(k => { + ret[k] = WorkloadHealth.fromJson(namespace, k, data[k], { + rateInterval: duration, + hasSidecar: true, + hasAmbient: false, + }); + }); + return ret; + } + + async getNamespaceTls( + namespace: string, + cluster?: string, + ): Promise { + const queryParams: any = {}; + if (cluster) { + queryParams.clusterName = cluster; + } + return kialiData.namespacesData[namespace].tls; + } + + async getMeshTls(cluster?: string): Promise { + const queryParams: any = {}; + if (cluster) { + queryParams.clusterName = cluster; + } + return kialiData.meshTls; + } + + async getOutboundTrafficPolicyMode(): Promise { + return kialiData.outboundTrafficPolicy; + } + + async getCanaryUpgradeStatus(): Promise { + return kialiData.meshCanaryStatus; + } + + async getIstiodResourceThresholds(): Promise { + return kialiData.meshIstioResourceThresholds; + } + + async getConfigValidations(cluster?: string): Promise { + const queryParams: any = {}; + if (cluster) { + queryParams.clusterName = cluster; + } + return kialiData.istioValidations; + } + + async getAllIstioConfigs( + namespaces: string[], + objects: string[], + validate: boolean, + labelSelector: string, + workloadSelector: string, + cluster?: string, + ): Promise { + const params: any = + namespaces && namespaces.length > 0 + ? { namespaces: namespaces.join(',') } + : {}; + if (objects && objects.length > 0) { + params.objects = objects.join(','); + } + if (validate) { + params.validate = validate; + } + if (labelSelector) { + params.labelSelector = labelSelector; + } + if (workloadSelector) { + params.workloadSelector = workloadSelector; + } + if (cluster) { + params.clusterName = cluster; + } + return kialiData.istioConfig; + } + + async getNamespaceMetrics( + namespace: string, + params: IstioMetricsOptions, + ): Promise> { + return kialiData.namespacesData[namespace].metrics[params.direction][ + params.duration as number + ]; + } + + async getIstioStatus(cluster?: string): Promise { + const queryParams: any = {}; + if (cluster) { + queryParams.clusterName = cluster; + } + return kialiData.istioStatus; + } + + async getIstioCertsInfo(): Promise { + return kialiData.istioCertsInfo; + } + isDevEnv(): boolean { + return true; } } +// @ts-expect-error +const MockProvider = ({ children }) => ( + + + {children} + + +); createDevApp() .registerPlugin(kialiPlugin) .addPage({ element: ( - - - - - - ), - title: 'Kiali Plugin Page', - path: '/kiali', - }) - .addPage({ - element: ( - - - - - + + + ), title: 'Overview Page', path: '/kiali/overview', diff --git a/plugins/kiali/dev/mockEntity.ts b/plugins/kiali/dev/mockEntity.ts new file mode 100644 index 0000000000..38ef109873 --- /dev/null +++ b/plugins/kiali/dev/mockEntity.ts @@ -0,0 +1,19 @@ +import { Entity } from '@backstage/catalog-model'; + +export const mockEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'backstage', + description: 'backstage.io', + annotations: { + 'backstage.io/kubernetes-namespace': + 'istio-system,bookinfo,travel-agency,travel-portal,travel-control', + }, + }, + spec: { + lifecycle: 'production', + type: 'service', + owner: 'user:guest', + }, +}; diff --git a/plugins/kiali/images/overview_tab.png b/plugins/kiali/images/overview_tab.png index 5a94aca9f916caee0bf29ef43292b3b183821889..e64e5ed802f15e0e307658a6728692810c1c6e00 100644 GIT binary patch literal 173330 zcmXt<8VO@b7c(Bkf{#a)WKyF(~$fdB~*F2DP~ zch=0BN#^6b&YrWMeclKSH3fVeG8_~X6nw?cvRWu8Sk5RYXgM!2pZ_5%5u<*-p}R>d zetG$P_`m!X{`{NNT~6O!+u7RP%iPrp#m33m(TdH@($&h!$<5Z;{T!`J90lbAilVI4 z7w?Rd4xeU6Jsk!_y1l^unz#TqtpL8=XX+QUKjU8U{gfZ!mc%nN;NPsvM+ZT_EAJnS1cemV0{qjSj2i9tN73iIaiCl*aPtcvbGs4`)1)h_WEL^C1v5a_;FsM=a$Lp_tiKWP09Zc4+S&YElwrIeofn`Q}C+JeH~Wp;`pQ zjDu&^H(QEt!f<1!csxSA_g2TbYqR&qQtywcL9?myK~xmdf7T<}8>Wfs9|pyj9|vZ1 zvbdqUzr>06yw(2RMTJOb%LUf5?s>n8zWE4V%a^zDF${&~?yNk6JmCLe5tiz{Ad)~)4;TldI&G>X7Hp@VF zM_$F5 z9Yj}Py?Gm+Fe#;QL5ulFSf#>ArdqQAqg%=yzLY3GQ$IXuwY(AYyBM*l0#+SuAA&XXkA5(S4K&A38)UCGZgYnL5e$0*>?p} zwf`y{7@UHqs>Jo@L_67i@yG?7U#{)HZq|l(t(LX`ejkJFUB>@r(`#YfTn@b5;SU1{ z=k?@-_2i_Do!*jp8uGSyj~~Z#wII$tT?{j%qjV~8-7gH57^aZ!=2crL{-F}51qb!q z8g$`GpNhqoh880vxWCLyQF{m^%}j{bRvcb_4Mx&~Cvc9St;gy#nau;D8Vm6)YY|*& zD%Sd>4W?r$yss%MCM~#CO1k=K{pz!i-hRHP>z5Qdb?HT{8LYd|5hY}&PICTPRx}Bx zn|e_iz{|4s&|>F57wGhrP4LyY^Z@+_sl4{{hl9O?riuRl%9i!FNVZ@Y+1z`RqJMH| ztiFW5ZOE!~7fBJKdQB-uV|m6hR9>eBG3t3<`%+@U*T$>)N5|n?xjXY}yGj)C`+Y)g z0t;jfcj&D6R1hyKu4K1VgkoNi)F%OTmNOwVI#9 z-y(;-Lx;V8e*{VQ@QnC<^2K%k^sEguOueTmA}+3;E7rLSMI`=>4-uDqQUR9Dyeb!7 zKdy$6jPxM(@w(0>n1a=(Dde3KVFT{W_B3Lu#_a25!+j&IAFpO|LFmn8zekTZ7-vlu zs0MMi#c~!!6`;<^Xm;XGwGhqP=|*+@+#e6eo8)Vcp?%o8)qSl3^;nezqg8=SuBQ@BRAKlVk)qIn6WlARxGZSROx7fK!Iv!r`VxW9adte%A>gBum&T6PS zy@^>nm;4a-pDa^0TdeGttzK>v2JbD}$?4 z=o+I=<-lRlp_GSrzTk`HcZ|{hzC%1VknXAmM|r67O`f)2OjqW@V3M-P#_WdCSgh8@ z$+~sw*UZYXE{_I#hu>DTkUBMBho(NO5s@Ean!R@=b}VTBEZe_*79SW&{?;v-fyTn39j!+%1!Dm3q0eub zd^@Ev70qGiYYNk2h(I!ap>{2)Yqh+4_bB{(Z(p+LK^&uAh8<%XH}WnOihOY2{vZOc z5U@CvHUzw?zsgU}Z}PeNxA~o6`9D3QSh<8Q=Cw@>Qv`+iJd3dfG|{<;#YJgcuRN1S zDD!qL^F@%qC{_k2?sm{cOsk#gz3@9USaPw|h2Oa|m=L8#+0?eD_@gW9VrO(K<2|C1eTp&VNmI9>Ef>3a%+NxLln|a} ze!9NJV(LC1)oGR^ z`yacHdLB_jd!-uM(t`cjD=->LCo{FNZX)Uzj~#1&zi9wXv(%W+P6>H)pARc38N?@( zHg_UG`f{K-zK9^S@Vm-3WKfRKs!hyp6#Vzsty)NmN)bs+QHq+)7TwhBgwGhxIpwJ; z`S%$o1MG0uLu~V)bA`5}f03I3!Y1Vy@1raXG)YL!V7Ddx$+aXb4xr+GG~tz7ss5NT zNamxT46yR?jfjp15x6J!Db9k4pOHoLUiPStMS__sM(4-OnT)f#V6CU5@}<(!WhOna z;-T%YU$#>zIbWHm0XjNYRpCxD5rhId>Wg7{!i z+|ZRjx+XNSGfh)ABktwNlMuo*65ran=(3V*To)Oc3%uF6I<;XfH&OVH0G!9Q^a=fw zcH^la-SmpgCN8Cz7{PDFJ8gkvd=FPSc#p~=`kMJU-j=SomB;F|f<+Gwx;tC8(ZlYcd<+CXCtB@%G1a^h%(CV4 zbvET-Haf%RghJkl=7jIeY%|}r_8CtxuNFs$mA$LP@zay1&TrqG6~FNOD6xO`tt#Sa)-ED3k*SEqaZto4RWlxz*h-Tw@)KY;2!>W&|!wlF3dF%AsW7m3Fb~VK*TjFi-NCZP=#U1^}nE(PG49WR6CW&F2T9AyJJ|l7at4W{q<(slj*{k!OK|KF&rn19HE6TKQVr5TS95U^>MTg znlO3=SAzQ3AzJMI#49b$&`8b9*hgNm{91K3| z+Xs38;vaYuF*sq8F!KuynC!kXS7)$NRxQ=Vj%!$nG|Wm2UzwZ@6;=EEi>dhaF-6HiR*Lta)_TI z_s3Q#96nzv7Gh?O%IAmqOpLI6C$5h~+-f4sbkOc?^&8et$qC^%K4V%{fyltBNNtgO z_xa|gJc5aK)sM-f`zb7^_~WNMsjd`cm1g|Ax>Kwrk6Ty18wTl;l@Eu8bJTUThL+h( z7?>{drU50;;p~pW*Z=#alOb)?i-UPtdyHN{*9zx**>SW#xa-q{S&JgY_|9ZB~d0({K5B? z5yDt(CAlogkVtyl3Hn+d0faBuE#IHSct=*~0nLgJ{F4AW1SU>kic=Tuau^DWclq@oc_*n9w1XmL7fYw943cvmp zWUNvBtg{G4p+Sd#sSUAxMVydgkx5(>o|L-Xx>yY&^pWC|vC7PuH5(SR80}BG7Hx5U zJ`(*RQR}%wK~sJMyT)2$>k~BhF{OU@B{zW=Gh%7&SFq!7L;RxpBFvlD$_F9Heo}R# z`rb}TGTzRYeIZOP`B+gw=9~wXc0S>GdS9~Q&j`5i;*w5V=52zZr_?yv|V8w zzK<_P$&?pzOM))T5vHf(iEJFP-smvl zRMDG0G~-oEV|YF$QNp5Sb~IkHRm5RI-^d7hydCz0GEoI5B3kOwura@K)l%q3PVVRY zKowoCp=c~vrk*PU-F}+UuNy2+zn!+tYrmbYpG^;i3#J@zYGgABOFU&39dSH0x*)r% zYtdP;G)T=`-YCMfI{}`|aFH0MP>aymacF33I|g%Hv3TD^JBFTlRp1t;D*=&*L_?f! z$^D<0ojTIjDgP93UgQW9dg*6}eND3Z8`S@juO9c^b1*JM2kN0a5@xQn;e>HYLCS1IJEaqp3A+n;v}Zu^;i2-qcZ=a9At3~ zS4moGuiiWQk@3>|a*ny45ku2sKuHe~j&@36MdjG(>{t@y*{F$DSYWa1EjoT7PKA+; z%2hi+xDu9E;Po7#-K55kBDsBGj(`7*9w@xDYd@U>P0}V`ivPU&r1xm2E6IHXW*{L~ zw-)3jTa1U7DW6zC%Q=^?T(B_%_9_^4R|aAVPxLaXfV6@*mR zTG9^#g8U9E0mz)s;4VJYQyyJ&Rf6g$=w^cJb+&O9bmv$e#!w8Z%R25&o7*Qi*=4)? zkBlt56{_vWF`GI!Z=M^h*(^uMyoN89Wqm~sc@!1%bcWVWX6!W3Cd#g$?W-9}MeLQn zoxO-d!O6e$fTotWUKauW`BFJK!Llu%B*5;E>6c%RcIw(Gr~dh=DjF^+xRt*%?dMb! zakJB6%hWP(jV4g#G)OIqI*$S?+3r#OxbgbbOfSALi)`I#JMpd##*>SGS;m(Xc#ZfV z4&G=*PCf93gLQXVS0Uftq*K&18uw0*lgLeiGj1m@skL$B#PTp)%n&3as5b;G|+!J(KCAF^)I6!_{4wo z-PkWhvk2}Y2Gll42(VXVa$SUb{d2uFG9EP;7`QP&M?n$8m8#aer&QWh2)0B~Dh(bv zPnh8_$Q=JUQEa$8{2h?yueLu`fQ+nyi;3SsRX>3Uyf)+uHU>Gm-pt9O3$PkhY0X|IC{$xP+0gMHBC`f zRw?FD(Je|Dx26(#XKKB3~ln^@sOzq)cM+uxx$iOW`At20p%HeZh_hhBO|UV3BdezB1zz`_U( z272D-KNOu6tBidb4Av#f^(GH63Su3fOyrk^l5y1b>+|Jm6ODDBaPWPu80f+&@icD0 zN)*cwb=YXrUQGPBBGIseUkw#&zi?|u+Qf9;t$hDLM0WEFQ4CDT$vJtrt22O&9_<%; ze*{6~-%*EY%n>SxR;q63?YgB#gL@xidOgjBG+H zQ!Ra(V{jwKzpwvTEU6=pGf?BQi!y6Y&61Bzzf1Z>p3lI}ZQxXoujel|r)PltDrMOR*Wrj?c!5b?vWRr5pOX_`Bne4f zMIOL|;u8hYmmhabKRr&0&UxrxX0&-LxaDLeJemB@C8e5NQ$Lwi%1xD+@7Ky({vN=I zWr`f6h#40CFr5=BtP>-w=lJcndE2nUB!t<5bjsH>FP_%4&hy6Hr@va0bAheJS2@vy z^cBbF7kcoF_zyzYqrSXDTz{@^n|zF#X-c+O_(<%@GAg5s1$omX0T!hp0RKN;N^sLF z#MXy=)+kr%ZE}pk!ydOtK(tGOO^k%AM$$ulV0sv3+VZY}vf~ktB&Ov`zkTM?zQGyT zb*F938$4iNM>_b;2;Vi4=dz_W$uj~^9OMP3ac6!qvkTvgK^qoGb5P5Qezj+l32zFQ zu=w~m*&#YU+l6gtb2?ea+*xxvYx{dbvl?9!xREeJb+-~`QXOZrOMpnt=5-yK#%Q4G zx)|7mViwFLLr%~iUK^im6g+t4E#U3`gq`7X>I!5LoXL(}NKn{TZaIx~C>6K>-rv|Q zBkv~r=B@CT!f-nlVqAJN8!H)H-r9<0nYVNIJLGwyqr=kIvr5TZN7V|D2WX#TS7GDm zl?YtQZ$qiFw4Jy|602RhgeA2LGhm_jAVK14EQCmA!aYH`TKe5zg(#l?b+Y;F2;d!= zv#DiVP)un=H6PWD<>^YiMq$a71q1aSUjhqkCC`>sDO|ocY$8)r8u)io?k;R0tu}Qbh9;YlBg?D4xjZY z&Yoo)wv}`-zn`$ZMWfFzId`}ij!^fP`YSGzLF)PEj-TiXz@7kV5G$a#g4rmc{IjZC z37)&j#Ryro?^nKt*U2-k*pH+$(negG6S1ndts7)fEOMKyWAia(#gyLPb)Roaj!3nO z={qvxgaW(53inVEuzh|XgJDtX$H9^gz(2TEfR9%7W^d@Ya)51@jJ|~BrQ6_92!D0% zoEPTz%UY#Lw!wBok%+9jqvb%0ecbvw4j+n!=5esJM1q$Y8XUtQcv9R%Mhf%XzRtqY zkoF-0S{Sx?VtJW|eAVfxAJHDCz?9%-k_i^cvfwXAKYS;53{(y`ES~-Kp$g%k=Y2`_ zSGjDLKS(E4(E`!nVpe<_IIc$s(NK z`>NKDZtbF4P8r{a%cAB;C@$U$cLCa?M$ifWVrksf@gv#7-rH=j+e+C>Uak9(+o~Am z=a8s>R;I7FC@LRdY%Z%Y2tq+y*bVsdMrdsQ&?bE$PJe%|xD8yWxi-VPFng?`rT01+jWWmEuL&zvI1NcB6}&&R2|``@9(AQ3?YI#svm0aS{yPQr zHsvPN6fcIRv+(>%FxMdQJ7_K*YNXCPS&R77Js~Q7?r*vp3T$$bLRRgtWy|cO%j<7i znK93WvJz{QEs33KcLZLbv4U%_NVZX%$pywd6P07w8`G&gWX18#hd{&meSOT;`Ee3)IF6@&D&lQ4I+%tUaBNaz_st0>%qA;WQ9RY!lcDG zo-cJSWVk)vHO*+AxPMD0Y^`)vKDOK0z*Ksc9~E+@l8lTx#<5MyCLpp%Am(LHT90N6 zVIwM+Q0>CoGK`*B{3|};N?8A>^mSFyWg;+f8l5GqD6n4il2_`|TVVGW+sUrCSN?Vs z0S?e+x3&jy{0FQFO3M}HCvw}kUEx*eyEDdyRl!peb zY?dH(653Yo1V7o{c)rJlGg3?Dd!*4^>T}V3s~l^Dg!#xvL-xqO=DiX17L7&4I6h6-LjAo?O{zKIS)*40H%|NwU&XYz@&z&g5|h8sDS*F zev8ncg-uCk*I}9VY))r9U++Q`6DgpaPv*3g98>S}g+WCv z`g3unvX5snAkrGE&nTejhz^GF-G#Mt!-dL$AK)Q+Cmlyz^m~wM&IR-2wZ9r<-YdyG ze&WkpCC^@h5P6)q3q8zpk_-X>|9qEzWI1%G+%@ZG7Ei84du2^*i8&8r_2H%A-z~L! zG8N_zlv}@{t^(8}Ihx3I!Gl}QgWCZ7?r+@v?%ciZ+%iJ>le(fXKf zlycgGGizORVec>~75@J$fI_Q?QUPR`IxE8W@ed*@_9pZKd6P%#dbW+B(S_6O<5KAY z7%F5&wbJeh)9iBQPv4%(eUm>;(z}h)`;1d1slOMVf>W2{0KX|(@&kN(g&#@PYh%)k zcYPKi-{f36B_p)##gCCi1GB!b?#DW&tFL1$@91?%GulgLiR|FSrfaVN=n-q}9EBF> zy?J;z%Y*m6OhvX&_Ck``>ebDN)TU`~HVx3&VDtC79Pd)+GSH7J0r|5^nA|98z9@-O z71;CuAy2bWQ&*P~*Zu;9*akzdvvhu9FPz29K%0;DW96|D!nPTZ0dE^kz z<`OQk(=(V}BK@iQ)~xu>;P?^IOfz;lJAZJlKaQh!DY|cn$vbzPxX%Kp;NQ)KRwxwBuDv3v&X0DVemiDGf&rgwXn*B2LQ%vDa2dFOzMwk zs9a-MmhbUi zR=YHxK2q#v7za38x)LHX?8mrYkq*;;ZwS}+&5crv>gvEAp&kM^?94JCeREotf}w8p zlNl@fI{88))xv>6t@R4Mh`J41)!ls2dld#c`grW+U&?1!nDRS~9!}X5k6vcrLVOuk zHbT)Z{t^i$)H1h=7-qAW{}9i-gY&{r*Z=TN=`ha7XpgO47d6aVDyp9<$Q5iJp_zY_ zp%^UnALUW*^*K+zTqIsst1LkS4KU4iQ#T;}*%R}1J~0>mfMcsTd2%sLeL)KKDNv>q z=kqo}F>!GiZS_@Q@E+}~O`w%_)lxXW;5AF{;p|;Naul8u_ z?-n%W+b)zHe;;smj&d+$-7qw3_nTu)?i?KP=Xx`m%w?y53`3TftS;rsB%ss(Pm-SL zNmrNaryWAnleD~!x851~-oV&eA4S|E{RSQ|l-)f`NY)2U&Xei98-2e#zcYNezc@#e z-)N7aP}IYr4Z@Rsy`_*PRIm1VR@D`F-GhGiZ$9!17B?Za^hGOJFMmtf#IZ!y!<6aH zS1x-?4tb53ZRD}YtD33-*u74Y(?h?a`u4(--=$J`w7?U0xLPL4x?G=Bk#yeq!&|Lyh)aaF#xDYDDur zLKmqQnag4ouRAfbiNUdoJz@_^PA_`-P04+m7^^mf*6i}vmh0kZ z9``KXXAr-~7mp@{y@lewkDZ3AI-WY5&-{jQQ@>+4`4ub-;IPE$9>#Fom!0+;DM*T$ z$(i4CP{P!S6lc5#D7ub!WKZNl=V?t4L;Q_;g_&;5%_Pi4CbDj!HxoHqyEhnj ztC>><%@^Vd%4Gn0_IH9A(dAFh{a%WZAD7}-xgt7WOPPR{RV|c66G*7BpHkj$&cnCM z+W8rssDuoe8;9LOMN0cQiYy5pdbp~Sp2(GP0_7>M*^4JG%CsXJ))=;(*k4Nl- z#l7r@)8HzJvQhU`=~MNYfAyG;lsvV${acT}CXjCSGijY0&Ya}U_^4m(Q3ja}6%-Sq7wsXYl z(8JKhXXhyrw`UDPiJ3H36ER;ViYvHSCqIEuw-8wE5side~;E)|Y@sHEUxH?N6nPo^^go#l$! z@ehy|g_xd99^-y{0>81MXGvhPo+Y)`uGQK8Z<58uKiBC1=C{=DVQCS>c2YNTIoVq-)EB{gvMw8%W1z+(PbaxOIi}|Wr=*MHh`Bi`Yu}h-H>}2CwZGfymZ!A^ zl0In)T=v4GC?CwZKg=NA6LJrL>pyn}4gso=16m2*U)E8MuAVqWea_=vPguL6K(0zdei`qQN#CbY^q67&*LD4tjIcTQ;cPY260VeJauf6$c~jH0Vl) z4^2%?73QoTl#9Qe-!*O4MUAEQB-1f^pp4AUunPR3Go{+uKG-?z+ay0F3=2 zQMVycwZ02;-iY~1?N5|}HmWSvzD;Mdbz%wbbAL|SzD0r%3mwgHkv3F<_QSloA7|JK zy8_6wD@J9HqCt*_^yEBKqPz2_JHGadKFf=@93a#zA;A|UQrJ>%^;hgD6y!;m z-o0>}tqRN7z6w?sGtvI1R=CNU|C7@Xf_8Xboy)&inoFd*Nw5K6VbNz&;{701BSv=X zrRV>Bxq&V6!`1=ldENCm3llsnIlU`A_xZT-RLZgY$L1!OYpX_;{Gu*7D~!+|Vc5PO zlZ=9rgoTFZi*Xa9i-JPkiAZ09&GDG65b#su#JDDy8D%8v*m*CV@2V1Kg;1{Y2fM|Z zRCn8S0tK7^7hLUk1(bIYQKkW6VY+xNPskz>m(6_jU4X;`j%erYhky|wa1oFH1486t zE8g#7)wvV>3BJ3ywPkB0(eJXOxq6w;AwmAM_ttb^vg^j*ADMLTaxVi8<_guzbbG@t z4ikJXKJSR?^+!(R-q+h8t}SDU>kg7@zZ5$Wwp$9L9?;X^akt z^~`&$mmOh~R5LHiLVN1)k{VbtKM44=_~{}Ypk{&Ly?;K&^H6Qn9DZ!O(#jOFynMCz zj^ijee$msrWWUdk(b>X4xQcvlpkGg)GY8_-IpPdpSFE(fj4$@o>Kg~~AE>YqA`Wi# z0q>paoH4MUezs=tn0$Wv_Cd5Aw=hc%(4Z&nC6=l*Pucs!qDJH+2JTfV3rpr67ZzYI zl}kHoEA{J$_&63;GwLDc%bdFH@C(ObJux;~4!les7TnKD&Lf-f=+g|>tZDj5r{MJD z$=4ept~Dbec|0cf5*%HIkU9l`P|=gxd*WBG-8a4uymjJz9t8?rAxG(;lBpIz;p}!d zv=C0U;Ai7Xp9q5ba!D4ozQvMwyW2cPn$5gVmdys5&$>lm@H?4QdAk&mRIh@tsw2_# z_9lmW|CJiGf;@*g9%I6k2y9OiLsrfwdieg}a2irSq>!147>881hG@L)n!Wj`3)I?} zmyK~O?L}lTNHEy_!}&y6h_(GsFUA6+$^d}fO=RmJ5%BXC=buopz)qN zA^9#k>s2&nETW25XGVjE@o?jcj7`|1dI!tr|U&>a2<0OrIo)3#n>OmW#nN z89U}ehN9*K!c?LxhPn#-7T=$T`(0md9?%kqwuj(Z8Nfp#DNN7G_TFGuEllhcnp5{m zQRn~XYUmq~A;#{}w6sSdW}gArSuM=*_z`tHqO%%MZ{OsEoG2}H~^)GS| zkjcuntZq|(a|V=9^RDhy(3Z6S{-s#+WA5PPY_%ZqrjG>&@OXqHhxOwoDvH47(@T`l zY4=d&NpaGrhe|&blzG}$N739yu$ZBI1ux9&7U_0E(W*aQwC-eBSJaD^%`soeI zR^BXlP;qiOw(Ai(4sH2vq1Z7hZ8GQk-dDiqN2p%s7A*yd>c#z{|2vc)eG97IqlFv; znQd7pDE{}`gO|j5px)m;qoH&ZWa<~4>P9rxV&^TZmsi!o#7~F$db{a_qM=u6D}fxF zHx{ z;#<-l$x6B!VG=VB%RUG>lX@Ie!sSaTsi4Jlb~@BO`vhNs*B^Pqp_Fg(D=haMrHdz9 zW_UaVta=*SX*iswtQL==0vQt|EQFo&DyZc%bzo7@Be4W^Swpp-T|{rc`VL$1SKq5f zF)Ri{Se&@fr_NzDR@#L5m-WK9O+7doh0gKL(OQ!i7fvdL37v}NQkZ!*x;3=o0N`d9C* zc(|c3{n076+h*bxINEcx7q_;n;^SRZ^IDh=CxflYr!e0D!9*-NXUl!T$ozg`1(MN3gKoA!{ft9?1p4z z+~i34C?#zsXOry3qE?kY5QOmKTo{qQC5pf@)UN2}(@DXqMA$8C{!S)|QK~SD?5PPW zl%jqV&+PLMJ#%5wglV_9q9Ni$@l_O7Uk@RzGLxFR)K}i&!!TmqX@g%sYIjZ{uqYb+ zHCgr79p<3i#!uhcC836)#+OnvY8p}=)yZiB^&gBFmJXIJb4<>AQEB+*+JQH+H;%%H#Wt zMHMLg4kTuFE#T=M?1B8!-q@<2^_wYCdP6vGdxjuFdv;Iq2hs_3E1|BxOs)NM%iCO) z$3QYKD{uOO>$~h?_9O7Q?4Vx@x?*|4Ltd1%U%gC=&_{yvzBhk|vt%VbRqfE2-p8GH z{mizQxv@4n-JR{bQF!^B^9B8rwi?zESpMM=ze#W3 z_{9?JSsf;dtNW`MZnygK2?iJNmG8z>y}s<5x^7+C-0WH|ZpJQVxoMp~2s!}|xQs3y zC}C|v6Jjn{PQW`Z6rJvT-Iy=U+^O4HgINQbtNh!2`Hhyx&^J}Bhn{2B6exqclPA4T zUXPC6r?_hJndJUwtGC-b+TecvKT0M}Zy_2cDI(rmvFqw)!YnQm*b0lu<;OezoNj!S zSo`w^uiIH!;A|BMth&Gv9p4wZKkTLyG+){YX07?5bX^T|SPHXPUF;7YZ3aC}PPo>XAhm=A7Ry zI}%@0ykT%$AUa)p*u7^?oi2d)91G3td&Fo_h|`FALEnt}L)O4s*!lvlLCK&G{(rMo zSkU^Zm6k*&B`ZbyHIA~O1$e@1r6C_Z3X5`+QH@&{h5$oiqT>M*_gQpHhMw#`?wb;pv*hjJroR_GzE z9Plu^&(s377U#Y{%J{Nl^1W( z)MAb!h&1sF+`G``^&;1&E`7OE7f4x5i^6nD7hmn!$U zKMH*eE2FF%bl8l(r{2_Vqi?V{?0l|LmoUd)ES6`$YrBUjWwpVX8gSw;-hOsHn%;&t zmYSf&S2_wbK_Vw3s;y{Bg)I;erb|%=z&!(55Y8#3N1lOBNGX~C%522F-om}bT`q(h zcQZVf`+6Ws_x;KuRO7$~AddeDV)X*IT8O$y;-UFVN=F?Iz3ERPV~`d)^Q%f=Ie9!; zbtg>xAxwF)w+(pU_hYn*;m3UygUg@{SUIfvM49|$`UG%8Q?Qx42bsuqBOyEh%>m~( zIrr13pZ6~hQpdDW`{7_Iyw!O{Qsa1qSutkq{e4r)GGr<@v9w-b_WGM`^`6q#QL4^F z^HP@KXdV5?CeQETZLXdZkl#y(N^rbyAv>g&lHike#=d>6^44lg;sVbLyLQ~|ZA;t3 z%^qd*sR=62v(G_A+m$H^{d?!_qIB$I$G3h!`go$tCUL5SFnzXrf ziqOI@D|x|w_bSr;Z#MoL*}Fc(7CXbAjlyO2eNfRt7&Ab7G6B3wqY{x_SQs8XIm6vx zi4xFnGp~*1LZAxl?y5~WgDA^7`=b(4zg~igbkLhxo%O}-tzW_iZeCV8Sd*dZ_|2T( z*3`3lLMDzVTX?jL#Zpye&z0O&Z7LZHvuWf6RwMRQM@jJRd(d}8(AI8O|C_$t{xyX} z_Z<5<l}^`MN;MFhnioRvkQf1l5y(_ncV{Cp4(B}IUjWe&2T(C2Xq zUYC_y;uW|+gImN!>(f}tM{mDm}- zBUD70|0b$f8`^803Qp5mcQ#?fX^_z#LN8j5fhIB6jqZ@#@5Cyh?2Hnk=yi&);ivoKvI$UK}Mn`eiOU zf4+MW;9Oj(qMQ_5^WX{m9!-S1f0va-pictW&5yYBsCbfzN?HdsYN~%#AyZ)ZUtJ$M zx8tEa^F9BUUPeCeIuS@hi*_&5lj-L%F)4!IHlG)42$Doy=|2j&>FoE92!^2O145m| z81*x^H9OMhyO@eUh@vGwu?e9}aIpw^0{36HU;LzOKL1nv$CgClc}C0b%ZHWY zP6dHyi)_IN;~C%f}%~y10QfgT-of#3!p%&6V;6(05q+V$Nj&X#BUd z7k3U$zq%Z8h3#%;xN)0!ot55g*>qw~1lGk0y*Wqbp7TMGf#sc;0hYaH0bZR*3zY8X z?!(*v9QEO{YmHW7)a5jja3^U0yt~+GDGFD8Ign!=nPb?{FQlQHt#UBWUo|k3NXb%c zYKN$-7A4j`9a}vsHXpI@efkARvcKKDdRvMQXD?MI~ zI8w(Bx@i3ccG$W^Sv(dkKJpb1A2w^MnPEREXscjtmAr83ao_XiA5@cg2`{as+vN`< z_jOw}~E7fwVlsS%EctRo3QA6yTiGzuo-oI)wWjv(`T?jCnx z+)W2cixUyZ61X%NTN*gsS@3$C)ywimOLn|?@rDmRx3vf%k7r1SjD<2 zRxcagvK!)MMKvkN6{T7K;hlDZe;RL`*>R+lF@{;Hhp&+u{FTywuPD5`3cAVkx3~E1 ze!DnL6PWIrt8#K6Q_cD)dTr|}7plJ7BD6|VM(<(XYy^m%^!S$=@<}bnpJ;m<`%}=h zxPt$$5M4mTTy{c_J#I*Q0)sCXW~lSW3Xnxm?$~-+FeogmSxW8lvkHPrUVD8c`Sc$3l>mM0wV0)S2UI_UKh{Uhzxo z;?64A%mu=gNO2(T`kUsIyF*eG0(B@#!(DP-^DtK){*`+?oG~;WECbMsaX_&Ktw=t!=`%eTe5d*J;4* z8U6~`xrqgXFc~`jy~#ihjQ-fMXYbM5xW#Hd-p$4q>&(Gm8{LyY6_e{lrvk3LhSqXd2BYdtOrkze||-3`8)J@X+h2`=5D=UO=d17RaA# zM!@(7ee6_rYl{3hOZ zYg((`i`k~`^bn}(7#F=-(1&A(POkfN03Tt*yUz`M6nTBRxoJ6v39!dN5P89tbi$5U zf;Cy*#`_gg^0O2Ec*e>KS<7MnUG`3o1i-!8wKBB2^g+R9mY{QX63>f z$w@ir#Ig1G%IH^;o}#&7tUvsB=t_FxZ?JS{D>6kPaOou>b$)^n??*1ni_^6R4Rj*0 zzl(1pelr9{6rR28_big1wEgya!|xxXsJ{r&Q&r@*#MiK|2qn)3S7YX&oG%}?*L@ch zPyQM|b1gE}k63XTZEd*eSg$yyd~EQ8muOPc`s#llVB7~f`2EV%zENWaKM296`#dVI ziP&-u39%;~s35@jFdwm`Z26ndADYNxEt$s+ko-WplpLb$6InTma`&B~?y?Aj9C7BV z9Y9GZtGVDcCkJ{m=?qWM#C5f|so$2Jy2ZgV)4Ba-?I=jli?(MiC?gm~PJ*}5LL!-AB~`ye|+zG%-EcqUx2 zL}Kblh8c7ATnm`HkFu%%^xP(d3s#pRX&A4S;3Y`Pc3eCE%qde`ac8qf&wpFTDu0CQ zFmWEW#BpJ+gAefHOD(*^ST*jA9SSgN+yfoO%2WLvFP!1O{UWIz>{W-crw9x7_u+Vv z$Jj?hsHT3qP9TTt&ZJpU4VUZ89Lcj}8Y9G>uZj#pqtD~a;RyNr4TGqJ! zkfxvb&UEChDsTDPPv2&ZJLNNjm~oU4??w%pb`$Sn*x-{!{X?4A1QW3ar{F864LZFK z5{=%zLYlx(?We>g@GqCIVgq|?Dv~~jU;oyU!`=5*13Qti=cvAC^~a;yL^><<)Su2F zmEtabFp#c4$!U0kDYRkp2DtMs*Ia~(Tn+8z$^82Pn((1=Y3`vQdP9P z97UE(0$idGeLUXo=cw)Qzjc>?j-C3y3HSd|pa{+O+iSU-eJ+n%aVYIgWwZ+%!)7a9 zU@g$VU`OcL(MMjX4<-t%nHSs)&ED6TPYntUiV;8gUoHMvD#BOvX+NJXuJEF0aeNCr zc$odMhwn9qiXXNV(RN%a)(p6P+YGthbs3~lAK65GEoxVlS%FEWUUb(t(e?xGyH;lS z|7QVwigok~-4=Q2gsD|3Y47_yn$lu?aTY{Mfk-}%{^T=nl*LW>D?!0V2mOJhy^mRI zUa!c*R=Zn%_qKehLGBBjt;XQ0iDMFH`+TZu*#p+RSn@N&AQWvO{x5qm@|t8Hvo5>( zKL92{*}etNffATbtMh_?{Nfkj&p!VRJaga>Hg8zMPhIjN+<5OJc=(CE)oaB0lVqi# z)GaqxxGA-ndFxH4iJ}a0SEG4ZhoN=%olas5I4^6(hh;t5B*7BAfUUUFC zS1a9P$T^B0LpkFpdZ>{}w07dUFuF5Zte_Q{NyIk#*Yp8E+c^*@KA9w*QT0In62wM3 zKk6MNvCeP(0wY69gMPgWcZ%tBifUrEjAPQyTcra--=h8sK4+(iZ{;^KQ9e(1N}D?f z;yVgb3u>eg$)AP)5!XHbiTbUK&Wb|u4xx~Y$79q{eNLi?yrO;^yip!I-iefprE1U& zUY2)antVrA+-v{OU3N8Zj_XEqG# z^?TT~W!ZlNaj$0fXglpG|GxT=T%l7lmQY_(0`jEz8Y-_rzGDiC$4T}H-&+5v4PU@M zDgB$6J(9j8B*f4Pa2NK-bH5(_d-$0Cv+ZHq$BMTNq?46?f8^@dCnNP|w7m&XN^uGG zcg8C-j8|sp_X{iy`xp#*-uPMQEf`;fUTEheRHXQ0pIMxnOpZ)YV2!B)t^iK+4WT&}3 z4!~fI3+RgX=qHPU?T6!DLW}_$HViNvs!3c`ZaI0}VGmC9U-EjGL__kNrD&~xQc*?(qt}n09IDu>SaPlyDA1OIa z-oOU^9ooU56R^?n6mC95&dFQX#4P~qe`q&8`_^B@^ImfaE`IkraO}%of;WBoZ*b!u zei%Fd@WbhEUC@I56;pI&Sf;Nx9aeSm!=K-YH=KVu-gCtzn9gR{b7%#B@uhF!;d*E7 zAAjyfy#D;t@mp`a1hcZl!SNKIxbaTh`rxCMIM?5|8^<5M1(SqNu1P64&w2*{T($Eq z{MHqh7}x&o3s1qN+Gmpi;L01t1aqZF#u#qCe>cAOwBxX8X`uA0>zSGNS6m#O!2k#T z>R&r9l-HNY{S{d9*N($;`=?^cfRiBxb>mO@YLQzV2tA{cRh@^z3M#t)|J=P*-$Gg!Kyrb?wj{uGUNEgA9yKx zMS;;|imPvX0AIV~0f<#lZ+rE5^=FfnE@7#MbW(NtP(CPsh$MhSOgVGN zQy=KWq!WAfd&CNQ!bf-J5~3Y>Rx;xm$=9*7;0^T1TLn691OU-b^`eMCg?x(ot z$1$SqzGrwXx`1w%?P4=*JK%NPAFLFKl2`s zQmgnPfg*CMGv^5}Zj1Sgx(x(aEm{I{5AQ;)eKL4i-aYoA`S(%KpE5C8VDhA1WEn8} zWdO=Y5=?p z*XQc_qF@*f3t-p>=cvxdtv|6`eYP%7|Gu3XmcD5LYY|@*y#kxJ=?hA!!_vyXRUaZ~ zz1Smg3;EsTb11Tar(tq~LG3~7Ye4>_{E&C43IwF+KY8bdkYD)8dB_jg{=X}G)YziG zlO}%%1NKREFpj}zb1|klS(7_A5LY*I^GL*?|A)K1=v00k{r#O;S>oVmg8pQR;c$SZ zVFA~fxbgU)PPcxsWe%%@5az|gylnf_D9&6J?IB}iCo~+q(2d61JET11sSmo`+;tzS zG83B&5VE)yNr(HNj`)p3UC$(=f12wvjC2eIK7HYyX z^!mW2%}W>#dpLApis@vUW)HgKw*Dd!#1{dDze~*yDR+7|%#F-=mS^^N`~|;;LXGFp z@x1g*X=u0`QHYHUkO!*=>e4|hsQarllwS~#uZ;M&?EVhTI&<>6uWo{-jsoIG%a4t^ ztfD@#qmDXC{xsaW1($u~LpbuxY6qJ>aZ72qbqg+f_d9U<6+Z|7iwWEXEhv%B{>2YK ztT+s>^bnQT?(Aft*fB+UCo*~VDmdWsqE+aBVK1|~y*u6DX8>BB3b8=Yy@q4wuV-cDkM{^m{gPN_ORbrdgg=CZT?);ChV z6I2R}1ha&^_sa@y@{tlWD@%-4CYVgbc5fPu=0>rh`YbL}-ht0z@~y^v~Q7}1glLgJ5%!|UwzT|kOT7ihfw{^YY|$u#x|z7xy*D^wb==^5okTo zx~>m#;w4b>64UVvvl+*1I<2m2@u`KY7{3W(jA85Hn;aRyvs(WlIc@i2#v5vI$DKgC z-vvi`MZ*6G^BKHD{@ieDO?YiXy66u?dHH;}Ki4fUcPWuyR>B}bu^pVISXoEYz&j7~1tQ1$_HswYXU zUE3$InE3=YcPcgzBT#?*?M<~?fH78|fm>ZyXW}aTY&`O4y!oMxmmDaViLv%5QNP`jFdXF- zI>!V20iYj=e`^}&x_8(bH)OZp>xwjaWql!h(4QqEeW8z!)6S1xt|o8k#O)m){|vtP zYyUZ#$Yp=~CphMU^8f&UdEToq-n%bLhy^X^UmLyPm;X=mLP}$n(71(&Hbg4cYTw&Z z!JEm4CZltmm5*3A$(S?;{l~~ZSe0*|q)w%1j5S_RtI53BV{-|J){x{Gf*POO>rM^^ z+FHD6qz24g6%56%E^>p#mL1m#Ox`ZtHUS0)pztjwzO!$bO?lRa9I zA+OeX3Gt1BHTxj)mELmkBzhaIuG!OJ_aPv+pWYR}dQwed%Dug~+W zKZ4d-JkU{nSRJzM)BH1o>m)vVR_WhChPoIE1`LONEDd_~E-imXIte{HNP|;m4fWqT z9_R3$o|3aTKY21bR)0FN4{zNbbk)t7cXuh3au==1=QQ4#7cr)kfjoAWS5kf$HXd^d ze*5j`;KHrIX#WA+_vi|yo44Zhqxu+Pf@`n58vp5E4qzJ4Nqa$b#w=iE35*Vnv2tiq z4;uvZ-n9Pci&uDBxF@A$^C_QMdJV;Qly6XfO37 z+W*jQ?0a}O?*H5u*W@hSi{AYX9De4hi?e(eG&d^C5)6ge*w9b!@Cddfo*l%p6S4{ql*FCbjdPt5z>Huuo9`9($yU5tBb9t(12)tq-9zLrCw_FL6it zWa)=!-gy%DQ;Y%qK@a^wfh7zvo6azsm6%N`bhZz% z_9oYj5ZZQllZo3pL8sp#C>2O}fywb5K>iV-c>B}HpV9caX@9ee`W&P=v_HXgeY}st z@pa>!>47u2gV|_6KL3?6nCL%KH4j@Av|xKcI|L#Fqys_gmGjJDib5&0NBs=koHKFL7M+;#%5gLuImnr-sLsUIyZ17_aOsdM8*=#oI7%?y1BdAUmkKi+ zh?yTX?!d5&*Is@$F5C({ddoNQOCP=m4~!YWdpP^ji|}86_&Ip_WiP^OZut%_ddCHL z#mZgyx%b|T+s4cI@n8D4c-!f~Q`cRK%m4CG6vOA?xBu(;c=^+};wS%zp{Jk0XFvOO{9j+( zi;``}TYuwH{MgZt;=_M^5U=>*6HtEX^LX=LJ%-834B)(W7u?c+{+X+_d$a1P4^0E| zj7dq`F6v8M?g}7or@UQ!jB?5&_gF>G_Pc&^CZP11*q@2{Iiv15cv+peDR0VW?d?f_ z)efbOCkTh2{I}_-lDiA3ZW};fpST_G+mBEE=v%S*xE=CL+c$pdZ*cQpe$@F%t$$+mA%ea$p)6|B7Gw$JN9Bi@cJ^U?QQl`eD?|K9Fc$9{(6ou5 z%#W4)3M=`Aqcu^cPI+kAWu}U~7-{Scp7AJF`md+Oib@bMVatSIlET4p`|?90Xumx; zxPjH>!i`{V9@HKkcAOm)I`dPaI9n)y)+$LsuHZ{Udc+YU_Mp{N>k8C*u1&~vp3C}N z1t!oYB*fIPc3tCLsNNjr8&Sv~X$leLUYwCpOE1x@V6Wp5w?%^EHY6OXs>lkhGigi( z^m;vP+StQ*JjG}{K{*r7$cIpT21)m}2~0UtmGcyY zhu3l%;nxM_)`ML4y;|`p<-3-j@*-v=lL54Sp=S*0yb;Gy-a=8e>SZcSZ^r`&n-}7^ z`KAi3u-EUQ*Y5#KgKAgqY*tO=PPK{LHWVp8TefY~LQbJHlF<<6o_TBaZz!K9KXu1; zeF)pf+x|vSr53SItNI+vU~a-WNHZ`Wvf7_iJ0OfQ{gg9q8~an$Z#4Nh`urV5|ChK# zbxmIk<;jAZ*L{ME2Xpfjh5jk8$J1H$IWa%tow;X1d$8mjqwy3gV_>+{$FN_Z*XzMP zQ!r9rjPVq%=`SyXM!aSu@AFsg>+(!o6m^qaoq?<3z#^D#q48X_?do-r;-QVZ$p0oz z26!-}vx;K&(Xdhc>Go$$$Hz_tXNsd>a4rV|U|$u^6A2;kGY* z9e@AQ@5fJ{bR;f4^IN#-LEx3AZ^x-yIqo@d6khN=U}a^7Z6_Xy6MK*1$>$x8Q#SyQ z?|cf6P7cEuKyk#0_}^dgB(DC-{TN?-GR}MP3-GJ2egr@FnFBca`!2-$-}oF%@3|X) z^p7K)c+r`7)4#hAKlGfZ7iXm4n|EYGr)afA?` z^2np;>zU0P>R@4{@mg+t9mx=FLUZOr^hG3nI&u5dZFl0S+wR08*X_g$e)30f{@dSz z^WXj!oPNb+xbYAF2k!d#X8-`td-Wx__}%Z2lbqMS?+=0kQ+ndIyYN-dGw1x&kKn{t zUxG`2_cyWox|@PKf1hm>oVZ@lzY3~W3M%j7@9c0CtU$?26e#45IGcCul*Vlm~?bruYaNun!$3?*l;&WeezmvllLRsya+xS zRC%fUElytVTxXq`jNln7V50LbuDe&Vi%CyfF4aoglui+C-63_!7&BHvS}2I}mVPr) zZ;h2gY-G%QoW431LkS{$cokio+N@la{!N|%RprxvCX|LoIjNu1_*5+F^3PE75al0+ zKh@)YOPz@^Y;kZ{uxYdf>B8<>5*=|t!4)-wiOnG>>KTAfl5LYW} z^;a~k561H`UU&O&^SrLVlo$1$2g-j4#fwZn#XcT;(DsDRPFk~-9$vO*zIM&6zs@N+ zJH7%j57dRr=%M<0ca+a4KPg`xX%rvI_F8Du+f7PV7PneWCSTj3W{ZM>u^tBfJ~-6pm1oluv)K&6L2ZIcbne}GxOwYF z^m^Lr4yz5#)PK|-4eiq+nUujC@8FK?pP76%I=;9f#yQ(9xQOe{=@KB|L$;{e8P4N(Zi)Le-1X4 zd+_I zehz-;KfVwb@91OS*S>^H-hV&FB(9L}m*YYzeo*p?3{m;@0tQ$+YqcG=J1)KySN;0) zz^?xi&VTQHm~odPS{fm@8Klp;y5|n`_IF$MKj{u_Zp?pBUK3Tj(5Dl(g2sFIVdo!y z7 z@hu1yD`5d22yf z%Fa~%ehZnChsXhARS+!73lwtjV0kvvb1PK-?%VK4c79oWh{R{3#qM0^z%%|dE@D`@NPb)71ndIy8PkE~z2!CffG+sHdIsu9zo0NBI z*-zwgN{lh|7{jIwOBfGk7>%a#Q~Y&K!kBC~7<{t$Znx9j;4|~^5tQ-~o+?`Op(r0h zkxs>%<(bxNU-@^IccTtc9_qdT&BGoBpQwKey`4)s7~NccH~Ac7*XVm+Ogxsx$I4F( zQ~frF$%}uB>%v*-d=6RJBke(Q`|d{nPI*yYD4!?3H$UQ?zMWJ}JO7D;&yB?=YP5M~ zW>af-tIsR<0HFG`vObY3k6K{08ca0$3(($Kt8e3$Wen=H!x>0K!O%~gMD*Wi(L=z$ zS>m&1sB*xZyu2>zLk5P@?~;<*n+Ez%t6%jyJUc^D^&icn+}UaJL4@+NvHr6iALPj& z&X4dn4qgUCt_Hcjf$}Msxs$4W@uUf)Ius9*_RQuzm`WMyoJ?n!OlI?r&{!NUZ z>1hh&tnV525m@B*#|aiB4lN2+y9Dz-X-{u^1_Oa+v6~%Nn(y-VKLr*Z^z~Og{T@Hz(2%LT(D_dJb9a6Ha9elw1rZO74* zr|?%_8R3-kPQV4HZNTWnEf|zf4o$Ffa16bk*gHTgoyB*` zqqYO(70T+CL3%&T_>utw7<#z$?LUk^dG!D{{@o|>qaWF$DZavzAHogS+=)NC^8jvo zhM_3>c-iY;j92a`aL*0j#Q*)pJ(w{B;_Je=F5-#gjU7T<&ld^l&9N_a_~L7em)#!+ z52eFsHp0z^h5qptH#Yw=8!SFVeb>aTPWvC)jjMk2t+?pj?~tEJzWFad3IN#q@NVq> z+D$m^iXX(0XP@5bCywv`+!t`U_w_rCiR`26 zbAc(pf7fnn!u#k8&ciVmsvWwq^M5xVw*x1<`VuT{-GY_9`*7FCKZEh!eK`7p^Dy4G z-}$VrkCnaqu=~25i!**#iDEmM%#|=EGNcb;23`;dHo2RZ0uWvx=g!NP{M^wQ_cfxP zU}xUMd8q|bq_4`Up8bOcxs6pCitz#2+%2_M{n8{3)dRED^RgU{-#1eGkdepGNmc=-^y zzrjwvl)*-Qb+B(R%)E~J4BZm0U^kgks!d878mf*EH?A|8zc`8qDQ}{XlloDQ{6;Sg z>IUg2!-^R8d+2Xu7>#FG8INJ?uqCL(GkHsOzMb|OFT{{X_7d?#Tabz>+uX@pSS!)V zm;UE`@3C|suhpJVmLtKNsP0Sm`!mIifJ zNjwxG8h@DOJ-idqhpoOG;ot=&)t3bQ$Ltzj>F;0p-w6f8lkKv&mGTYsAFWTmf@e`; z|HeDYtKvH+AN!J17xsu3k3hWb@iS75K8Nbzvk@L%P`-a_j=C%v9h_GB52Tra2d7GAUKsO!J29PV*D zt(>^zpxuTUs-3qS))}{6^z}zIm1vx52k1^(UVvi;hMs{r4o39v$4?x@BNO0xo44ca z=W=}G!9w0v^p+PfYvu?=y_XQ<%Fc zR6XeR7&dJlVle1obZCOfXexJv`r>Qvj(HhBz{8W*OLEm8@+E-aq60o)`M%r% zVhr!+K_A?>>C`$KGa5LZ14@%kc7d{jznA z?aJPL_{#e~m^A5p_?f5SbszZ`PF(e4Z?h(wFMQW8pt=!?7Ds@ zuKLlp8o!_S_P5}ipZpQ)J@O@-Uj>Y#PRG(J`TK6}_2FOnVWB` zf3>nz@P+h86&4oCbQmo|HqwPxl4o%8rg$+PW*OD_x;=JI4ycCPkJ8X12vK~3>94SEHpvdb955%JTOZOLuG_!H z`k~dkOg=_pCz5!rb0?GU%NnWekJjJmT7LPEuD0X`IuVJ#w0Q?+$sa=Dk0_tvV7!VK zw^2S*{^Sf&zH9WoFHyXvuZvNJKbp)+Or|CJlM+kA9{T+vULUON`^tM-0={-5S3&xY zTeFV4zc?g+ckBeM$Xuvvp_G;zy!?WbXm9Fwx+VvGlZRD1q|lno({-3rev;~%-#&8U zqk%v2?r^pIX&9L@Pg-6`Jk6kxJLHNYDKFKB5t(w|_0HSqm))q__hmthC5gLu)vuRF zi}9XB&dQAbrpQOdO;>i|n%mC7%U*mq{{5StkL&*QUfg|%fw3M=x%fr6{3M3*(4+XL zZ%r^AJ&9W$1};AKM0{@_c<9Eb@YrM@Zn+TV|V>@R@B610y(Q+ zN_@=#rWxFv=u2x?^l}CO{r-0Rhu?cWe(B_=@xga}3G8RyfL}fp05;-h-~AK#-@fsc z|DU~gkF%<{^2NWk&!Zo7H*ci@5mcIhf(i&mqo@%zikg5k!Dtf9jT4M_Ow5e&cgKu* zjG0MvqIb+BUd)MCQ zbc6TT%TK#l`|Mq{YOShOs~)Qgr``T8M$MXmzq{rnd}>@3+>SQ9{QPUU?N{4ypoah? zhg0Xx#;unhhpETp;IS_a17&q%T~F-!B}DEl>V8F8|m4C{c=$Q_sSG{^LY6 zZhajW-24Vwhfly){_1orJhdJpodULQ+J-ytUxTOLQ){v3PaijXCjRc4lW@-Q9xpFF z_Zn{d_pLZk5dCdcz!A!bvp*5`s8as5(VoTBtz_d(#a~5WgR>(hH{q(s9>n@zJ&fj` z-XFbi=O##=ZYGkXR$Eqfg6?)@b$T5=bbUvonw)3$K(qJ|_pN6VUKw5(Z&wf+L_%$u$U08TT0>c%$W{2$*Az>93&=9jx}Y$ImgbUj8)Zo(;x z7D`7*&-wA~n6zLX+IF>I?Y;M*eP;`D4Rx4#(_i7DC3kAeGTpz!c>l&Nc(#L<=VF9NAt1+S-W`zLisBG!4;_mA704{*yk(leyHiH6Z-z+d;11&O&Rjiac8d7}HU-obSd1j5 zb{G+uA~v!)F48$En0Bm&v67)mL!30D5}kB0n`fcW`T`heL%$%l8|H`c+*De37ZJx$ zcA~RlCu~2u>Ly~bd^(y#Tz%~ee@&&6m?I1OCdtzh8!Ln*#F3 zC)OUQffjHklY!$9QT8nn|5V{WsbPsTpwQF(2L0LJ_?Q_a24(rzmXOdM6^kVlizVc;4)XbI_+IhE zv(09Cgy*Lc_BR@wL?6_3K^#YjxOEkMsO`xtVs)Ho5^$7%t^Z9;N6nZ<7c&JUnlFf` zPqF%nITKWv?@rWD*zVX1DHL(6HP_Tk>jEtk0R`(5z43+{A^&)LCXWMx z21sca{`}~A%o=|h&TTpk4}5GSH{bsV&a@$zh!UQ8cds7{EOJ9a3Nf z{3e1IfA&u7rp*^8czf@GFVC!Zuy3B!Cm zDB#e0qCTVvE!6-DpZar#(s)y8ptRJ1XC7_F2@9v=%CQB!^rtuQhu8LC(D>7E@86z` z$sHfyu3vtD!o(@~_GM?|r(GZ6>R%tk@t-~iKmV(-7_@f>9{7Dbj+xzr`JcZ4BfB2O zH&<>)^ZaQz^|Wy~F}Dw!dw}sX8gU$eS6_J#`<-KO<89~Q#uIwc{OlWesB;vqy`%|0 zyN%+in_k8NMNoq$oQ|K~axx~j@4#LEXD50lPQk77d|nbxKJW;pE?S6b*IkWC3+Cb3TYrEZ%7Q82E&8!&*S1~WV&}FM zt1y1<94z?bgLw0SM-oMB4*$eNT=vkC(DN~~y@=c0uQX%jx4$o?**JR!>c=)>|HdtN za7BxT3zX!adfzP__W&pqwi*Jz@Xj|5-Lr+^f&c5wtOucTQaq(7M{8f*a z9zS;u0ASP7<#_v74@+sc?P|gKKfWE~=gvXPnst29l`Y^YSboi7?KczV&Ozhs z8Ig<(y_sW1hgaVFeLun1}g0j+CjVyQo+*iV2mkr$f;+a z**V97yc9b2&Q&#Sk_=l%QmN_NmBAFb7K#-0bxj=6f?Etu5)5`^nq{HVa#@2rOS~tJ zvSXG1uGUj{@)K^|4QLU&j<=i9$DqVNmL|YV51jUaYNA$%qOasTsiGj>S*{f3wN#1J zA(q${%y$BsaZvUsEZZ;nnLYo}t&f2C&KFT>>Lo_}f=SqSk3+N$lYka&a>zy#X6l6c zsnA-89*qk1U!a*J2vD8RA)Cpdr_hUHQB2H$xu9X74cHbW{+W-xNP-LNFhZDCKB4wLo{!1>qrc8Epx!9C`C4tG0Ws{^mSYn+&UBShGaQ{mTw2vLd z-Vp;$1iME9oXKR6$pX2278DevUKb_TMRiRc*{o9m{zV+LADK=d{}huoiRxU!YHi;6 zZ@{d-QfZs2K`vT zw=|A14%qZq>OZLNb7G+NIBCV^{go5Ye8OJhKN^hcis%;9%{Hb;TVn#;KI$+_il*|- zq-v=)^F-+c`cb2e0a5OH9Lytu(_(pIt)D?Wsr*%?JfJY26k)vwys=*?^K3Q+}Ixw<+0Lkbo~h_V9Rrx@y4g8JK*LKN==j31sI41<(yn*0 z{GDl-K5ZP%pV*A|-x-XvPpbnhe27O^wqf{bXW%bSASk}O9$)|88*s3+0qfg7h0h)D zU|b`l#an_bPPxeA<@w*eiU*$QM*f8&PMdiyF8kC8XnOb%wiKcT%xws7$1+{+cqiOl z6D^&#-!adpnrJ(=vpubC_`~Jb;;g^_7DmsSj!Tx@gd_z5^Ubm1h zin3_fQx+|hqDb3zwP4ufChL1ss~YO1g;o`Ye_|p|`SL=n{^32+b1`~04vwGe-2=P( zm1gZ?o765wM+5vcxw(615+gGf@V>@p^kL7~){*9Zl_TKxjd70h4 z!A%Ri#oqOXMe{cwc$i1>GMKu^J6?J1z4vLqS$ppt_e#KQyQ8TNWEJ?l7R-O`kNqszJt#ocjqEAu%s?mwRwHJuA}<0un{kk>sX|Yoh@L_* zS-)YDe-xHJ*F+-n_DVVJVd!a;fg}4dwzJZ+gQ3Y3KOz>w6D#>!MV?z$Xw%O&@;41$ ztgCe&*fWZV{t9~9#6Q;udmfg*DUw#D2z=K>d?SR5}tz_~Fd|mUsNCw1RoPW_NCRpx(h1e$ z#ydeitX^pBlL;H_SSlQ8KdGKpPa1$CXI6oz^1JXJt7j(t@ET&^+XE7`X9`4(e@x>Y zZGXht3x$RjS`$_INkOwYb(Ln#TSv3+S!9#IkV*cLwDIH!vp(YJ$o7LEa!I>*N#ik> zKV<(_g*fXM>b+95qfnYuOg*4eoumSwP$;5M0IKp?t<8_K{B} z2?w$WpHyJOzl^yZ)Z`v=XBF(#fA3^6SM0Z?_GFlUGyU#TNE=>k$`O*kLR0nkr20t! zqFjA@W6!@KIWBMEp)ETR<>3psEI}?Vfo6B$x&Sf?&947q402j1pTsnQcb6^8$iL&3 zX5e3<3-uCS%Hv&SI{r>v4+{Ht;WrQN#&7&NI2P&ZVfd%50sCKDgUi3p*!7+re)kP5 zxZw>yC9jhybZ^1`ym1SYASfsftY3?HH@qrHy$i2A@iJa{LZ^^c{M(O~;oq2mCAPzg z*7;%CEJ0m;HF~Q{=xQsVSS)$sR8IO3DuF_WvDc5V6J`8*l4iW4I#`^Az-G&fl`?PP zNM8q|N7VoTKJ~@<*ziT+=fYr|R686ejRt6`4V&Ke5^deO0pGu4183{`RW0b5)`*K| z4aFk|#^RI50Uhsd!+(E3F#60oj0CXngTpvfqM#_?g$JL(3%-6Dj}f_ZFs9Mt<(w}q zz{W4hyqs9$Vsnq!sP$Nide_jr`B5V2Qaol@q$4qF_bMHMnc|pTR{NizKeod*kezwc z^_Vn&9#-D^18njmGsn!HpAGs<5JKlNsYW+fF zp|vd(0n4Ci{$-ftFOatG+^t=d-m-K#rY%~C5tEzng_VzD*P3-`S<{T&tJh(VVZqo0 zJ65dX5zqOCdJOx-L=2zOg!(a!oDZpNeiSr2s#yhH-ovLf0RTqNn$BgTgVD35Tk|~t z$st)}pWgS48~pRUVvLn&{5-zGI{+DG_K4E~hfs}`TmU5M%fgP9ns(qSdv;RBaXb5h z$Xpd0@>BTZ#gC%F}e`u$4QvS{0`#yHJQ#kXZh47^tesBAfN1aC>_#C%zN1BMZP3 zhZs~476ApvUBnfn{>CDG`^P9N-hwS7qG>&ayDS825YlAyEE z4jv7drK8d|_N}b?qyyyzMcxD!G=g@>7B&DCL-8+Qj}eUnNin&4D4;F%VT0o$X%ov| zfl#)@Kx^7VSr`VolAvinG4fXZ1LElEFlHM|0=r~|Rl8@hi(nHOg1wU_8c2xNVq?b! znNA6V>uZq91&8gie^>eZmP!<*lItzzx-@X2ITLaENsC$WRJzib7-%0`j&DNPn7)Gj z|0TlwdsAeyKwF*+5IQL!Mt=;5Z$DK&)$y9jKmXn!7yi2=3JT`&jbXfFI_V+cpJ@4w z8*i%o>uDxWl_p^Qf{{kFJ~++#FO}x}sirPKI?G=u$ zZ|0K$;_u|vW8>yy?z5ULVB4)h9zf&;Ms&jFg&!d6x4cfuSX~#1P4*3VHI02on$|7HtMOSApx;qMR zOEe+>0voK32;&*A8ilbeD2+k!Y1x%EwDXAiM~MrN5`Xu_|5|~cye=l%gAcj~;{pIc z24L3-3w51RiVt6SAJ2Zd5f{!l4o!QG#mV^+o_l!*+MwQHtkaVS!JN~J%U0l@UlVyL z;Jq#<`EVh&dXO+JaZIAI$zLD@Q>Kl=Pj5aO0PxrMyoe3!_r%(R5c^bCVUo>S;`&aO zMcrQh&=PE2u?kNtz6HalG-1NrImr9R?zZi0!R|Hd(7v+;3;y^Zp1k4P=xuGs_zUM? z>Y{}pgy4@~SR9HzO#|&aTd-y4ava#O8CN~_Af_%_h;>4wXyfb|_~dtP#)!#H{F_2s zJ9e*G#}_mijF{4dv@mJGJnY}-?G_m^xe3#6x<0hfTWD>=(#x;KDT@|j(t>%xqPHKB zylLrjy!^v^tdEmEdC@{lTXeNlj<#JbIPlJv(1L2*0&A2Wbc?|vsB#>D%0k?k>AIb{ z%qV*8R*ifUvFtqY$V*~@Eh0Jt=ibkUvq}9q$6x|VJGh}5T8E*$lb~;>(i(-zfc!xa zX(iJkOvBh0oPb5yB<+y`-9`&08Vj{X7K@@j5|^C9mvv{u%b^ECmBlxe)(kSW5hPHs zOM>H2DCOz$_r;@?c{;jscst9PbD8u`v?P%jsllclFr-%h7zpmBWAZl8(A9qd!$3z4 zo)Ap@!$Nn6abMF_=NZ_Miiyx`k;P z=*N4mv0E%6iaocEoQjB~QtTNoF1KF_t;#=YSCQ^LQ7@(r#zr5N0_$p|sSvgeF!AE4 z$egMDPzlBAf%=i_!uQ&fQFy&ju~0uznN2UcNF`PtBdv-+uy27QfimS^LH&g22rSiq z1CFw9S~>=jvKg0_QEcH3%qp@_L+c{lhYqPlHf!!{2qDO339?xSRq*b^EBTAK#iHw< zYGfmTGWs7aT|r1wojF90cdW72pI}L=w8DI3KajLNq|^_$m&rck&r-`j4lMPP>ZA`= zba4?#P2Ql7z(7|LV7_k&gCa^h&rm6J&K1!>W%nKEDuYFjb}t!^zU?$Z7v%}L05cgpabku z%7*_Sl=28@R#-XGIc!gL9gjux=gPld63LWc&kJ8(+?$pEw@e(z;bU7bZfmqAxY0X<#4dMRuCR1@2g&07(Z zd@_@2&m;zXB+o~S6H1g)h$rcQdI{~`-3833K|`h+Z#Ex*lc`11tYc8q+J^3~gLwBt zfJ|(_q;XkbdkGEWPR0*zJ^_upH{ph#Y{tQktysQx7A~GM7K=YM6#4Ew_~Q#500<6# z(2iCN!_cuqFeI}J2a6fZx&Bi8^~pVW>bEQKsG{FpySo4|UJfFYsX^21V^IB38;U)t zwtFP+2GwtM!#@pH_5RbF&&If81_1!>{pQ*D^mR+46EI*(bu?3XczuVnDm=(uyyPyt z_`|!g{#OsF-`*2|6W}LEUAtub7gWaz* z#pnBK6-=7sAQe|V2EVs`-g0f-ol z1gJZ8022-VJXwDBSc+^DXe~zgdE|G>`wU_u-4gIG;ZOo1P55)~4heuq=J<3Zj?6Hj zeo_Lf8$qw{bq3lyJyjgxe5tg$k%-Z8SkE+MT@%O?4e);_F&OX`WT`zJ@E;5WZ1~rf z$I(0k0O}7b9Dw*va1aR~;J`ajoZ~3W7@|y&T!vZ<1I^2&{~@@Oj>%h3mwg9R2*W@} z^Btm%k>7q9gytJ*-wYUzg89Z31Q=nOf#azB%h)8Mz_~Z|2hlj6&moti=iaB7)!r!(c|3#Az{c7q+8{v&R0lzOSsv5`|Mt)?%S z5`lHb7a#L0{6|mznDikQIH4Ylks~74Bupm3kq|N{>9B44u{kLs0snG;Mih2rpD_}{ z5>v0tLBHzL++Pag;21_(9xAOP&HX`A`TN)yDepguilj;diasFt#nJWO5*uf`*=sTO zEfN1ZET?TBSt!&j62`-Z3_>Q8jBm#waGVTs08kYy=CUJ}T{ljdCi~VGm_dww5?1eU zQlSPVW9ySU1RR-uCHXh$L!kS5vyH8PZIpiu7*7(4lfQ=6Fc9+Vlky)~9wt`}?e`0j z@=v4>52uZMw}y~ANwl$88)YGHt5}llf4vs1LdHrh4!F&QIpxO()@a zC$7J-9Pg-lNeWzVypInyr1~#-$mmaa;IQ{Zeg!&CEDnV4eOl5;8S@LEFwc)F?TWL{ z<`w)$)b*LA5tuVW`Fy0woQJ?*|HZp-8DE~w27*2~4l_GRIrho}`|*y3Kg#(aLL{(A z3pk_4yx;LFLM98;4X#F2Z5CY}z3A;J2Jf-)4zKrY{G$e8AbzikPR`V9&lsF^_H6v@ z1Ud1xch=+fWd%8HY2wJ+-23*k@8j)@Pmk>LnE(JF07*naRK_V6eG>oBwGG>cpM)DO z9D>#tpT(tb@4$*DTkzG1jhKJkxi~oHBOG_;WL$O>gpEgtAlz<%y7jHlFKHk3M zWSnyGCvivjHf$eu5^k95@$#7s9Z_Lo}++=kZ@YF>M0RWF(v)C$nxqa7ejF>zT!=^MvG@WHsTV1z> zad(H}P^`EVhvHJaSaG-F?!jG)mf}#{-QC^Y-Q6L{&3niA{-5NWk&(U6Tys8aFY-j@ zzCiT99NtT=4VN30z%a69y_@ShvVbn=voMdpaAZ)o9`hp&QH{ep+FVct^%haz_LH0- zChhz?0`s9Mv@QKFqw)gxyK=$j1u}IU*j}2zrK<+>Q%*~T%|Dic3~F7e${i{ zRPYULrpiCZwXFKizt;pWlFA+S1b*N2WWH4YdaV~%;5GYufdX3LG@2gf#7e}GbG4C( zxt8JwU;x=X>7K$8>;@F;*b(wtP(SV{NcM@sZ;?=eEdojXYfLAx@*(ZyBK*J*b}&JF z8?h1t`G+6fvpx~Rgas;v{3NgFqkY+srk8U(>F3kwv3?*(i%&Gn@?RlSQTRru6aNRO zcFOQRbXjEW+>kl4Abax5?}g8-p!A`>6;y)HE!AFgmA-0O)?164lnutCG^bc0TNII6 z`;z~H%oK(!;bn-QR0`Gr?sMM}Iwt$=yK^Qy-{b|iV3MUUcYm3AXqWgVi?CYZ(en3P z4U&Z=e@N-G`7zy7f19E3nGRmlWz4X$5~CQ)I$ui)44Faqm#LMHzW${|@OzlosbRzr z-pW8V^ljLBbs@xxjvs7nRF!|~=)rhC+`mvl;nq=fNAbk?lBB+;JFf=dyDzg(Yda!cE9Qsl4P@2~L zPejeA8SdfXAzy`oI%k?PXENhcM(H~y6IKxDTQCcpP4>0w{cNxXt$Aj5CEhme(~Xg` ze_#N~v1JVQ`jB`3Sa6L$_ecM603iYcq#Nf=^-gKUz(kA&P)ia%ZikuYY*nVafgGMl z?*}miJ4hg@F!7t99j>ts3>C3scd!<7(8{3cts68VhzyxftXJTjgvt+=%;=M-j<4w7 z$y|R&gw4&)lhYHa+T}-}RbIE&8MBsOga#c_Q56G#Z>58)?N?bkhw8a>odeQoDqR0e-HlA|0yCVOdUU0%DM*yrm0 zws@%54NWdK02q}`QG?>fy!v1320jU=9ZV9yMvwwLD5!}rOt%dTg5>W|3-H>FUFm1> z_N+5-tCh-C1An`{;f^=G1piy!wmNO3fR8>Cb)E&9WtH%oo8B7Som zf5Bt<|HO~s&St}f)Ba|y^{yEpuW4q9T*qpG#a?NF6u!f6P(IUdeUb>U+p1m6Kj)PX zShy`RkC(Zd^S$;X5`7l>Z@YjfrtIxr!TV3krXQg$Hy4$nNGx;|8!g0kg1R^qVijL> zlZtG0A)fPpwcT18p~?8%;oR&5#JidJI6s%zw$-ggfk>hO$WHVMY@;THq-;~*+spsL zJWpc8T+E&e8HBJ<&#*xf8H*y%=~9|Bv<670jSRjdvjb@p-27Av-vp=HDLZT#Jwo?N z)CRteH)f#09(zP}9*T)3LfMGeKesytw5B)3JclNub1BG_H){+Lk)THqzo>cZB4>zSz`xW~;f1YeH#iW@QK8nGaKz>7LL zWu*IJL~Hf%!ye)Q8>!cVqr~goE@WfaC;2f>CH#%*ljaKJtt!}O5T6#@?^YXL8A<9D zTBx{YPG1rGRzqp%7n7h1ReS=MOvNPyR)}^>rDWcT5OjK-vrw&6Cwk2y4)+}KG(Db% zXXGBSyRE*7H)N`!HSv^u)EB}B=syxXUsCa*70yGLis#T6TUp!ElvB~>z%Wc@WlZtVU*9GyP95d8D9P$P86uKwRa#f7sAeK zdScAV)0%IP{V9SRrMt#4<3H7rKQNQ|1J`aOeq9+AR5?~KBk#jAclO?Nsw=Hj_7Og0 zF8@aQ=>$tMD9R;w?hlcFIJe4$M#?c0Py z#zRSXk3XybLL9q*#ZEc#|oZSUPgg zJ(z3xSo3FzZKybZbE155rV7}oX28IXbV~Om{k+WEpHGH$5`pXUB8`Iz>i>L8RoZQ_ zpqz2rwLva^PFve^FgEd{<71y-y#;`!)vc)A!JRzRJ$+%85KprJ4F#EHDmehPa#>ib zt=9rSj<&P!iK}Ty-{nv}M#F6oOsPceN--c0a^k~_{4UuE-TRS+#?$Dfu5~=S`16~G zOyD$48A!(V5&JFpU_T2dyY<kZJnABU)t&lR;{l?Cr?5?RWz7=kX>;=nV?Ky+Qu_5g6lB-O+S$Z+Y?=9-b27py#1h2(dNdx1`#0;TqxscI*B?IqG5z^ z+~@0tIUuet1Vz&Ke2w$@uhN2`!|8lrPRPzsoeyD{AY#Q4^5W|T`uQXNzuOGCWb0=e z={{R#aJ_vyaW0t zQhCm6Ac0!UA7R)u;RZOiZ70lQyS2 zzpER;Cn9v?@G=BkB6rZB@z++QD?aJVj_By*=ss`LO6_*eOeky@OCkH)Z;O$1{6Hl+ z!{s8fu_b)5p|2W>a9{k0t}M{ThUr>(xQ!|e$&Dwj?{a6;Ih{B}f@RT>+wo{&d+^83 z*-3EF!{3zj1YTW)TgF_>kF~$y9z7{J_dZD95YB)Iq^dCo5NAV+U|+}%?p zPw$qb%~Lx_1yIa|1>^3HFiO|^hra*4jb0JEHOoj_g$bvp=w};y8o~JcrGYH{GLc+{ zK1>!!n{|O^I_+=H*;A9}*5@*0$`6Vez4Baff(kcF829RKPtI3=i2v!1{WQ-Fj7@E7 zl5v-+A++3%@i5{2E>?*CLC+XJ=HHs277OMTuNxIFTS5BXUt;8TUsP*Y?&WzuX`)GpSH za^X8NHD_x%Ot&YTaA3TnNKdikAgLAJ@}yr1^s9U~SA7~v_E_h}ii#Mmz6ko4@OW*C z&$~<}mgzQINtEf~m{x~z_M57xF=DaZm5o12hXwIo&p2A{#&K^-t%vtN#6EYt6*tdp zD8*2u@@~zp@UqywtP%|W8yx)A+$x-9NM=UlF9}x;$q__^ATWX0pxT~xL+ha>NN!Z;O*Ykj@7{%!fl##V>USb*L58=sCNWHd`FA7a zi~l>SHFi=jWN1UsTWu^vfRIA!X4 zGF8T-yvoR+#JDswp2aWLD&zNM8m1`q07`^pg6)4VA-Y5BUEzH`+-)Rt| zRaNuD{E;~iZ~In@M_PYz7wdxzJm;j#c_$sKEtU!P1Ge0QYr|^3CD{*E4jdIniIZ^o zatG2@neAtF-7MD;1F(6ES@+%y!z~NrBLG^Z{v4|uJ>H0snj>rp#Im}S(HDH+l5`Pq z+d_?m9(s+Fm$$-N2mEqJG;4zSf#toAmUPpY{Z)1f#p3L~M5e5g{Z#ykN8Daj*H9X) z>QLEOc;cwPh(6v?C-&D;war%7hG{?b$Eq9z{BatN*V^P>(hRdA`IVqQ8R&-(imt{8 z!xQB~`bYxb^_3jOyx7gxN_JKbd<+J{9(_!@PorN8j$}koo%_FhIT`oZ2p(FU=eTSu zPlVLHMgPWY0s9uDD?2O#OYwFCFRjP_#SbeGy<742^!$Uc3ldavPhqf;M$Oa}5hH%J zPmP1IP~mOD9-bD)G__EcQT`?PpK}OdZ}TDKEMckl<|0Ew)9xf9d1@Rb`PWut2>jI> z!KnBjuCb)glD8iQea&zP$s$)eY`sp9_8|y|(k-DubEWnmq4{9LJ&0qT+_1dC9LpSX z!#`}Pb*|Dm{IC$ATmyN|sr{B#mzzlV`x-$1(G2$Dg0>zw#W=8s5SFd@H$TeuotlTOFQb#Fn@J@z%7>NS&`3!>6zM@om+ z*8K>NOL3CqaKZHmG#oxpDny>Zev-ENIl7)oIPyQgI!JCK#Ps^j9&?DcF|S->itN_4 zNLltqaqG~F%|{kny%O+~8p!BA52v7bxv zxZhnwHbEhk;Cm}FC6Y{|eAH9o3d78wON~xZ|L~}ypC#j`uR(mrh)dsM`nxZB0-eC+ zz0LV2WQ|||RTs7&%7saw@aGzM5o87}5@cOQh_p^tpe4;e8e2vrF0ZM5I6SI`r0!O; zZ8cSOcC-#e_xT_}D%E~M@?|UMTdj_5q->K~VsZ+n<ssR%pdSUZ5_xbpu3x+()v(!F))wEBR{55cDlWeWWJL}Lcau2wyEJA2Jj*@P(vV&NNMfh}Q#y^{4Z453o^&Dumi$pE5E2;HN}5+F)9j zGkg_pmEP6L3v-qB8*~)DdsjXJ*W9!{f~5f`g=jYZMT-ZfLm&G0rJbzbzxB}`el^&W zI1!6^21YbMual1B6=N=qmb+#Q%Du;Wr3B00VcrO;H{?baDz=S~HouTM4{@h+p-T9j z4ZmTIDA1OIsoI3PzTkLS=zXAOvhnNpIox!6{`fD(~%Ktqk+cLeV<78yFB7( zdd=*|ZKnO;3EoPGEM}(PO0B@Qf6ND6N6z>UMxn}|9FtFukw&`nu~xIu-}vR%FuQd= zR|=n5mV8o=UCZcrsOU%{e_QTSJ=^@F%cPpD*KcyAx=Ar0!Jp)S|3~P1BrIs3X7=6Y zu7-N1_^E+n4b_lL(+ZR!FH`dj7CeCkuCO9JsDcxo3mku70^^tfEQ&`7i@O;Qd2b#z zFn39Ht9AHMv4B~_4#^^D?ZzCWRtk-x;U0xO5VNsaRd&jq7E)({F*>Nh+P7e4;kbwy zAn@C0&ybzzDT^h&&5Ei> z--8F__{w9BYy>qH4zc|lA*@!-uSo$R|4H;Y2S}|AspFfWAT%nhqibNtJqr_#B2gxa z2S#Z_jP=ShY@vhQ6%xBX0`ZFTpQSB+q~5SQaHtUKwJ4aQdrxCmmuG0MP@}Lmm4deI zht}8(LVX4I6bv?!w9LrQ0&OWux{A}vxfn!6qj~HMZ~C6@^FH*03HRJmZc-KTSL~62 z&)#bx)w55dYf&ifGc>o<`~u1EA<#N(>Xk2)QKoNn zd$6`&{5*rhZSu%Amrl5_s;V$L5&P`iT$T(YwczTr*nxLCLdRW{E72Uc1-!0j^r|hw zpP1lmNRm$u>}2z1*D!*g57Aagd;UyK7EG)@_J@}s$wP&c<$>l%R@u+*`@F#f(78^~ zdhaAx^v7l5Kff|ONw-LxCG`l}`j1G2dTN)Vr-~<7x%FTsFyF;8VI-{itwh6e)5?*O zw?2S!$>Rkm>J|!;?Sf^Bz$?8m3liwXp9>e4(cu0hOw_GKTm$#L{#S}^;fi=Y^3r8q zRTT=)B7aMjZkTX=jYH(sw;w0k7gEXQTu}eEYI6sFqb|F3j_9FWiAtUY5Lta?l_kNd zJfoMS@5d9ED65i3eElK*xvQk0SYpLd#zU1~AwQU5hl_JYdNNjTbnr2wWyxKAVo+g=Cj#1w;`^&ylSeqvcEf&iFuDF=gVOov{Z0s+qX z*wxnBh!+N(e6k?nxOu+Ym+Ar=;8iH z5HThv`LbM>GyMDJ@w+Q0N&prSp*eDZ*rV4$Ip@h4LvC|BgC<)XeiXW0t>@?VA|6>HOMTeX~gUtg~XL+U=e5HT*! zis@@y_Z(J4`AydjlYfVH=)F$bekF)+dyJdgM29M|DH(>K25W+Ph&v;_5OTXOVib3_ zsUA`RdnL}iz6mI#`;bH6@>IWpN2mvMqU#RDAk!W^FQ3;rG6pGsYyOr6zTpC;Rw(e( zYHbPZOs&)Bs+WFuywCJKy%*7hJ4&DL+;4v`dKO!qUCc`y{y;POC^3c#u=BxqGG z(0>{@Am#GYMz^3L;MHiel_javwJ^I(gs`7Bxw)EsJcX2J#jENv&4urzP?Fgf!M?&(VbgPnIxFAZy=^70s| znG(R_`L|QpbJN4cxoWB@PoFlMAdz9P{IM<>tM6_*B*J{X9^B?vrSJq<8#k<8z5 zd|Z>i9RxL)1M}9LKR+Wf?X{!G(VoVCZWDKLHaMsdF@a}|#CJ&stNKe(h{$VR(g4fN*b<}#8A%;LmIOKLhsvhqKu@7-g8 zb$7T3X?1}FC$Kx41#LcxTuYnp-!HK{VI9o^HhTp}yNlC$L9x0wN3fWfq;`JEeFLAs z$Oa))`0i(k+9Wj&8{ZK6WzdHs^ll0l+>oxXt`(0e{@B)v;~qGT8h(5REt@&;dJl(L za$I8ys;K#nW>EEWREgl4>0b;WvRt0jG2)D?2ztiU$Hl1rQL4MoxmO6(t$uLd-gq4* ztmG%`PMf>GHrdHi4H9fUAY1shKr7w0B^0MAO87uCi}FOq`*;=3Kx#%me<;qM^z$@K zjZ@zr8jaYBW4-tI{(7BIri4C5)5)9~dfdh0&4-f%pVrD?E6xF^1el)!J!qTH40N-X z;5g=~K#*9iuKQA$DQ8lT$nSrPw#3TG)9I6dAlO5NDxwF|dOi!27pnm7L1pwJ5To!> zB0AIL`*Dci-+c z#EEoaS?ddG^XwZ(3a{JipY4h{O^}8hs1bvhEB|;37xA=2IRiAQxgP{s!%o zoNud#_cn{Ul#4zp6B~wQf^B4L^O1tN6dy3jd6#ci1&dqoLIA&OGr^WNc(35-C~88YO1{ zD(}$$>RbW)0`q|3rd%nb42Vt#40YPrE}*+p9$l89*~|P5SE;4GToW zk9`o%&&6hVHo#viiSN4ph}}4^izUib3B7uMqp?iz{n0mj9P=k$_UpIbpxnMUM}0!! zP#kZc=rCU93*EBS8>IIo%&9u0Lxm#>FBJqZewMGr%xnB!SL*T)RcndV2q-{dNKC?y$9YxZV;n!&4AkiD`;!S+SR?Zp zoPMYl+4s*rXT6YiCT+j5s{@vIN7Vn`NC&ja>xW`JAu~w%^Ih&}yLMT5I=xfvlLY0wpI(D6YqXxyYDCsRwk8tOEIkSDaA-qg&hFWU(rdCR<0N*h{FYH(JFUNj|r@oXi++)g7_-Ra% z$W=?Mil42Rog*d(1G|nGfC%CDfWkLiB6X`2aLA~pN*sioLy1%DI+n84?xuGlIh>&2 z05rcQPVZR>4O>B}_4V{!4#TTk>dv2*c$AP#l`L;z7nod4yytLcyr;fp3Ckf&)BKGa zK3z1y?%eMy0SlHue+foas()|`Mvcz8wQe+l&H2mo2kZl?7=4)>Tds6JxzVG2{2`2i z$2?D>Km)L()$o7HLIvkgRV024%HyP^15YQMY&h_|O~gVF zT4SwWRSbQP;{N`R>`(##fTe=I<=0(<>A<{iGkZ>qr=1i@L2HGuwV8$xC^>qD=p3UDoU5kDsYq4odHQHIWQre%3nu(t@vlPjZ*vu8; z{1a0c&j)k_kF0RVB*5o&*AZ6nP?6Bp$bb76=LjRqWaK6ISuE%!?i zvJwHD4-ylP4(Su`n)0CUv-HRZPGpb@ldMnzHUrCNe0B8xt!6cGaEE~4joe1asR=!J z5?2F19=j1w|3$bG4nsF->r`l3{grizEA0cc30@&75P7c|Y0jAxWUsz_4r4vX@WAKt ztFghI)4~H6`BRaU@8_4?#yu0H{7y_QT{yDm+@@}VKHCh}Wfu5P+403a{7uds)SsrS z5R?o0WK#-*(x9?Ko7iXr#zI<1V(8A_D2^qfLjxF48*$xD8K)>|lmDc^I;fs|@{SZ( zrBU>gU+||M`jh{mcqv~-PNER8tE8vf&Ne1dRxO|CDOy|-O!C2FO3TV2ly=>B=&>h% z9f|fwx$k}@@sjLVxzZ=&Hr7%G+ejXJg6b`_`KFo?|IH!Kz|1CmKfgZA^zn^sMb7jM zGv6Hh51N?11peJ3@qcSimtkxWS#4+kQxQe^TkjigipAsi`D@2SriQWh7{#|4z>4D> zpPrxemo0C5HjGs7T90w^f5K-#ST0x_5d`)-ilhogmM#C#SF1pamtx+WSf^n$7^sc+ z$@<&V^VBh9$vw*c7D0OrX5Ue;FqW4f6zt4&uJjfi{9paVi>dYM7>sN;+ncM7#X*Hw zB0m^b5j&wN0ec2%-UTC1Hw4G;C#P2fRT31tkI&ovroWo^I-e-7Z7sOFZXa7)oMpM$ z{sGS+{qpxDO$1!&z(21ozcg9ElfoYuT~GHsD`rRsW9R||$5VXBE=oj^Mc9u-s+W|Q zkY2e^KSWGZU|g2x+arson}b7zf-RN3<4exAHf3k~&6YX-xFA~Dv(2oWc8vY&l-}sy zy?<&a{s__YhBz``hQ7URW*Gw|S+?l2Z273M`1z>bw&O|I8wqnb`Gzk#&4w{Dvg+&B z;u%{RY?=HiB$V?AL&YETTb%uP6)l)t7tFNfCthdKEh=7{`(^NH%Z9|E<9<7{x0XE& zD}k8o=CdUd`Y2>$72!`TOKL=t7IF$7OW9~pC8D7-w`rrH{V;iWycRIa&bAdQ^I$HA z+h~k(K0AmetAV@p9B?bn)ml>24pc7?5GWLyJKFha9`t=|j$uW6la9nZ^$q#af6Wti7Zn|Dj$4l)a~r(EucnR>Z}A zT`JxQr2l>QaiX5D+TJb?Up^qYD)6WGrB6$_f8>bJak_jfz03ZB_G7b;j%gh>^!hSn6JZB?{r4mj; z=}(y8)alUvHTTXuSDokWbNQ{S1&Y0)N-#cVM-=@Ne+e-^Nly0#TqXx~aGwm~?KYd6 z(UE0X-s^I;(O_@hf6p^KvTV>JT#YBjs3Pi)1G+lX`FLjgd;1}SVIxf*{rca1YrV`i zsYNzwQM-ZnUu~^nITs}ycRwq(oD7hlO~G7-i}c-3+YL}OlLdcr$c#w3$75()x%n7x zc|Rnd%^fQL=_2oJX$h@wx1~Df-nOgL^JiHvqfU%B(dzNoWBT88^41hP!H9rYah;x8Yz{WaFMK@&L4K=rwj$rYSz-KO z%?cbmMjM_{fWEHY4&nW!G#CoccJ~B};z0PFM zexQImg%dO^XLnnozrDO4-hVl}A=FspArfpuFqPrmoBK&$;C1=sr8nx5PrEF}iTg6( z^`R7Zmahe48}RTMs>h>R2sKCKkyhyOjUXOiPH*~~%;Rk;^2|D-svTk83hl8KtnWBn zr+~N;_+4MblO`k})wdUiX+9G18iw_hJ>a*YA)0`~oSgLORu`xwhJAQDByX_vEV>qo z0dOPrm@ZSq2`f%@EAeedZMm9$EUOiiYxr+6Y5s{Xfr8pvgW_U)&&T<|CE#bEhR%?} z)B+_yGZ9b6Pj$1`c5Tu{+>~si-rO<$?A``!Q zgqadIk154DfvfUZu0;2)_r2ltSj1}D*mjnmE<_&9V8N13a1HTdeN@!`rsSG#g75bq zT4WBvWUNp3k+45P72<$3IArT!b)@A* zt%4?jGzBXlO0o<|-P;s3#^;P2+T5Ai#89TN5B&S;ysZi#ojK2Ddt@kkmqnb4Fu76`Fqp}SKW z!QDei@ri(_q%2!%MMe6mbn2f42NdCl?&ft8$PcCo-CNn>hU8KpF&~Vz=m>MEXn1*N zde$$A=f|Jdf4A)qneH+pmivQYve%?IE+9iND72LNAi&UmykPil)HGx)Gv?Qwcfd+` zTv=Ya&#F3eS0Uo|`!qohynG{#lg?%P%XYU=gkKh(eZPN≀2*8Qef=uNR?WUqfGe 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�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

)P{%nxq2pxLB*WP2CJG?sCo{T%~+R_X9m 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ಊ&c-Zh~Jf%i^ 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 z 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 literal 116063 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 Ng?kV07T+-m{$F}Xh$#R7 diff --git a/plugins/kiali/package.json b/plugins/kiali/package.json index 35dabf5428..7d2dc2e1b8 100644 --- a/plugins/kiali/package.json +++ b/plugins/kiali/package.json @@ -31,13 +31,25 @@ "@backstage/errors": "^1.2.3", "@backstage/plugin-catalog-react": "^1.8.5", "@backstage/theme": "^0.4.3", - "@janus-idp/backstage-plugin-kiali-common": "1.4.1", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.45", "@patternfly/patternfly": "^5.1.0", "@patternfly/react-charts": "^7.1.1", - "react-use": "^17.4.0" + "@patternfly/react-core": "^5.1.1", + "@patternfly/react-icons": "^5.1.1", + "axios": "^1.5.1", + "cytoscape": "3.15.5", + "deep-freeze": "0.0.1", + "history": "^5.3.0", + "json-beautify": "1.0.1", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "react-ace": "9.1.3", + "react-copy-to-clipboard": "5.x", + "react-use": "^17.4.0", + "typesafe-actions": "^4.2.1", + "typestyle": "^2.4.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0", @@ -52,14 +64,21 @@ "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", "@testing-library/user-event": "14.5.1", - "@types/node": "18.18.5", + "@types/node": "20.2.5", + "@types/react-copy-to-clipboard": "^5.0.6", + "@types/react-router-dom": "^5.2.0", "cross-fetch": "4.0.0", - "msw": "1.3.2" + "jest-canvas-mock": "^2.5.2", + "msw": "1.3.2", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "6.14.1" }, "files": [ "dist", "dist-scalprum" ], + "configSchema": "config.d.ts", "repository": "github:janus-idp/backstage-plugins", "keywords": [ "backstage", diff --git a/plugins/kiali/src/Router.tsx b/plugins/kiali/src/Router.tsx index d9d27177c6..a9e238f3fb 100644 --- a/plugins/kiali/src/Router.tsx +++ b/plugins/kiali/src/Router.tsx @@ -7,12 +7,13 @@ import { useEntity } from '@backstage/plugin-catalog-react'; import { Button } from '@material-ui/core'; -import { KUBERNETES_NAMESPACE } from '@janus-idp/backstage-plugin-kiali-common'; - -import { KialiComponent, KialiNoPath } from './components/KialiComponent'; +import { KialiNoPath, KialiPage } from './pages/Kiali'; +import { KialiProvider } from './store/KialiProvider'; export const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id'; -const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION = +export const KUBERNETES_NAMESPACE = 'backstage.io/kubernetes-namespace'; + +export const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION = 'backstage.io/kubernetes-label-selector'; export const isKubernetesAvailable = (entity: Entity) => @@ -38,11 +39,13 @@ export const Router = () => { kubernetesLabelSelectorQueryAnnotationValue ) { return ( - - } /> - } /> - } /> - + + + } /> + } /> + } /> + + ); } diff --git a/plugins/kiali/src/actions/ActionKeys.ts b/plugins/kiali/src/actions/ActionKeys.ts new file mode 100644 index 0000000000..004213390f --- /dev/null +++ b/plugins/kiali/src/actions/ActionKeys.ts @@ -0,0 +1,106 @@ +export enum ActionKeys { + INCREMENT_LOADING_COUNTER = 'INCREMENT_LOADING_COUNTER', + DECREMENT_LOADING_COUNTER = 'DECREMENT_LOADING_COUNTER', + SET_PAGE_VISIBILITY_HIDDEN = 'SET_PAGE_VISIBILITY_HIDDEN', + SET_PAGE_VISIBILITY_VISIBLE = 'SET_PAGE_VISIBILITY_VISIBLE', + + GRAPH_ON_NAMESPACE_CHANGE = 'GRAPH_ON_NAMESPACE_CHANGE', + GRAPH_SET_DEFINITION = 'GRAPH_SET_DEFINITION', + GRAPH_SET_EDGE_MODE = 'GRAPH_SET_EDGE_MODE', + GRAPH_SET_LAYOUT = 'GRAPH_SET_LAYOUT', + GRAPH_SET_NAMESPACE_LAYOUT = 'GRAPH_SET_NAMESPACE_LAYOUT', + GRAPH_SET_NODE = 'GRAPH_SET_NODE', + GRAPH_SET_RANK_RESULT = 'GRAPH_SET_RANK_RESULT', + GRAPH_SET_UPDATE_TIME = 'GRAPH_SET_UPDATE_TIME', + + GRAPH_TOOLBAR_RESET_SETTINGS = 'GRAPH_TOOLBAR_RESET_SETTINGS', + + GRAPH_TOOLBAR_SET_EDGE_LABELS = 'GRAPH_TOOLBAR_SET_EDGE_LABEL_MODE', + GRAPH_TOOLBAR_SET_FIND_VALUE = 'GRAPH_TOOLBAR_SET_FIND_VALUE', + GRAPH_TOOLBAR_SET_GRAPH_TYPE = 'GRAPH_TOOLBAR_SET_GRAPH_TYPE', + GRAPH_TOOLBAR_SET_HIDE_VALUE = 'GRAPH_TOOLBAR_SET_HIDE_VALUE', + GRAPH_TOOLBAR_SET_IDLE_NODES = 'GRAPH_TOOLBAR_SET_IDLE_NODES', + GRAPH_TOOLBAR_SET_RANK_BY = 'GRAPH_TOOLBAR_SET_RANK_BY', + GRAPH_TOOLBAR_SET_TRAFFIC_RATES = 'GRAPH_TOOLBAR_SET_TRAFFIC_RATES', + + // Toggle Actions + GRAPH_TOOLBAR_TOGGLE_BOX_BY_CLUSTER = 'GRAPH_TOOLBAR_TOGGLE_BOX_BY_CLUSTER', + GRAPH_TOOLBAR_TOGGLE_BOX_BY_NAMESPACE = 'GRAPH_TOOLBAR_TOGGLE_BOX_BY_NAMESPACE', + GRAPH_TOOLBAR_TOGGLE_COMPRESS_ON_HIDE = 'GRAPH_TOOLBAR_TOGGLE_COMPRESS_ON_HIDE', + GRAPH_TOOLBAR_TOGGLE_GRAPH_VIRTUAL_SERVICES = 'GRAPH_TOOLBAR_TOGGLE_GRAPH_VIRTUAL_SERVICES', + GRAPH_TOOLBAR_TOGGLE_GRAPH_MISSING_SIDECARS = 'GRAPH_TOOLBAR_TOGGLE_GRAPH_MISSING_SIDECARS', + GRAPH_TOOLBAR_TOGGLE_GRAPH_SECURITY = 'GRAPH_TOOLBAR_TOGGLE_GRAPH_SECURITY', + GRAPH_TOOLBAR_TOGGLE_LEGEND = 'GRAPH_TOOLBAR_TOGGLE_LEGEND', + GRAPH_TOOLBAR_TOGGLE_FIND_HELP = 'GRAPH_TOOLBAR_TOGGLE_FIND_HELP', + GRAPH_TOOLBAR_TOGGLE_IDLE_EDGES = 'GRAPH_TOOLBAR_TOGGLE_IDLE_EDGES', + GRAPH_TOOLBAR_TOGGLE_IDLE_NODES = 'GRAPH_TOOLBAR_TOGGLE_IDLE_NODES', + GRAPH_TOOLBAR_TOGGLE_OPERATION_NODES = 'GRAPH_TOOLBAR_TOGGLE_OPERATION_NODES', + GRAPH_TOOLBAR_TOGGLE_RANK = 'GRAPH_TOOLBAR_TOGGLE_RANK', + GRAPH_TOOLBAR_TOGGLE_RANK_BY = 'GRAPH_TOOLBAR_TOGGLE_RANK_BY', + GRAPH_TOOLBAR_TOGGLE_SERVICE_NODES = 'GRAPH_TOOLBAR_TOGGLE_SERVICE_NODES', + GRAPH_TOOLBAR_TOGGLE_TRAFFIC_ANIMATION = 'GRAPH_TOOLBAR_TOGGLE_TRAFFIC_ANIMATION', + + GRAPH_UPDATE_SUMMARY = 'GRAPH_UPDATE_SUMMARY', + + // Disable Actions + ENABLE_GRAPH_FILTERS = 'ENABLE_GRAPH_FILTERS', + + HELP_STATUS_REFRESH = 'HELP_STATUS_REFRESH', + + JAEGER_SET_URL = 'JAEGER_SET_URL', + JAEGER_SET_ENABLED = 'JAEGER_SET_ENABLED', + JAEGER_SET_INFO = 'JAEGER_SET_INFO', + JAEGER_SET_TRACE_ID = 'JAEGER_SET_TRACE_ID', + JAEGER_SET_TRACE = 'JAEGER_SET_TRACE', + + LOGIN_REQUEST = 'LOGIN_REQUEST', + LOGIN_EXTEND = 'LOGIN_EXTEND', + LOGIN_SUCCESS = 'LOGIN_SUCCESS', + LOGIN_FAILURE = 'LOGIN_FAILURE', + LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', + SESSION_EXPIRED = 'SESSION_EXPIRED', + SET_LANDING_ROUTE = 'SET_LANDING_ROUTE', + + MTLS_SET_INFO = 'MTLS_SET_INFO', + + ISTIO_STATUS_SET_INFO = 'ISTIO_STATUS_SET_INFO', + ISTIO_SET_CERTS_INFO = 'ISTIO_SET_CERTS_INFO', + + MC_ADD_MESSAGE = 'MC_ADD_MESSAGE', + MC_REMOVE_MESSAGE = 'MC_REMOVE_MESSAGE', + MC_MARK_MESSAGE_AS_READ = 'MC_MARK_MESSAGE_AS_READ', + MC_TOGGLE_MESSAGE_DETAIL = 'MC_TOGGLE_MESSAGE_DETAIL', + MC_SHOW = 'MC_SHOW', + MC_HIDE = 'MC_HIDE', + MC_TOGGLE_EXPAND = 'MC_TOGGLE_EXPAND', + MC_TOGGLE_GROUP = 'MC_TOGGLE_GROUP', + MC_HIDE_NOTIFICATION = 'MC_HIDE_NOTIFICATION', + MC_EXPAND_GROUP = 'MC_EXPAND_GROUP', + + METRICS_STATS_SET = 'METRICS_STATS_SET', + + NAMESPACE_REQUEST_STARTED = 'NAMESPACE_REQUEST_STARTED', + NAMESPACE_SUCCESS = 'NAMESPACE_SUCCESS', + NAMESPACE_FAILED = 'NAMESPACE_FAILED', + TOGGLE_ACTIVE_NAMESPACE = 'TOGGLE_ACTIVE_NAMESPACE', + SET_ACTIVE_NAMESPACES = 'SET_ACTIVE_NAMESPACES', + NAMESPACE_SET_FILTER = 'NAMESPACE_SET_FILTER', + + CLUSTER_SET_FILTER = 'CLUSTER_SET_FILTER', + SET_ACTIVE_CLUSTERS = 'SET_ACTIVE_CLUSTERS', + TOGGLE_ACTIVE_CLUSTER = 'TOGGLE_ACTIVE_CLUSTER', + + NAV_COLLAPSE = 'NAV_COLLAPSE', + SET_DURATION = 'SET_DURATION', + SET_KIOSK = 'SET_KIOSK', + SET_THEME = 'SET_THEME', + SET_LAST_REFRESH = 'SET_LAST_REFRESH', + SET_REFRESH_INTERVAL = 'SET_REFRESH_INTERVAL', + SET_REPLAY_QUERY_TIME = 'SET_REPLAY_QUERY_TIME', + SET_TIME_RANGE = 'SET_TIME_RANGE', + TOGGLE_REPLAY_ACTIVE = 'TOGGLE_REPLAY_ACTIVE', + + TOUR_END = 'TOUR_END', + TOUR_SET_STOP = 'TOUR_SET_STOP', + TOUR_START = 'TOUR_START', +} diff --git a/plugins/kiali/src/actions/HelpDropdownActions.ts b/plugins/kiali/src/actions/HelpDropdownActions.ts new file mode 100644 index 0000000000..3c794dbe8d --- /dev/null +++ b/plugins/kiali/src/actions/HelpDropdownActions.ts @@ -0,0 +1,19 @@ +import { ActionType, createAction } from 'typesafe-actions'; + +import { StatusState } from '../types/StatusState'; +import { ActionKeys } from './ActionKeys'; + +export const HelpDropdownActions: { [key: string]: any } = { + statusRefresh: createAction( + ActionKeys.HELP_STATUS_REFRESH, + resolve => (status: StatusState) => + resolve({ + status: status.status, + externalServices: status.externalServices, + warningMessages: status.warningMessages, + istioEnvironment: status.istioEnvironment, + }), + ), +}; + +export type HelpDropdownAction = ActionType; diff --git a/plugins/kiali/src/actions/IstioCertsInfoActions.ts b/plugins/kiali/src/actions/IstioCertsInfoActions.ts new file mode 100644 index 0000000000..125e4dc31e --- /dev/null +++ b/plugins/kiali/src/actions/IstioCertsInfoActions.ts @@ -0,0 +1,10 @@ +import { ActionType, createStandardAction } from 'typesafe-actions'; + +import { CertsInfo } from '../types/CertsInfo'; +import { ActionKeys } from './ActionKeys'; + +export const IstioCertsInfoActions = { + setinfo: createStandardAction(ActionKeys.ISTIO_SET_CERTS_INFO)(), +}; + +export type IstioCertsInfoAction = ActionType; diff --git a/plugins/kiali/src/actions/IstioStatusActions.ts b/plugins/kiali/src/actions/IstioStatusActions.ts new file mode 100644 index 0000000000..608e0c25dd --- /dev/null +++ b/plugins/kiali/src/actions/IstioStatusActions.ts @@ -0,0 +1,12 @@ +import { ActionType, createStandardAction } from 'typesafe-actions'; + +import { ComponentStatus } from '../types/IstioStatus'; +import { ActionKeys } from './ActionKeys'; + +export const IstioStatusActions = { + setinfo: createStandardAction(ActionKeys.ISTIO_STATUS_SET_INFO)< + ComponentStatus[] + >(), +}; + +export type IstioStatusAction = ActionType; diff --git a/plugins/kiali/src/actions/KialiAppAction.ts b/plugins/kiali/src/actions/KialiAppAction.ts new file mode 100644 index 0000000000..e7a0278ba2 --- /dev/null +++ b/plugins/kiali/src/actions/KialiAppAction.ts @@ -0,0 +1,18 @@ +import { HelpDropdownAction } from './HelpDropdownActions'; +import { IstioCertsInfoAction } from './IstioCertsInfoActions'; +import { IstioStatusAction } from './IstioStatusActions'; +import { LoginAction } from './LoginActions'; +import { MeshTlsAction } from './MeshTlsActions'; +import { MessageCenterAction } from './MessageCenterActions'; +import { NamespaceAction } from './NamespaceAction'; +import { UserSettingsAction } from './UserSettingsActions'; + +export type KialiAppAction = + | HelpDropdownAction + | LoginAction + | NamespaceAction + | UserSettingsAction + | IstioCertsInfoAction + | IstioStatusAction + | MeshTlsAction + | MessageCenterAction; diff --git a/plugins/kiali/src/actions/LoginActions.ts b/plugins/kiali/src/actions/LoginActions.ts new file mode 100644 index 0000000000..a74dbcc9e4 --- /dev/null +++ b/plugins/kiali/src/actions/LoginActions.ts @@ -0,0 +1,63 @@ +import { + ActionType, + createAction, + createStandardAction, +} from 'typesafe-actions'; + +import { LoginSession, LoginStatus } from '../store/Store'; +import { ActionKeys } from './ActionKeys'; + +export interface LoginPayload { + error?: any; + landingRoute?: string; + session?: LoginSession; + status: LoginStatus; +} + +// synchronous action creators +export const LoginActions: { [key: string]: any } = { + loginRequest: createAction(ActionKeys.LOGIN_REQUEST), + loginExtend: createAction( + ActionKeys.LOGIN_EXTEND, + resolve => (session: LoginSession) => + resolve({ + status: LoginStatus.loggedIn, + session: session, + error: undefined, + } as LoginPayload), + ), + loginSuccess: createAction( + ActionKeys.LOGIN_SUCCESS, + resolve => (session: LoginSession) => + resolve({ + status: LoginStatus.loggedIn, + session: session, + error: undefined, + uiExpiresOn: session.expiresOn, + } as LoginPayload), + ), + loginFailure: createAction( + ActionKeys.LOGIN_FAILURE, + resolve => (error: any) => + resolve({ + status: LoginStatus.error, + session: undefined, + error: error, + } as LoginPayload), + ), + logoutSuccess: createAction( + ActionKeys.LOGOUT_SUCCESS, + resolve => () => + resolve({ + status: LoginStatus.loggedOut, + session: undefined, + error: undefined, + } as LoginPayload), + ), + sessionExpired: createAction(ActionKeys.SESSION_EXPIRED), + setLandingRoute: createStandardAction(ActionKeys.SET_LANDING_ROUTE)< + string | undefined + >(), +}; + +export type LoginAction = ActionType; diff --git a/plugins/kiali/src/actions/MeshTlsActions.ts b/plugins/kiali/src/actions/MeshTlsActions.ts new file mode 100644 index 0000000000..0612b11225 --- /dev/null +++ b/plugins/kiali/src/actions/MeshTlsActions.ts @@ -0,0 +1,10 @@ +import { ActionType, createStandardAction } from 'typesafe-actions'; + +import { TLSStatus } from '../types/TLSStatus'; +import { ActionKeys } from './ActionKeys'; + +export const MeshTlsActions = { + setinfo: createStandardAction(ActionKeys.MTLS_SET_INFO)(), +}; + +export type MeshTlsAction = ActionType; diff --git a/plugins/kiali/src/actions/MessageCenterActions.ts b/plugins/kiali/src/actions/MessageCenterActions.ts new file mode 100644 index 0000000000..e382ff3a1c --- /dev/null +++ b/plugins/kiali/src/actions/MessageCenterActions.ts @@ -0,0 +1,59 @@ +import { ActionType, createAction } from 'typesafe-actions'; + +import { MessageType } from '../types/MessageCenter'; +import { ActionKeys } from './ActionKeys'; + +const DEFAULT_GROUP_ID = 'default'; +const DEFAULT_MESSAGE_TYPE = MessageType.ERROR; + +type numberOrNumberArray = number | number[]; + +const toNumberArray = (n: numberOrNumberArray) => (Array.isArray(n) ? n : [n]); + +export const MessageCenterActions = { + addMessage: createAction( + ActionKeys.MC_ADD_MESSAGE, + resolve => + ( + content: string, + detail: string, + groupId: string = DEFAULT_GROUP_ID, + messageType: MessageType = DEFAULT_MESSAGE_TYPE, + showNotification: boolean = true, + ) => + resolve({ content, detail, groupId, messageType, showNotification }), + ), + removeMessage: createAction( + ActionKeys.MC_REMOVE_MESSAGE, + resolve => (messageId: numberOrNumberArray) => + resolve({ messageId: toNumberArray(messageId) }), + ), + toggleMessageDetail: createAction( + ActionKeys.MC_TOGGLE_MESSAGE_DETAIL, + resolve => (messageId: numberOrNumberArray) => + resolve({ messageId: toNumberArray(messageId) }), + ), + markAsRead: createAction( + ActionKeys.MC_MARK_MESSAGE_AS_READ, + resolve => (messageId: numberOrNumberArray) => + resolve({ messageId: toNumberArray(messageId) }), + ), + toggleGroup: createAction( + ActionKeys.MC_TOGGLE_GROUP, + resolve => (groupId: string) => resolve({ groupId }), + ), + expandGroup: createAction( + ActionKeys.MC_EXPAND_GROUP, + resolve => (groupId: string) => resolve({ groupId }), + ), + hideNotification: createAction( + ActionKeys.MC_HIDE_NOTIFICATION, + resolve => (messageId: numberOrNumberArray) => + resolve({ messageId: toNumberArray(messageId) }), + ), + showMessageCenter: createAction(ActionKeys.MC_SHOW), + hideMessageCenter: createAction(ActionKeys.MC_HIDE), + toggleExpandedMessageCenter: createAction(ActionKeys.MC_TOGGLE_EXPAND), +}; + +export type MessageCenterAction = ActionType; diff --git a/plugins/kiali/src/actions/NamespaceAction.ts b/plugins/kiali/src/actions/NamespaceAction.ts new file mode 100644 index 0000000000..55201d68c7 --- /dev/null +++ b/plugins/kiali/src/actions/NamespaceAction.ts @@ -0,0 +1,30 @@ +import { + ActionType, + createAction, + createStandardAction, +} from 'typesafe-actions'; + +import { Namespace } from '../types/Namespace'; +import { ActionKeys } from './ActionKeys'; + +export const NamespaceActions = { + toggleActiveNamespace: createStandardAction( + ActionKeys.TOGGLE_ACTIVE_NAMESPACE, + )(), + setActiveNamespaces: createStandardAction(ActionKeys.SET_ACTIVE_NAMESPACES)< + Namespace[] + >(), + setFilter: createStandardAction(ActionKeys.NAMESPACE_SET_FILTER)(), + requestStarted: createAction(ActionKeys.NAMESPACE_REQUEST_STARTED), + requestFailed: createAction(ActionKeys.NAMESPACE_FAILED), + receiveList: createAction( + ActionKeys.NAMESPACE_SUCCESS, + resolve => (newList: Namespace[], receivedAt: Date) => + resolve({ + list: newList, + receivedAt: receivedAt, + }), + ), +}; + +export type NamespaceAction = ActionType; diff --git a/plugins/kiali/src/actions/UserSettingsActions.ts b/plugins/kiali/src/actions/UserSettingsActions.ts new file mode 100644 index 0000000000..bc758c545a --- /dev/null +++ b/plugins/kiali/src/actions/UserSettingsActions.ts @@ -0,0 +1,33 @@ +import { + ActionType, + createAction, + createStandardAction, +} from 'typesafe-actions'; + +import { + DurationInSeconds, + IntervalInMilliseconds, + TimeInMilliseconds, + TimeRange, +} from '../types/Common'; +import { ActionKeys } from './ActionKeys'; + +export const UserSettingsActions = { + navCollapse: createAction( + ActionKeys.NAV_COLLAPSE, + resolve => (collapsed: boolean) => resolve({ collapse: collapsed }), + ), + setDuration: createStandardAction( + ActionKeys.SET_DURATION, + )(), + setTimeRange: createStandardAction(ActionKeys.SET_TIME_RANGE)(), + setRefreshInterval: createStandardAction( + ActionKeys.SET_REFRESH_INTERVAL, + )(), + setReplayQueryTime: createStandardAction( + ActionKeys.SET_REPLAY_QUERY_TIME, + )(), + toggleReplayActive: createAction(ActionKeys.TOGGLE_REPLAY_ACTIVE), +}; + +export type UserSettingsAction = ActionType; diff --git a/plugins/kiali/src/actions/index.ts b/plugins/kiali/src/actions/index.ts new file mode 100644 index 0000000000..698497743b --- /dev/null +++ b/plugins/kiali/src/actions/index.ts @@ -0,0 +1,4 @@ +export * from './HelpDropdownActions'; +export * from './LoginActions'; +export * from './MessageCenterActions'; +export * from './NamespaceAction'; diff --git a/plugins/kiali/src/api/apiClient.ts b/plugins/kiali/src/api/apiClient.ts deleted file mode 100644 index c774d5aad3..0000000000 --- a/plugins/kiali/src/api/apiClient.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; -import { - createApiRef, - DiscoveryApi, - IdentityApi, -} from '@backstage/core-plugin-api'; - -import { - DirectionType, - FetchResponseWrapper, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -export interface KialiApi { - getConfig(): Promise; - getInfo(): Promise; - getOverview( - overviewType: OverviewType, - duration: number, - direction: DirectionType, - ): Promise; - getNamespaces(): Promise; - setEntity(entity: Entity): void; -} - -export const kialiApiRef = createApiRef({ - id: 'plugin.kiali.service', -}); - -export const KialiEndpoints = { - getInfo: 'info', - getOverview: 'overview', - getConfig: 'config', - getNamespaces: 'namespaces', -}; - -/** - * Provides A KialiClient class to query backend - */ -export class KialiApiClient implements KialiApi { - private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; - protected entity: Entity | null; - - constructor(options: { - discoveryApi: DiscoveryApi; - identityApi: IdentityApi; - }) { - this.discoveryApi = options.discoveryApi; - this.identityApi = options.identityApi; - this.entity = null; - } - - setEntity = (entity: Entity) => { - this.entity = entity; - }; - - private async getAPI( - endpoint: string, - requestBody: any, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('kiali')}/${endpoint}`; - const { token: idToken } = await this.identityApi.getCredentials(); - const jsonResponse = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - body: JSON.stringify(requestBody), - }); - return jsonResponse.json(); - } - - async getInfo(): Promise { - return this.getAPI(KialiEndpoints.getInfo, {}); - } - - async getNamespaces(): Promise { - const requestBody = { - entityRef: this.entity ? stringifyEntityRef(this.entity) : '', - }; - return this.getAPI(KialiEndpoints.getNamespaces, requestBody); - } - - async getConfig(): Promise { - const requestBody = { - entityRef: this.entity ? stringifyEntityRef(this.entity) : '', - }; - return this.getAPI(KialiEndpoints.getConfig, requestBody); - } - - async getOverview( - overviewType: OverviewType, - duration: number, - direction: DirectionType, - ): Promise { - const requestBody = { - entityRef: this.entity ? stringifyEntityRef(this.entity) : '', - query: { - duration, - overviewType, - direction, - }, - }; - return this.getAPI(KialiEndpoints.getOverview, requestBody); - } -} diff --git a/plugins/kiali/src/api/index.ts b/plugins/kiali/src/api/index.ts deleted file mode 100644 index dcb4f49d74..0000000000 --- a/plugins/kiali/src/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './apiClient'; diff --git a/plugins/kiali/src/app/History.ts b/plugins/kiali/src/app/History.ts new file mode 100644 index 0000000000..3c25ac51f4 --- /dev/null +++ b/plugins/kiali/src/app/History.ts @@ -0,0 +1,220 @@ +import { + createBrowserHistory, + createHashHistory, + createMemoryHistory, +} from 'history'; + +import { toValidDuration } from '../config/ServerConfig'; +import { BoundsInMilliseconds } from '../types/Common'; + +const historyMode = (window as any).HISTORY_MODE + ? (window as any).HISTORY_MODE + : 'browser'; + +const createHistory = () => { + if (process.env.TEST_RUNNER) { + return createMemoryHistory(); + } else if (historyMode === 'hash') { + return createHashHistory(); + } + return createBrowserHistory(); +}; + +let history = createHistory(); + +/** + * Some platforms set a different basename for each page (e.g., Openshift Console) + * A setHistory method is defined to be able to modify the history basename when user + * routes to a different page within Kiali in these platforms. + * This method is not used in standalone Kiali application + */ +export const setHistory = () => { + history = createHistory(); +}; + +export { history }; + +export enum URLParam { + AGGREGATOR = 'aggregator', + BY_LABELS = 'bylbl', + CLUSTERNAME = 'clusterName', + DIRECTION = 'direction', + DISPLAY_MODE = 'displayMode', + DURATION = 'duration', + FOCUS_SELECTOR = 'focusSelector', + FROM = 'from', + GRAPH_ANIMATION = 'animation', + GRAPH_BADGE_SECURITY = 'badgeSecurity', + GRAPH_BADGE_SIDECAR = 'badgeSidecar', + GRAPH_BADGE_VS = 'badgeVS', + GRAPH_BOX_CLUSTER = 'boxCluster', + GRAPH_BOX_NAMESPACE = 'boxNamespace', + GRAPH_COMPRESS_ON_HIDE = 'graphCompressOnHide', + GRAPH_EDGE_LABEL = 'edges', + GRAPH_EDGE_MODE = 'edgeMode', + GRAPH_FIND = 'graphFind', + GRAPH_HIDE = 'graphHide', + GRAPH_IDLE_EDGES = 'idleEdges', + GRAPH_IDLE_NODES = 'idleNodes', + GRAPH_LAYOUT = 'layout', + GRAPH_NAMESPACE_LAYOUT = 'namespaceLayout', + GRAPH_OPERATION_NODES = 'operationNodes', + GRAPH_RANK = 'rank', + GRAPH_RANK_BY = 'rankBy', + GRAPH_REPLAY_ACTIVE = 'replayActive', + GRAPH_REPLAY_INTERVAL = 'replayInterval', + GRAPH_REPLAY_START = 'replayStart', + GRAPH_SERVICE_NODES = 'injectServiceNodes', + GRAPH_TRAFFIC = 'traffic', + GRAPH_TYPE = 'graphType', + JAEGER_ERRORS_ONLY = 'errs', + JAEGER_LIMIT_TRACES = 'limit', + JAEGER_PERCENTILE = 'percentile', + JAEGER_SHOW_SPANS_AVG = 'showSpansAvg', + JAEGER_TRACE_ID = 'traceId', + JAEGER_SPAN_ID = 'spanId', + NAMESPACES = 'namespaces', + OVERVIEW_TYPE = 'otype', + DIRECTION_TYPE = 'drtype', + QUANTILES = 'quantiles', + RANGE_DURATION = 'rangeDuration', + REFRESH_INTERVAL = 'refresh', + REPORTER = 'reporter', + SHOW_AVERAGE = 'avg', + SHOW_SPANS = 'spans', + SHOW_TRENDLINES = 'trendlines', + SORT = 'sort', + TO = 'to', + EXPERIMENTAL_FLAGS = 'xflags', +} + +export interface URLParamValue { + name: URLParam; + value: any; +} + +export enum ParamAction { + APPEND, + SET, +} + +export class HistoryManager { + static setParam = (name: URLParam | string, value: string) => { + const urlParams = new URLSearchParams(history.location.search); + urlParams.set(name, value); + history.replace(`${history.location.pathname}?${urlParams.toString()}`); + }; + + static getParam = ( + name: URLParam | string, + urlParams?: URLSearchParams, + ): string | undefined => { + let calculatedParams: URLSearchParams | undefined = urlParams; + if (!calculatedParams) { + calculatedParams = new URLSearchParams(history.location.search); + } + const p = calculatedParams.get(name); + return p ?? undefined; + }; + + static getNumericParam = ( + name: URLParam, + urlParams?: URLSearchParams, + ): number | undefined => { + const p = HistoryManager.getParam(name, urlParams); + return p !== undefined ? Number(p) : undefined; + }; + + static getBooleanParam = ( + name: URLParam | string, + urlParams?: URLSearchParams, + ): boolean | undefined => { + const p = HistoryManager.getParam(name, urlParams); + return p !== undefined ? p === 'true' : undefined; + }; + + static deleteParam = (name: URLParam, historyReplace?: boolean) => { + const urlParams = new URLSearchParams(history.location.search); + urlParams.delete(name); + if (historyReplace) { + history.replace(`${history.location.pathname}?${urlParams.toString()}`); + } else { + history.push(`${history.location.pathname}?${urlParams.toString()}`); + } + }; + + static setParams = ( + params: URLParamValue[], + paramAction?: ParamAction, + historyReplace?: boolean, + ) => { + const urlParams = new URLSearchParams(history.location.search); + + if (params.length > 0 && paramAction === ParamAction.APPEND) { + params.forEach(param => urlParams.delete(param.name)); + } + + params.forEach(param => { + if (param.value === '') { + urlParams.delete(param.name); + } else if (paramAction === ParamAction.APPEND) { + urlParams.append(param.name, param.value); + } else { + urlParams.set(param.name, param.value); + } + }); + + if (historyReplace) { + history.replace(`${history.location.pathname}?${urlParams.toString()}`); + } else { + history.push(`${history.location.pathname}?${urlParams.toString()}`); + } + }; + + static getClusterName = (urlParams?: URLSearchParams): string | undefined => { + let calculatedParams: URLSearchParams | undefined = urlParams; + if (!calculatedParams) { + calculatedParams = new URLSearchParams(history.location.search); + } + return calculatedParams.get(URLParam.CLUSTERNAME) || undefined; + }; + + static getDuration = (urlParams?: URLSearchParams): number | undefined => { + const duration = HistoryManager.getNumericParam( + URLParam.DURATION, + urlParams, + ); + if (duration) { + return toValidDuration(Number(duration)); + } + return undefined; + }; + + static getRangeDuration = ( + urlParams?: URLSearchParams, + ): number | undefined => { + const rangeDuration = HistoryManager.getNumericParam( + URLParam.RANGE_DURATION, + urlParams, + ); + if (rangeDuration) { + return toValidDuration(Number(rangeDuration)); + } + return undefined; + }; + + static getTimeBounds = ( + urlParams?: URLSearchParams, + ): BoundsInMilliseconds | undefined => { + const from = HistoryManager.getNumericParam(URLParam.FROM, urlParams); + if (from) { + const to = HistoryManager.getNumericParam(URLParam.TO, urlParams); + // "to" can be undefined (stands for "now") + return { + from: from, + to: to, + }; + } + return undefined; + }; +} diff --git a/plugins/kiali/src/assets/img/go-logo.png b/plugins/kiali/src/assets/img/go-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..aee71daf20c9fb434bcd46379b63d4d7d486c65c GIT binary patch literal 5007 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 Z(f@`#B99u;(Te~8002ovPDHLkV1g)fk*xp# literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/graphql-logo.svg b/plugins/kiali/src/assets/img/graphql-logo.svg new file mode 100644 index 0000000000..95c03941cf --- /dev/null +++ b/plugins/kiali/src/assets/img/graphql-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/grpc-logo.svg b/plugins/kiali/src/assets/img/grpc-logo.svg new file mode 100644 index 0000000000..6f5c5a4c95 --- /dev/null +++ b/plugins/kiali/src/assets/img/grpc-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/hollow-pin.png b/plugins/kiali/src/assets/img/hollow-pin.png new file mode 100644 index 0000000000000000000000000000000000000000..14477559f687de6da0b717d75a9c5349cd6beb24 GIT binary patch literal 7707 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 LebowO`|y7QpHp)d literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/jaeger-icon.svg b/plugins/kiali/src/assets/img/jaeger-icon.svg new file mode 100644 index 0000000000..d4fae63a2f --- /dev/null +++ b/plugins/kiali/src/assets/img/jaeger-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/java-logo.png b/plugins/kiali/src/assets/img/java-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..75ad9e281bea8e95f695a71c234f3199dc21bcde GIT binary patch literal 13841 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

Q?LnZ397Zi%y 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 \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/aggregate.svg b/plugins/kiali/src/assets/img/legend-nooptimize/aggregate.svg new file mode 100755 index 0000000000..13908e47c4 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/aggregate.svg @@ -0,0 +1,74 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/app.svg b/plugins/kiali/src/assets/img/legend-nooptimize/app.svg new file mode 100755 index 0000000000..a72267f4b1 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/app.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/edge-danger.svg b/plugins/kiali/src/assets/img/legend-nooptimize/edge-danger.svg new file mode 100755 index 0000000000..1fc4ed9d81 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/edge-danger.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/edge-idle.svg b/plugins/kiali/src/assets/img/legend-nooptimize/edge-idle.svg new file mode 100755 index 0000000000..2c62a1eb10 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/edge-idle.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/edge-success.svg b/plugins/kiali/src/assets/img/legend-nooptimize/edge-success.svg new file mode 100755 index 0000000000..65fee59c66 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/edge-success.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/edge-tcp.svg b/plugins/kiali/src/assets/img/legend-nooptimize/edge-tcp.svg new file mode 100755 index 0000000000..b58e722729 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/edge-tcp.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/edge-warn.svg b/plugins/kiali/src/assets/img/legend-nooptimize/edge-warn.svg new file mode 100755 index 0000000000..e30a4ba648 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/edge-warn.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/external-namespace.svg b/plugins/kiali/src/assets/img/legend-nooptimize/external-namespace.svg new file mode 100755 index 0000000000..6857f7e774 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/external-namespace.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/mtls-badge.svg b/plugins/kiali/src/assets/img/legend-nooptimize/mtls-badge.svg new file mode 100755 index 0000000000..164474f83c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/mtls-badge.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-circuit-breaker.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-circuit-breaker.svg new file mode 100755 index 0000000000..eb1ce6ec8f --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-circuit-breaker.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-fault-injection.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-fault-injection.svg new file mode 100644 index 0000000000..ffd0490210 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-fault-injection.svg @@ -0,0 +1,79 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-gateways.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-gateways.svg new file mode 100755 index 0000000000..296ab4777f --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-gateways.svg @@ -0,0 +1,81 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-mirroring.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-mirroring.svg new file mode 100644 index 0000000000..c4c9ed65ce --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-mirroring.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-missing-sidecar.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-missing-sidecar.svg new file mode 100755 index 0000000000..f8005984ad --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-missing-sidecar.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-request-timeout.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-request-timeout.svg new file mode 100644 index 0000000000..09df86b5a9 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-request-timeout.svg @@ -0,0 +1,95 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-shifting.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-shifting.svg new file mode 100644 index 0000000000..6ee8675e05 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-shifting.svg @@ -0,0 +1,82 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-source.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-source.svg new file mode 100755 index 0000000000..e31cafcac7 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-traffic-source.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-virtual-services.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-virtual-services.svg new file mode 100755 index 0000000000..f187caa18c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-virtual-services.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-workload-entry.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-workload-entry.svg new file mode 100644 index 0000000000..cc6a5eeee9 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-badge-workload-entry.svg @@ -0,0 +1,500 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-color-danger.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-danger.svg new file mode 100755 index 0000000000..88fa40b792 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-danger.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-color-idle.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-idle.svg new file mode 100755 index 0000000000..21ea604d15 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-idle.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-color-normal.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-normal.svg new file mode 100755 index 0000000000..bbc283b45c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-normal.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node-color-warning.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-warning.svg new file mode 100755 index 0000000000..759998942c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node-color-warning.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/node.svg b/plugins/kiali/src/assets/img/legend-nooptimize/node.svg new file mode 100755 index 0000000000..eaebd1914a --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/node.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/restricted-namespace.svg b/plugins/kiali/src/assets/img/legend-nooptimize/restricted-namespace.svg new file mode 100755 index 0000000000..0a22dfc226 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/restricted-namespace.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/service-entry.svg b/plugins/kiali/src/assets/img/legend-nooptimize/service-entry.svg new file mode 100755 index 0000000000..6ec2c0432b --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/service-entry.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/service.svg b/plugins/kiali/src/assets/img/legend-nooptimize/service.svg new file mode 100755 index 0000000000..b024b386ea --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/service.svg @@ -0,0 +1,65 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/traffic-failed-request.svg b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-failed-request.svg new file mode 100755 index 0000000000..881e4fc5da --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-failed-request.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/traffic-normal-request.svg b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-normal-request.svg new file mode 100755 index 0000000000..951171e5aa --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-normal-request.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/traffic-tcp.svg b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-tcp.svg new file mode 100755 index 0000000000..a7fc17472a --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/traffic-tcp.svg @@ -0,0 +1,92 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-nooptimize/virtualservice.svg b/plugins/kiali/src/assets/img/legend-nooptimize/virtualservice.svg new file mode 100755 index 0000000000..0cc7ea7759 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-nooptimize/virtualservice.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/aggregate.svg b/plugins/kiali/src/assets/img/legend-pf/aggregate.svg new file mode 100644 index 0000000000..ff211b6aaa --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/aggregate.svg @@ -0,0 +1,19 @@ + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/app.svg b/plugins/kiali/src/assets/img/legend-pf/app.svg new file mode 100644 index 0000000000..f5ff9a691e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/edge-danger.svg b/plugins/kiali/src/assets/img/legend-pf/edge-danger.svg new file mode 100644 index 0000000000..013913b2d6 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/edge-danger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/edge-idle.svg b/plugins/kiali/src/assets/img/legend-pf/edge-idle.svg new file mode 100644 index 0000000000..7c35624d61 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/edge-idle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/edge-success.svg b/plugins/kiali/src/assets/img/legend-pf/edge-success.svg new file mode 100644 index 0000000000..e1467ec020 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/edge-success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/edge-tcp.svg b/plugins/kiali/src/assets/img/legend-pf/edge-tcp.svg new file mode 100644 index 0000000000..6877978009 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/edge-tcp.svg @@ -0,0 +1,12 @@ + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/edge-warn.svg b/plugins/kiali/src/assets/img/legend-pf/edge-warn.svg new file mode 100644 index 0000000000..f94a0b46c3 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/edge-warn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/external-namespace.svg b/plugins/kiali/src/assets/img/legend-pf/external-namespace.svg new file mode 100644 index 0000000000..36faf0831e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/external-namespace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/mtls-badge.svg b/plugins/kiali/src/assets/img/legend-pf/mtls-badge.svg new file mode 100644 index 0000000000..0f2c461565 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/mtls-badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-circuit-breaker.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-circuit-breaker.svg new file mode 100644 index 0000000000..c0b994e96e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-circuit-breaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-fault-injection.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-fault-injection.svg new file mode 100644 index 0000000000..aaee7f2a8d --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-fault-injection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-gateways.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-gateways.svg new file mode 100644 index 0000000000..1a68c7528d --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-gateways.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-mirroring.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-mirroring.svg new file mode 100644 index 0000000000..3d0a35b6ff --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-mirroring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-missing-sidecar.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-missing-sidecar.svg new file mode 100644 index 0000000000..920bcd75cd --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-missing-sidecar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-request-timeout.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-request-timeout.svg new file mode 100644 index 0000000000..2669972377 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-request-timeout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-shifting.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-shifting.svg new file mode 100644 index 0000000000..ffabc60ced --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-shifting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-source.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-source.svg new file mode 100644 index 0000000000..c8935e695c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-traffic-source.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-virtual-services.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-virtual-services.svg new file mode 100644 index 0000000000..c5f6063317 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-virtual-services.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-badge-workload-entry.svg b/plugins/kiali/src/assets/img/legend-pf/node-badge-workload-entry.svg new file mode 100644 index 0000000000..2274f8843d --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-badge-workload-entry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-color-danger.svg b/plugins/kiali/src/assets/img/legend-pf/node-color-danger.svg new file mode 100644 index 0000000000..5da21fd0fd --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-color-danger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-color-healthy.svg b/plugins/kiali/src/assets/img/legend-pf/node-color-healthy.svg new file mode 100644 index 0000000000..bfaa1e90de --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-color-healthy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-color-idle.svg b/plugins/kiali/src/assets/img/legend-pf/node-color-idle.svg new file mode 100644 index 0000000000..6ad4a0bae6 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-color-idle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node-color-warning.svg b/plugins/kiali/src/assets/img/legend-pf/node-color-warning.svg new file mode 100644 index 0000000000..d4fcbefa52 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node-color-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/node.svg b/plugins/kiali/src/assets/img/legend-pf/node.svg new file mode 100644 index 0000000000..be7c352c8e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/restricted-namespace.svg b/plugins/kiali/src/assets/img/legend-pf/restricted-namespace.svg new file mode 100644 index 0000000000..682af9d738 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/restricted-namespace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend-pf/service-entry.svg b/plugins/kiali/src/assets/img/legend-pf/service-entry.svg new file mode 100644 index 0000000000..0a0c8d2f94 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/service-entry.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/service.svg b/plugins/kiali/src/assets/img/legend-pf/service.svg new file mode 100644 index 0000000000..e7925f6b06 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/service.svg @@ -0,0 +1,18 @@ + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/traffic-failed-request.svg b/plugins/kiali/src/assets/img/legend-pf/traffic-failed-request.svg new file mode 100644 index 0000000000..0629173a38 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/traffic-failed-request.svg @@ -0,0 +1,22 @@ + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/traffic-healthy-request.svg b/plugins/kiali/src/assets/img/legend-pf/traffic-healthy-request.svg new file mode 100644 index 0000000000..2f606a6d34 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/traffic-healthy-request.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/plugins/kiali/src/assets/img/legend-pf/traffic-tcp.svg b/plugins/kiali/src/assets/img/legend-pf/traffic-tcp.svg new file mode 100644 index 0000000000..0fd634efbb --- /dev/null +++ b/plugins/kiali/src/assets/img/legend-pf/traffic-tcp.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/plugins/kiali/src/assets/img/legend/aggregate.svg b/plugins/kiali/src/assets/img/legend/aggregate.svg new file mode 100644 index 0000000000..7bf7199af5 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/aggregate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/app.svg b/plugins/kiali/src/assets/img/legend/app.svg new file mode 100644 index 0000000000..f5ff9a691e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/edge-danger.svg b/plugins/kiali/src/assets/img/legend/edge-danger.svg new file mode 100644 index 0000000000..013913b2d6 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/edge-danger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/edge-idle.svg b/plugins/kiali/src/assets/img/legend/edge-idle.svg new file mode 100644 index 0000000000..7c35624d61 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/edge-idle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/edge-success.svg b/plugins/kiali/src/assets/img/legend/edge-success.svg new file mode 100644 index 0000000000..e1467ec020 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/edge-success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/edge-tcp.svg b/plugins/kiali/src/assets/img/legend/edge-tcp.svg new file mode 100644 index 0000000000..ee5bc699cd --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/edge-tcp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/edge-warn.svg b/plugins/kiali/src/assets/img/legend/edge-warn.svg new file mode 100644 index 0000000000..f94a0b46c3 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/edge-warn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/external-namespace.svg b/plugins/kiali/src/assets/img/legend/external-namespace.svg new file mode 100644 index 0000000000..36faf0831e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/external-namespace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/mtls-badge.svg b/plugins/kiali/src/assets/img/legend/mtls-badge.svg new file mode 100644 index 0000000000..0f2c461565 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/mtls-badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-circuit-breaker.svg b/plugins/kiali/src/assets/img/legend/node-badge-circuit-breaker.svg new file mode 100644 index 0000000000..794e5ebbd4 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-circuit-breaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-fault-injection.svg b/plugins/kiali/src/assets/img/legend/node-badge-fault-injection.svg new file mode 100644 index 0000000000..f9f47dc9e7 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-fault-injection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-gateways.svg b/plugins/kiali/src/assets/img/legend/node-badge-gateways.svg new file mode 100644 index 0000000000..d83528be78 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-gateways.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-mirroring.svg b/plugins/kiali/src/assets/img/legend/node-badge-mirroring.svg new file mode 100644 index 0000000000..6e3d6623c9 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-mirroring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-missing-sidecar.svg b/plugins/kiali/src/assets/img/legend/node-badge-missing-sidecar.svg new file mode 100644 index 0000000000..f8977dbb17 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-missing-sidecar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-request-timeout.svg b/plugins/kiali/src/assets/img/legend/node-badge-request-timeout.svg new file mode 100644 index 0000000000..338b8725e2 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-request-timeout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-traffic-shifting.svg b/plugins/kiali/src/assets/img/legend/node-badge-traffic-shifting.svg new file mode 100644 index 0000000000..d09ff2bba5 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-traffic-shifting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-traffic-source.svg b/plugins/kiali/src/assets/img/legend/node-badge-traffic-source.svg new file mode 100644 index 0000000000..5d2282c32d --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-traffic-source.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-virtual-services.svg b/plugins/kiali/src/assets/img/legend/node-badge-virtual-services.svg new file mode 100644 index 0000000000..4067a3dc12 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-virtual-services.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-badge-workload-entry.svg b/plugins/kiali/src/assets/img/legend/node-badge-workload-entry.svg new file mode 100644 index 0000000000..2c4d532960 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-badge-workload-entry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-color-danger.svg b/plugins/kiali/src/assets/img/legend/node-color-danger.svg new file mode 100644 index 0000000000..5da21fd0fd --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-color-danger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-color-idle.svg b/plugins/kiali/src/assets/img/legend/node-color-idle.svg new file mode 100644 index 0000000000..b276cdace3 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-color-idle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-color-normal.svg b/plugins/kiali/src/assets/img/legend/node-color-normal.svg new file mode 100644 index 0000000000..6ad4a0bae6 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-color-normal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node-color-warning.svg b/plugins/kiali/src/assets/img/legend/node-color-warning.svg new file mode 100644 index 0000000000..d4fcbefa52 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node-color-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/node.svg b/plugins/kiali/src/assets/img/legend/node.svg new file mode 100644 index 0000000000..be7c352c8e --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/restricted-namespace.svg b/plugins/kiali/src/assets/img/legend/restricted-namespace.svg new file mode 100644 index 0000000000..682af9d738 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/restricted-namespace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/service-entry.svg b/plugins/kiali/src/assets/img/legend/service-entry.svg new file mode 100644 index 0000000000..cee2228f11 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/service-entry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/service.svg b/plugins/kiali/src/assets/img/legend/service.svg new file mode 100644 index 0000000000..12dd4a177a --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/service.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/traffic-failed-request.svg b/plugins/kiali/src/assets/img/legend/traffic-failed-request.svg new file mode 100644 index 0000000000..1eed174bd7 --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/traffic-failed-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/traffic-normal-request.svg b/plugins/kiali/src/assets/img/legend/traffic-normal-request.svg new file mode 100644 index 0000000000..631c85d12c --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/traffic-normal-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/traffic-tcp.svg b/plugins/kiali/src/assets/img/legend/traffic-tcp.svg new file mode 100644 index 0000000000..0de0e373fc --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/traffic-tcp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/legend/virtualservice.svg b/plugins/kiali/src/assets/img/legend/virtualservice.svg new file mode 100644 index 0000000000..3e5a985eaa --- /dev/null +++ b/plugins/kiali/src/assets/img/legend/virtualservice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/logo-alt.svg b/plugins/kiali/src/assets/img/logo-alt.svg new file mode 100644 index 0000000000..97465ab35d --- /dev/null +++ b/plugins/kiali/src/assets/img/logo-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/logo-lightbkg.svg b/plugins/kiali/src/assets/img/logo-lightbkg.svg new file mode 100644 index 0000000000..3889ec9224 --- /dev/null +++ b/plugins/kiali/src/assets/img/logo-lightbkg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/microprofile-logo.png b/plugins/kiali/src/assets/img/microprofile-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d3deb1ec7b98e71d0f790d70367ef1675bf985a1 GIT binary patch literal 2538 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 A_5c6? literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/mtls-status-full-dark.svg b/plugins/kiali/src/assets/img/mtls-status-full-dark.svg new file mode 100644 index 0000000000..76285e3cdd --- /dev/null +++ b/plugins/kiali/src/assets/img/mtls-status-full-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/mtls-status-full.svg b/plugins/kiali/src/assets/img/mtls-status-full.svg new file mode 100644 index 0000000000..92cbd24642 --- /dev/null +++ b/plugins/kiali/src/assets/img/mtls-status-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/mtls-status-partial-dark.svg b/plugins/kiali/src/assets/img/mtls-status-partial-dark.svg new file mode 100644 index 0000000000..c0dfa62257 --- /dev/null +++ b/plugins/kiali/src/assets/img/mtls-status-partial-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/mtls-status-partial.svg b/plugins/kiali/src/assets/img/mtls-status-partial.svg new file mode 100644 index 0000000000..95b16d2574 --- /dev/null +++ b/plugins/kiali/src/assets/img/mtls-status-partial.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/node-background-key.png b/plugins/kiali/src/assets/img/node-background-key.png new file mode 100644 index 0000000000000000000000000000000000000000..2349638800e9f61995786ffb1da14fdbedfbcf67 GIT binary patch literal 4286 zcmeH~do)yQAICSQw@G+M>NMeuX%vMqmz-lv<9;16$RtOcFg8Z!(ik%v>Ee2va*0YM z6+$JK(&Y^qk|YO5jxH{_bcQ0gkWhN}bXs-Rde{3$f4qOZYuRh<{p{!Y{hsgsJ)h^Y zemlvPVXv&Ht_T2tGTp&uJ6yWTZlpZ?lnVpr;UXcD=_%aqC&uyxyg+s+h!sZgK`aQd z0|5Z)3{R<5zDGnNJ~EH4BK9qNXkwcr{N8)6R$w6EV#>T~<$WaM&JE|UriK4=Lrr{s z{ys;9%dy)lZnYILo|hCK3(aB{SR|B>T1I`a-HG5Di-l(>`H!UA+b((~248psX^S0p zcg;8K-c7Tj8(?azJ@WuSQJxJ)=G6}$$n<>&DDlEVOg??Cw6Uq&v zLZ-M+x>WdG)=a=*Ke-6^nBqL0U9r|Y0f;p+G%_UOZ6UUZj5AZj(gXp4)a^EQpC#a$ zDK1zj;kjYjGKs%5Y``#dJ<| zP){2+2jqsqA(-MQ#7|mZjX(XTQKmzsbNqt9o$LT%@Rv?s+Z{l5P%u1<&uX6xE&?_P zk6$)YQyd9LkWnOj#t6>};mfqh*!=IWrH_0Q<0V{6xW0+NH#sk@t|eUGMBtm8msZ#R z7cRxGi5hp>GvVGR5ByQHfbfw@*nN&oULnqGIGX%3uPP*}9UYO1ueji}iz0c1s}s;bHx z8AHZsD#pJSEnKLjkVq!fg0|sl_rUUo7c&qo$90oNf**QNFtr90#OFGBdEvfG5`;Y| z{;6EY&A3eOcq!ybz7H^^C&&NqVx3d=nlc{P!5BUaH!a0 zm&C zN|Z=O21nnvlw?j#-OJ8*`=hm-d{m_G#o^j3)jT`MVdS7`B5iz%~I;c)t^49c67Z<{glV^;C;k|lx zaXY&7SWP?Vh)VC0LYI&4N5&7}t`JZYE*Nvs^n?D~+`IK#wvLRTY#`}ycx>BsCdVmn z?nUEyrq}6nnf6!EXUSPnn8nw8s z(bRjBLp48_5-w{*n()@Ohu}R%^oKa2O1q-0?Cj{`#j2h$EQ*3w%Hwxi#y? z8_$!v^-V$x^O)WPzq77-?q0CIgC5j%^}0KGUc)vogltCI`$~|d5uSO>0Z{MJLZXxJ zXNkvBQrORpVyaGRX)`e?V3HY)Oa;#lP94UgCj1spomyAa@pkZeUH?;&T%&vt2$D`Y z9j;$BGd;$qXnxc>gF?sw7U$6gZNZ^G>PHl*Bjx3&*%rPi;9qldjS553!Rl&9x-KQ_ zC|=JlefbH8d$#fMHgYd0+#8+#O&O{xTBn#^t~J^Sbi>}*HsJ?5`;Rpi=`~24K}Hi= z$!@;(aOX|^C)7%wNp{Hv+RvzqogSMrju#xbb=_No9+LD!%RFoZClY5eN+^^kOMteTyGgFKYS`EITROaefb)BH$kum;mdovmVn z9ppX8f4sZVW6aaFUI`qus44Bf7Gdu^Q{b!-;mx(G$iH8jJaOmGE?h>VFBENdu63L*>BRw}g3_DTP^(S{sw(bRAdL@twmvS}MGhrlO*&`4mJ97I{!pMZ+@g z7uOp9jr%3G&Y9l<2k}|P(fdlksE(T*MeegwTVY>(e0*H6iKa;UxY#~bv-8?qEHfle zfnZ>8vapc#*m1)^Z1cj})G*5HUlor#c{!%C)z0C{S`%m9PZI}&cHvYH%t&n=E4Hn! zTVdPwHl+o!Bda>D?gKEt4^JOcB5XcQ^hFNn1WVIe^1-4f9vk8-M$ VH+zXy;VrVCe7Y^ero_rO=HI5j)++!2 literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/node-background-topology.png b/plugins/kiali/src/assets/img/node-background-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..557ce49865594760c86fba881c4abad3df8c0b11 GIT binary patch literal 6587 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 WZugpdgWvwXp{I3Lv*e7^)qeqDLsjMg literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/nodejs-logo.png b/plugins/kiali/src/assets/img/nodejs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f86029656590ec0bac33b94603e68a8e237cd262 GIT binary patch literal 1418 zcmV;51$Fv~P)oY6Eczu1(~j2?>fsM34jm5hFQ^;0u%h6+&%*QfRmBKJFbqa7&hD`v6L46aQa! zXXebz`Old-kL%+$Lu)-dH#fI3H#hfCZEbDcjjUmD`{4&32M%hjLt5+1-bf-@mtr8V zHfLk(a)l7l=k+s5b$YTpf%78bv}*~i z^$g&5;F#8Wt=4)_XA8f!bXOk2tN^M+B$hm|VCR$qzsal$IN6g_G?Q4XOH)%T zN~zzJrCwQ6wBd&|%8Uo9fQLKKxj;k?0~8b#DBHFRftA4BDRP^D#UfIpwALR1FLv@i z{s!iV$bnf^)BFST;+3}Fl-f?F3MBfXqM5{^hL)BXiMWI9v#@;Xo3o7rU!+(3<|_5^-WqyxD7}3f=3u$Vwn5Wi0CbHmCHD1isZ;f2Xw`nM%TV z-l)(RQ<*X`AJ~xGvGJ*$#}hGkA_@VhIdcKmH7R#RolusVwQbRk?EuD@FMu+jzJtR) z2Fw(Zqu!iK;Dt1ErT~Yv)*mL5CA zh{!77J}<_^Q{vXn>v~ij;nDXd^~+Fp#mFJU1|^(C#p*fhYXM5DOC|sx0R4dVC{r1n z_gU17+@pbafV03-5$Uv|^RCb`U@x#XIe)ro5~(PyE*T8G>nXnoObw_qU=FY%SQOgQ z-FtQ$i3MxsVf?-dFS5TLEDC*}LT(W7mKR9p0|S8UU{Ppax2NJa3gh>U1}0;e=(;9R zT3s^EXGae>iNF)UFyLsUp?Q9GPS#A|RbUs;ALuzGfJ~q+SR6_`A1bzhPn<+x6L3GU z2UwDwlQjwm1OD#3RyMa5HZagj)T)6$fvnzEo^Au)^n%?q&%5-x8hary9XJIXzg3%b zCy59!xU{+?Gs)>K^wRrrz-gewOF3F^>4$Sb%H~;_u literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/rest-logo.svg b/plugins/kiali/src/assets/img/rest-logo.svg new file mode 100644 index 0000000000..6b04ce7d70 --- /dev/null +++ b/plugins/kiali/src/assets/img/rest-logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/kiali/src/assets/img/solid-pin.png b/plugins/kiali/src/assets/img/solid-pin.png new file mode 100644 index 0000000000000000000000000000000000000000..57769bdd7361eb8d49b956186756dc3d0234de0f GIT binary patch literal 6067 zcmeI0XH=6}x5pVBbSQ!)fCX_Z^j<`S05(LBCQa!g0xBRyAhf`|6cJ>IQluv62uKI% z5CjQmq=YC4(gM;3frNyJQUc_jGxx50<9xVlyfpMCZ@=OHA{%tU_| z??GNJF0Ne$moAudacv9Y;@Y14%XVNn^eS-^TvW}BEcJ|T`>Ow&Kl7K>Qhcfny-VZR+V=O7uTiHcx;mnQ?0=n$oobt+ z*--q}ea4Jdd>fTZ7Q~#er@xBMn$qXKbZ5|45fWC@BrPrB(@mry>?xGo_dRZ|s1j!; zlbVRwm*$Q-5&X+k<0Ppt+4Y=pxS#gr%r^A?LGks&aX7_pIgQf+Dyd^_jA;ELm-IoK z922a)>q<{9A}Li|D6RTL=&s{hZF$^tI#WH7B1Dtg6{V^#Lxstu(}|_&=NglqPJT2% z{i6CXTXT)jee8uvvr^T&!eo%vZ+@EZAM-yw`On=DxmwW~divlx({n{h>3*}!NY58a zFZqx&kH4&KboK=KIm5$gKPc{LTnv1YdoHOmg{jzeT(85yw(I!FG{r`Y$BnwU zxJasze<(UeD2PYS1>fR<^2onns^CIl_*7gkGw>XqRQ9yxQR|Eb$VrCb&zRniV=XeRe!5 zRQu(%p;L`gQ(lhBiq#pogTWtB*9o)e2AapRcIvuHzU2+aXT9Zn&EGP38}do6K{5P_ zX%j*!P4@S%zGL_ua6H=9^^ftNhWtO9;aMzB?Au|brgOv(X-U-g?a!OHmpgzB$Pz`H z&fs?B7?xqI#lF*mlheN3UOs8;!$?V+EzXEV@3eQ!T{@@Lp)V=O8o=eI#3in{C`u3? zCaDayZa&N#b37EMjDDZ&ttI_dsQz93#MU!B zF--EpD-R^4|LeQ~eXl7hAh=hH+~?qhTkc`_*X=M3(G702BNmfO!k$w~FZQvf`gU%! zUY@!5R?&O0yR4Hk5;3YPxSURp#*~aw(2Yfdzj^Rq6*_c))z-11!&Q?p`!A=vre!kf zYKvVoRt09wBw@{O;Z9)aaYsu0+fc^I(XP^4O@gtl;qwIjD}gS#p>;ByxP(5d(UQTm zD`Q`@Lqh9RV~^@&l#J2dbLo*We^AZoEm>jZV>HY0maLM4^sD5w{W%J%KZNwaj^!B6%@$fXbwn`Tni|Za+Dj zCUL@d?C^LFt#`4-+XICiUSn?P7}f=%M+SBcC(?p~bDry}JB)Z=5>8~*ewv}MR)nx4 zw|n+68tb~UC3E?&$6lFnT_)b-$Fof3bbOHyuxQ~iZkp8hfZGd^fui`v>ThiKolH@& zEz)Yl$oU}>dpR&RP)V!dwU4uMCffdbeZ_vVf`SrGKLT;FI*N}UC3nAi?QlZh7tHGd z_3$hdop8gfe59FVkr-PuH-hwL?5Lf9m;1FtBS>y<&#YG@3&xJR462jeT%i(U0=AV&8ITxQW>T~DWOM&GH4`sGwxA9fV{? zj9id$a9VYD9W|R-C!}(p>aUNOc)_SIBy94ryCb*FvA4Cocqa9DYfVZz=2%A#CdphX zIomQ9!J8M(p6n>JkokJ*Yc3*Q-1;UhorkHDH_3zH>6NLJL`rmoboM1?oS2g)lT;)+ z8+`xd!Ni&0%?>8GR3^|hL%=2a1=2LAyJAC{yiZJ3F1+(=G*Rwy4ih<{+(r1yRb4Uz z!t?d&6gOOcabmCHqt87m@AC3M3sGJy`FhBO?=6;>lo|?dN#~{?d|ZPt zHtUMmHqfbY+hlwI3fknn2~({dm4H zDMfPfaoOjncPl1Xf9~>{yJ=TiY4p<}d3ljpb)=?V^Eagz8R&#G-{n+svpaW{_jU8x^GLud{0xyy_N)__EW)QlP@DzCCGM2 z>)qqi!4HYNge;^G#%d!hd6wl`GC7-tJaxzB4SA$y>#TgS#@2qEh5O-g)NEBy$uuiX z6itat4Pz7Cn(ww6HnWpr!nzHc#c!^q_itbyIoMTXwzMowIUTRcY}r@o!Dsz-V`n}^ zg*!upD$FkxG0!sz5@Cy2;{s{V$>}O-&xzdpiWreL7d}N5F}omjWG$oR5z2>ILbK9! z+qbTq^&T_S-oY3obP&%B5uS&43p?^-uv0>+ zFS!MIPTUOgY$YxY5%+{Vdw%|sc58@`%vOKB zBb*%}WExyDN7nVWw7f=M*lcOTuY8ai@LO$SO?9AE2bR20C{)f9;=yLzVl!*`&SmtG z6_hR|LIX7)g66Mcy(ntZM9rUWitgC_E+KBwu_WJ3^ERcEaVwVs!-5{tX^qU_5Z0IF z^$o^S(0Z`mJKX5Xs*6%=T2GEjBV&kyk0~erBvvahiu&%ynx8A%lQZ$sBXbt-=KoEH z?#I62-MD@%PGtDL|FT}v?1v*=`Tonvlb@1xSxkgji(AKACQ(DFzA zdGY|TeCMD*w0;1)Jw;4yKX7nTK}`f$bU0`dtsgkvp5mw`3>=)it0n|2;T%ki)${h*xolrgovz`@BywLQSH#X*4>{on{t<;mT^!AS+04(I@S z7!6vsUd;kM_Lo8SvX131-$JgSv4Api}vcQQ&QAbf8rLK13b{NnC%{;+MJ6xjPWHR+*{*)GmV#K)qv+*qT?I z^z%U*@Nn^E6-bqE!yTkL@U|=xs1qHyfm#&L3)E)_&sz>FyPnX350?UAhaXd=K&taY z#z6hHG7G9*PeHZOwb$zHDIcgOfcyE70Zd+%4BY(gaJL>HE5PJ9ho3?6GtS%#`z*jG z4XD1g6S_YLKzA+))}PP8f{N$>Q0y?=h@5O2hW(i0;C4QbYnXJE%3Ux8C#y*?bu9<$ zD;@-ZbR%#koNOC`#e0I?fl%j>xxs4(ij6S_YJvo)(HYLBLJv%=e&iU48}$O42C=p0 z$(2tGI|zh2V<_@&4Uh#A*f^`)(W~!i(!2^ z*09vG*9>%&V{XNOnBApd&m#U1f*}X`;V`E;XyYv1(2nRS76mb!R44%3453iSL0*rJ z+!7AavT? z%t5Ej#0#1n*;!_iux;kz|HIMMEZ_hZ>!bfm#3ey68}VLH_PMxaFpk+V?E;N}_!Hnf z#i(?E$v-pl1MQGXfrT38!Ty;SLbK%^Gv9f{W!TD@y-?oJ4&+K_Z9#PUG1yz9$KY*( z$iKuVV}dnEg6&Z=fa{u>3j?nh!ax~VkWnh!0{MnKAh2UE4D{!LfsN5Hux%2O_#-Dj zX1F@T1nwNXeFa)Pex&bwckQoWknXzwqnwf!u*fO@NR0sD4Db#`JmH{lCV2lLp8Sp3 znh#08fQff;kVgWxj6WH+;y!HL(Y9&$y{57<7-~8lyn_*bMzC8365znc$R&e$o_hit za)tnb)FbW43}(3NW^I|r!jl$ZeJ8?#VM^-Q{>s3m$KliaxMcslM7|%u1EeC DeaW@z literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/thorntail-logo.png b/plugins/kiali/src/assets/img/thorntail-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf3ddc66a0860c93edae4933bd5325cc0810fed GIT binary patch literal 825 zcmeAS@N?(olHy`uVBq!ia0vp^sX#2u!3-q#-85DNQqKc?LR^6~5r7e)hKrvepsMdY zLuefn3l|>)pC|*bFas+Gleny!1!Lz41_mAH%8iT+LNe->49*Em43=T?%3^`}{8s*4 z3~E-gS_};K2|OCoDU+Exjxkt7vNi5uv?K))f>KEL_4AT*b)1El{+SLCcPT zfnPkTi5V#FlFVQg0#XLF(b$V2XD$PSq9Fq-I|Gj}0~?p5q$Go|jEV_^ii(Pw1<0|J zt}rlkoMR~8!jLwLA-4DDt`%>9qV6R@e!&b44hIAjJ|;X^5O86m!GwZOKfiyvyeaF~ z&u{PEzBp6%=f{ul-@ktT00i&fzIpxP#q(#69^JWd?ee*kM~>`RyJFeA=~J4TQf%iL zE?C6Cz_`oP#WBR<^xa9QhVA!9<|vOjCC0qgkIw%1EYo;!a$eQlbx*Z|8v5=j z^~LQBT;qN8)SV9s(IHLOXECv;3f^A)gH5dZ3(L;Bg59r|oi9i(TpAH_C%eMsFt@3v zS3$0*&3RR~hND!JimW>{`ywSM|<@??OHD(xm_&Sd&%@E3w9<{n60}% z$w>F=+xTfmit=sdz0v>DnljTYJzC01AZD&kYlP0vg-0!Y)1w&r_5c2=UHtCai-kus zmjz9lHBI}R#LsC{QhGW3qbKN0am|<9G-FNJ_sfy%;^y${E=}~Gt6 zFB?v!=zREE@U{4wYoF2gN7@|~%Cegsm+QQ{sp*|LrRZRq&IX&9yL&`+H=o%X;k9pD4Um05bD|77;^MNbXW?#&S5y{78M9W-&$!IpbL$MyXqxnd`QZws9ok^|@ir*G=*EHMoy8Upkv#Bk4`r_!*b`I-jr@arn d=lHK&&u~7?Xvy5rXlYP-@pScbS?83{1OOm1Ne%!2 literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/assets/img/vertx-logo.png b/plugins/kiali/src/assets/img/vertx-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c438d22778fc8a1c18b794ea8a9c82aeaf945fff GIT binary patch literal 1345 zcmV-H1-|-;P)+00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY4c7nw4c7reD4Tcy000McNliru;{ps0ISgUG!_NQ!1kOoB zK~z}7)tGB+6-5-se=|jaR=`?$XhQs=71<*75m#{47^BgcAcBcRycE{(FkSHR!5BqM zj0Pc+7`ZXQnw1JF#NY>wpG+{d3wA4Dld?q~5hFeb0c@*c<&<`fZBxmM{Qlv?qg{+%g{#q^!6uLoGFM_W`B_8Qh@z=(`zz2Wy&!gdAF0Q3M? z0&f6z${evIZ8m@(c;5rhCL;D+CqGMqI{CMN z=TgjD{lNRr={F)VZqgv!EiJoNMd1O1_yD$M1bL@^(xff%(GO@c1gHWWed6>3E6bEw zFX=UE4Bb!0cVvU0qOe6;c1(~i@Pf4LDLPPq4H=8J0{b#-`+=*Cag{#OOz8%eswmuE zCJ+`U05}6Y;Rjy#kRTYx1F5AhO6{lZwKo5-ESrAkd>*Q}t_6WdgKk!;zCtn5B`+>LPtoSl&xDS{BJPPFEEKpI~g-5x!9S|N6 zXw75i(IyW8!1~IR=qBc`GIoflsO_}wK92wsfzPC6 zSCygpTn9`w6ur6(&bUr~t}gM3K(p)QWvOzAo1w>z9ojMkYYW}*WZex?fC!IC%bpE< zt^58>vd*5X_J+k_mcb(_#MkHt-Vd&mAD!T{`}OJZX56=@)bMQrL>Dk6ZdX+lwx`s3 z=XqYBwyP+-H!;UNon-=eT}9!_ly!=l+$J|v=X%A!DRv+51+Yw^3KIF7F5y@<_g9Eq zb4z0k(i-4-!+si=;RoIxnZ8TyMWci4P@Nb(W6T zjnT4=!3w2sx3L@s8e;$k(ow7gOk%$j_|SFoRU=Bo*905{4zYJoZII91ika+#7IQ0!%mmhe&XWup(3%X>I*Yt6Mi%eW6 zKTY$<<-iGGrXP3*F7z(XMF_-r;3i;~A9yD(biRK9dcc&D-N2+700000NkvXXu0mjf DqTF>H literal 0 HcmV?d00001 diff --git a/plugins/kiali/src/components/About/AboutUIModal.tsx b/plugins/kiali/src/components/About/AboutUIModal.tsx new file mode 100644 index 0000000000..8515d72ad1 --- /dev/null +++ b/plugins/kiali/src/components/About/AboutUIModal.tsx @@ -0,0 +1,219 @@ +import * as React from 'react'; + +import { Link } from '@backstage/core-components'; + +import { + Card, + CardContent, + CardHeader, + Collapse, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Grid, + IconButton, + Typography, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import { Alert } from '@material-ui/lab'; + +import { config, KialiIcon, KialiLogo } from '../../config'; +import { kialiStyle } from '../../styles/StyleUtils'; +import { + ExternalServiceInfo, + Status, + StatusKey, +} from '../../types/StatusState'; + +type AboutUIModalProps = { + status: Status; + externalServices: ExternalServiceInfo[]; + warningMessages: string[]; + showModal: boolean; + setShowModal: React.Dispatch>; +}; + +const iconStyle = kialiStyle({ + marginRight: '10px', +}); + +const textContentStyle = kialiStyle({ + $nest: { + '& dt, & dd': { + lineHeight: 1.667, + }, + }, +}); + +const closeButton = kialiStyle({ + position: 'absolute', + right: 10, + top: 10, +}); + +export const AboutUIModal = (props: AboutUIModalProps) => { + const [showWarnings, setShowWarnings] = React.useState(false); + + const additionalComponentInfoContent = ( + externalService: ExternalServiceInfo, + ) => { + if (!externalService.version && !externalService.url) { + return 'N/A'; + } + const version = externalService.version ? externalService.version : ''; + const url = externalService.url ? ( + + {externalService.url} + + ) : ( + '' + ); + return ( + <> + {version} {url} + + ); + }; + + const renderComponent = (externalService: ExternalServiceInfo) => { + const name = externalService.version + ? externalService.name + : `${externalService.name} URL`; + const additionalInfo = additionalComponentInfoContent(externalService); + return ( +

+ + {name} + + + {additionalInfo} + +
+ ); + }; + + const renderWebsiteLink = () => { + if (config.about?.website) { + return ( + + + {config.about.website.linkText} + + ); + } + + return null; + }; + + const renderProjectLink = () => { + if (config.about?.project) { + return ( + + + {config.about.project.linkText} + + ); + } + + return null; + }; + + const coreVersion = + props.status[StatusKey.KIALI_CORE_COMMIT_HASH] === '' || + props.status[StatusKey.KIALI_CORE_COMMIT_HASH] === 'unknown' + ? props.status[StatusKey.KIALI_CORE_VERSION] + : `${props.status[StatusKey.KIALI_CORE_VERSION]} (${ + props.status[StatusKey.KIALI_CORE_COMMIT_HASH] + })`; + const containerVersion = props.status[StatusKey.KIALI_CONTAINER_VERSION]; + const meshVersion = props.status[StatusKey.MESH_NAME] + ? `${props.status[StatusKey.MESH_NAME]} ${ + props.status[StatusKey.MESH_VERSION] || '' + }` + : 'Unknown'; + + return ( + props.setShowModal(false)} + aria-labelledby="Kiali" + aria-describedby="Kiali" + fullWidth + > + + + props.setShowModal(false)} + className={closeButton} + > + + + + + + Kiali + + + Kiali + + + {coreVersion || 'Unknown'} + + + Kiali Container + + + {containerVersion || 'Unknown'} + + + Service Mesh + + + {meshVersion || 'Unknown'} + + + + {props.warningMessages.length > 0 && ( + + + {props.warningMessages.length} warnings.{' '} + setShowWarnings(!showWarnings)} + style={{ color: '#2b9af3' }} + > + ({showWarnings ? 'Close' : 'See'} them) + + + } + /> + + + {props.warningMessages.map(warn => ( + + {warn} + + ))} + + + + )} + + Components + {props?.externalServices.map(renderComponent)} + + + + {renderWebsiteLink()} + {renderProjectLink()} + + + ); +}; diff --git a/plugins/kiali/src/components/Ambient/AmbientBadge.tsx b/plugins/kiali/src/components/Ambient/AmbientBadge.tsx new file mode 100644 index 0000000000..0dcdc5d6ee --- /dev/null +++ b/plugins/kiali/src/components/Ambient/AmbientBadge.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; + +import { Chip, Tooltip } from '@material-ui/core'; + +type AmbientLabelProps = { + style?: React.CSSProperties; + tooltip: string; +}; + +export const AmbientBadge = (props: AmbientLabelProps) => { + const iconComponent = ( + + + + ); + return ( + + {iconComponent} + + ); +}; diff --git a/plugins/kiali/src/components/Ambient/AmbientLabel.tsx b/plugins/kiali/src/components/Ambient/AmbientLabel.tsx new file mode 100644 index 0000000000..ee291e8ee6 --- /dev/null +++ b/plugins/kiali/src/components/Ambient/AmbientLabel.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; + +import { Chip, Tooltip } from '@material-ui/core'; + +type AmbientLabelProps = { + tooltip: boolean; + style?: React.CSSProperties; + waypoint?: boolean; +}; + +const AmbientComponent = 'ztunnel'; + +export class AmbientLabel extends React.Component { + render() { + const msg = 'Component is labeled as part of the Istio Ambient Mesh'; + + const tooltipContent = ( +
+
+ {msg} +
+
+
+ ); + const iconComponent = ( + + + {this.props.waypoint && ( + + )} + {!this.props.tooltip && ( + + {msg} + + + + + )} + + ); + return this.props.tooltip ? ( + + {iconComponent} + + ) : ( + iconComponent + ); + } +} diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/CustomTooltip.tsx b/plugins/kiali/src/components/Charts/CustomTooltip.tsx similarity index 58% rename from plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/CustomTooltip.tsx rename to plugins/kiali/src/components/Charts/CustomTooltip.tsx index b3fcddd004..613af2b925 100644 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/CustomTooltip.tsx +++ b/plugins/kiali/src/components/Charts/CustomTooltip.tsx @@ -4,14 +4,12 @@ import { ChartCursorFlyout, ChartLabel, ChartPoint, + ChartTooltip, + ChartTooltipProps, } from '@patternfly/react-charts'; -import { - toLocaleStringWithConditionalDate, - VCDataPoint, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { HookedChartTooltip, HookedTooltipProps } from './HookedChartTooltip'; +import { VCDataPoint } from '../../types/VictoryChartInfo'; +import { toLocaleStringWithConditionalDate } from '../../utils/Date'; const dy = 15; const headSizeDefault = 2 * dy; @@ -27,7 +25,7 @@ const CustomLabel = (props: any) => { const textsWithHead = props.head ? [props.head, ' '].concat(props.text) : props.text; - const headSize = props.head ? headSizeDefault : 0; + const headSize = props.head ? 2 * dy : 0; const startY = yMargin + props.y - (textsWithHead.length * dy) / 2 + headSize; return ( @@ -38,7 +36,7 @@ const CustomLabel = (props: any) => { const symbol = pt.symbol || 'square'; return ( { return undefined; }; -type Props = HookedTooltipProps<{}> & { - showTime?: boolean; +export type HookedTooltipProps = ChartTooltipProps & { + activePoints?: (VCDataPoint & T)[]; + onOpen?: (items: VCDataPoint[]) => void; + onClose?: () => void; }; -type State = { - texts: string[]; - head?: string; - textWidth: number; - width: number; - height: number; +export class HookedChartTooltip extends React.Component< + HookedTooltipProps +> { + componentDidMount() { + if (this.props.onOpen && this.props.activePoints) { + this.props.onOpen(this.props.activePoints); + } + } + + componentWillUnmount() { + if (this.props.onClose) { + this.props.onClose(); + } + } + + render() { + return ; + } +} + +type Props = HookedTooltipProps<{}> & { + showTime?: boolean; }; -export class CustomTooltip extends React.Component { - static getDerivedStateFromProps(props: Props): State { +export const CustomTooltip = (props: Props) => { + const getDerivedStateFromProps = () => { const head = props.showTime ? getHeader(props.activePoints) : undefined; - const texts: string[] = []; - if (props.text) { - if (Array.isArray(props.text)) { - texts.push(...(props.text as string[])); - } else { - texts.push(props.text as string); - } + let texts: string[] = []; + + if (props.text && Array.isArray(props.text)) { + texts = props.text as string[]; + } else if (props.text) { + texts = [props.text as string]; } + let height = texts.length * dy + 2 * yMargin; if (head) { height += headSizeDefault; @@ -105,30 +121,25 @@ export class CustomTooltip extends React.Component { width: width, height: height, }; - } + }; - constructor(p: Props) { - super(p); - this.state = CustomTooltip.getDerivedStateFromProps(p); - } + const initialState = getDerivedStateFromProps(); - render() { - return ( - - } - labelComponent={ - - } - /> - ); - } -} + return ( + + } + labelComponent={ + + } + /> + ); +}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/SparklineChart.tsx b/plugins/kiali/src/components/Charts/SparklineChart.tsx similarity index 52% rename from plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/SparklineChart.tsx rename to plugins/kiali/src/components/Charts/SparklineChart.tsx index 6450bd65f3..d90f1275a0 100644 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/SparklineChart.tsx +++ b/plugins/kiali/src/components/Charts/SparklineChart.tsx @@ -13,13 +13,12 @@ import { } from '@patternfly/react-charts'; import { - addLegendEvent, RichDataPoint, VCDataPoint, - VCEvent, VCLines, -} from '@janus-idp/backstage-plugin-kiali-common'; - +} from '../../types/VictoryChartInfo'; +import { addLegendEvent, VCEvent } from '../../utils/VictoryEvents'; +import { PFColors } from '../Pf/PfColors'; import { CustomTooltip } from './CustomTooltip'; type Props = ChartProps & { @@ -33,79 +32,72 @@ type Props = ChartProps & { thresholds?: VCLines; }; -type State = { - width: number; - hiddenSeries: Set; +const axisStyle = { + tickLabels: { fill: PFColors.Color100 }, }; export const INTERPOLATION_STRATEGY = 'monotoneX'; -export class SparklineChart extends React.Component { - containerRef?: React.RefObject; - - constructor(props: Props) { - super(props); - if (props.width === undefined) { - this.containerRef = React.createRef(); +export const SparklineChart = (props: Props) => { + const [width, setWidth] = React.useState(props.width || 1); + const [hiddenSeries, setHiddenSeries] = React.useState>( + new Set(), + ); + const containerRef: React.RefObject | undefined = + props.width === undefined ? React.createRef() : undefined; + + const handleResize = () => { + if (containerRef?.current) { + setWidth(containerRef.current.clientWidth); } - this.state = { width: props.width || 1, hiddenSeries: new Set() }; - } + }; - componentDidMount() { - if (this.containerRef) { + React.useEffect(() => { + if (containerRef) { setTimeout(() => { - this.handleResize(); - window.addEventListener('resize', this.handleResize); + handleResize(); + window.addEventListener('resize', handleResize); }); - } - } - componentWillUnmount() { - if (this.containerRef) { - window.removeEventListener('resize', this.handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; } - } + return () => {}; + }); - private handleResize = () => { - if (this.containerRef?.current) { - this.setState({ width: this.containerRef.current.clientWidth }); - } - }; - - private renderChart() { + const renderChart = () => { const legendHeight = 30; - let height = this.props.height || 300; + let height = props.height || 300; let padding = { top: 0, bottom: 0, left: 0, right: 0 }; - if (this.props.padding) { - const p = this.props.padding as number; + if (props.padding) { + const p = props.padding as number; if (Number.isFinite(p)) { padding = { top: p, bottom: p, left: p, right: p }; } else { - padding = { ...padding, ...(this.props.padding as object) }; + padding = { ...padding, ...(props.padding as object) }; } } const events: VCEvent[] = []; - if (this.props.showLegend) { + if (props.showLegend) { padding.bottom += legendHeight; height += legendHeight; - this.props.series.forEach((_, idx) => { + props.series.forEach((_, idx) => { addLegendEvent(events, { - legendName: `${this.props.name}-legend`, + legendName: `${props.name}-legend`, idx: idx, - serieID: [`${this.props.name}-area-${idx}`], + serieID: [`${props.name}-area-${idx}`], onClick: () => { - if (!this.state.hiddenSeries.delete(idx)) { + if (!hiddenSeries.delete(idx)) { // Was not already hidden => add to set - this.state.hiddenSeries.add(idx); + hiddenSeries.add(idx); } - this.setState(prevState => ({ - hiddenSeries: new Set(prevState.hiddenSeries), - })); + setHiddenSeries(new Set(hiddenSeries)); return null; }, - onMouseOver: props => { + onMouseOver: prs => { return { - style: { ...props.style, strokeWidth: 4, fillOpacity: 0.5 }, + style: { ...prs.style, strokeWidth: 4, fillOpacity: 0.5 }, }; }, }); @@ -115,13 +107,11 @@ export class SparklineChart extends React.Component { const container = ( - this.props.tooltipFormat - ? this.props.tooltipFormat(obj.datum) - : obj.datum.y + props.tooltipFormat ? props.tooltipFormat(obj.datum) : obj.datum.y } labelComponent={} - voronoiBlacklist={this.props.series.map( - (_, idx) => `${this.props.name}-scatter-${idx}`, + voronoiBlacklist={props.series.map( + (_, idx) => `${props.name}-scatter-${idx}`, )} /> ); @@ -133,18 +123,18 @@ export class SparklineChart extends React.Component { return ( - {this.props.showXAxisValues ? ( + {props.showXAxisValues ? ( dp.x)} + tickValues={props.series[0].datapoints.map(dp => dp.x)} tickFormat={x => (x as Date).toLocaleTimeString([], { hour: '2-digit', @@ -152,44 +142,52 @@ export class SparklineChart extends React.Component { }) } tickCount={2} + style={axisStyle} /> ) : ( )} - {this.props.showYAxis ? ( + {props.showYAxis ? ( + } tickCount={2} dependentAxis + style={axisStyle} /> ) : ( )} - {this.props.series.map((serie, idx) => { - if (this.state.hiddenSeries.has(idx)) { + {props.series.map((serie, idx) => { + if (hiddenSeries.has(idx)) { return undefined; } return ( (active ? 5 : 2)} /> ); })} - {this.props.series.map((serie, idx) => { - if (this.state.hiddenSeries.has(idx)) { + {props.series.map((serie, idx) => { + if (hiddenSeries.has(idx)) { return undefined; } return ( { /> ); })} - {this.props.showLegend && ( + {props.showLegend && ( { - if (this.state.hiddenSeries.has(idx)) { - return { ...s.legendItem, symbol: { fill: '#72767b' } }; + name={`${props.name}-legend`} + data={props.series.map((s, idx) => { + if (hiddenSeries.has(idx)) { + return { ...s.legendItem, symbol: { fill: PFColors.Color200 } }; } return s.legendItem; })} y={height - legendHeight} height={legendHeight} - width={this.state.width} + width={width} /> )} - {this.props.thresholds && - this.props.thresholds.map((serie, idx) => { - if (this.state.hiddenSeries.has(idx)) { + {props.thresholds && + props.thresholds.map((serie, idx) => { + if (hiddenSeries.has(idx)) { return undefined; } return ( { })} ); - } + }; - render() { - if (this.containerRef) { - return
{this.renderChart()}
; - } - return this.renderChart(); + if (containerRef) { + return
{renderChart()}
; } -} + return renderChart(); +}; diff --git a/plugins/kiali/src/components/DebugInformation/DebugInformation.tsx b/plugins/kiali/src/components/DebugInformation/DebugInformation.tsx new file mode 100644 index 0000000000..4690d94a40 --- /dev/null +++ b/plugins/kiali/src/components/DebugInformation/DebugInformation.tsx @@ -0,0 +1,296 @@ +// @ts-nocheck +import * as React from 'react'; +import AceEditor from 'react-ace'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; + +import { + CardTab, + TabbedCard, + Table, + TableColumn, +} from '@backstage/core-components'; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, +} from '@material-ui/core'; +import { + Alert, + AlertActionCloseButton, + AlertVariant, +} from '@patternfly/react-core'; + +import { serverConfig } from '../../config'; +import { authenticationConfig } from '../../config/AuthenticationConfig'; +import { ComputedServerConfig } from '../../config/ServerConfig'; +import { KialiAppState } from '../../store/Store'; +import { istioAceEditorStyle } from '../../styles/AceEditorStyle'; +import { AuthConfig } from '../../types/Auth'; +import { aceOptions } from '../../types/IstioConfigDetails'; + +const beautify = require('json-beautify'); + +enum CopyStatus { + NOT_COPIED, // We haven't copied the current output + COPIED, // Current output is in the clipboard + OLD_COPY, // We copied the prev output, but there are changes in the KialiAppState +} + +type DebugInformationProps = { + appState: KialiAppState; + showDebug: boolean; + setShowDebug: React.Dispatch>; +}; + +type DebugInformationData = { + backendConfigs: { + authenticationConfig: AuthConfig; + computedServerConfig: ComputedServerConfig; + }; + currentURL: string; + reduxState: KialiAppState; +}; + +const copyToClipboardOptions = { + message: + 'We failed to automatically copy the text, please use: #{key}, Enter\t', +}; + +// Will be shown in Kiali Config and hidden in Additional state +const propsToShow = [ + 'accessibleNamespaces', + 'authStrategy', + 'clusters', + 'gatewayAPIClasses', + 'gatewayAPIEnabled', + 'istioConfigMap', + 'istioIdentityDomain', + 'istioNamespace', + 'istioStatusEnabled', + 'logLevel', + 'istioCanaryRevision', + 'istioAnnotationsAction', + 'istioInjectionAction', +]; + +const propsToPatch = ['cyRef', 'summaryTarget', 'token', 'username']; + +const defaultTab = 'kialiConfig'; + +export const DebugInformation = (props: DebugInformationProps) => { + const aceEditorRef: React.RefObject = React.createRef(); + let kialiConfig: { [key: string]: string } = {}; + + for (const key in serverConfig) { + if (propsToShow.includes(key)) { + // @ts-expect-error + if (typeof serverConfig[key] === 'string') { + // @ts-expect-error + kialiConfig[key] = serverConfig[key]; + } else { + // @ts-expect-error + kialiConfig[key] = JSON.stringify(serverConfig[key]); + } + } + } + + kialiConfig = Object.keys(kialiConfig) + .sort((a, b) => a.localeCompare(b)) + .reduce((obj, key) => { + // @ts-expect-error + obj[key] = kialiConfig[key]; + return obj; + }, {}); + + const [currentTab, setCurrentTab] = React.useState(defaultTab); + const [copyStatus, setCopyStatus] = React.useState( + CopyStatus.NOT_COPIED, + ); + + const close = () => { + props.setShowDebug(false); + }; + + const copyCallback = (_text: string, result: boolean) => { + setCopyStatus(result ? CopyStatus.COPIED : CopyStatus.NOT_COPIED); + }; + + // Properties shown in Kiali Config are not shown again in Additional State + const filterDebugInformation = (info: any) => { + if (info !== null) { + for (const [key] of Object.entries(info)) { + if (propsToShow.includes(key)) { + delete info[key]; + continue; + } + } + } + return info; + }; + + const parseConfig = (key: string, value: any) => { + // We have to patch some runtime properties we don't want to serialize + if (propsToPatch.includes(key)) { + return null; + } + return value; + }; + + const renderDebugInformation = () => { + let debugInformation: DebugInformationData = { + backendConfigs: { + authenticationConfig: authenticationConfig, + computedServerConfig: serverConfig, + }, + currentURL: window.location.href, + reduxState: props.appState, + }; + debugInformation = filterDebugInformation(debugInformation); + return beautify(debugInformation, parseConfig, 2); + }; + + const getCopyText = (): string => { + const text = + currentTab === 'kialiConfig' + ? JSON.stringify(kialiConfig, null, 2) + : renderDebugInformation(); + return text; + }; + + const download = () => { + const element = document.createElement('a'); + const file = new Blob([getCopyText()], { type: 'text/plain' }); + element.href = URL.createObjectURL(file); + element.download = `debug_${ + currentTab === 'kialiConfig' ? 'kiali_config' : 'additional_state' + }.json`; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + }; + + const hideAlert = () => { + setCopyStatus(CopyStatus.NOT_COPIED); + }; + + const debugInformation = renderDebugInformation(); + + const columns = (): TableColumn[] => { + return [ + { title: 'Configuration', field: 'configuration' }, + { title: 'Value', field: 'value' }, + ]; + }; + + const getRows = () => { + const conf: Array<{}> = []; + + for (const [k, v] of Object.entries(kialiConfig)) { + if (typeof v !== 'string') { + conf.push({ configuration: k, value: JSON.stringify(v) }); + } else { + conf.push({ configuration: k, value: v }); + } + } + return conf; + }; + + const renderTabs = () => { + const copyClip = ( + + + + ); + + const kialiConfigCard = ( + + {copyClip} + + ); + + const additionalState = ( + + Please include this information when opening a bug: + + + + + ); + // TODO additionalState + const tabsArray: JSX.Element[] = [kialiConfigCard]; + return tabsArray; + }; + + return props.showDebug ? ( + + Debug information + + {copyStatus === CopyStatus.COPIED && ( + } + /> + )} + {copyStatus === CopyStatus.OLD_COPY && ( + } + /> + )} + setCurrentTab(tabName as string)} + > + {renderTabs()} + + + + + + + + + + + ) : null; +}; diff --git a/plugins/kiali/src/components/FilterList/FitlerHelper.ts b/plugins/kiali/src/components/FilterList/FitlerHelper.ts new file mode 100644 index 0000000000..aa4276fc68 --- /dev/null +++ b/plugins/kiali/src/components/FilterList/FitlerHelper.ts @@ -0,0 +1,174 @@ +import { camelCase } from 'lodash'; + +import { history, HistoryManager, URLParam } from '../../app/History'; +import { config } from '../../config'; +import { + ActiveFilter, + ActiveFiltersInfo, + DEFAULT_LABEL_OPERATION, + FilterType, + ID_LABEL_OPERATION, + LabelOperation, + RunnableFilter, +} from '../../types/Filters'; +import { SortField } from '../../types/SortFilters'; + +export const perPageOptions: number[] = [5, 10, 15]; +const defaultDuration = 600; +const defaultRefreshInterval = config.toolbar.defaultRefreshInterval; + +export const getFiltersFromURL = ( + filterTypes: FilterType[], +): ActiveFiltersInfo => { + const urlParams = new URLSearchParams(history.location.search); + const activeFilters: ActiveFilter[] = []; + filterTypes.forEach(filter => { + urlParams.getAll(camelCase(filter.category)).forEach(value => { + activeFilters.push({ + category: filter.category, + value: value, + }); + }); + }); + + return { + filters: activeFilters, + op: + (urlParams.get(ID_LABEL_OPERATION) as LabelOperation) || + DEFAULT_LABEL_OPERATION, + }; +}; + +export const setFiltersToURL = ( + filterTypes: FilterType[], + filters: ActiveFiltersInfo, +): ActiveFiltersInfo => { + const urlParams = new URLSearchParams(history.location.search); + filterTypes.forEach(type => { + urlParams.delete(camelCase(type.category)); + }); + // Remove manually the special Filter opLabel + urlParams.delete('opLabel'); + const cleanFilters: ActiveFilter[] = []; + + filters.filters.forEach(activeFilter => { + const filterType = filterTypes.find( + filter => filter.category === activeFilter.category, + ); + if (!filterType) { + return; + } + cleanFilters.push(activeFilter); + urlParams.append(camelCase(filterType.category), activeFilter.value); + }); + urlParams.append(ID_LABEL_OPERATION, filters.op); + // Resetting pagination when filters change + history.push(`${history.location.pathname}?${urlParams.toString()}`); + return { filters: cleanFilters, op: filters.op || DEFAULT_LABEL_OPERATION }; +}; + +export const filtersMatchURL = ( + filterTypes: FilterType[], + filters: ActiveFiltersInfo, +): boolean => { + // This can probably be improved and/or simplified? + const fromFilters: Map = new Map(); + filters.filters.forEach(activeFilter => { + const existingValue = fromFilters.get(activeFilter.category) || []; + fromFilters.set( + activeFilter.category, + existingValue.concat(activeFilter.value), + ); + }); + + const fromURL: Map = new Map(); + const urlParams = new URLSearchParams(history.location.search); + filterTypes.forEach(filter => { + const values = urlParams.getAll(camelCase(filter.category)); + if (values.length > 0) { + const existing = fromURL.get(camelCase(filter.category)) || []; + fromURL.set(filter.category, existing.concat(values)); + } + }); + + if (fromFilters.size !== fromURL.size) { + return false; + } + let equalFilters = true; + fromFilters.forEach((filterValues, filterName) => { + const aux = fromURL.get(filterName) || []; + equalFilters = + equalFilters && + filterValues.every(value => aux.includes(value)) && + filterValues.length === aux.length; + }); + + return equalFilters; +}; + +export const isCurrentSortAscending = (): boolean => { + return (HistoryManager.getParam(URLParam.DIRECTION) || 'asc') === 'asc'; +}; + +export const currentDuration = (): number => { + return HistoryManager.getDuration() || defaultDuration; +}; + +export const currentRefreshInterval = (): number => { + const refreshInterval = HistoryManager.getNumericParam( + URLParam.REFRESH_INTERVAL, + ); + if (refreshInterval === undefined) { + return defaultRefreshInterval; + } + return refreshInterval; +}; + +export const currentSortField = ( + sortFields: SortField[], +): SortField => { + const queriedSortedField = + HistoryManager.getParam(URLParam.SORT) || sortFields[0].param; + return ( + sortFields.find(sortField => { + return sortField.param === queriedSortedField; + }) || sortFields[0] + ); +}; + +export const compareNullable = ( + a: T | undefined, + b: T | undefined, + safeComp: (a2: T, b2: T) => number, +): number => { + if (!a) { + return !b ? 0 : 1; + } + if (!b) { + return -1; + } + return safeComp(a, b); +}; + +const runOneFilter = ( + items: T[], + filter: RunnableFilter, + active: ActiveFiltersInfo, +) => { + const relatedActive = { + filters: active.filters.filter(af => af.category === filter.category), + op: active.op, + }; + if (relatedActive.filters.length) { + return items.filter(item => filter.run(item, relatedActive)); + } + return items; +}; + +export const runFilters = ( + items: T[], + filters: RunnableFilter[], + active: ActiveFiltersInfo, +) => { + return filters.reduce((i, f) => runOneFilter(i, f, active), items); +}; diff --git a/plugins/kiali/src/components/Filters/CommonFilters.ts b/plugins/kiali/src/components/Filters/CommonFilters.ts new file mode 100644 index 0000000000..611298a3fe --- /dev/null +++ b/plugins/kiali/src/components/Filters/CommonFilters.ts @@ -0,0 +1,96 @@ +import { + ActiveFiltersInfo, + AllFilterTypes, + FILTER_ACTION_APPEND, + FILTER_ACTION_UPDATE, + FilterType, + FilterValue, +} from '../../types/Filters'; +import { DEGRADED, FAILURE, HEALTHY, NA, NOT_READY } from '../../types/Health'; +import { removeDuplicatesArray } from '../../utils/Common'; + +export const presenceValues: FilterValue[] = [ + { + id: 'present', + title: 'Present', + }, + { + id: 'notpresent', + title: 'Not Present', + }, +]; + +export const istioSidecarFilter: FilterType = { + category: 'Istio Sidecar', + placeholder: 'Filter by Istio Sidecar Validation', + filterType: AllFilterTypes.select, + action: FILTER_ACTION_UPDATE, + filterValues: presenceValues, +}; + +export const healthFilter: FilterType = { + category: 'Health', + placeholder: 'Filter by Health', + filterType: AllFilterTypes.select, + action: FILTER_ACTION_APPEND, + filterValues: [ + { + id: HEALTHY.name, + title: HEALTHY.name, + }, + { + id: DEGRADED.name, + title: DEGRADED.name, + }, + { + id: FAILURE.name, + title: FAILURE.name, + }, + { + id: NOT_READY.name, + title: NOT_READY.name, + }, + { + id: 'na', + title: NA.name, + }, + ], +}; + +export const labelFilter: FilterType = { + category: 'Label', + placeholder: 'Filter by Label', + filterType: AllFilterTypes.label, + action: FILTER_ACTION_APPEND, + filterValues: [], +}; + +export const getFilterSelectedValues = ( + filter: FilterType, + activeFilters: ActiveFiltersInfo, +): string[] => { + const selected: string[] = activeFilters.filters + .filter(activeFilter => activeFilter.category === filter.category) + .map(activeFilter => activeFilter.value); + return removeDuplicatesArray(selected); +}; + +export const getPresenceFilterValue = ( + filter: FilterType, + activeFilters: ActiveFiltersInfo, +): boolean | undefined => { + const presenceFilters = activeFilters.filters.filter( + activeFilter => activeFilter.category === filter.category, + ); + + if (presenceFilters.length > 0) { + return presenceFilters[0].value === 'Present'; + } + return undefined; +}; + +export const filterByHealth = (items: any[], filterValues: string[]): any[] => { + return items.filter(itemWithHealth => + filterValues.includes(itemWithHealth.health.getGlobalStatus().name), + ); +}; diff --git a/plugins/kiali/src/components/Filters/LabelFilter.tsx b/plugins/kiali/src/components/Filters/LabelFilter.tsx new file mode 100644 index 0000000000..8ef1d6cbee --- /dev/null +++ b/plugins/kiali/src/components/Filters/LabelFilter.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; + +import { + Button, + ButtonVariant, + Popover, + PopoverPosition, + TextInput, +} from '@patternfly/react-core'; + +import { KialiIcon } from '../../config/KialiIcon'; +import { kialiStyle } from '../../styles/StyleUtils'; + +interface LabelFiltersProps { + filterAdd: (value: string) => void; + isActive: (value: string) => boolean; + onChange: (value: any) => void; + value: string; +} + +const infoIconStyle = kialiStyle({ + marginLeft: '0.5rem', + alignSelf: 'center', +}); + +export const LabelFilters: React.FC = ( + props: LabelFiltersProps, +) => { + const onkeyPress = (e: any) => { + if (e.key === 'Enter') { + if (props.value?.length > 0) { + props.value + .split(' ') + .forEach(val => !props.isActive(val) && props.filterAdd(val)); + } + } + }; + + return ( + <> + props.onChange(value)} + onKeyPress={e => onkeyPress(e)} + style={{ width: 'auto' }} + /> + Label Filter Help} + position={PopoverPosition.right} + bodyContent={ + <> + To set a label filter you must enter values like. +
+
    +
  • Filter by label presence: label
  • +
  • Filter by label and value: label=value
  • +
  • + Filter by more than one label and one or more values: +
    + label=value label2=value2,value2-2 +
    + (separate with ' ') +
  • +
+ + } + > + +
+ + ); +}; diff --git a/plugins/kiali/src/components/Filters/StatefulFilters.tsx b/plugins/kiali/src/components/Filters/StatefulFilters.tsx new file mode 100644 index 0000000000..ee8f4b0edb --- /dev/null +++ b/plugins/kiali/src/components/Filters/StatefulFilters.tsx @@ -0,0 +1,84 @@ +import { history, HistoryManager } from '../../app/History'; +import { + ActiveFilter, + ActiveFiltersInfo, + ActiveTogglesInfo, + FilterType, + LabelOperation, + ToggleType, +} from '../../types/Filters'; +import * as FilterHelper from '../FilterList/FitlerHelper'; + +export class FilterSelected { + static selectedFilters: ActiveFilter[] | undefined = undefined; + static opSelected: LabelOperation; + + static init = (filterTypes: FilterType[]) => { + let active = FilterSelected.getSelected(); + if (!FilterSelected.isInitialized()) { + active = FilterHelper.getFiltersFromURL(filterTypes); + FilterSelected.setSelected(active); + } else if (!FilterHelper.filtersMatchURL(filterTypes, active)) { + active = FilterHelper.setFiltersToURL(filterTypes, active); + FilterSelected.setSelected(active); + } + return active; + }; + + static resetFilters = () => { + FilterSelected.selectedFilters = undefined; + }; + + static setSelected = (activeFilters: ActiveFiltersInfo) => { + FilterSelected.selectedFilters = activeFilters.filters; + FilterSelected.opSelected = activeFilters.op; + }; + + static getSelected = (): ActiveFiltersInfo => { + return { + filters: FilterSelected.selectedFilters || [], + op: FilterSelected.opSelected || 'or', + }; + }; + + static isInitialized = () => { + return FilterSelected.selectedFilters !== undefined; + }; +} + +// Column toggles +export class Toggles { + static checked: ActiveTogglesInfo = new Map(); + static numChecked = 0; + + static init = (toggles: ToggleType[]): number => { + Toggles.checked.clear(); + Toggles.numChecked = 0; + + // Prefer URL settings + const urlParams = new URLSearchParams(history.location.search); + toggles.forEach(t => { + const urlIsChecked = HistoryManager.getBooleanParam( + `${t.name}Toggle`, + urlParams, + ); + const isChecked = urlIsChecked === undefined ? t.isChecked : urlIsChecked; + Toggles.checked.set(t.name, isChecked); + if (isChecked) { + Toggles.numChecked++; + } + }); + return Toggles.numChecked; + }; + + static setToggle = (name: string, value: boolean): number => { + HistoryManager.setParam(`${name}Toggle`, `${value}`); + Toggles.checked.set(name, value); + Toggles.numChecked = value ? Toggles.numChecked++ : Toggles.numChecked--; + return Toggles.numChecked; + }; + + static getToggles = (): ActiveTogglesInfo => { + return new Map(Toggles.checked); + }; +} diff --git a/plugins/kiali/src/components/Health/HealthStyle.ts b/plugins/kiali/src/components/Health/HealthStyle.ts new file mode 100644 index 0000000000..e69c5bb2ea --- /dev/null +++ b/plugins/kiali/src/components/Health/HealthStyle.ts @@ -0,0 +1,17 @@ +import { kialiStyle } from '../../styles/StyleUtils'; +import { PFColors } from '../Pf/PfColors'; + +export const healthIndicatorStyle = kialiStyle({ + $nest: { + '& .pf-v5-c-tooltip__content': { + borderWidth: '1px', + textAlign: 'left', + }, + + '& .pf-v5-c-content ul': { + marginBottom: 'var(--pf-v5-c-content--ul--MarginTop)', + marginTop: 0, + color: PFColors.Color100, + }, + }, +}); diff --git a/plugins/kiali/src/components/Health/Helper.ts b/plugins/kiali/src/components/Health/Helper.ts new file mode 100644 index 0000000000..96e28a1bb9 --- /dev/null +++ b/plugins/kiali/src/components/Health/Helper.ts @@ -0,0 +1,19 @@ +import * as React from 'react'; + +import { Icon } from '@patternfly/react-core'; + +import { kialiStyle } from '../../styles/StyleUtils'; +import { Status } from '../../types/Health'; + +type Size = 'sm' | 'md' | 'lg' | 'xl'; + +export const createIcon = (status: Status, size?: Size) => { + const classForColor = kialiStyle({ + color: status.color, + }); + return React.createElement( + Icon, + { size: size, className: `${status.class} ${classForColor}` }, + React.createElement(status.icon), + ); +}; diff --git a/plugins/kiali/src/components/Icons/MTLSStatusFull.tsx b/plugins/kiali/src/components/Icons/MTLSStatusFull.tsx deleted file mode 100644 index 0714d8e887..0000000000 --- a/plugins/kiali/src/components/Icons/MTLSStatusFull.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; - -export const MTLSStatusFull = (): JSX.Element => ( - - - - - -); - -export const MTLSStatusFullDark = (): JSX.Element => ( - - - - - -); diff --git a/plugins/kiali/src/components/Icons/MTLSStatusPartial.tsx b/plugins/kiali/src/components/Icons/MTLSStatusPartial.tsx deleted file mode 100644 index 3f5cc6d984..0000000000 --- a/plugins/kiali/src/components/Icons/MTLSStatusPartial.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; - -export const MTLSStatusPartial = (): JSX.Element => ( - - - - - -); - -export const MTLSStatusPartialDark = (): JSX.Element => ( - - - - - -); diff --git a/plugins/kiali/src/components/Icons/index.ts b/plugins/kiali/src/components/Icons/index.ts deleted file mode 100644 index 7354460be3..0000000000 --- a/plugins/kiali/src/components/Icons/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { createElement } from 'react'; - -import SvgIcon from '@material-ui/core/SvgIcon'; -import CheckCircleRounded from '@material-ui/icons/CheckCircleRounded'; -import ErrorRounded from '@material-ui/icons/ErrorRounded'; -import InfoOutlined from '@material-ui/icons/InfoOutlined'; -import RemoveCircleRounded from '@material-ui/icons/RemoveCircleRounded'; -import ReportProblemRounded from '@material-ui/icons/ReportProblemRounded'; -import WarningRounded from '@material-ui/icons/WarningRounded'; - -type SvgIconComponent = typeof SvgIcon; - -export const Colors = { - error: 'error', - info: 'primary', - ok: 'green', - warning: 'rgb(255, 152, 0)', -}; -export type ComponentIcon = { - props: { [key: string]: any }; - icon: SvgIconComponent; -}; - -export const ErrorCoreComponent: ComponentIcon = { - props: { color: Colors.error }, - icon: ErrorRounded, -}; - -export const ErrorAddonComponent: ComponentIcon = { - props: { htmlColor: Colors.warning }, - icon: ReportProblemRounded, -}; - -export const NotReadyComponent: ComponentIcon = { - props: { color: Colors.info }, - icon: RemoveCircleRounded, -}; - -export const WarningIcon: ComponentIcon = { - props: { htmlColor: Colors.warning }, - icon: WarningRounded, -}; - -export const InfoIcon: ComponentIcon = { - props: { color: Colors.info }, - icon: InfoOutlined, -}; - -export const OkIcon: ComponentIcon = { - props: { htmlColor: Colors.ok }, - icon: CheckCircleRounded, -}; - -export const SuccessComponent = OkIcon; -export const ErrorIcon = ErrorCoreComponent; - -export const createIcon = ( - icon: ComponentIcon, - extraProp?: { [key: string]: string }, -) => { - let iconProps = icon.props; - if (extraProp) { - iconProps = { ...iconProps, ...extraProp }; - } - return createElement(icon.icon, iconProps); -}; - -export * from './MTLSStatusFull'; -export * from './MTLSStatusPartial'; diff --git a/plugins/kiali/src/components/IstioStatus/IstioComponentStatus.tsx b/plugins/kiali/src/components/IstioStatus/IstioComponentStatus.tsx new file mode 100644 index 0000000000..05da0e8508 --- /dev/null +++ b/plugins/kiali/src/components/IstioStatus/IstioComponentStatus.tsx @@ -0,0 +1,92 @@ +import * as React from 'react'; + +import { ListItem, ListItemText } from '@material-ui/core'; +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + MinusCircleIcon, +} from '@patternfly/react-icons'; +import { SVGIconProps } from '@patternfly/react-icons/dist/js/createIcon'; + +import { ComponentStatus, Status } from '../../types/IstioStatus'; +import { PFColors } from '../Pf/PfColors'; + +type Props = { + componentStatus: ComponentStatus; +}; + +export type ComponentIcon = { + color: string; + icon: React.ComponentClass; +}; + +const ErrorCoreComponent: ComponentIcon = { + color: PFColors.Danger, + icon: ExclamationCircleIcon, +}; + +const ErrorAddonComponent: ComponentIcon = { + color: PFColors.Warning, + icon: ExclamationTriangleIcon, +}; + +const NotReadyComponent: ComponentIcon = { + color: PFColors.Info, + icon: MinusCircleIcon, +}; + +const SuccessComponent: ComponentIcon = { + color: PFColors.Success, + icon: CheckCircleIcon, +}; + +// Mapping Valid-Core to Icon representation. +const validToIcon: { [valid: string]: ComponentIcon } = { + 'false-false': ErrorAddonComponent, + 'false-true': ErrorCoreComponent, + 'true-false': SuccessComponent, + 'true-true': SuccessComponent, +}; + +const statusMsg = { + [Status.NotFound]: 'Not found', + [Status.NotReady]: 'Not ready', + [Status.Unhealthy]: 'Not healthy', + [Status.Unreachable]: 'Unreachable', + [Status.Healthy]: 'Healthy', +}; + +export const IstioComponentStatus = (props: Props): JSX.Element => { + const renderIcon = (status: Status, isCore: boolean) => { + let compIcon = validToIcon[`${status === Status.Healthy}-${isCore}`]; + if (status === Status.NotReady) { + compIcon = NotReadyComponent; + } + const IconComponent = compIcon.icon; + return ; + }; + + const comp = props.componentStatus; + const state = statusMsg[comp.status]; + return ( + + + + {renderIcon( + props.componentStatus.status, + props.componentStatus.is_core, + )} + + + {comp.name} + + {state && <>{state}} + + } + /> + + ); +}; diff --git a/plugins/kiali/src/components/IstioStatus/IstioStatus.tsx b/plugins/kiali/src/components/IstioStatus/IstioStatus.tsx new file mode 100644 index 0000000000..074cb251bf --- /dev/null +++ b/plugins/kiali/src/components/IstioStatus/IstioStatus.tsx @@ -0,0 +1,111 @@ +import * as React from 'react'; + +import { Tooltip } from '@material-ui/core'; +import { ResourcesFullIcon } from '@patternfly/react-icons'; +import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon'; + +import { ComponentStatus, Status } from '../../types/IstioStatus'; +import { PFColors } from '../Pf/PfColors'; +import { IstioStatusList } from './IstioStatusList'; + +type StatusIcons = { + ErrorIcon?: React.ComponentClass; + WarningIcon?: React.ComponentClass; + InfoIcon?: React.ComponentClass; + HealthyIcon?: React.ComponentClass; +}; + +type Props = { + status: ComponentStatus[]; + icons?: StatusIcons; + cluster?: string; +}; + +const ValidToColor = { + 'true-true-true': PFColors.Danger, + 'true-true-false': PFColors.Danger, + 'true-false-true': PFColors.Danger, + 'true-false-false': PFColors.Danger, + 'false-true-true': PFColors.Warning, + 'false-true-false': PFColors.Warning, + 'false-false-true': PFColors.Info, + 'false-false-false': PFColors.Success, +}; + +const defaultIcons = { + ErrorIcon: ResourcesFullIcon, + WarningIcon: ResourcesFullIcon, + InfoIcon: ResourcesFullIcon, + HealthyIcon: ResourcesFullIcon, +}; + +export const IstioStatus = (props: Props): React.JSX.Element => { + const tooltipContent = () => { + return ; + }; + + const tooltipColor = () => { + let coreUnhealthy: boolean = false; + let addonUnhealthy: boolean = false; + let notReady: boolean = false; + + (props.status || []).forEach(compStatus => { + const { status, is_core } = compStatus; + const isNotReady: boolean = status === Status.NotReady; + const isUnhealthy: boolean = status !== Status.Healthy && !isNotReady; + + if (is_core) { + coreUnhealthy = coreUnhealthy || isUnhealthy; + } else { + addonUnhealthy = addonUnhealthy || isUnhealthy; + } + + notReady = notReady || isNotReady; + }); + + return ValidToColor[`${coreUnhealthy}-${addonUnhealthy}-${notReady}`]; + }; + + const healthyComponents = () => { + return props.status.reduce( + (healthy: boolean, compStatus: ComponentStatus) => { + return healthy && compStatus.status === Status.Healthy; + }, + true, + ); + }; + + if (healthyComponents()) { + return <>; + } + const icons = props.icons + ? { ...defaultIcons, ...props.icons } + : defaultIcons; + const iconColor = tooltipColor(); + let Icon: React.ComponentClass = ResourcesFullIcon; + let dataTestID: string = 'istio-status'; + + if (iconColor === PFColors.Danger) { + Icon = icons.ErrorIcon; + dataTestID += '-danger'; + } else if (iconColor === PFColors.Warning) { + Icon = icons.WarningIcon; + dataTestID += '-warning'; + } else if (iconColor === PFColors.Info) { + Icon = icons.InfoIcon; + dataTestID += '-info'; + } else if (iconColor === PFColors.Success) { + Icon = icons.HealthyIcon; + dataTestID += '-success'; + } + + return ( + + + + ); +}; diff --git a/plugins/kiali/src/components/IstioStatus/IstioStatusInline.tsx b/plugins/kiali/src/components/IstioStatus/IstioStatusInline.tsx new file mode 100644 index 0000000000..3f5962786d --- /dev/null +++ b/plugins/kiali/src/components/IstioStatus/IstioStatusInline.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; + +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + MinusCircleIcon, +} from '@patternfly/react-icons'; + +import { ComponentStatus } from '../../types/IstioStatus'; +import { IstioStatus } from './IstioStatus'; + +type Props = { + status: ComponentStatus[]; + cluster?: string; +}; + +export const IstioStatusInline = (props: Props): React.JSX.Element => { + return ( + + ); +}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatusList.tsx b/plugins/kiali/src/components/IstioStatus/IstioStatusList.tsx similarity index 67% rename from plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatusList.tsx rename to plugins/kiali/src/components/IstioStatus/IstioStatusList.tsx index 01049ef725..7106d5c272 100644 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatusList.tsx +++ b/plugins/kiali/src/components/IstioStatus/IstioStatusList.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; -import { - ComponentStatus, - IStatus, -} from '@janus-idp/backstage-plugin-kiali-common'; +import { List, Typography } from '@material-ui/core'; +import { ComponentStatus, Status } from '../../types/IstioStatus'; import { IstioComponentStatus } from './IstioComponentStatus'; type Props = { @@ -14,7 +12,7 @@ type Props = { export const IstioStatusList = (props: Props) => { const nonhealthyComponents = () => { return props.status.filter( - (c: ComponentStatus) => c.status !== IStatus.Healthy, + (c: ComponentStatus) => c.status !== Status.Healthy, ); }; @@ -34,8 +32,7 @@ export const IstioStatusList = (props: Props) => { return ['core', 'addon'].map((group: string) => // @ts-expect-error - groups[group]().map(status => ( - // @ts-expect-error + groups[group]().map((status: ComponentStatus) => ( { }; return ( -
-

Istio Components Status

-
    +
    + Istio Components Status + {renderComponentList()} -
+
); }; diff --git a/plugins/kiali-common/src/types/IstioWizards.ts b/plugins/kiali/src/components/IstioWizards/WizardActions.ts similarity index 100% rename from plugins/kiali-common/src/types/IstioWizards.ts rename to plugins/kiali/src/components/IstioWizards/WizardActions.ts diff --git a/plugins/kiali/src/components/KialiComponent/Header/Header.tsx b/plugins/kiali/src/components/KialiComponent/Header/Header.tsx deleted file mode 100644 index 7ceb499b4d..0000000000 --- a/plugins/kiali/src/components/KialiComponent/Header/Header.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; - -import { ContentHeader, Select } from '@backstage/core-components'; - -import { Chip, Drawer, IconButton, Tooltip } from '@material-ui/core'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Info from '@material-ui/icons/Info'; -import StorageRounded from '@material-ui/icons/StorageRounded'; - -import { - KialiConfigT, - KialiInfo, - Namespace, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { getHomeCluster } from '../../../helper'; -import { StatusContent } from './StatusContent'; - -const useDrawerStyles = makeStyles((theme: Theme) => - createStyles({ - paper: { - width: '50%', - justifyContent: 'space-between', - padding: theme.spacing(2.5), - }, - }), -); - -export const KialiHeader = (props: { - title: string; - kialiStatus: KialiInfo; - config: KialiConfigT; - namespaces: Namespace[]; - namespacesFiltered: string[]; - setNamespaceFilter: (ns: string[]) => void; -}) => { - const [isOpen, toggleDrawer] = React.useState(false); - const kialiHomeCluster = getHomeCluster(props.config.server); - const classes = useDrawerStyles(); - - return ( - <> - - {props.config.username && ( -
- - User : - {props.config.username} - -
- )} - {kialiHomeCluster && ( - Kiali home cluster: {kialiHomeCluster?.name}} - > - } label={kialiHomeCluster?.name} /> - - )} - - toggleDrawer(true)} - style={{ marginTop: '-10px' }} - > - - - - toggleDrawer(false)} - > - - -
-
- - - - Strategy - - {props.kialiStatus.auth.strategy} - - - - Kiali status - - - {getStatusIcon( - props.kialiStatus.status.status['Kiali state'], - )} - - - - Kiali - {kialiVersion} - - - - Kiali Container - - {kialiContainer} - - - - Service Mesh - - {MeshVersion} - - -
- - - - - - Components - - - - - - {props.kialiStatus.status.externalServices.map(comp => ( - - - {comp.name} - - {comp.version || 'N/A'} - - ))} - -
-
-
-
- - - Configuration - - - - - - - - Configuration - - Value - - - - {getRows().map(row => ( - - {row[0]} - {row[1]} - - ))} - -
-
-
-
- - - ); -}; diff --git a/plugins/kiali/src/components/KialiComponent/Header/index.ts b/plugins/kiali/src/components/KialiComponent/Header/index.ts deleted file mode 100644 index 8a0a0dfef6..0000000000 --- a/plugins/kiali/src/components/KialiComponent/Header/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Header'; -export * from './StatusContent'; diff --git a/plugins/kiali/src/components/KialiComponent/KialiComponent.tsx b/plugins/kiali/src/components/KialiComponent/KialiComponent.tsx deleted file mode 100644 index 982fde41a6..0000000000 --- a/plugins/kiali/src/components/KialiComponent/KialiComponent.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import useAsyncFn from 'react-use/lib/useAsyncFn'; -import useDebounce from 'react-use/lib/useDebounce'; - -import { - CardTab, - CodeSnippet, - Content, - Page, - TabbedCard, - WarningPanel, -} from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; - -import { CircularProgress } from '@material-ui/core'; - -import { - AuthStrategy, - DefaultKialiConfig, - INITIAL_STATUS_STATE, - KialiConfigT, - KialiFetchError, - KialiInfo, - Namespace, - setServerConfig, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { kialiApiRef } from '../../api'; -import { handleMultipleMessage } from '../../helper'; -import { Overview } from '../Overview'; -import { KialiHeader } from './Header'; - -const getPathPage = () => { - const pathname = window.location.pathname.split('/').pop() || 'overview'; - return pathname === 'kiali' ? 'overview' : pathname; -}; - -export const KialiComponent = () => { - const kialiClient = useApi(kialiApiRef); - kialiClient.setEntity(useEntity().entity); - const [kialiTab, setKialiTab] = React.useState(getPathPage()); - const [kialiConfig, setKialiConfig] = - React.useState(DefaultKialiConfig); - const [kialiStatus, setKialiStatus] = React.useState({ - status: INITIAL_STATUS_STATE, - auth: { sessionInfo: {}, strategy: AuthStrategy.anonymous }, - }); - const [namespacesFiltered, setNamespacesFiltered] = React.useState( - [], - ); - const [namespaces, setNamespaces] = React.useState([]); - const [errors, setErrors] = React.useState([]); - - const fetchConfig = async () => { - let config = kialiConfig; - if (config.kialiConsole === '') { - await kialiClient.getConfig().then(resp => { - if (resp.errors.length > 0) { - setErrors(resp.errors); - } - config = resp.response as KialiConfigT; - config.server = setServerConfig(kialiConfig?.server, config.server); - setKialiConfig(config); - }); - } - }; - - const fetchNamespaces = async () => { - await kialiClient.getNamespaces().then(resp => { - if (resp.errors.length > 0) { - setErrors(resp.errors); - } - setNamespaces(resp.response as Namespace[]); - setNamespacesFiltered((resp.response as Namespace[]).map(ns => ns.name)); - }); - }; - - const [{ loading }, refresh] = useAsyncFn( - async () => { - await kialiClient.getInfo().then(response => { - if (response.errors.length > 0) { - setErrors(response.errors); - } else { - setKialiStatus(response.response as KialiInfo); - fetchConfig(); - fetchNamespaces(); - } - }); // Check if the config is loaded - }, - [], - { loading: true }, - ); - - useDebounce(refresh, 10); - - return loading ? ( - - ) : ( - - - - {errors.length > 0 && ( - - - - )} - {kialiConfig.kialiConsole !== '' && ( - <> - setKialiTab(v as string)} - > - {/* - // @ts-ignore */} - - - - - - )} - - - ); -}; diff --git a/plugins/kiali/src/components/KialiComponent/KialiNoPath.tsx b/plugins/kiali/src/components/KialiComponent/KialiNoPath.tsx deleted file mode 100644 index e6aa227a54..0000000000 --- a/plugins/kiali/src/components/KialiComponent/KialiNoPath.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import { Content, Page, WarningPanel } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; - -import { kialiApiRef } from '../../api'; - -export const KialiNoPath = () => { - const kialiClient = useApi(kialiApiRef); - kialiClient.setEntity(useEntity().entity); - - return ( - - - - Path {window.location.pathname} not exist in Kiali Plugin - - - - ); -}; diff --git a/plugins/kiali/src/components/KialiComponent/index.ts b/plugins/kiali/src/components/KialiComponent/index.ts deleted file mode 100644 index 55f491571c..0000000000 --- a/plugins/kiali/src/components/KialiComponent/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { KialiComponent } from './KialiComponent'; -export { KialiNoPath } from './KialiNoPath'; diff --git a/plugins/kiali/src/components/Link/IstioConfigListLink.tsx b/plugins/kiali/src/components/Link/IstioConfigListLink.tsx new file mode 100644 index 0000000000..7a5efd5040 --- /dev/null +++ b/plugins/kiali/src/components/Link/IstioConfigListLink.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +import { Paths } from '../../config'; +import { FilterSelected } from '../Filters/StatefulFilters'; + +interface Props { + namespaces: string[]; + errors?: boolean; + warnings?: boolean; +} + +export class IstioConfigListLink extends React.Component { + namespacesToParams = () => { + let param: string = ''; + if (this.props.namespaces.length > 0) { + param = `namespaces=${this.props.namespaces.join(',')}`; + } + return param; + }; + + validationToParams = () => { + let params: string = ''; + + if (this.props.warnings) { + params += 'configvalidation=Warning'; + } + + let errorParams: string = ''; + if (this.props.errors) { + errorParams += 'configvalidation=Not+Valid'; + } + + if (params !== '' && errorParams !== '') { + params += '&'; + } + + params += errorParams; + + return params; + }; + + cleanFilters = () => { + FilterSelected.resetFilters(); + }; + + render() { + let params: string = this.namespacesToParams(); + const validationParams: string = this.validationToParams(); + if (params !== '' && validationParams !== '') { + params += '&'; + } + params += validationParams; + + return ( + + {this.props.children} + + ); + } +} diff --git a/plugins/kiali/src/components/Link/ValidationSummaryLink.tsx b/plugins/kiali/src/components/Link/ValidationSummaryLink.tsx new file mode 100644 index 0000000000..a64fe47f5e --- /dev/null +++ b/plugins/kiali/src/components/Link/ValidationSummaryLink.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; + +import { IstioConfigListLink } from './IstioConfigListLink'; + +type Props = { + namespace: string; + errors: number; + warnings: number; + objectCount?: number; + children: React.ReactNode; +}; + +export class ValidationSummaryLink extends React.Component { + hasIstioObjects = () => { + return this.props.objectCount && this.props.objectCount > 0; + }; + + render() { + let link: any = ( +
N/A
+ ); + + if (this.hasIstioObjects()) { + // Kiosk actions are used when the kiosk specifies a parent, + // otherwise the kiosk=true will keep the links inside Kiali + link = ( + 0} + errors={this.props.errors > 0} + > + {this.props.children} + + ); + } + + return link; + } +} diff --git a/plugins/kiali/src/components/MTls/MTLSIcon.tsx b/plugins/kiali/src/components/MTls/MTLSIcon.tsx new file mode 100644 index 0000000000..69bcd9d2ad --- /dev/null +++ b/plugins/kiali/src/components/MTls/MTLSIcon.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; + +import { Tooltip, TooltipPosition } from '@patternfly/react-core'; + +const fullIcon = require('../../assets/img/mtls-status-full.svg') as string; +const hollowIcon = + require('../../assets/img/mtls-status-partial.svg') as string; +const fullIconDark = + require('../../assets/img/mtls-status-full-dark.svg') as string; +const hollowIconDark = + require('../../assets/img/mtls-status-partial-dark.svg') as string; + +export { fullIcon, hollowIcon, fullIconDark, hollowIconDark }; + +type Props = { + icon: string; + iconClassName: string; + tooltipText: string; + tooltipPosition: TooltipPosition; +}; + +export enum MTLSIconTypes { + LOCK_FULL = 'LOCK_FULL', + LOCK_HOLLOW = 'LOCK_HOLLOW', + LOCK_FULL_DARK = 'LOCK_FULL_DARK', + LOCK_HOLLOW_DARK = 'LOCK_HOLLOW_DARK', +} + +const nameToSource = new Map([ + [MTLSIconTypes.LOCK_FULL, fullIcon], + [MTLSIconTypes.LOCK_FULL_DARK, fullIconDark], + [MTLSIconTypes.LOCK_HOLLOW, hollowIcon], + [MTLSIconTypes.LOCK_HOLLOW_DARK, hollowIconDark], +]); + +export class MTLSIcon extends React.Component { + render() { + return ( + + {this.props.tooltipPosition} + + ); + } +} diff --git a/plugins/kiali/src/components/MTls/MTLSStatus.tsx b/plugins/kiali/src/components/MTls/MTLSStatus.tsx new file mode 100644 index 0000000000..ff0e916826 --- /dev/null +++ b/plugins/kiali/src/components/MTls/MTLSStatus.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; + +import { TooltipPosition } from '@patternfly/react-core'; + +import { MTLSIcon } from './MTLSIcon'; + +type Props = { + status: string; + statusDescriptors: Map; + className?: string; + overlayPosition?: TooltipPosition; +}; + +export type StatusDescriptor = { + message: string; + icon: string; + showStatus: boolean; +}; + +export const emptyDescriptor = { + message: '', + icon: '', + showStatus: false, +}; + +export class MTLSStatus extends React.Component { + statusDescriptor() { + return ( + this.props.statusDescriptors.get(this.props.status) || emptyDescriptor + ); + } + + icon() { + return this.statusDescriptor().icon; + } + + message() { + return this.statusDescriptor().message; + } + + showStatus() { + return this.statusDescriptor().showStatus; + } + + overlayPosition() { + return this.props.overlayPosition || TooltipPosition.left; + } + + iconClassName() { + return this.props.className || ''; + } + + render() { + if (this.showStatus()) { + return ( + + ); + } + + return null; + } +} diff --git a/plugins/kiali/src/components/MTls/NamespaceMTLSStatus.tsx b/plugins/kiali/src/components/MTls/NamespaceMTLSStatus.tsx new file mode 100644 index 0000000000..72aa2a04a4 --- /dev/null +++ b/plugins/kiali/src/components/MTls/NamespaceMTLSStatus.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; + +import { kialiStyle } from '../../styles/StyleUtils'; +import { MTLSStatuses } from '../../types/TLSStatus'; +import { MTLSIconTypes } from './MTLSIcon'; +import { emptyDescriptor, MTLSStatus, StatusDescriptor } from './MTLSStatus'; + +type Props = { + status: string; +}; + +const statusDescriptors = new Map([ + [ + MTLSStatuses.ENABLED, + { + message: 'mTLS is enabled for this namespace', + icon: MTLSIconTypes.LOCK_FULL_DARK, + showStatus: true, + }, + ], + [ + MTLSStatuses.ENABLED_EXTENDED, + { + message: + 'mTLS is enabled for this namespace, extended from Mesh-wide config', + icon: MTLSIconTypes.LOCK_FULL_DARK, + showStatus: true, + }, + ], + [ + MTLSStatuses.PARTIALLY, + { + message: 'mTLS is partially enabled for this namespace', + icon: MTLSIconTypes.LOCK_HOLLOW_DARK, + showStatus: true, + }, + ], + [MTLSStatuses.DISABLED, emptyDescriptor], + [MTLSStatuses.NOT_ENABLED, emptyDescriptor], +]); + +// Magic style to align Istio Config icons on top of status overview +const iconStyle = kialiStyle({ + marginTop: -3, + marginRight: 18, + marginLeft: 2, + width: 10, +}); + +export class NamespaceMTLSStatus extends React.Component { + render() { + return ( + + ); + } +} diff --git a/plugins/kiali/src/components/MessageCenter/AlertDrawer.tsx b/plugins/kiali/src/components/MessageCenter/AlertDrawer.tsx new file mode 100644 index 0000000000..2b5d3b19fa --- /dev/null +++ b/plugins/kiali/src/components/MessageCenter/AlertDrawer.tsx @@ -0,0 +1,91 @@ +import * as React from 'react'; + +import { ItemCardHeader } from '@backstage/core-components'; + +import { + Accordion, + AccordionDetails, + AccordionSummary, + Card, + CardContent, + CardMedia, + Typography, +} from '@material-ui/core'; +import ExpandMoreRounded from '@material-ui/icons/ExpandMoreRounded'; +import { InfoIcon } from '@patternfly/react-icons'; + +import { KialiAppAction } from '../../actions/KialiAppAction'; +import { MessageCenterState } from '../../store'; +import { kialiStyle } from '../../styles/StyleUtils'; +import { + NotificationGroup, + NotificationMessage, +} from '../../types/MessageCenter'; +import { AlertDrawerGroup } from './AlertDrawerGroup'; + +type AlertDrawerProps = { + toggleDrawer: (isOpen: boolean) => void; + messages: MessageCenterState; + messageDispatch: React.Dispatch; +}; + +const hideGroup = (group: NotificationGroup): boolean => { + return group.hideIfEmpty && group.messages.length === 0; +}; + +const getUnreadCount = (messages: NotificationMessage[]) => { + return messages.reduce((count, message) => { + return message.seen ? count : count + 1; + }, 0); +}; + +const getUnreadMessageLabel = (messages: NotificationMessage[]) => { + const unreadCount = getUnreadCount(messages); + return unreadCount === 1 + ? '1 Unread Message' + : `${getUnreadCount(messages)} Unread Messages`; +}; + +const drawer = kialiStyle({ + display: 'flex', + width: '500px', + justifyContent: 'space-between', +}); + +const noNotificationsMessage = ( + <> + + No Messages Available + +); + +export const AlertDrawer = (props: AlertDrawerProps) => { + return ( + + + + + + {props.messages.groups.length === 0 + ? noNotificationsMessage + : props.messages.groups.map(group => + hideGroup(group) ? null : ( + + } + > + + {group.title} {getUnreadMessageLabel(group.messages)} + + + + + + + ), + )} + + + ); +}; diff --git a/plugins/kiali/src/components/MessageCenter/AlertDrawerGroup.tsx b/plugins/kiali/src/components/MessageCenter/AlertDrawerGroup.tsx new file mode 100644 index 0000000000..839be0834f --- /dev/null +++ b/plugins/kiali/src/components/MessageCenter/AlertDrawerGroup.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; + +import { Button, Card, CardActions, CardContent } from '@material-ui/core'; +import { InfoIcon } from '@patternfly/react-icons'; + +import { MessageCenterActions } from '../../actions'; +import { KialiAppState, KialiContext } from '../../store'; +import { NotificationGroup } from '../../types/MessageCenter'; +import { AlertDrawerMessage } from './AlertDrawerMessage'; + +type AlertDrawerGroupProps = { + group: NotificationGroup; + reverseMessageOrder?: boolean; +}; + +const noNotificationsMessage = ( + <> + + No Messages Available + +); + +export const AlertDrawerGroup = (props: AlertDrawerGroupProps) => { + const kialiState = React.useContext(KialiContext) as KialiAppState; + + const markGroupAsRead = (groupId: string) => { + const foundGroup = kialiState.messageCenter.groups.find( + group => group.id === groupId, + ); + if (foundGroup) { + kialiState.dispatch.messageDispatch( + MessageCenterActions.markAsRead( + foundGroup.messages.map(message => message.id), + ), + ); + } + }; + + const clearGroup = (groupId: string) => { + const foundGroup = kialiState.messageCenter.groups.find( + group => group.id === groupId, + ); + if (foundGroup) { + kialiState.dispatch.messageDispatch( + MessageCenterActions.removeMessage( + foundGroup.messages.map(message => message.id), + ), + ); + } + }; + + const getMessages = () => { + return props.reverseMessageOrder + ? [...props.group.messages].reverse() + : props.group.messages; + }; + + const group: NotificationGroup = props.group; + + return ( + + + {group.messages.length === 0 && noNotificationsMessage} + {getMessages().map(message => ( + + ))} + + {group.showActions && group.messages.length > 0 && ( + + + + + )} + + ); +}; diff --git a/plugins/kiali/src/components/MessageCenter/AlertDrawerMessage.tsx b/plugins/kiali/src/components/MessageCenter/AlertDrawerMessage.tsx new file mode 100644 index 0000000000..6861cbf894 --- /dev/null +++ b/plugins/kiali/src/components/MessageCenter/AlertDrawerMessage.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; + +import { + Accordion, + AccordionDetails, + AccordionSummary, + Card, + CardContent, + Typography, +} from '@material-ui/core'; +import ExpandMoreRounded from '@material-ui/icons/ExpandMoreRounded'; +import moment from 'moment'; + +import { MessageCenterActions } from '../../actions/MessageCenterActions'; +import { KialiIcon } from '../../config/KialiIcon'; +import { KialiAppState, KialiContext } from '../../store'; +import { MessageType, NotificationMessage } from '../../types/MessageCenter'; + +const getIcon = (type: MessageType) => { + switch (type) { + case MessageType.ERROR: + return ; + case MessageType.INFO: + return ; + case MessageType.SUCCESS: + return ; + case MessageType.WARNING: + return ; + default: + throw Error('Unexpected type'); + } +}; + +type AlertDrawerMessageProps = { + message: NotificationMessage; +}; + +export const AlertDrawerMessage = (props: AlertDrawerMessageProps) => { + const kialiState = React.useContext(KialiContext) as KialiAppState; + + // const markAsRead = (message: NotificationMessage) => kialiState.dispatch.messageDispatch(MessageCenterActions.markAsRead(message.id)); + const toggleMessageDetail = (message: NotificationMessage) => + kialiState.dispatch.messageDispatch( + MessageCenterActions.toggleMessageDetail(message.id), + ); + + return ( + + + {getIcon(props.message.type)}{' '} + {props.message.seen ? ( + props.message.content + ) : ( + {props.message.content} + )} + {props.message.detail && ( + + toggleMessageDetail(props.message)} + expandIcon={} + > + + {props.message.showDetail ? 'Hide Detail' : 'Show Detail'} + + + +
+                {props.message.detail}
+              
+
+
+ )} + {props.message.count > 1 && ( +
+ {props.message.count} {moment().from(props.message.firstTriggered)} +
+ )} +
+ + {props.message.created.toLocaleDateString()} + + + {props.message.created.toLocaleTimeString()} + +
+
+
+ ); +}; diff --git a/plugins/kiali/src/components/MessageCenter/MessageCenter.tsx b/plugins/kiali/src/components/MessageCenter/MessageCenter.tsx new file mode 100644 index 0000000000..6a66f8072a --- /dev/null +++ b/plugins/kiali/src/components/MessageCenter/MessageCenter.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; + +import { Badge, Button, Drawer } from '@material-ui/core'; + +import { KialiIcon } from '../../config/KialiIcon'; +import { KialiAppState, KialiContext } from '../../store'; +import { kialiStyle } from '../../styles/StyleUtils'; +import { + MessageType, + NotificationGroup, + NotificationMessage, +} from '../../types/MessageCenter'; +import { AlertDrawer } from './AlertDrawer'; + +const bell = kialiStyle({ + position: 'relative', + right: '5px', + top: '5px', +}); + +const calculateMessageStatus = (state: KialiAppState) => { + type MessageCenterTriggerPropsToMap = { + newMessagesCount: number; + badgeDanger: boolean; + systemErrorsCount: number; + }; + + const dangerousMessageTypes = [MessageType.ERROR, MessageType.WARNING]; + let systemErrorsCount = 0; + + const systemErrorsGroup = state.messageCenter.groups.find( + item => item.id === 'systemErrors', + ); + if (systemErrorsGroup) { + systemErrorsCount = systemErrorsGroup.messages.length; + } + + return state.messageCenter.groups + .reduce( + (unreadMessages: NotificationMessage[], group: NotificationGroup) => { + return unreadMessages.concat( + group.messages.reduce( + ( + unreadMessagesInGroup: NotificationMessage[], + message: NotificationMessage, + ) => { + if (!message.seen) { + unreadMessagesInGroup.push(message); + } + return unreadMessagesInGroup; + }, + [], + ), + ); + }, + [], + ) + .reduce( + ( + propsToMap: MessageCenterTriggerPropsToMap, + message: NotificationMessage, + ) => { + propsToMap.newMessagesCount++; + propsToMap.badgeDanger = + propsToMap.badgeDanger || + dangerousMessageTypes.includes(message.type); + return propsToMap; + }, + { + newMessagesCount: 0, + systemErrorsCount: systemErrorsCount, + badgeDanger: false, + }, + ); +}; + +export const MessageCenter = () => { + const kialiState = React.useContext(KialiContext) as KialiAppState; + const [isOpen, toggleDrawer] = React.useState(false); + const messageCenterStatus = calculateMessageStatus(kialiState); + /* + const onDismiss = (message: NotificationMessage, userDismissed: boolean) => { + if (userDismissed) { + kialiState.messageDispatch(MessageCenterActions.markAsRead(message.id)); + } else { + kialiState.messageDispatch(MessageCenterActions.hideNotification(message.id)); + } + } + */ + return ( + <> + + toggleDrawer(false)}> + + + + ); +}; diff --git a/plugins/kiali/src/components/MissingSidecar/MissingSidecar.tsx b/plugins/kiali/src/components/MissingSidecar/MissingSidecar.tsx new file mode 100644 index 0000000000..b73104da84 --- /dev/null +++ b/plugins/kiali/src/components/MissingSidecar/MissingSidecar.tsx @@ -0,0 +1,103 @@ +import * as React from 'react'; + +import { Tooltip, TooltipPosition } from '@patternfly/react-core'; +import { SVGIconProps } from '@patternfly/react-icons/dist/js/createIcon'; + +import { icons } from '../../config/Icons'; +import { KialiIcon } from '../../config/KialiIcon'; +import { isIstioNamespace, serverConfig } from '../../config/ServerConfig'; +import { kialiStyle } from '../../styles/StyleUtils'; + +type MissingSidecarProps = { + 'data-test'?: string; + text: string; + textmesh?: string; + texttooltip: string; + tooltip: boolean; + meshtooltip: string; + icon: React.ComponentClass; + color: string; + namespace: string; + style?: React.CSSProperties; + isGateway?: boolean; +}; + +const infoStyle = kialiStyle({ + margin: '0px 5px 2px 4px', + verticalAlign: '-5px !important', +}); + +export class MissingSidecar extends React.Component { + static defaultProps = { + textmesh: 'Out of mesh', + text: 'Missing Sidecar', + meshtooltip: + 'Out of mesh. Istio sidecar container or Ambient labels not found in Pod(s). Check if the istio-injection label/annotation is correctly set on the namespace/workload.', + texttooltip: + 'Istio sidecar container not found in Pod(s). Check if the istio-injection label/annotation is correctly set on the namespace/workload.', + tooltip: false, + icon: icons.istio.missingSidecar.icon, + color: icons.istio.missingSidecar.color, + }; + + render() { + const { + text, + texttooltip, + icon, + namespace, + color, + tooltip, + style, + ...otherProps + } = this.props; + const iconComponent = ( + + {React.createElement(icon, { + style: { color: color, verticalAlign: '-2px' }, + })} + {!tooltip && ( + + {serverConfig.ambientEnabled + ? this.props.textmesh + : this.props.text} + + {serverConfig.ambientEnabled + ? this.props.meshtooltip + : this.props.texttooltip} + + } + > + + + + )} + + ); + + if (isIstioNamespace(namespace) || this.props.isGateway) { + return <>; + } + + return tooltip ? ( + + {serverConfig.ambientEnabled + ? this.props.meshtooltip + : this.props.texttooltip} + + } + position={TooltipPosition.right} + > + {iconComponent} + + ) : ( + iconComponent + ); + } +} diff --git a/plugins/kiali/src/components/Overview/Overview.tsx b/plugins/kiali/src/components/Overview/Overview.tsx deleted file mode 100644 index a811dc9669..0000000000 --- a/plugins/kiali/src/components/Overview/Overview.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import React, { useState } from 'react'; -import useAsyncFn from 'react-use/lib/useAsyncFn'; -import useDebounce from 'react-use/lib/useDebounce'; - -import { CodeSnippet, WarningPanel } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; - -import { CircularProgress, Grid } from '@material-ui/core'; - -import { - CanaryUpgradeStatus, - ComponentStatus, - IstiodResourceThresholds, - KialiConfigT, - KialiFetchError, - KialiInfo, - NamespaceInfo, - OutboundTrafficPolicy, - OverviewData, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { kialiApiRef } from '../../api'; -import { calculateHealth } from './health'; -import { OverviewCard } from './OverviewCard'; -import { OverviewToolbar } from './OverviewToolbar'; - -export declare enum OverviewDisplayMode { - COMPACT = 0, - EXPAND = 1, - LIST = 2, -} - -declare const directionTypes: { - inbound: string; - outbound: string; -}; -export type DirectionType = keyof typeof directionTypes; - -type OverviewProps = { - kialiConfig: KialiConfigT; - kialiStatus: KialiInfo; - namespacesFiltered: string[]; -}; - -export const Overview = (props: OverviewProps) => { - const kialiClient = useApi(kialiApiRef); - kialiClient.setEntity(useEntity().entity); - const [namespaces, setNamespaces] = useState([]); - const [canaryStatus, setCanaryStatus] = React.useState< - CanaryUpgradeStatus | undefined - >(undefined); - const [canaryUpgrade, setCanaryUpgrade] = React.useState(false); - const [componentStatus, setComponentStatus] = React.useState< - ComponentStatus[] - >([]); - const [direction, setDirection] = React.useState('inbound'); - const [duration, setDuration] = React.useState(600); - const [overviewType, setOverviewType] = React.useState('app'); - const [outboundTrafficPolicy, setoutboundTrafficPolicy] = - React.useState({ mode: '' }); - const [istiodResourceThresholds, setIstiodResourceThresholds] = - React.useState(undefined); - const [errors, setErrors] = React.useState([]); - const [warnings, setWarnings] = React.useState([]); - - const fetchInfo = async ( - config: KialiConfigT = props.kialiConfig, - dur: number = duration, - ovType: OverviewType = overviewType, - dir: DirectionType = direction, - ) => { - await kialiClient - .getOverview(ovType, dur, dir) - .then(response => { - if (response.errors.length > 0) { - setErrors(response.errors); - } else { - setWarnings(response.warnings); - const ovData = response.response as OverviewData; - const ns = ovData.namespaces; - try { - if (ns.length > 0) { - ns.forEach((n, i) => { - ns[i] = calculateHealth(config.server, ovType, n, dur); - }); - } else { - const newWarnings = warnings; - newWarnings.push({ - errorType: 'SYSTEM_ERROR', - message: `No namespaces for Health`, - }); - setWarnings(newWarnings); - } - } catch (e) { - const newWarnings = warnings; - newWarnings.push({ - errorType: 'SYSTEM_ERROR', - message: `Error calculating Health : ${e}`, - }); - setWarnings(newWarnings); - } - setNamespaces(ns); - setIstiodResourceThresholds(ovData.istiodResourceThresholds); - setoutboundTrafficPolicy(ovData.outboundTraffic || { mode: '' }); - setComponentStatus(ovData.istioStatus || []); - setCanaryStatus(ovData.canaryUpgrade); - setCanaryUpgrade( - ovData.canaryUpgrade!.pendingNamespaces.length > 0 || - ovData.canaryUpgrade!.migratedNamespaces.length > 0, - ); - } - }) - .catch(e => { - const newWarnings = warnings; - newWarnings.push({ - errorType: 'SYSTEM_ERROR', - message: `Error calculating Health : ${e}`, - }); - setWarnings(newWarnings); - }); - }; - - const [{ loading }, refresh] = useAsyncFn( - async () => { - // Check if the config is loaded - await fetchInfo(); - }, - [duration, overviewType, direction], - { loading: true }, - ); - useDebounce(refresh, 10); - - if (errors.length > 0) { - const message = errors - .map( - e => - `Error ${e.errorType.toString()}, Code: ${e.statusCode} fetching ${ - e.resourcePath - } : ${e.message}`, - ) - .join('\n'); - return ( - - - - ); - } - - if (loading) { - return ; - } - - const handleToolbar = ( - dur: number = duration, - ovType: OverviewType = overviewType, - dir: DirectionType = direction, - ) => { - setWarnings([]); - fetchInfo(props.kialiConfig, dur, ovType, dir); - setDuration(dur); - setOverviewType(ovType); - setDirection(dir); - }; - - return ( - <> - {warnings.length > 0 && ( - - - `Error ${e.errorType.toString()},${ - e.statusCode ? `Code: ${e.statusCode}` : '' - } ${e.resourcePath ? ` fetching ${e.resourcePath}` : ''} : ${ - e.message - }`, - ) - .join('\n')} - /> - - )} - {namespaces.length > 0 && ( - <> - handleToolbar(duration, overviewType, direction)} - duration={duration} - setDuration={e => handleToolbar(e, overviewType, direction)} - overviewType={overviewType} - setOverviewType={e => handleToolbar(duration, e, direction)} - direction={direction} - setDirection={e => handleToolbar(duration, overviewType, e)} - /> - - {namespaces.map( - (ns, _) => - props.namespacesFiltered.indexOf(ns.name) > -1 && ( - - ), - )} - - - )} - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCard.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCard.tsx deleted file mode 100644 index 34011bb2be..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCard.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react'; - -import { Link } from '@backstage/core-components'; - -import { Card, CardActions, CardContent, Grid } from '@material-ui/core'; - -import { - CanaryUpgradeStatus, - ComponentStatus, - IstiodResourceThresholds, - KialiConfigT, - NamespaceInfo, - OutboundTrafficPolicy, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { OverviewCardBody } from './OverviewCardBody'; -import { OverviewCardHeader } from './OverviewCardHeader'; -import { OverviewMetrics } from './OverviewMetrics'; - -declare const directionTypes: { - inbound: string; - outbound: string; -}; -export type DirectionType = keyof typeof directionTypes; - -export type DirectionTypeOptions = 'inbound' | 'outbound'; - -type OverviewCardProps = { - canaryStatus?: CanaryUpgradeStatus; - canaryUpgrade?: boolean; - direction: DirectionTypeOptions; - duration: number; - ns: NamespaceInfo; - outboundTrafficPolicy: OutboundTrafficPolicy; - istioAPIEnabled?: boolean; - istiodResourceThresholds?: IstiodResourceThresholds; - istioStatus?: ComponentStatus[]; - kialiConfig: KialiConfigT; - type: OverviewType; -}; - -const getLinks = ( - ns: NamespaceInfo, - graphtype: string, - duration: number, - link: string, -): { [key: string]: string } => { - // graph Params - - const graphTypeQ = `graphType=${graphtype}`; - const durationQ = `duration=${duration}`; - const namespaces = `namespaces=${ns.name}`; - const graph = `${ - new URL('/console/graph/namespaces', link).href - }?${graphTypeQ}&${durationQ}&${namespaces}`; - - // Istio Config List Params - const validations = ns.validations; - let validationP = ''; - if (validations) { - if (validations.warnings) { - validationP += 'configvalidation=Warning'; - } - if (validationP !== '') { - validationP += '&'; - } - if (validations.errors) { - validationP += 'configvalidation=Not+Valid'; - } - } - - const istioValidations = `${ - new URL('/console/istio', link).href - }?${namespaces}&${validationP}`; - - return { - graph, - istioValidations, - }; -}; - -export const OverviewCard = (props: OverviewCardProps) => { - const links = getLinks( - props.ns, - props.type, - props.duration, - props.kialiConfig.kialiConsole, - ); - const isIstioNs = props.ns.name === props.kialiConfig.server.istioNamespace; - return ( - - - - - - - - - - - - - - - Go To Kiali Graph - - - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/ControlPlaneNamespaceStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/ControlPlaneNamespaceStatus.tsx deleted file mode 100644 index 37bec4ede5..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/ControlPlaneNamespaceStatus.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; - -import { Chip, Tooltip } from '@material-ui/core'; -import InfoOutlined from '@material-ui/icons/InfoOutlined'; - -import { - NamespaceInfo, - OutboundTrafficPolicy, -} from '@janus-idp/backstage-plugin-kiali-common'; - -type ControlPlaneNamespaceStatusProps = { - namespace: NamespaceInfo; - outboundTrafficPolicy?: OutboundTrafficPolicy; -}; - -const getContentLabel = (mode?: string) => { - return ( -
- {mode} -
- ); -}; -export const ControlPlaneNamespaceStatus = ( - props: ControlPlaneNamespaceStatusProps, -) => { - let maxProxyPushTime: number | undefined = undefined; - if (props.namespace.controlPlaneMetrics?.istiod_proxy_time) { - maxProxyPushTime = - props.namespace.controlPlaneMetrics?.istiod_proxy_time[0].datapoints.reduce( - (a, b) => (a[1] < b[1] ? a : b), - )[1] * 1000; - } - let showProxyPushTime = false; - if (maxProxyPushTime && !isNaN(maxProxyPushTime)) { - showProxyPushTime = true; - } - - return ( -
- {props.outboundTrafficPolicy && ( -
-
- Outbound policy -
- - This value represents the meshConfig.outboundTrafficPolicy.mode, - that configures the sidecar handling of external services, that - is, those services that are not defined in Istio’s internal - service registry. If this option is set to ALLOW_ANY, the Istio - proxy lets calls to unknown services pass through. If the option - is set to REGISTRY_ONLY, then the Istio proxy blocks any host - without an HTTP service or service entry defined within the mesh -
- } - placement="right" - > - - -
- )} - {showProxyPushTime && ( -
-
- Proxy push time -
- - This value represents the delay in seconds between config change - and a proxy receiving all required configuration. -
- } - > - } - style={{ color: '#002952', backgroundColor: '#e7f1fa' }} - /> - - - )} - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/IstioConfigStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/IstioConfigStatus.tsx deleted file mode 100644 index e3197a83b9..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/IstioConfigStatus.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; - -import { Link } from '@backstage/core-components'; - -import { Tooltip } from '@material-ui/core'; - -import { - NamespaceInfo, - ValidationStatus, - ValidationTypes, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { - createIcon, - ErrorIcon, - InfoIcon, - OkIcon, - WarningIcon, -} from '../../../Icons'; - -declare const directionTypes: { - inbound: string; - outbound: string; -}; -export type DirectionType = keyof typeof directionTypes; - -type IstioConfigStatusProps = { - ns: NamespaceInfo; - kialiConsole: string; -}; - -const getIcon = (severity: ValidationTypes, props?: any) => { - switch (severity) { - case ValidationTypes.Error: - return createIcon(ErrorIcon, props); - case ValidationTypes.Warning: - return createIcon(WarningIcon, props); - case ValidationTypes.Info: - return createIcon(InfoIcon, props); - default: - return createIcon(OkIcon, props); - } -}; - -const getTypeMessage = (count: number, type: ValidationTypes): string => { - return count > 1 ? `${count} ${type}s found` : `${count} ${type} found`; -}; - -const severitySummary = (warnings: number, errors: number) => { - const issuesMessages: string[] = []; - - if (errors > 0) { - issuesMessages.push(getTypeMessage(errors, ValidationTypes.Error)); - } - - if (warnings > 0) { - issuesMessages.push(getTypeMessage(warnings, ValidationTypes.Warning)); - } - - if (issuesMessages.length === 0) { - issuesMessages.push('No issues found'); - } - - return issuesMessages; -}; - -const tooltipContent = (validations: ValidationStatus) => { - if (validations.objectCount) { - if (validations.objectCount === 0) { - return <>No Istio config objects found; - } - - return ( - <> - Istio config objects analyzed: {validations.objectCount} -
-
    - {severitySummary(validations.warnings, validations.errors).map( - cat => ( -
  • {cat}
  • - ), - )} -
- - ); - } - return <>No Istio config validation available; -}; - -export const IstioConfigStatus = (props: IstioConfigStatusProps) => { - let validations: ValidationStatus = { - objectCount: 0, - errors: 0, - warnings: 0, - }; - let validationP = ''; - let severity = ValidationTypes.Correct; - if (props.ns.validations) { - validations = props.ns.validations; - if (validations.warnings) { - validationP += 'configvalidation=Warning'; - } - if (validationP !== '') { - validationP += '&'; - } - if (validations.errors) { - validationP += 'configvalidation=Not+Valid'; - } - if (validations.errors > 0) { - severity = ValidationTypes.Error; - } else if (validations.warnings > 0) { - severity = ValidationTypes.Warning; - } - } - const namespaces = `namespaces=${props.ns.name}`; - const linkIstioValidations = `${ - new URL('/console/istio', props.kialiConsole).href - }?${namespaces}&${validationP}`; - - return validations.objectCount && validations.objectCount > 0 ? ( - - - {validations.objectCount > 0 ? ( - getIcon(severity, { fontSize: 'inherit' }) - ) : ( -
N/A
- )} -
- - ) : ( -
N/A
- ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceMTLSStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceMTLSStatus.tsx deleted file mode 100644 index 0bcbcf8e60..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceMTLSStatus.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; - -import { Tooltip } from '@material-ui/core'; - -import { MTLSStatuses } from '@janus-idp/backstage-plugin-kiali-common'; - -import { - MTLSStatusFull, - MTLSStatusFullDark, - MTLSStatusPartial, - MTLSStatusPartialDark, -} from '../../../Icons'; - -type NamespaceMTLSStatusProps = { - status: string; -}; - -enum MTLSIconTypes { - LOCK_FULL = 'LOCK_FULL', - LOCK_HOLLOW = 'LOCK_HOLLOW', - LOCK_FULL_DARK = 'LOCK_FULL_DARK', - LOCK_HOLLOW_DARK = 'LOCK_HOLLOW_DARK', -} - -const emptyDescriptor = { - message: '', - icon: '', - showStatus: false, -}; - -type StatusDescriptor = { - message: string; - icon: string; - showStatus: boolean; -}; - -const statusDescriptors = new Map([ - [ - MTLSStatuses.ENABLED, - { - message: 'mTLS is enabled for this namespace', - icon: MTLSIconTypes.LOCK_FULL_DARK, - showStatus: true, - }, - ], - [ - MTLSStatuses.ENABLED_EXTENDED, - { - message: - 'mTLS is enabled for this namespace, extended from Mesh-wide config', - icon: MTLSIconTypes.LOCK_FULL_DARK, - showStatus: true, - }, - ], - [ - MTLSStatuses.PARTIALLY, - { - message: 'mTLS is partially enabled for this namespace', - icon: MTLSIconTypes.LOCK_HOLLOW_DARK, - showStatus: true, - }, - ], - [MTLSStatuses.DISABLED, emptyDescriptor], - [MTLSStatuses.NOT_ENABLED, emptyDescriptor], -]); - -const nameToSource: { [key: string]: JSX.Element } = { - LOCK_FULL: , - LOCK_HOLLOW: , - LOCK_FULL_DARK: , - LOCK_HOLLOW_DARK: , -}; - -export const NamespaceMTLSStatus = (props: NamespaceMTLSStatusProps) => { - const stat = statusDescriptors.get(props.status) || emptyDescriptor; - - return ( - - <>{nameToSource[stat.icon]} - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceStatuses.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceStatuses.tsx deleted file mode 100644 index 30f486bae7..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/NamespaceStatuses.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; - -import { Grid } from '@material-ui/core'; - -import { - DEGRADED, - FAILURE, - HEALTHY, - NamespaceInfo, - NOT_READY, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { switchType } from '../../../../helper'; -import { OverviewStatus } from './OverviewStatus'; - -type NamespaceStatusesProps = { - ns: NamespaceInfo; - type: OverviewType; -}; -export const NamespaceStatuses = (props: NamespaceStatusesProps) => { - const name = props.ns.name; - const status = props.ns.status; - const nbItems = status - ? Object.values(status).reduce((acc, val) => acc + val.length, 0) - : 0; - const text = - nbItems === 1 - ? switchType(props.type, '1 application', '1 service', '1 workload') - : nbItems + - switchType(props.type, ' applications', ' services', ' workloads'); - - const link = ( -
- {text} -
- ); - - return nbItems === props.ns.status?.notAvailable.length ? ( -
- {text} -
- ) : ( - <> - - {link} - - {status && status.inNotReady.length > 0 && ( - - )} - {status && status.inError.length > 0 && ( - - )} - {status && status.inWarning.length > 0 && ( - - )} - {status && status.inSuccess.length > 0 && ( - - )} - - - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewCardBody.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewCardBody.tsx deleted file mode 100644 index 3f6c6aa2f8..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewCardBody.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; - -import { Grid, Tooltip } from '@material-ui/core'; - -import { - CanaryUpgradeStatus, - ComponentStatus, - IstiodResourceThresholds, - KialiConfigT, - NamespaceInfo, - OutboundTrafficPolicy, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { DirectionTypeOptions } from '../OverviewCard'; -import { ControlPlaneNamespaceStatus } from './ControlPlaneNamespaceStatus'; -import { IstioConfigStatus } from './IstioConfigStatus'; -import { NamespaceMTLSStatus } from './NamespaceMTLSStatus'; -import { NamespaceStatuses } from './NamespaceStatuses'; -import { TLSInfo } from './TLSInfo'; - -type OverviewCardBodyProps = { - canaryStatus?: CanaryUpgradeStatus; - canaryUpgrade?: boolean; - direction: DirectionTypeOptions; - duration: number; - ns: NamespaceInfo; - outboundTrafficPolicy: OutboundTrafficPolicy; - istioAPIEnabled?: boolean; - istiodResourceThresholds?: IstiodResourceThresholds; - istioStatus?: ComponentStatus[]; - kialiConfig: KialiConfigT; - type: OverviewType; -}; - -const render_labels = (labels: { [key: string]: string } | undefined) => { - const labelsLength = labels ? `${Object.entries(labels).length}` : 'No'; - const labelContent = labels ? ( - - {Object.entries(labels || []).map(([key, value]) => ( -
  • - {key}={value} -
  • - ))} - - } - > -
    - {labelsLength} label{labelsLength !== '1' ? 's' : ''} -
    -
    - ) : ( -
    No labels
    - ); - - return labelContent; -}; - -export const OverviewCardBody = (props: OverviewCardBodyProps) => { - const isIstioNs = props.kialiConfig.server.istioNamespace === props.ns.name; - return ( - - - {render_labels(props.ns.labels)} -
    -
    - Istio config -
    - {props.ns.tlsStatus && ( - - - - )} - {props.istioAPIEnabled && ( - - )} -
    - {props.ns.status && ( - - )} - {isIstioNs && ( - <> - - - - )} -
    -
    - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewStatus.tsx deleted file mode 100644 index bbfd3b1697..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/OverviewStatus.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; - -import { Tooltip } from '@material-ui/core'; - -import { Status } from '@janus-idp/backstage-plugin-kiali-common'; - -import { createHealthIcon } from '../../../../helper'; - -type OverviewStatusProps = { - id: string; - namespace: string; - status: Status; - items: string[]; -}; -export const OverviewStatus = (props: OverviewStatusProps) => { - const length = props.items.length; - let items = props.items; - if (items.length > 6) { - items = items.slice(0, 5); - items.push(`and ${length - items.length} more...`); - } - const tooltipContent = ( - <> - {props.status.name} - {items.map((app, idx) => { - return ( -
    - - {createHealthIcon(props.status)} - {' '} - {app} -
    - ); - })} - - ); - return ( - -
    - {createHealthIcon(props.status)} - {` ${length}`} -
    -
    - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/TLSInfo.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/TLSInfo.tsx deleted file mode 100644 index 76fba881f2..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/TLSInfo.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import * as React from 'react'; - -import { Chip, Grid, Tooltip } from '@material-ui/core'; -import InfoOutlined from '@material-ui/icons/InfoOutlined'; -import LockRounded from '@material-ui/icons/LockRounded'; - -import { CertsInfo } from '@janus-idp/backstage-plugin-kiali-common'; - -type Props = { - certificatesInformationIndicators?: boolean; - version?: string; - certsInfo?: CertsInfo[]; -}; - -const generateRow = (item: CertsInfo) => { - return ( -
    -
    - From {item.issuer} -
    -
    -
    Issuer:
    -
    {item.secretName}
    -
    -
    -
    Valid From:
    -
    {item.notAfter}
    -
    -
    -
    Valid To:
    -
    {item.notBefore}
    -
    -
    - ); -}; - -const showCerts = (certs: CertsInfo[] | undefined) => { - if (certs) { - const rows = certs.map(item => generateRow(item)); - return
    {rows}
    ; - } - - return 'No cert info'; -}; - -const LockIcon = (props: Props) => { - return props.certificatesInformationIndicators === true ? ( - -
    - -
    -
    - ) : ( - - ); -}; - -const chipContent = (props: Props) => { - return ( - - - {props.version} - - - - - - - The meshConfig.meshMTLS.minProtocolVersion field specifies the - minimum TLS version for the TLS connections among Istio workloads. - N/A if it was not set. - - } - > - - - - - ); -}; - -export const TLSInfo = (props: Props) => { - return ( -
    -
    -
    - Min TLS version -
    - -
    -
    - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/index.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/index.tsx deleted file mode 100644 index f2be882458..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardBody/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './OverviewCardBody'; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/ControlPlaneVersionBadge.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/ControlPlaneVersionBadge.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioComponentStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioComponentStatus.tsx deleted file mode 100644 index 3cf8c625d8..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioComponentStatus.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from 'react'; - -import { Grid } from '@material-ui/core'; - -import { - ComponentStatus, - IStatus, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { - ComponentIcon, - createIcon, - ErrorAddonComponent, - ErrorCoreComponent, - NotReadyComponent, - SuccessComponent, -} from '../../../../Icons'; - -type Props = { - componentStatus: ComponentStatus; -}; - -// Mapping Valid-Core to Icon representation. -const validToIcon: { [valid: string]: ComponentIcon } = { - 'false-false': ErrorAddonComponent, - 'false-true': ErrorCoreComponent, - 'true-false': SuccessComponent, - 'true-true': SuccessComponent, -}; - -const statusMsg = { - [IStatus.Healthy]: 'Healthy', - [IStatus.NotFound]: 'Not found', - [IStatus.NotReady]: 'Not ready', - [IStatus.Unhealthy]: 'Not healthy', - [IStatus.Unreachable]: 'Unreachable', -}; - -export const IstioComponentStatus = (props: Props) => { - const renderIcon = (status: IStatus, isCore: boolean) => { - let compIcon = validToIcon[`${status === IStatus.Healthy}-${isCore}`]; - if (status === IStatus.NotReady) { - compIcon = NotReadyComponent; - } - return createIcon(compIcon, { fontSize: 'small' }); - }; - - const renderCells = () => { - const comp = props.componentStatus; - - return [ -
  • - - - {renderIcon( - props.componentStatus.status, - props.componentStatus.is_core, - )} - - {comp.name} - {statusMsg[comp.status]} - -
  • , - ]; - }; - - return renderCells(); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatus.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatus.tsx deleted file mode 100644 index 79ff6c9604..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/IstioStatus.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; - -import { Tooltip } from '@material-ui/core'; -import WarningRounded from '@material-ui/icons/WarningRounded'; - -import { - ComponentStatus, - IStatus, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { Colors } from '../../../../Icons'; -import { IstioStatusList } from './IstioStatusList'; - -type Props = { - istioStatus: ComponentStatus[]; -}; - -const ValidToColor = { - 'true-true-true': { color: Colors.error }, - 'true-true-false': { color: Colors.error }, - 'true-false-true': { color: Colors.error }, - 'true-false-false': { color: Colors.error }, - 'false-true-true': { htmlColor: Colors.warning }, - 'false-true-false': { htmlColor: Colors.warning }, - 'false-false-true': { color: Colors.info }, - 'false-false-false': { htmlColor: Colors.ok }, -}; - -export const IstioStatus = (props: Props) => { - const tooltipContent = () => { - return ; - }; - - const tooltipColor = (): { [key: string]: string } => { - let coreUnhealthy: boolean = false; - let addonUnhealthy: boolean = false; - let notReady: boolean = false; - - Object.keys(props.istioStatus || {}).forEach((compKey: string) => { - const { status, is_core } = props.istioStatus[compKey as any]; - const isNotReady: boolean = status === IStatus.NotReady; - const isUnhealthy: boolean = status !== IStatus.Healthy && !isNotReady; - - if (is_core) { - coreUnhealthy = coreUnhealthy || isUnhealthy; - } else { - addonUnhealthy = addonUnhealthy || isUnhealthy; - } - - notReady = notReady || isNotReady; - }); - - return ValidToColor[`${coreUnhealthy}-${addonUnhealthy}-${notReady}`]; - }; - - const healthyComponents = () => { - return props.istioStatus.reduce( - (healthy: boolean, compStatus: ComponentStatus) => { - return healthy && compStatus.status === IStatus.Healthy; - }, - true, - ); - }; - - return !healthyComponents() ? ( - -
    - -
    -
    - ) : null; -}; - -export default IstioStatus; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/index.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/index.tsx deleted file mode 100644 index 8e6e7027b9..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/IstioStatus/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './IstioStatus'; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/OverviewCardHeader.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/OverviewCardHeader.tsx deleted file mode 100644 index c9862669c6..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/OverviewCardHeader.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; - -import { CardHeader, Chip, Grid } from '@material-ui/core'; - -import { - CanaryUpgradeStatus, - ComponentStatus, - KialiConfigT, - NamespaceInfo, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { IstioStatus } from './IstioStatus'; - -type OverviewCardHeaderProps = { - ns: NamespaceInfo; - canaryUpgrade?: boolean; - canaryStatus?: CanaryUpgradeStatus; - istioStatus?: ComponentStatus[]; - istioAPIEnabled?: boolean; - kialiConfig: KialiConfigT; -}; - -const getTitleHeader = ( - ns: NamespaceInfo, - istioNamespace: boolean, - istioStatus?: ComponentStatus[], - istioAPIEnabled?: boolean, - hasCanaryUpgradeConfigured?: boolean, - canaryStatus?: CanaryUpgradeStatus, -) => { - return ( - - - - {ns.name} - - - {istioNamespace && istioStatus && ( - <> - - - - - - - - )} - {istioNamespace && - hasCanaryUpgradeConfigured && - canaryStatus?.migratedNamespaces.includes(ns.name) && ( - - - - )} - {istioNamespace && !istioAPIEnabled && ( - - - - )} - - ); -}; - -export const OverviewCardHeader = (props: OverviewCardHeaderProps) => { - return ( - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/index.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/index.tsx deleted file mode 100644 index bfb4fed958..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewCardHeader/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './OverviewCardHeader'; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/Charts.css b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/Charts.css deleted file mode 100644 index 6f9792c1b2..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/Charts.css +++ /dev/null @@ -1,3 +0,0 @@ -.pf-v5-c-chart svg { - overflow: visible !important; -} diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/ControlPlaneChart.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/ControlPlaneChart.tsx deleted file mode 100644 index 84fac8a5a5..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/ControlPlaneChart.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import React from 'react'; - -import { Card, CardContent, Grid, Tooltip } from '@material-ui/core'; - -import { - ComputedServerConfig, - Datapoint, - getName, - IstiodResourceThresholds, - Metric, - RichDataPoint, - toLocaleStringWithConditionalDate, - toVCLine, - VCLine, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { createIcon, InfoIcon } from '../../../../Icons'; -import { SparklineChart } from './SparklineChart'; - -import './Charts.css'; - -type ControlPlaneCharts = { - pilotLatency?: Metric[]; - istiodMemory?: Metric[]; - istiodCpu?: Metric[]; - duration: number; - istiodResourceThresholds?: IstiodResourceThresholds; - config: ComputedServerConfig; -}; - -const showMetrics = (metrics: Metric[] | undefined): boolean => { - // show metrics if metrics exists and some values at least are not zero - if ( - metrics && - metrics.length > 0 && - metrics[0].datapoints.length > 0 && - metrics[0].datapoints.some(dp => Number(dp[1]) !== 0) - ) { - return true; - } - - return false; -}; - -export const ControlPlaneChart = (props: ControlPlaneCharts) => { - const memorySeries: VCLine[] = []; - const cpuSeries: VCLine[] = []; - const memoryThresholds: VCLine[] = []; - const cpuThresholds: VCLine[] = []; - if (showMetrics(props.istiodMemory)) { - if (props.istiodMemory && props.istiodMemory?.length > 0) { - const data = toVCLine(props.istiodMemory[0].datapoints, 'Mb', '#38812F'); - - if (props.istiodResourceThresholds?.memory) { - const datapoint0: Datapoint = [ - props.istiodMemory[0].datapoints[0][0], - props.istiodMemory[0].datapoints[0][1], - ]; - datapoint0[1] = props.istiodResourceThresholds?.memory; - const datapointn: Datapoint = [ - props.istiodMemory[0].datapoints[ - props.istiodMemory[0].datapoints.length - 1 - ][0], - props.istiodMemory[0].datapoints[ - props.istiodMemory[0].datapoints.length - 1 - ][0], - ]; - datapointn[1] = props.istiodResourceThresholds?.memory; - const dataThre = toVCLine( - [datapoint0, datapointn], - 'Mb (Threshold)', - '#4CB140', - ); - memoryThresholds.push(dataThre); - } - - memorySeries.push(data); - } - } - - if (showMetrics(props.istiodCpu)) { - if (props.istiodCpu && props.istiodCpu?.length > 0) { - const data = toVCLine(props.istiodCpu[0].datapoints, 'cores', '#38812F'); - - if (props.istiodResourceThresholds?.cpu) { - const datapoint0: Datapoint = [ - props.istiodCpu[0].datapoints[0][0], - props.istiodCpu[0].datapoints[0][1], - ]; - datapoint0[1] = props.istiodResourceThresholds?.cpu; - const datapointn: Datapoint = [ - props.istiodCpu[0].datapoints[ - props.istiodCpu[0].datapoints.length - 1 - ][0], - props.istiodCpu[0].datapoints[ - props.istiodCpu[0].datapoints.length - 1 - ][0], - ]; - datapointn[1] = props.istiodResourceThresholds?.cpu; - const dataThre = toVCLine([datapoint0, datapointn], 'cores', '#4CB140'); - cpuThresholds.push(dataThre); - } - - cpuSeries.push(data); - } - } - return ( -
    -
    -
    - Control plane metrics -
    -
    -
    - - - - {showMetrics(props.istiodMemory) && ( - <> - - - - Memory - - - {getName(props.config, props.duration).toLowerCase()} - - {createIcon(InfoIcon, { fontSize: 'small' })} - - - - - - - `${toLocaleStringWithConditionalDate( - dp.x as Date, - )}\n${dp.y.toFixed(2)} ${dp.name}` - } - series={memorySeries} - labelName="mb" - thresholds={memoryThresholds} - /> - - - )} - {showMetrics(props.istiodCpu) && ( - <> - - - - CPU - - - {getName(props.config, props.duration).toLowerCase()} - - {createIcon(InfoIcon, { fontSize: 'small' })} - - - - - - - `${toLocaleStringWithConditionalDate( - dp.x as Date, - )}\n${dp.y.toFixed(2)} ${dp.name}` - } - series={cpuSeries} - labelName="cores" - thresholds={cpuThresholds} - /> - - - )} - - - -
    -
    - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/DataPlaneChart.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/DataPlaneChart.tsx deleted file mode 100644 index 97502806fb..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/DataPlaneChart.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; - -import { - ComputedServerConfig, - DirectionType, - getName, - Metric, - RichDataPoint, - toLocaleStringWithConditionalDate, - toVCLine, - VCLine, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { SparklineChart } from './SparklineChart'; - -import './Charts.css'; - -type DataPlaneChartProps = { - metrics?: Metric[]; - errorMetrics?: Metric[]; - duration: number; - direction: DirectionType; - config: ComputedServerConfig; - width?: string; -}; - -const showMetrics = (metrics: Metric[] | undefined): boolean => { - // show metrics if metrics exists and some values at least are not zero - if ( - metrics && - metrics.length > 0 && - metrics[0].datapoints.some(dp => Number(dp[1]) !== 0) - ) { - return true; - } - - return false; -}; - -export const DataPlaneChart = (props: DataPlaneChartProps) => { - const series: VCLine[] = []; - - if (showMetrics(props.metrics)) { - if (props.metrics && props.metrics.length > 0) { - const data = toVCLine(props.metrics[0].datapoints, 'ops (Total)', '#06c'); - series.push(data); - } - - if (props.errorMetrics && props.errorMetrics.length > 0) { - const dataErrors = toVCLine( - props.errorMetrics[0].datapoints, - 'ops (4xx+5xx)', - '#c9190b', - ); - series.push(dataErrors); - } - } - return ( -
    - {series.length > 0 && ( - <> -
    - {`${props.direction} traffic, ${getName( - props.config, - props.duration, - ).toLowerCase()}`} -
    - - `${toLocaleStringWithConditionalDate( - dp.x as Date, - )}\n${dp.y.toFixed(2)} ${dp.name}` - } - series={series} - labelName="ops" - /> - - )} - {series.length === 0 && ( -
    - No {props.direction.toLowerCase()} traffic -
    - )} -
    - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/HookedChartTooltip.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/HookedChartTooltip.tsx deleted file mode 100644 index f5ef3944a1..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/HookedChartTooltip.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react'; - -import { ChartTooltip, ChartTooltipProps } from '@patternfly/react-charts'; - -import { VCDataPoint } from '@janus-idp/backstage-plugin-kiali-common'; - -export type HookedTooltipProps = ChartTooltipProps & { - activePoints?: (VCDataPoint & T)[]; - onOpen?: (items: VCDataPoint[]) => void; - onClose?: () => void; -}; - -export class HookedChartTooltip extends React.Component< - HookedTooltipProps -> { - componentDidMount() { - if (this.props.onOpen && this.props.activePoints) { - this.props.onOpen(this.props.activePoints); - } - } - - componentWillUnmount() { - if (this.props.onClose) { - this.props.onClose(); - } - } - - render() { - return ; - } -} diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/OverviewChart.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/OverviewChart.tsx deleted file mode 100644 index 109578a61a..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/OverviewChart.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; - -import { - ComputedServerConfig, - ControlPlaneMetricsMap, - DirectionType, - IstiodResourceThresholds, - Metric, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { ControlPlaneChart } from './ControlPlaneChart'; -import { DataPlaneChart } from './DataPlaneChart'; - -type OverviewChartProps = { - metrics?: Metric[]; - errorMetrics?: Metric[]; - controlPlaneMetrics?: ControlPlaneMetricsMap; - istiodResourceThresholds?: IstiodResourceThresholds; - duration: number; - direction: DirectionType; - config: ComputedServerConfig; - isIstioNamespace?: boolean; - istioAPIEnabled?: boolean; -}; - -export const OverviewChart = (props: OverviewChartProps) => { - return ( - <> - {(!props.isIstioNamespace || !props.istioAPIEnabled) && ( - - )} - {props.isIstioNamespace && props.istioAPIEnabled && ( - - )} - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/index.ts b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/index.ts deleted file mode 100644 index ec9fe9f373..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './OverviewChart'; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewMetrics.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewMetrics.tsx deleted file mode 100644 index 5084ba0100..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/OverviewMetrics.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; - -import { Grid } from '@material-ui/core'; - -import { - CanaryUpgradeStatus, - ComponentStatus, - IstiodResourceThresholds, - KialiConfigT, - NamespaceInfo, - OutboundTrafficPolicy, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -import { DirectionTypeOptions } from '../OverviewCard'; -import { CanaryUpgradeProgress } from './CanaryUpgradeProgress'; -import { OverviewChart } from './OverviewChart'; - -type OverviewMetricsProps = { - canaryStatus?: CanaryUpgradeStatus; - canaryUpgrade?: boolean; - direction: DirectionTypeOptions; - duration: number; - ns: NamespaceInfo; - outboundTrafficPolicy: OutboundTrafficPolicy; - isIstioNs?: boolean; - istioAPIEnabled?: boolean; - istiodResourceThresholds?: IstiodResourceThresholds; - istioStatus?: ComponentStatus[]; - kialiConfig: KialiConfigT; - type: OverviewType; -}; - -export const OverviewMetrics = (props: OverviewMetricsProps) => { - return ( - - {props.isIstioNs && props.istioAPIEnabled ? ( - - - {props.canaryUpgrade && props.canaryStatus && ( - - - - )} - - - - - - ) : ( - - - - )} - - ); -}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/index.tsx b/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/index.tsx deleted file mode 100644 index c2fd07c885..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './OverviewMetrics'; diff --git a/plugins/kiali/src/components/Overview/OverviewToolbar.tsx b/plugins/kiali/src/components/Overview/OverviewToolbar.tsx deleted file mode 100644 index 1a526a7d09..0000000000 --- a/plugins/kiali/src/components/Overview/OverviewToolbar.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; - -import { Select, SelectItem } from '@backstage/core-components'; - -import { Grid, IconButton, Tooltip } from '@material-ui/core'; -import Refresh from '@material-ui/icons/Refresh'; - -import { - durationsTuples, - OverviewType, -} from '@janus-idp/backstage-plugin-kiali-common'; - -declare const directionTypes: { - inbound: string; - outbound: string; -}; -export type DirectionType = keyof typeof directionTypes; - -type OverviewTypeOptions = 'app' | 'workload' | 'service'; -type DirectionTypeOptions = 'inbound' | 'outbound'; - -type OverviewToolbarProps = { - direction: DirectionType; - setDirection: (e: DirectionTypeOptions) => void; - duration: number; - setDuration: (e: number) => void; - overviewType: OverviewType; - setOverviewType: (e: OverviewTypeOptions) => void; - refresh: () => void; -}; - -const healthType = [ - { - label: 'Apps', - value: 'app', - }, - { - label: 'Workloads', - value: 'workload', - }, - { - label: 'Services', - value: 'service', - }, -]; - -const directionType = [ - { - label: 'Inbound', - value: 'inbound', - }, - { - label: 'Outbound', - value: 'outbound', - }, -]; - -const getDurationType = (): SelectItem[] => { - const items: SelectItem[] = []; - durationsTuples.forEach((value, _) => - items.push({ - label: `Last ${value[1]}`, - value: value[0], - }), - ); - return items; -}; - -export const OverviewToolbar = (props: OverviewToolbarProps) => { - return ( - - - props.setDirection(e as DirectionTypeOptions)} - label="Traffic" - items={directionType} - selected={props.direction} - /> - - - ({ + label: ns.name, + value: ns.name, + }))} + selected={kialiState.namespaces.activeNamespaces.map(ns => ns.name)} + multiple + onChange={values => { + kialiState.dispatch.namespaceDispatch( + NamespaceActions.setActiveNamespaces( + kialiState.namespaces.items!.filter(ns => + (values as string[]).includes(ns.name), + ), + ), + ); + }} + /> + + + + {homeCluster && ( + + Kiali home cluster: {homeCluster?.name}} + > + } + label={homeCluster?.name} + /> + + + )} + + + + + + + + + + View Debug Info + + About + + + + {kialiState.authentication.session && ( +
    + + User : + {kialiState.authentication.session.username} + +
    + )} +
    +
    +
    + + + + ); +}; diff --git a/plugins/kiali/src/pages/Kiali/KialiHelper.tsx b/plugins/kiali/src/pages/Kiali/KialiHelper.tsx new file mode 100644 index 0000000000..2818ea52b0 --- /dev/null +++ b/plugins/kiali/src/pages/Kiali/KialiHelper.tsx @@ -0,0 +1,74 @@ +import React from 'react'; + +import { + CodeSnippet, + Content, + InfoCard, + Link, + Page, + WarningPanel, +} from '@backstage/core-components'; + +import HelpRounded from '@material-ui/icons/HelpRounded'; + +import { KialiChecker } from '../../store/KialiProvider'; + +export const KialiHelper = (props: { check: KialiChecker }) => { + const pretty = () => { + if (props.check.message) { + const helper = props.check.helper; + const attributes = + props.check.missingAttributes && + `Missing attributes: ${props.check.missingAttributes.join(',')}.`; + return ( + <> +
    +
    + + + + {attributes && ( + <> +
    {attributes} + + )} + {helper && ( + <> +
    + {helper} + + )} + + ); + } + return <>; + }; + + const printAuthentication = ( + <> + The authentication provided by Kiali is{' '} + {props.check.authData?.strategy}.
    + You need to install the kiali backend to be able to use this kiali. +
    Follow the steps in{' '} + + Kiali Plugin + + {pretty()} + + ); + + const getTitle = () => { + if (!props.check.verify) { + return 'Authentication failed.'; + } + + return 'Unexpected Check'; + }; + return ( + + + {printAuthentication} + + + ); +}; diff --git a/plugins/kiali/src/pages/Kiali/KialiPage.tsx b/plugins/kiali/src/pages/Kiali/KialiPage.tsx new file mode 100644 index 0000000000..bb5a93fc8a --- /dev/null +++ b/plugins/kiali/src/pages/Kiali/KialiPage.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { Content, Page } from '@backstage/core-components'; + +import { OverviewPage } from '../Overview/OverviewPage'; +import { KialiHeader } from './Header/Header'; +import { KialiNoPath } from './NoPath'; + +const noPath = 'noPath'; +const getPathPage = () => { + const pathname = window.location.pathname.split('/').pop(); + if (pathname && pathname === 'kiali') { + return 'overview'; + } else if (pathname) { + return pathname; + } + return noPath; +}; + +export const KialiPage = () => { + const [kialiTab, _] = React.useState(getPathPage()); + + const renderPath = () => { + switch (kialiTab) { + case 'overview': + return ; + default: + return ; + } + }; + + return ( + + + + {renderPath()} + + + ); +}; diff --git a/plugins/kiali/src/pages/Kiali/NoPath.tsx b/plugins/kiali/src/pages/Kiali/NoPath.tsx new file mode 100644 index 0000000000..0898bf88e2 --- /dev/null +++ b/plugins/kiali/src/pages/Kiali/NoPath.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; + +import { Content, Link, Page, WarningPanel } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; + +import { rootRouteRef } from '../../routes'; + +export const KialiNoPath = () => { + const location = useLocation().pathname; + const link = useRouteRef(rootRouteRef); + return ( + + + + Path {location} not exist in Kiali Plugin.{' '} + Go to Kiali Plugin + + + + ); +}; diff --git a/plugins/kiali/src/pages/Kiali/index.ts b/plugins/kiali/src/pages/Kiali/index.ts new file mode 100644 index 0000000000..4f19dd7d5d --- /dev/null +++ b/plugins/kiali/src/pages/Kiali/index.ts @@ -0,0 +1,2 @@ +export * from './KialiPage'; +export * from './NoPath'; diff --git a/plugins/kiali/src/pages/Overview/NamespaceInfo.ts b/plugins/kiali/src/pages/Overview/NamespaceInfo.ts new file mode 100644 index 0000000000..98d83950ad --- /dev/null +++ b/plugins/kiali/src/pages/Overview/NamespaceInfo.ts @@ -0,0 +1,28 @@ +import { IstioConfigList } from '../../types/IstioConfigList'; +import { ValidationStatus } from '../../types/IstioObjects'; +import { ControlPlaneMetricsMap, Metric } from '../../types/Metrics'; +import { TLSStatus } from '../../types/TLSStatus'; + +export type NamespaceInfo = { + name: string; + cluster?: string; + outboundPolicyMode?: string; + status?: NamespaceStatus; + tlsStatus?: TLSStatus; + istioConfig?: IstioConfigList; + validations?: ValidationStatus; + metrics?: Metric[]; + errorMetrics?: Metric[]; + labels?: { [key: string]: string }; + annotations?: { [key: string]: string }; + controlPlaneMetrics?: ControlPlaneMetricsMap; + isAmbient?: boolean; +}; + +export type NamespaceStatus = { + inNotReady: string[]; + inError: string[]; + inWarning: string[]; + inSuccess: string[]; + notAvailable: string[]; +}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/CanaryUpgradeProgress.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/CanaryUpgradeProgress.tsx similarity index 68% rename from plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/CanaryUpgradeProgress.tsx rename to plugins/kiali/src/pages/Overview/OverviewCard/CanaryUpgradeProgress.tsx index 01cb27924d..c0a58c2aa7 100644 --- a/plugins/kiali/src/components/Overview/OverviewCard/OverviewMetrics/CanaryUpgradeProgress.tsx +++ b/plugins/kiali/src/pages/Overview/OverviewCard/CanaryUpgradeProgress.tsx @@ -1,18 +1,24 @@ -import React from 'react'; +import * as React from 'react'; import { Tooltip } from '@material-ui/core'; -import InfoRounded from '@material-ui/icons/InfoRounded'; import { ChartDonutUtilization, ChartThemeColor, } from '@patternfly/react-charts'; -import { CanaryUpgradeStatus } from '@janus-idp/backstage-plugin-kiali-common'; +import { KialiIcon } from '../../../config/KialiIcon'; +import { kialiStyle } from '../../../styles/StyleUtils'; +import { CanaryUpgradeStatus } from '../../../types/IstioObjects'; -type CanaryUpgradeProgressProps = { +type Props = { canaryUpgradeStatus: CanaryUpgradeStatus; }; -export const CanaryUpgradeProgress = (props: CanaryUpgradeProgressProps) => { + +export const infoStyle = kialiStyle({ + margin: '0px 0px -1px 4px', +}); + +export const CanaryUpgradeProgress = (props: Props) => { const total = props.canaryUpgradeStatus.migratedNamespaces.length + props.canaryUpgradeStatus.pendingNamespaces.length; @@ -20,20 +26,23 @@ export const CanaryUpgradeProgress = (props: CanaryUpgradeProgressProps) => { total > 0 ? (props.canaryUpgradeStatus.migratedNamespaces.length * 100) / total : 0; - return ( -
    - <> - <> +
    +
    +
    Canary upgrade status - + + + - +
    { themeColor={ChartThemeColor.green} />
    - <> +

    {`${props.canaryUpgradeStatus.migratedNamespaces.length} of ${total} namespaces migrated`}

    - - +
    +
    ); }; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneBadge.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneBadge.tsx new file mode 100644 index 0000000000..183b5e78e1 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneBadge.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; + +import { Chip, makeStyles } from '@material-ui/core'; + +import { AmbientBadge } from '../../../components/Ambient/AmbientBadge'; +import { IstioStatusInline } from '../../../components/IstioStatus/IstioStatusInline'; +import { serverConfig } from '../../../config'; +import { ComponentStatus } from '../../../types/IstioStatus'; +import { isRemoteCluster } from './OverviewCardControlPlaneNamespace'; +import { RemoteClusterBadge } from './RemoteClusterBadge'; + +type Props = { + cluster?: string; + annotations?: { [key: string]: string }; + status: ComponentStatus[]; +}; + +const useStyles = makeStyles(() => ({ + controlPlane: { + backgroundColor: '#f3faf2', + color: '#1e4f18', + marginLeft: '5px', + }, +})); + +export const ControlPlaneBadge = (props: Props): React.JSX.Element => { + const classes = useStyles(); + return ( + <> + + {isRemoteCluster(props.annotations) && } + {serverConfig.ambientEnabled && ( + + )}{' '} + + + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneNamespaceStatus.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneNamespaceStatus.tsx new file mode 100644 index 0000000000..adc40a3b7e --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneNamespaceStatus.tsx @@ -0,0 +1,109 @@ +import * as React from 'react'; + +import { Chip, Tooltip } from '@material-ui/core'; + +import { KialiIcon } from '../../../config/KialiIcon'; +import { OutboundTrafficPolicy } from '../../../types/IstioObjects'; +import { NamespaceInfo } from '../NamespaceInfo'; +import { infoStyle } from './OverviewCardControlPlaneNamespace'; + +type Props = { + namespace: NamespaceInfo; + outboundTrafficPolicy?: OutboundTrafficPolicy; +}; + +export class ControlPlaneNamespaceStatus extends React.Component { + render() { + let maxProxyPushTime: number | undefined = undefined; + if ( + this.props.namespace.controlPlaneMetrics && + this.props.namespace.controlPlaneMetrics.istiod_proxy_time + ) { + maxProxyPushTime = + this.props.namespace.controlPlaneMetrics?.istiod_proxy_time[0].datapoints.reduce( + (a, b) => (a[1] < b[1] ? a : b), + )[1] * 1000; + } + let showProxyPushTime = false; + if (maxProxyPushTime && !isNaN(maxProxyPushTime)) { + showProxyPushTime = true; + } + + return ( +
    + {this.props.outboundTrafficPolicy && ( +
    +
    + Outbound policy +
    + + This value represents the + meshConfig.outboundTrafficPolicy.mode, that configures the + sidecar handling of external services, that is, those services + that are not defined in Istio’s internal service registry. If + this option is set to ALLOW_ANY, the Istio proxy lets calls to + unknown services pass through. If the option is set to + REGISTRY_ONLY, then the Istio proxy blocks any host without an + HTTP service or service entry defined within the mesh +
    + } + > + + {this.props.outboundTrafficPolicy.mode} + + + } + /> + +
    + )} + {showProxyPushTime && ( +
    +
    + Proxy push time +
    + + This value represents the delay in seconds between config + change and a proxy receiving all required configuration. +
    + } + > + + {maxProxyPushTime?.toFixed(2)} ms + + + } + /> + +
    + )} + + ); + } +} diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneVersionBadge.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneVersionBadge.tsx new file mode 100644 index 0000000000..5069782ab5 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/ControlPlaneVersionBadge.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; + +import { Chip } from '@material-ui/core'; + +type Props = { + version: string; + isCanary: boolean; +}; + +export class ControlPlaneVersionBadge extends React.Component { + render() { + return ( + + ); + } +} diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceHeader.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceHeader.tsx new file mode 100644 index 0000000000..68687552a9 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceHeader.tsx @@ -0,0 +1,83 @@ +import React from 'react'; + +import { CardHeader, Chip } from '@material-ui/core'; + +import { serverConfig } from '../../../config'; +import { CanaryUpgradeStatus } from '../../../types/IstioObjects'; +import { ComponentStatus } from '../../../types/IstioStatus'; +import { NamespaceInfo } from '../NamespaceInfo'; +import { ControlPlaneBadge } from './ControlPlaneBadge'; +import { ControlPlaneVersionBadge } from './ControlPlaneVersionBadge'; + +type NamespaceHeaderProps = { + namespace: NamespaceInfo; + istioAPIEnabled?: boolean; + canaryUpgradeStatus?: CanaryUpgradeStatus; + istioStatus: ComponentStatus[]; +}; + +export const NamespaceHeader = (props: NamespaceHeaderProps) => { + const isIstioSystem = serverConfig.istioNamespace === props.namespace.name; + + const hasCanaryUpgradeConfigured = (): boolean => { + return props.canaryUpgradeStatus + ? props.canaryUpgradeStatus.pendingNamespaces.length > 0 || + props.canaryUpgradeStatus.migratedNamespaces.length > 0 + : false; + }; + return ( + + {props.namespace.name} + {isIstioSystem && ( + + )} + {!props.istioAPIEnabled && ( + + )} + + } + subheader={ + <> + {props.namespace.name !== serverConfig.istioNamespace && + hasCanaryUpgradeConfigured() && + props.canaryUpgradeStatus?.migratedNamespaces.includes( + props.namespace.name, + ) && ( + + )} + {props.namespace.name !== serverConfig.istioNamespace && + hasCanaryUpgradeConfigured() && + props.canaryUpgradeStatus?.pendingNamespaces.includes( + props.namespace.name, + ) && ( + + )} + {props.namespace.name === serverConfig.istioNamespace && + !props.istioAPIEnabled && ( + + )} + + } + /> + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceLabels.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceLabels.tsx new file mode 100644 index 0000000000..2375bd6c7c --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceLabels.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import { Tooltip } from '@material-ui/core'; + +import { PFColors } from '../../../components/Pf/PfColors'; + +type NamespaceLabelsprops = { + labels?: { [key: string]: string }; +}; +export const NamespaceLabels = (props: NamespaceLabelsprops) => { + const labelsLength = props.labels + ? `${Object.entries(props.labels).length}` + : 'No'; + const tooltipTitle = ( +
      + {Object.entries(props.labels || []).map(([key, value]) => ( +
    • + {key}={value} +
    • + ))} +
    + ); + return props.labels ? ( + +
    + {labelsLength} label{labelsLength !== '1' ? 's' : ''} +
    +
    + ) : ( +
    No labels
    + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceStatus.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceStatus.tsx new file mode 100644 index 0000000000..94a1736444 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/NamespaceStatus.tsx @@ -0,0 +1,124 @@ +import React from 'react'; + +import { Paths } from '../../../config'; +import { + DurationInSeconds, + IntervalInMilliseconds, +} from '../../../types/Common'; +import { DEGRADED, FAILURE, HEALTHY, NOT_READY } from '../../../types/Health'; +import { NamespaceInfo } from '../NamespaceInfo'; +import { switchType } from '../OverviewHelper'; +import { OverviewStatus } from '../OverviewStatus'; +import { OverviewType } from '../OverviewToolbar'; + +type NamespaceStatusProps = { + namespace: NamespaceInfo; + type: OverviewType; + duration: DurationInSeconds; + refreshInterval: IntervalInMilliseconds; +}; + +export const NamespaceStatus = (props: NamespaceStatusProps) => { + const ns = props.namespace; + const targetPage = switchType( + props.type, + Paths.APPLICATIONS, + Paths.SERVICES, + Paths.WORKLOADS, + ); + const name = ns.name; + let nbItems = 0; + if (ns.status) { + nbItems = + ns.status.inError.length + + ns.status.inWarning.length + + ns.status.inSuccess.length + + ns.status.notAvailable.length + + ns.status.inNotReady.length; + } + let text: string; + if (nbItems === 1) { + text = switchType(props.type, '1 application', '1 service', '1 workload'); + } else { + text = `${nbItems}${switchType( + props.type, + ' applications', + ' services', + ' workloads', + )}`; + } + const mainLink = ( +
    + {text} +
    + ); + if (nbItems === ns.status?.notAvailable.length) { + return ( +
    + + {mainLink} +
    N/A
    +
    +
    + ); + } + return ( +
    + + {mainLink} +
    + {ns.status && ns.status.inNotReady.length > 0 && ( + + )} + {ns.status && ns.status.inError.length > 0 && ( + + )} + {ns.status && ns.status.inWarning.length > 0 && ( + + )} + {ns.status && ns.status.inSuccess.length > 0 && ( + + )} +
    +
    +
    + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCard.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCard.tsx new file mode 100644 index 0000000000..bf632d0664 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCard.tsx @@ -0,0 +1,165 @@ +import React from 'react'; + +import { Card, CardContent, Grid } from '@material-ui/core'; + +import * as FilterHelper from '../../../components/FilterList/FitlerHelper'; +import { NamespaceMTLSStatus } from '../../../components/MTls/NamespaceMTLSStatus'; +import { TLSInfo } from '../../../components/Overview/TLSInfo'; +import { PFBadge, PFBadges } from '../../../components/Pf/PfBadges'; +import { ValidationSummary } from '../../../components/Validations/ValidationSummary'; +import { isMultiCluster, serverConfig } from '../../../config'; +import { CertsInfo } from '../../../types/CertsInfo'; +import { + DurationInSeconds, + IntervalInMilliseconds, +} from '../../../types/Common'; +import { + CanaryUpgradeStatus, + OutboundTrafficPolicy, + ValidationStatus, +} from '../../../types/IstioObjects'; +import { + ComponentStatus, + IstiodResourceThresholds, +} from '../../../types/IstioStatus'; +import { NamespaceInfo } from '../NamespaceInfo'; +import { DirectionType, OverviewType } from '../OverviewToolbar'; +import { CanaryUpgradeProgress } from './CanaryUpgradeProgress'; +import { ControlPlaneNamespaceStatus } from './ControlPlaneNamespaceStatus'; +import { NamespaceHeader } from './NamespaceHeader'; +import { NamespaceLabels } from './NamespaceLabels'; +import { NamespaceStatus } from './NamespaceStatus'; +import { OverviewCardSparklineCharts } from './OverviewCardSparklineCharts'; + +type OverviewCardProps = { + namespace: NamespaceInfo; + canaryUpgradeStatus?: CanaryUpgradeStatus; + duration: DurationInSeconds; + refreshInterval: IntervalInMilliseconds; + istioAPIEnabled?: boolean; + type: OverviewType; + direction: DirectionType; + certsInfo: CertsInfo[]; + minTLS: string; + outboundTrafficPolicy: OutboundTrafficPolicy; + istiodResourceThresholds?: IstiodResourceThresholds; + istioStatus: ComponentStatus[]; +}; + +export const OverviewCard = (props: OverviewCardProps) => { + const isIstioSystem = serverConfig.istioNamespace === props.namespace.name; + const hasCanaryUpgradeConfigured = (): boolean => { + return props.canaryUpgradeStatus + ? props.canaryUpgradeStatus.pendingNamespaces.length > 0 || + props.canaryUpgradeStatus.migratedNamespaces.length > 0 + : false; + }; + + const renderCharts = (): React.JSX.Element => { + const chart = ( + + ); + const canaryConfigured = hasCanaryUpgradeConfigured(); + return isIstioSystem ? ( + + {canaryConfigured && ( + + + + )} + + {chart} + + + ) : ( + chart + ); + }; + + const renderIstioConfigStatus = (ns: NamespaceInfo): React.JSX.Element => { + let validations: ValidationStatus = { + objectCount: 0, + errors: 0, + warnings: 0, + }; + if (!!ns.validations) { + validations = ns.validations; + } + + return ( + + ); + }; + + return ( + + + + {isMultiCluster && props.namespace.cluster && ( + <> + + {props.namespace.cluster} + + )} + + + +
    +
    + Istio config +
    + {props.namespace.tlsStatus && ( + + + + )} + {props.istioAPIEnabled + ? renderIstioConfigStatus(props.namespace) + : 'N/A'} +
    + + {isIstioSystem && ( + <> + + + + )} +
    + + {renderCharts()} + +
    +
    +
    + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardControlPlaneNamespace.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardControlPlaneNamespace.tsx new file mode 100644 index 0000000000..46a6cc6bc8 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardControlPlaneNamespace.tsx @@ -0,0 +1,263 @@ +import * as React from 'react'; + +import { Card, CardContent, Grid, Tooltip } from '@material-ui/core'; + +import { SparklineChart } from '../../../components/Charts/SparklineChart'; +import { PFColors } from '../../../components/Pf/PfColors'; +import { KialiIcon } from '../../../config/KialiIcon'; +import { kialiStyle } from '../../../styles/StyleUtils'; +import { DurationInSeconds } from '../../../types/Common'; +import { IstiodResourceThresholds } from '../../../types/IstioStatus'; +import { Datapoint, Metric } from '../../../types/Metrics'; +import { RichDataPoint, VCLine } from '../../../types/VictoryChartInfo'; +import { toLocaleStringWithConditionalDate } from '../../../utils/Date'; +import { getName } from '../../../utils/RateIntervals'; +import { toVCLine } from '../../../utils/VictoryChartsUtils'; + +export const infoStyle = kialiStyle({ + margin: '0px 0px -1px 4px', +}); + +const controlPlaneAnnotation = 'topology.istio.io/controlPlaneClusters'; + +type ControlPlaneProps = { + pilotLatency?: Metric[]; + istiodContainerMemory?: Metric[]; + istiodContainerCpu?: Metric[]; + istiodProcessMemory?: Metric[]; + istiodProcessCpu?: Metric[]; + duration: DurationInSeconds; + istiodResourceThresholds?: IstiodResourceThresholds; +}; + +export function isRemoteCluster(annotations?: { + [key: string]: string; +}): boolean { + if (annotations && annotations[controlPlaneAnnotation]) { + return true; + } + return false; +} + +function showMetrics(metrics: Metric[] | undefined): boolean { + // show metrics if metrics exists and some values at least are not zero + if ( + metrics && + metrics.length > 0 && + metrics[0].datapoints.length > 0 && + metrics[0].datapoints.some(dp => Number(dp[1]) !== 0) + ) { + return true; + } + + return false; +} + +export class OverviewCardControlPlaneNamespace extends React.Component< + ControlPlaneProps, + {} +> { + render() { + const memorySeries: VCLine[] = []; + const cpuSeries: VCLine[] = []; + const memoryThresholds: VCLine[] = []; + const cpuThresholds: VCLine[] = []; + + // The CPU metric can be respresented by a container or a process metric. We need to check which one to use + let cpuMetricSource = 'container'; + let cpu = this.props.istiodContainerCpu; + if (!showMetrics(this.props.istiodContainerCpu)) { + cpu = this.props.istiodProcessCpu; + cpuMetricSource = 'process'; + } + + // The memory metric can be respresented by a container or a process metric. We need to check which one to use + let memoryMetricSource = 'process'; + let memory = this.props.istiodContainerMemory; + if (!showMetrics(this.props.istiodContainerMemory)) { + memory = this.props.istiodProcessMemory; + memoryMetricSource = 'container'; + } + + if (showMetrics(memory)) { + if (memory && memory?.length > 0) { + const data = toVCLine(memory[0].datapoints, 'Mb', PFColors.Green400); + + if (this.props.istiodResourceThresholds?.memory) { + const datapoint0: Datapoint = [ + memory[0].datapoints[0][0], + memory[0].datapoints[0][1], + ]; + datapoint0[1] = this.props.istiodResourceThresholds?.memory; + const datapointn: Datapoint = [ + memory[0].datapoints[memory[0].datapoints.length - 1][0], + memory[0].datapoints[memory[0].datapoints.length - 1][0], + ]; + datapointn[1] = this.props.istiodResourceThresholds?.memory; + const dataThre = toVCLine( + [datapoint0, datapointn], + 'Mb (Threshold)', + PFColors.Green300, + ); + memoryThresholds.push(dataThre); + } + + memorySeries.push(data); + } + } + + if (showMetrics(cpu)) { + if (cpu && cpu?.length > 0) { + const data = toVCLine(cpu[0].datapoints, 'cores', PFColors.Green400); + + if (this.props.istiodResourceThresholds?.cpu) { + const datapoint0: Datapoint = [ + cpu[0].datapoints[0][0], + cpu[0].datapoints[0][1], + ]; + datapoint0[1] = this.props.istiodResourceThresholds?.cpu; + const datapointn: Datapoint = [ + cpu[0].datapoints[cpu[0].datapoints.length - 1][0], + cpu[0].datapoints[cpu[0].datapoints.length - 1][0], + ]; + datapointn[1] = this.props.istiodResourceThresholds?.cpu; + const dataThre = toVCLine( + [datapoint0, datapointn], + 'cores', + PFColors.Green300, + ); + cpuThresholds.push(dataThre); + } + + cpuSeries.push(data); + } + } + return ( +
    +
    +
    + Control plane metrics +
    +
    +
    + + + {showMetrics(memory) && ( + + + + + Memory + + + {getName(this.props.duration).toLowerCase()} + + This values represents the memory of the istiod{' '} + {memoryMetricSource} +
    + } + > + + + + + + + + `${toLocaleStringWithConditionalDate( + dp.x as Date, + )}\n${dp.y.toFixed(2)} ${dp.name}` + } + series={memorySeries} + labelName="mb" + thresholds={memoryThresholds} + /> + + + )} + {showMetrics(cpu) && ( + + + + + CPU + + + {getName(this.props.duration).toLowerCase()} + + This values represents cpu of the istiod{' '} + {cpuMetricSource} +
    + } + > + + +
    +
    + + + + `${toLocaleStringWithConditionalDate( + dp.x as Date, + )}\n${dp.y.toFixed(2)} ${dp.name}` + } + series={cpuSeries} + labelName="cores" + thresholds={cpuThresholds} + /> + + + )} + + + + + ); + } +} diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardDataPlaneNamespace.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardDataPlaneNamespace.tsx new file mode 100644 index 0000000000..680384663a --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardDataPlaneNamespace.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; + +import { SparklineChart } from '../../../components/Charts/SparklineChart'; +import { PFColors } from '../../../components/Pf/PfColors'; +import { DurationInSeconds } from '../../../types/Common'; +import { Metric } from '../../../types/Metrics'; +import { RichDataPoint, VCLine } from '../../../types/VictoryChartInfo'; +import { toLocaleStringWithConditionalDate } from '../../../utils/Date'; +import { getName } from '../../../utils/RateIntervals'; +import { toVCLine } from '../../../utils/VictoryChartsUtils'; +import { DirectionType } from '../OverviewToolbar'; + +type Props = { + metrics?: Metric[]; + errorMetrics?: Metric[]; + duration: DurationInSeconds; + direction: DirectionType; +}; + +function showMetrics(metrics: Metric[] | undefined): boolean { + // show metrics if metrics exists and some values at least are not zero + if ( + metrics && + metrics.length > 0 && + metrics[0].datapoints.some(dp => Number(dp[1]) !== 0) + ) { + return true; + } + + return false; +} + +export class OverviewCardDataPlaneNamespace extends React.Component { + render() { + const series: VCLine[] = []; + + if (showMetrics(this.props.metrics)) { + if (this.props.metrics && this.props.metrics.length > 0) { + const data = toVCLine( + this.props.metrics[0].datapoints, + 'ops (Total)', + PFColors.Info, + ); + series.push(data); + } + + if (this.props.errorMetrics && this.props.errorMetrics.length > 0) { + const dataErrors = toVCLine( + this.props.errorMetrics[0].datapoints, + 'ops (4xx+5xx)', + PFColors.Danger, + ); + series.push(dataErrors); + } + } + + return ( +
    +
    + <> +
    + {series.length > 0 && ( + <> +
    + {`${this.props.direction} traffic, ${getName( + this.props.duration, + ).toLowerCase()}`} +
    + + `${toLocaleStringWithConditionalDate( + dp.x as Date, + )}\n${dp.y.toFixed(2)} ${dp.name}` + } + series={series} + labelName="ops" + /> + + )} + {series.length === 0 && ( +
    + No {this.props.direction.toLowerCase()} traffic +
    + )} +
    + ); + } +} diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardSparklineCharts.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardSparklineCharts.tsx new file mode 100644 index 0000000000..df9d9c1fd3 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/OverviewCardSparklineCharts.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; + +import { serverConfig } from '../../../config'; +import { DurationInSeconds } from '../../../types/Common'; +import { IstiodResourceThresholds } from '../../../types/IstioStatus'; +import { ControlPlaneMetricsMap, Metric } from '../../../types/Metrics'; +import { DirectionType } from '../OverviewToolbar'; +import { + isRemoteCluster, + OverviewCardControlPlaneNamespace, +} from './OverviewCardControlPlaneNamespace'; +import { OverviewCardDataPlaneNamespace } from './OverviewCardDataPlaneNamespace'; + +type Props = { + name: string; + annotations?: { [key: string]: string }; + duration: DurationInSeconds; + direction: DirectionType; + metrics?: Metric[]; + istioAPIEnabled: boolean; + errorMetrics?: Metric[]; + controlPlaneMetrics?: ControlPlaneMetricsMap; + istiodResourceThresholds?: IstiodResourceThresholds; +}; + +export const OverviewCardSparklineCharts = (props: Props) => { + return ( + <> + {props.name !== serverConfig.istioNamespace && ( + + )} + {props.name === serverConfig.istioNamespace && + props.istioAPIEnabled && + !isRemoteCluster(props.annotations) && ( + + )} + + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewCard/RemoteClusterBadge.tsx b/plugins/kiali/src/pages/Overview/OverviewCard/RemoteClusterBadge.tsx new file mode 100644 index 0000000000..896d1ad123 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewCard/RemoteClusterBadge.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import { Chip } from '@material-ui/core'; + +export const RemoteClusterBadge = (): React.JSX.Element => { + return ( + + ); +}; diff --git a/plugins/kiali/src/components/Overview/OverviewCard/index.ts b/plugins/kiali/src/pages/Overview/OverviewCard/index.ts similarity index 100% rename from plugins/kiali/src/components/Overview/OverviewCard/index.ts rename to plugins/kiali/src/pages/Overview/OverviewCard/index.ts diff --git a/plugins/kiali/src/pages/Overview/OverviewHelper.ts b/plugins/kiali/src/pages/Overview/OverviewHelper.ts new file mode 100644 index 0000000000..ece5c56d85 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewHelper.ts @@ -0,0 +1,15 @@ +import { OverviewType } from './OverviewToolbar'; + +export const switchType = ( + type: OverviewType, + caseApp: T, + caseService: U, + caseWorkload: V, +): T | U | V => { + if (type === 'app') { + return caseApp; + } else if (type === 'service') { + return caseService; + } + return caseWorkload; +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewPage.tsx b/plugins/kiali/src/pages/Overview/OverviewPage.tsx new file mode 100644 index 0000000000..babde8e7f4 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewPage.tsx @@ -0,0 +1,473 @@ +import React from 'react'; +import { useAsyncFn, useDebounce } from 'react-use'; + +import { Content, Page } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { CircularProgress, Grid } from '@material-ui/core'; +import _ from 'lodash'; + +import * as FilterHelper from '../../components/FilterList/FitlerHelper'; +import { isMultiCluster, serverConfig } from '../../config'; +import { getErrorString, kialiApiRef } from '../../services/Api'; +import { computePrometheusRateParams } from '../../services/Prometheus'; +import { KialiContext } from '../../store/Context'; +import { KialiAppState } from '../../store/Store'; +import { + DEGRADED, + FAILURE, + Health, + HEALTHY, + NamespaceAppHealth, + NamespaceServiceHealth, + NamespaceWorkloadHealth, + NOT_READY, +} from '../../types/Health'; +import { + CanaryUpgradeStatus, + OutboundTrafficPolicy, +} from '../../types/IstioObjects'; +import { IstiodResourceThresholds } from '../../types/IstioStatus'; +import { MessageType } from '../../types/MessageCenter'; +import { IstioMetricsOptions } from '../../types/MetricsOptions'; +import { SortField } from '../../types/SortFilters'; +import { nsWideMTLSStatus } from '../../types/TLSStatus'; +import { PromisesRegistry } from '../../utils/CancelablePromises'; +import { NamespaceInfo, NamespaceStatus } from './NamespaceInfo'; +import { OverviewCard } from './OverviewCard'; +import { switchType } from './OverviewHelper'; +import { + currentDirectionType, + currentOverviewType, + DirectionType, + OverviewToolbar, + OverviewType, +} from './OverviewToolbar'; +import * as Sorts from './Sorts'; + +export const OverviewPage = () => { + const kialiClient = useApi(kialiApiRef); + kialiClient.setEntity(useEntity().entity); + const kialiState = React.useContext(KialiContext) as KialiAppState; + const promises = new PromisesRegistry(); + const [namespaces, setNamespaces] = React.useState([]); + const [outboundTrafficPolicy, setOutboundTrafficPolicy] = + React.useState({}); + const [canaryUpgradeStatus, setCanaryUpgradeStatus] = React.useState< + CanaryUpgradeStatus | undefined + >(undefined); + const [istiodResourceThresholds, setIstiodResourceThresholds] = + React.useState({ memory: 0, cpu: 0 }); + const [duration, setDuration] = React.useState( + FilterHelper.currentDuration(), + ); + const [overviewType, setOverviewType] = React.useState( + currentOverviewType(), + ); + const [directionType, setDirectionType] = React.useState( + currentDirectionType(), + ); + + const sortedNamespaces = (nss: NamespaceInfo[]) => { + nss.sort((a, b) => { + if (a.name === serverConfig.istioNamespace) return -1; + if (b.name === serverConfig.istioNamespace) return 1; + return a.name.localeCompare(b.name); + }); + return nss; + }; + + const fetchHealthChunk = (chunk: NamespaceInfo[]): Promise => { + const apiFunc = switchType( + overviewType, + kialiClient.getNamespaceAppHealth, + kialiClient.getNamespaceServiceHealth, + kialiClient.getNamespaceWorkloadHealth, + ); + + return Promise.all( + chunk.map(nsInfo => { + const healthPromise: Promise< + NamespaceAppHealth | NamespaceWorkloadHealth | NamespaceServiceHealth + > = apiFunc(nsInfo.name, duration, nsInfo.cluster); + return healthPromise.then(rs => ({ health: rs, nsInfo: nsInfo })); + }), + ) + .then(results => { + results.forEach(result => { + const nsStatus: NamespaceStatus = { + inNotReady: [], + inError: [], + inWarning: [], + inSuccess: [], + notAvailable: [], + }; + Object.keys(result.health).forEach(item => { + const health: Health = result.health[item]; + const status = health.getGlobalStatus(); + if (status === FAILURE) { + nsStatus.inError.push(item); + } else if (status === DEGRADED) { + nsStatus.inWarning.push(item); + } else if (status === HEALTHY) { + nsStatus.inSuccess.push(item); + } else if (status === NOT_READY) { + nsStatus.inNotReady.push(item); + } else { + nsStatus.notAvailable.push(item); + } + }); + result.nsInfo.status = nsStatus; + }); + }) + .catch(err => + kialiState.alertUtils!.add( + `Could not fetch health: ${getErrorString(err)}`, + ), + ); + }; + + const fetchHealth = ( + nss: NamespaceInfo[], + isAscending: boolean, + sortField: SortField, + ): void => { + _.chunk(nss, 10).forEach(chunk => { + promises + .registerChained('healthchunks', undefined, () => + fetchHealthChunk(chunk), + ) + .then(() => { + let newNamespaces = nss.slice(); + if (sortField.id === 'health') { + newNamespaces = Sorts.sortFunc( + newNamespaces, + sortField, + isAscending, + ); + } + return nss; + }) + .catch(error => { + kialiState.alertUtils!.add( + `Could not fetch health: ${getErrorString(error)}`, + ); + if (error.isCanceled) { + return []; + } + return error; + }); + }); + }; + + const fetchTLSChunk = (chunk: NamespaceInfo[]): Promise => { + return Promise.all( + chunk.map(nsInfo => { + return kialiClient + .getNamespaceTls(nsInfo.name, nsInfo.cluster) + .then(rs => ({ status: rs, nsInfo: nsInfo })); + }), + ) + .then(results => { + results.forEach(result => { + result.nsInfo.tlsStatus = { + status: nsWideMTLSStatus( + result.status.status, + kialiState.meshTLSStatus.status, + ), + autoMTLSEnabled: result.status.autoMTLSEnabled, + minTLS: result.status.minTLS, + }; + }); + }) + .catch(err => + kialiState.alertUtils!.add( + `Could not fetch TLS status: ${getErrorString(err)}`, + ), + ); + }; + + const fetchTLS = ( + nss: NamespaceInfo[], + isAscending: boolean, + sortField: SortField, + ): void => { + _.chunk(nss, 10).forEach(chunk => { + promises + .registerChained('tlschunks', undefined, () => fetchTLSChunk(chunk)) + .then(() => { + let newNamespaces = nss.slice(); + if (sortField.id === 'mtls') { + newNamespaces = Sorts.sortFunc( + newNamespaces, + sortField, + isAscending, + ); + } + return newNamespaces; + }); + }); + }; + + const fetchOutboundTrafficPolicyMode = () => { + kialiClient + .getOutboundTrafficPolicyMode() + .then(response => { + setOutboundTrafficPolicy({ mode: response.mode }); + }) + .catch(error => { + kialiState.alertUtils!.addError( + 'Error fetching Mesh OutboundTrafficPolicy.Mode.', + error, + 'default', + MessageType.ERROR, + ); + }); + }; + + const fetchCanariesStatus = async () => + kialiClient + .getCanaryUpgradeStatus() + .then(response => { + setCanaryUpgradeStatus({ + currentVersion: response.currentVersion, + upgradeVersion: response.upgradeVersion, + migratedNamespaces: response.migratedNamespaces, + pendingNamespaces: response.pendingNamespaces, + }); + }) + .catch(error => { + kialiState.alertUtils!.addError( + 'Error fetching canary upgrade status.', + error, + 'default', + MessageType.ERROR, + ); + }); + + const fetchIstiodResourceThresholds = () => { + kialiClient + .getIstiodResourceThresholds() + .then(response => setIstiodResourceThresholds(response)) + .catch(error => { + kialiState.alertUtils!.addError( + 'Error fetching Istiod resource thresholds.', + error, + 'default', + MessageType.ERROR, + ); + }); + }; + + const fetchValidationResultForCluster = ( + nss: NamespaceInfo[], + cluster: string, + ) => { + return Promise.all([ + kialiClient.getConfigValidations(cluster), + kialiClient.getAllIstioConfigs([], [], false, '', '', cluster), + ]) + .then(results => { + nss.forEach(nsInfo => { + if ( + nsInfo.cluster && + nsInfo.cluster === cluster && + (results[0] as any)[nsInfo.cluster] + ) { + // @ts-expect-error + nsInfo.validations = results[0][nsInfo.cluster][nsInfo.name]; + } + if (nsInfo.cluster && nsInfo.cluster === cluster) { + nsInfo.istioConfig = results[1][nsInfo.name]; + } + }); + }) + .catch(err => + kialiState.alertUtils!.add( + `Could not fetch validations status: ${getErrorString(err)}`, + ), + ); + }; + + const fetchValidations = ( + nss: NamespaceInfo[], + isAscending: boolean, + sortField: SortField, + ) => { + const uniqueClusters = new Set(); + nss.forEach(namespace => { + if (namespace.cluster) { + uniqueClusters.add(namespace.cluster); + } + }); + + uniqueClusters.forEach(cluster => { + promises + .registerChained('validation', undefined, () => + fetchValidationResultForCluster(nss, cluster), + ) + .then(() => { + let newNamespaces = nss.slice(); + if (sortField.id === 'validations') { + newNamespaces = Sorts.sortFunc( + newNamespaces, + sortField, + isAscending, + ); + } + return newNamespaces; + }); + }); + }; + + const fetchMetricsChunk = (chunk: NamespaceInfo[]) => { + const rateParams = computePrometheusRateParams(duration, 10); + const options: IstioMetricsOptions = { + filters: ['request_count', 'request_error_count'], + duration: duration, + step: rateParams.step, + rateInterval: rateParams.rateInterval, + direction: directionType, + reporter: directionType === 'inbound' ? 'destination' : 'source', + }; + return Promise.all( + chunk.map(nsInfo => { + if (nsInfo.cluster && isMultiCluster) { + options.clusterName = nsInfo.cluster; + } + return kialiClient + .getNamespaceMetrics(nsInfo.name, options) + .then(rs => { + nsInfo.metrics = rs.request_count; + nsInfo.errorMetrics = rs.request_error_count; + if (nsInfo.name === serverConfig.istioNamespace) { + nsInfo.controlPlaneMetrics = { + istiod_proxy_time: rs.pilot_proxy_convergence_time, + istiod_container_cpu: rs.container_cpu_usage_seconds_total, + istiod_container_mem: rs.container_memory_working_set_bytes, + istiod_process_cpu: rs.process_cpu_seconds_total, + istiod_process_mem: rs.process_resident_memory_bytes, + }; + } + return nsInfo; + }); + }), + ).catch(err => + kialiState.alertUtils!.add( + `Could not fetch metrics: ${getErrorString(err)}`, + ), + ); + }; + + const fetchMetrics = (nss: NamespaceInfo[]) => { + // debounce async for back-pressure, ten by ten + _.chunk(nss, 10).forEach(chunk => { + promises + .registerChained('metricschunks', undefined, () => + fetchMetricsChunk(chunk), + ) + .then(() => nss.slice()); + }); + }; + + const filterActiveNamespaces = () => { + const activeNs = kialiState.namespaces.activeNamespaces.map(ns => ns.name); + return namespaces.filter(ns => activeNs.includes(ns.name)); + }; + + const load = async () => { + kialiClient.getNamespaces().then(namespacesResponse => { + const allNamespaces: NamespaceInfo[] = namespacesResponse.map(ns => { + const previous = namespaces.find(prev => prev.name === ns.name); + return { + name: ns.name, + cluster: ns.cluster, + isAmbient: ns.isAmbient, + status: previous ? previous.status : undefined, + tlsStatus: previous ? previous.tlsStatus : undefined, + metrics: previous ? previous.metrics : undefined, + errorMetrics: previous ? previous.errorMetrics : undefined, + validations: previous ? previous.validations : undefined, + labels: ns.labels, + annotations: ns.annotations, + controlPlaneMetrics: previous + ? previous.controlPlaneMetrics + : undefined, + }; + }); + + // Calculate information + const isAscending = FilterHelper.isCurrentSortAscending(); + const sortField = FilterHelper.currentSortField(Sorts.sortFields); + const sortNs = sortedNamespaces(allNamespaces); + fetchHealth(sortNs, isAscending, sortField); + fetchTLS(sortNs, isAscending, sortField); + fetchValidations(sortNs, isAscending, sortField); + fetchMetrics(sortNs); + + fetchOutboundTrafficPolicyMode(); + fetchCanariesStatus(); + fetchIstiodResourceThresholds(); + + setNamespaces(sortNs); + }); + }; + + const [{ loading }, refresh] = useAsyncFn( + async () => { + // Check if the config is loaded + await load(); + }, + [], + { loading: true }, + ); + useDebounce(refresh, 10); + + React.useEffect(() => { + load(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [duration, overviewType, directionType]); + + if (loading) { + return ; + } + + return ( + + + load()} + overviewType={overviewType} + setOverviewType={setOverviewType} + directionType={directionType} + setDirectionType={setDirectionType} + duration={duration} + setDuration={setDuration} + /> + + {filterActiveNamespaces().map((ns, i) => ( + + + + ))} + + + + ); +}; diff --git a/plugins/kiali/src/pages/Overview/OverviewStatus.tsx b/plugins/kiali/src/pages/Overview/OverviewStatus.tsx new file mode 100644 index 0000000000..30218ad873 --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewStatus.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; + +import { Tooltip } from '@material-ui/core'; + +import { healthFilter } from '../../components/Filters/CommonFilters'; +import { FilterSelected } from '../../components/Filters/StatefulFilters'; +import { healthIndicatorStyle } from '../../components/Health/HealthStyle'; +import { createIcon } from '../../components/Health/Helper'; +import { Paths } from '../../config'; +import { DurationInSeconds, IntervalInMilliseconds } from '../../types/Common'; +import { ActiveFilter, DEFAULT_LABEL_OPERATION } from '../../types/Filters'; +import { Status } from '../../types/Health'; + +type Props = { + id: string; + namespace: string; + status: Status; + items: string[]; + targetPage: Paths; + duration: DurationInSeconds; + refreshInterval: IntervalInMilliseconds; +}; + +export class OverviewStatus extends React.Component { + setFilters = () => { + const filters: ActiveFilter[] = [ + { + category: healthFilter.category, + value: this.props.status.name, + }, + ]; + FilterSelected.setSelected({ + filters: filters, + op: DEFAULT_LABEL_OPERATION, + }); + }; + + render() { + const length = this.props.items.length; + let items = this.props.items; + if (items.length > 6) { + items = items.slice(0, 5); + items.push(`and ${length - items.length} more...`); + } + const tooltipContent = ( +
    + {this.props.status.name} + {items.map((app, idx) => { + return ( +
    + + {createIcon(this.props.status, 'sm')} + {' '} + {app} +
    + ); + })} +
    + ); + + return ( + <> + +
    + {createIcon(this.props.status)} + {` ${length}`} +
    +
    + + ); + } +} diff --git a/plugins/kiali/src/pages/Overview/OverviewToolbar.tsx b/plugins/kiali/src/pages/Overview/OverviewToolbar.tsx new file mode 100644 index 0000000000..ec198fa06b --- /dev/null +++ b/plugins/kiali/src/pages/Overview/OverviewToolbar.tsx @@ -0,0 +1,143 @@ +import React from 'react'; + +import { Select, SelectItem } from '@backstage/core-components'; + +import { Grid, IconButton, Tooltip } from '@material-ui/core'; +import Refresh from '@material-ui/icons/Refresh'; + +import { HistoryManager, URLParam } from '../../app/History'; +import { serverConfig } from '../../config'; + +export enum OverviewDisplayMode { + COMPACT, + EXPAND, + LIST, +} + +const overviewTypes = { + app: 'Apps', + workload: 'Workloads', + service: 'Services', +}; + +const directionTypes = { + inbound: 'Inbound', + outbound: 'Outbound', +}; + +export type OverviewType = keyof typeof overviewTypes; +export type DirectionType = keyof typeof directionTypes; + +type OverviewToolbarProps = { + onRefresh: () => void; + overviewType: OverviewType; + setOverviewType: React.Dispatch>; + directionType: DirectionType; + setDirectionType: React.Dispatch>; + duration: number; + setDuration: React.Dispatch>; +}; + +const healthTypeItems = Object.keys(overviewTypes).map(k => ({ + label: (overviewTypes as any)[k], + value: k, +})); + +const directionTypeItems = Object.keys(directionTypes).map(k => ({ + label: (directionTypes as any)[k], + value: k, +})); + +export const currentOverviewType = (): OverviewType => { + const otype = HistoryManager.getParam(URLParam.OVERVIEW_TYPE); + return (otype as OverviewType) || 'app'; +}; + +export const currentDirectionType = (): DirectionType => { + const drtype = HistoryManager.getParam(URLParam.DIRECTION_TYPE); + return (drtype as DirectionType) || 'inbound'; +}; + +const getDurationType = (): SelectItem[] => { + const items: SelectItem[] = []; + Object.entries(serverConfig.durations).forEach(([key, value]) => + items.push({ + label: `Last ${value}`, + value: key, + }), + ); + return items; +}; + +export const OverviewToolbar = (props: OverviewToolbarProps) => { + const updateOverviewType = (otype: String) => { + const isOverviewType = (val: String): val is OverviewType => + val === 'app' || val === 'workload' || val === 'service'; + + if (isOverviewType(otype)) { + HistoryManager.setParam(URLParam.OVERVIEW_TYPE, otype); + props.setOverviewType(otype); + } else { + throw new Error('Overview type is not valid.'); + } + }; + + const updateDirectionType = (dtype: String) => { + const isDirectionType = (val: String): val is DirectionType => + val === 'inbound' || val === 'outbound'; + + if (isDirectionType(dtype)) { + HistoryManager.setParam(URLParam.DIRECTION_TYPE, dtype); + props.setDirectionType(dtype); + } else { + throw new Error('Direction type is not valid.'); + } + }; + + const updateDurationType = (duration: number) => { + HistoryManager.setParam(URLParam.DURATION, duration.toString()); + props.setDuration(duration); + }; + + return ( + + + updateDirectionType(e as String)} + label="Traffic" + items={directionTypeItems} + selected={props.directionType} + /> + + +