diff --git a/workspaces/kiali/.changeset/purple-cars-add.md b/workspaces/kiali/.changeset/purple-cars-add.md
new file mode 100644
index 000000000..e73be657c
--- /dev/null
+++ b/workspaces/kiali/.changeset/purple-cars-add.md
@@ -0,0 +1,5 @@
+---
+'@backstage-community/plugin-kiali': minor
+---
+
+Create kiali-react component library
diff --git a/workspaces/kiali/examples/kialiEntities.yaml b/workspaces/kiali/examples/kialiEntities.yaml
index 987c80484..bc21b11e3 100644
--- a/workspaces/kiali/examples/kialiEntities.yaml
+++ b/workspaces/kiali/examples/kialiEntities.yaml
@@ -46,7 +46,7 @@ metadata:
- core
- servicemesh
annotations:
- kiali.io/provider: kubernetes
+ kiali.io/provider: Kubernetes
kiali.io/namespace: bookinfo
spec:
type: service
diff --git a/workspaces/kiali/plugins/kiali-backend/package.json b/workspaces/kiali/plugins/kiali-backend/package.json
index c0b0633c8..3e2afcf13 100644
--- a/workspaces/kiali/plugins/kiali-backend/package.json
+++ b/workspaces/kiali/plugins/kiali-backend/package.json
@@ -14,7 +14,8 @@
"pluginPackages": [
"@backstage-community/plugin-kiali",
"@backstage-community/plugin-kiali-backend",
- "@backstage-community/plugin-kiali-common"
+ "@backstage-community/plugin-kiali-common",
+ "@backstage-community/plugin-kiali-react"
]
},
"exports": {
diff --git a/workspaces/kiali/plugins/kiali-common/package.json b/workspaces/kiali/plugins/kiali-common/package.json
index b7666eb7c..c0df56e83 100644
--- a/workspaces/kiali/plugins/kiali-common/package.json
+++ b/workspaces/kiali/plugins/kiali-common/package.json
@@ -46,7 +46,8 @@
"pluginPackages": [
"@backstage-community/plugin-kiali",
"@backstage-community/plugin-kiali-backend",
- "@backstage-community/plugin-kiali-common"
+ "@backstage-community/plugin-kiali-common",
+ "@backstage-community/plugin-kiali-react"
]
},
"sideEffects": false,
diff --git a/workspaces/kiali/plugins/kiali-react/.eslintrc.js b/workspaces/kiali/plugins/kiali-react/.eslintrc.js
new file mode 100644
index 000000000..e2a53a6ad
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/workspaces/kiali/plugins/kiali-react/README.md b/workspaces/kiali/plugins/kiali-react/README.md
new file mode 100644
index 000000000..5715706de
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/README.md
@@ -0,0 +1,37 @@
+# @backstage/plugin-kiali-react
+
+> Shared web components for the Kiali plugin in Backstage
+
+This package provides a reusable set of UI components, utilities, and hooks designed to support the development of the [Kiali plugin for Backstage](https://github.com/backstage/community-plugins/blob/main/workspaces/kiali). It aims to keep the plugin codebase clean and modular, while enabling faster iteration and better maintainability.
+
+## ✨ Features
+
+- Reusable UI components tailored to Kiali data and workflows
+- Shared logic for interfacing with Istio/Kiali APIs
+- Designed to integrate seamlessly within Backstage plugins
+- Focused on developer experience and visual consistency
+
+## 🧱 Usage
+
+Import and use the shared components within your Kiali Backstage plugin:
+
+```tsx
+import { TrafficGraph } from '@backstage/plugin-kiali-react';
+
+;
+```
+
+📁 Project Structure
+
+``bash
+src/
+├── components/ # Shared React components
+├── hooks/ # Reusable custom hooks
+└── utils/ # Utility functions
+
+```
+
+
+🤝 Contributing
+Contributions, issues, and feature requests are welcome! Please open a PR or issue in the main plugin repo if it relates to this shared library.
+```
diff --git a/workspaces/kiali/plugins/kiali-react/dev/__data__/bookinfoGraphModel.json b/workspaces/kiali/plugins/kiali-react/dev/__data__/bookinfoGraphModel.json
new file mode 100644
index 000000000..a6e4da374
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/dev/__data__/bookinfoGraphModel.json
@@ -0,0 +1,2485 @@
+{
+ "nodes": [
+ {
+ "children": [
+ "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "e73d17c7ce69b6449aa320b78d088e1679f604d5ef299d5c7e4816193c90223d"
+ ],
+ "collapsed": false,
+ "data": {
+ "app": "details",
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [
+ {
+ "name": "details-v1",
+ "desiredReplicas": 1,
+ "currentReplicas": 1,
+ "availableReplicas": 1,
+ "syncedProxies": 1
+ }
+ ],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "details-v1: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ }
+ ]
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": null,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "isBox": "app",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "ee30ec3824c952c6a97c245cb381968595af81dfb3951299315770d4dc205841",
+ "nodeType": "box",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy",
+ "badge": "A"
+ },
+ "group": true,
+ "id": "ee30ec3824c952c6a97c245cb381968595af81dfb3951299315770d4dc205841",
+ "status": "success",
+ "style": {
+ "padding": [35, 35, 35, 35]
+ },
+ "type": "group",
+ "label": "details"
+ },
+ {
+ "children": [
+ "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343"
+ ],
+ "collapsed": false,
+ "data": {
+ "app": "productpage",
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [
+ {
+ "name": "productpage-v1",
+ "desiredReplicas": 1,
+ "currentReplicas": 1,
+ "availableReplicas": 1,
+ "syncedProxies": 1
+ }
+ ],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 2
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "productpage-v1: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ }
+ ]
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Outbound:0.00%",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 2
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": null,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "isBox": "app",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "15e753d164d02320b78b4db88c02828eb2802fc425916fe849f97a464c25df80",
+ "nodeType": "box",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy",
+ "badge": "A"
+ },
+ "group": true,
+ "id": "15e753d164d02320b78b4db88c02828eb2802fc425916fe849f97a464c25df80",
+ "status": "success",
+ "style": {
+ "padding": [35, 35, 35, 35]
+ },
+ "type": "group",
+ "label": "productpage"
+ },
+ {
+ "children": [
+ "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "055acd1ee48a90ba90aa90df8a0d789664810dc5036eaebc0bf19a19e0834abe"
+ ],
+ "collapsed": false,
+ "data": {
+ "app": "ratings",
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [
+ {
+ "name": "ratings-v1",
+ "desiredReplicas": 1,
+ "currentReplicas": 1,
+ "availableReplicas": 1,
+ "syncedProxies": 1
+ }
+ ],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "ratings-v1: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ }
+ ]
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "text": "Inbound:0.61%",
+ "value": 0.6106870229007634
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0.6106870229007634
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": null,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "isBox": "app",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "b7a3aed047b45436340c6f1207e95cb39c88368d012458b6ce9a04a52f72f20f",
+ "nodeType": "box",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Degraded",
+ "badge": "A"
+ },
+ "group": true,
+ "id": "b7a3aed047b45436340c6f1207e95cb39c88368d012458b6ce9a04a52f72f20f",
+ "status": "warning",
+ "style": {
+ "padding": [35, 35, 35, 35]
+ },
+ "type": "group",
+ "label": "ratings"
+ },
+ {
+ "children": [
+ "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "9a60fd88bb986e9cc15ba01934a9e967727c84861bec05158b59a85d04300c57",
+ "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6"
+ ],
+ "collapsed": false,
+ "data": {
+ "app": "reviews",
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "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.349
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "reviews-v1: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ },
+ {
+ "text": "reviews-v2: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ },
+ {
+ "text": "reviews-v3: 1 / 1",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ }
+ }
+ ]
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.349
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": null,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "isBox": "app",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "nodeType": "box",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy",
+ "badge": "A"
+ },
+ "group": true,
+ "id": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "status": "success",
+ "style": {
+ "padding": [35, 35, 35, 35]
+ },
+ "type": "group",
+ "label": "reviews"
+ },
+ {
+ "data": {
+ "app": "details",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "details"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "Inbound: 0.00%",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 1,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 1,
+ "service": "details",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "parent": "ee30ec3824c952c6a97c245cb381968595af81dfb3951299315770d4dc205841",
+ "nodeType": "service",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "shape": "rhombus",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "details"
+ },
+ {
+ "data": {
+ "app": "details",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "details"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 1,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v1",
+ "workload": "details-v1",
+ "id": "e73d17c7ce69b6449aa320b78d088e1679f604d5ef299d5c7e4816193c90223d",
+ "parent": "ee30ec3824c952c6a97c245cb381968595af81dfb3951299315770d4dc205841",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "e73d17c7ce69b6449aa320b78d088e1679f604d5ef299d5c7e4816193c90223d",
+ "shape": "rect",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "v1"
+ },
+ {
+ "data": {
+ "app": "kiali-traffic-generator",
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {},
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Inbound:No requests",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Outbound:0.00%",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {},
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": null,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 1,
+ "isRoot": true,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "latest",
+ "workload": "kiali-traffic-generator",
+ "id": "0d2eed710a9c76de480b9d24d064da9f69e416a52b72770044e4452a53ced5be",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "0d2eed710a9c76de480b9d24d064da9f69e416a52b72770044e4452a53ced5be",
+ "shape": "rect",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "kiali-traffic-generator"
+ },
+ {
+ "data": {
+ "app": "productpage",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "productpage"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "hasRequestRouting": true,
+ "hasVS": {
+ "hostnames": ["*"]
+ },
+ "healthData": {
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "Inbound: 0.00%",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 1,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 1,
+ "service": "productpage",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "parent": "15e753d164d02320b78b4db88c02828eb2802fc425916fe849f97a464c25df80",
+ "nodeType": "service",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "shape": "rhombus",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "productpage"
+ },
+ {
+ "data": {
+ "app": "productpage",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "productpage"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 2
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Outbound:0.00%",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 2
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 1,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 2,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v1",
+ "workload": "productpage-v1",
+ "id": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "parent": "15e753d164d02320b78b4db88c02828eb2802fc425916fe849f97a464c25df80",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "shape": "rect",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "v1"
+ },
+ {
+ "data": {
+ "app": "ratings",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "ratings"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "children": [
+ {
+ "text": "Inbound: 0.61%",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "value": 0.6106870229007634
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0.6106870229007634
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 0.66,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": 0.004,
+ "httpInNoResponse": null,
+ "httpOut": 0.66,
+ "service": "ratings",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "parent": "b7a3aed047b45436340c6f1207e95cb39c88368d012458b6ce9a04a52f72f20f",
+ "nodeType": "service",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Degraded"
+ },
+ "height": 50,
+ "id": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "shape": "rhombus",
+ "status": "warning",
+ "type": "node",
+ "width": 50,
+ "label": "ratings"
+ },
+ {
+ "data": {
+ "app": "ratings",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "ratings"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "text": "Inbound:0.61%",
+ "value": 0.6106870229007634
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0.6106870229007634
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.651,
+ "503": 0.004
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 0.66,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": 0.004,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v1",
+ "workload": "ratings-v1",
+ "id": "055acd1ee48a90ba90aa90df8a0d789664810dc5036eaebc0bf19a19e0834abe",
+ "parent": "b7a3aed047b45436340c6f1207e95cb39c88368d012458b6ce9a04a52f72f20f",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Degraded"
+ },
+ "height": 50,
+ "id": "055acd1ee48a90ba90aa90df8a0d789664810dc5036eaebc0bf19a19e0834abe",
+ "shape": "rect",
+ "status": "warning",
+ "type": "node",
+ "width": 50,
+ "label": "v1"
+ },
+ {
+ "data": {
+ "app": "reviews",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "reviews"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "text": "Inbound: 0.00%",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 1
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 1,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 1,
+ "service": "reviews",
+ "tcpIn": null,
+ "tcpOut": null,
+ "id": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "parent": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "nodeType": "service",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "shape": "rhombus",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "reviews"
+ },
+ {
+ "data": {
+ "app": "reviews",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "reviews"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.349
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "text": "Outbound:No requests",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.349
+ }
+ },
+ "outbound": {},
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 0.35,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": null,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v1",
+ "workload": "reviews-v1",
+ "id": "9a60fd88bb986e9cc15ba01934a9e967727c84861bec05158b59a85d04300c57",
+ "parent": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "9a60fd88bb986e9cc15ba01934a9e967727c84861bec05158b59a85d04300c57",
+ "shape": "rect",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "v1"
+ },
+ {
+ "data": {
+ "app": "reviews",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "reviews"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.328
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.326,
+ "503": 0.004
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "text": "Outbound:1.21%",
+ "value": 1.2121212121212122
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Degraded",
+ "color": "var(--pf-v5-global--warning-color--100)",
+ "priority": 3,
+ "class": "icon-degraded"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0.60790273556231
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.328
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.326,
+ "503": 0.004
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 0.33,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 0.33,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v2",
+ "workload": "reviews-v2",
+ "id": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "parent": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Degraded"
+ },
+ "height": 50,
+ "id": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "shape": "rect",
+ "status": "warning",
+ "type": "node",
+ "width": 50,
+ "label": "v2"
+ },
+ {
+ "data": {
+ "app": "reviews",
+ "destServices": [
+ {
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "name": "reviews"
+ }
+ ],
+ "grpcIn": null,
+ "grpcInErr": null,
+ "grpcOut": null,
+ "healthData": {
+ "workloadStatuses": [],
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.323
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.325
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "health": {
+ "health": {
+ "items": [
+ {
+ "title": "Pod Status",
+ "status": {
+ "name": "No health information",
+ "color": "var(--pf-v5-global--Color--200)",
+ "priority": 0,
+ "class": "icon-na"
+ },
+ "children": []
+ },
+ {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "children": [
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Inbound:0.00%",
+ "value": 0
+ },
+ {
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "text": "Outbound:0.00%",
+ "value": 0
+ }
+ ]
+ }
+ ],
+ "statusConfig": {
+ "title": "Traffic Status (Last 10m)",
+ "status": {
+ "name": "Healthy",
+ "color": "var(--pf-v5-global--success-color--100)",
+ "priority": 1,
+ "class": "icon-healthy"
+ },
+ "threshold": {
+ "code": {},
+ "degraded": 0,
+ "failure": 10,
+ "protocol": {},
+ "direction": {}
+ },
+ "value": 0
+ }
+ },
+ "requests": {
+ "inbound": {
+ "http": {
+ "200": 0.323
+ }
+ },
+ "outbound": {
+ "http": {
+ "200": 0.325
+ }
+ },
+ "healthAnnotations": {}
+ }
+ },
+ "httpIn": 0.32,
+ "httpIn3xx": null,
+ "httpIn4xx": null,
+ "httpIn5xx": null,
+ "httpInNoResponse": null,
+ "httpOut": 0.33,
+ "tcpIn": null,
+ "tcpOut": null,
+ "version": "v3",
+ "workload": "reviews-v3",
+ "id": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "parent": "a7f0ea966626a632f524c63ca4d794ed666f3415f3544b989474d27aad08c694",
+ "nodeType": "app",
+ "cluster": "Kubernetes",
+ "namespace": "bookinfo",
+ "healthStatus": "Healthy"
+ },
+ "height": 50,
+ "id": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "shape": "rect",
+ "status": "success",
+ "type": "node",
+ "width": 50,
+ "label": "v3"
+ }
+ ],
+ "edges": [
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 1,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "productpage.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "e8d8b5b675d24521f91705859c590bb5e2df2217c46d91077cedae96da787da7",
+ "source": "0d2eed710a9c76de480b9d24d064da9f69e416a52b72770044e4452a53ced5be",
+ "target": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "1rps",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "e8d8b5b675d24521f91705859c590bb5e2df2217c46d91077cedae96da787da7",
+ "source": "0d2eed710a9c76de480b9d24d064da9f69e416a52b72770044e4452a53ced5be",
+ "target": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 1,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "productpage.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "c7d55f74489a0a50489cf9de929602d1f6e3d0ac15fdb3bb6259949600e0b633",
+ "source": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "target": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "1rps",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "c7d55f74489a0a50489cf9de929602d1f6e3d0ac15fdb3bb6259949600e0b633",
+ "source": "67b0759a08c2dad027cefe1e1d7dfa26c04ed16e5d99ce76e0fb76dfb6ea29e7",
+ "target": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.66,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0.004,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0.6,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "99.4"
+ },
+ "hosts": {
+ "ratings.bookinfo.svc.cluster.local": "99.4"
+ }
+ },
+ "503": {
+ "flags": {
+ "-": "0.3",
+ "UC": "0.3"
+ },
+ "hosts": {
+ "ratings.bookinfo.svc.cluster.local": "0.6"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "a36fb73dd568fce8e83acf2f6f8e471c7210acc596ab4c9ffbb03fd97483d270",
+ "source": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "target": "055acd1ee48a90ba90aa90df8a0d789664810dc5036eaebc0bf19a19e0834abe",
+ "isMtls": null,
+ "healthStatus": "Degraded",
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--warning-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.66rps\n0.6%err",
+ "tagStatus": "warning"
+ },
+ "edgeStyle": "solid",
+ "id": "a36fb73dd568fce8e83acf2f6f8e471c7210acc596ab4c9ffbb03fd97483d270",
+ "source": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "target": "055acd1ee48a90ba90aa90df8a0d789664810dc5036eaebc0bf19a19e0834abe",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.35,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 34.9,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "reviews.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "ad5e102260107b6a502176ef47f7ca4fe8349c0c50f4eab375c092ba688712db",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "9a60fd88bb986e9cc15ba01934a9e967727c84861bec05158b59a85d04300c57",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.35rps\n34.9%",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "ad5e102260107b6a502176ef47f7ca4fe8349c0c50f4eab375c092ba688712db",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "9a60fd88bb986e9cc15ba01934a9e967727c84861bec05158b59a85d04300c57",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.32,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 32.3,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "reviews.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "79db8b374993e1e06463d7a842cba32adf003418fd139e798f21b1b81b75339c",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.32rps\n32.3%",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "79db8b374993e1e06463d7a842cba32adf003418fd139e798f21b1b81b75339c",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.33,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 32.8,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "reviews.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "c1a7fe654f99cfada03a25ea6cdedf194a1ce06b7f960e3b28a2af9902105059",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.33rps\n32.8%",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "c1a7fe654f99cfada03a25ea6cdedf194a1ce06b7f960e3b28a2af9902105059",
+ "source": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "target": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 1,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "details.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "1cbe0f4da9e5693eb7bba37afdb1dd18ad21ff06605fc8e1ef933ef1af5dd2a8",
+ "source": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "target": "e73d17c7ce69b6449aa320b78d088e1679f604d5ef299d5c7e4816193c90223d",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "1rps",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "1cbe0f4da9e5693eb7bba37afdb1dd18ad21ff06605fc8e1ef933ef1af5dd2a8",
+ "source": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "target": "e73d17c7ce69b6449aa320b78d088e1679f604d5ef299d5c7e4816193c90223d",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.33,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "ratings.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "218dd394d8d76154ff23aadeeb338ae2374d05c60079f172a3aea1f9f89d35e7",
+ "source": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "target": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.33rps",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "218dd394d8d76154ff23aadeeb338ae2374d05c60079f172a3aea1f9f89d35e7",
+ "source": "e31fea265aa898ba3a63d30e31913960a2a45fa3f589b8a2fcf2ff0747a46bc6",
+ "target": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 1,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 50,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "reviews.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "673255c47e882ff8cdee898df528b00cb875e0ff71337d537d0816a1856df238",
+ "source": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "target": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "1rps\n50%",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "673255c47e882ff8cdee898df528b00cb875e0ff71337d537d0816a1856df238",
+ "source": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "target": "bc40873761174888869f91912758eaa77cbd8ff835000d16dcde347442b17a10",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 1,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0,
+ "httpNoResponse": 0,
+ "httpPercentErr": 0,
+ "httpPercentReq": 50,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "100.0"
+ },
+ "hosts": {
+ "details.bookinfo.svc.cluster.local": "100.0"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "c7f35cc32f3dd74f75051c1265dbf4c660947a973aed332acee4db0c5da15af9",
+ "source": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "target": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "isMtls": null,
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--success-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "1rps\n50%",
+ "tagStatus": "success"
+ },
+ "edgeStyle": "solid",
+ "id": "c7f35cc32f3dd74f75051c1265dbf4c660947a973aed332acee4db0c5da15af9",
+ "source": "efcc2ed361215b3c82e67fd0d0cb09899b9ee1e18dee32e8e828b487a7cb9343",
+ "target": "bf3790a7d4ebc316387250389da94818b402ecc9e7cfe6b39f89c40d5e7370b8",
+ "type": "edge"
+ },
+ {
+ "animationSpeed": "none",
+ "data": {
+ "grpc": null,
+ "grpcErr": null,
+ "grpcPercentErr": null,
+ "grpcPercentReq": null,
+ "hasTraffic": true,
+ "http": 0.33,
+ "http3xx": 0,
+ "http4xx": 0,
+ "http5xx": 0.004,
+ "httpNoResponse": 0,
+ "httpPercentErr": 1.2,
+ "httpPercentReq": 100,
+ "isMTLS": -1,
+ "protocol": "http",
+ "responses": {
+ "200": {
+ "flags": {
+ "-": "98.8"
+ },
+ "hosts": {
+ "ratings.bookinfo.svc.cluster.local": "98.8"
+ }
+ },
+ "503": {
+ "flags": {
+ "-": "0.6",
+ "UC": "0.6"
+ },
+ "hosts": {
+ "ratings.bookinfo.svc.cluster.local": "1.2"
+ }
+ }
+ },
+ "responseTime": null,
+ "tcp": null,
+ "throughput": null,
+ "id": "3c2b3bd5a5424269e76186e16382eff51cd383ccc360154478782059a2aa7a80",
+ "source": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "target": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "isMtls": null,
+ "healthStatus": "Degraded",
+ "endTerminalType": "directional",
+ "pathStyle": {
+ "stroke": "var(--pf-v5-global--warning-color--100)",
+ "strokeWidth": 3
+ },
+ "tag": "0.33rps\n1%err",
+ "tagStatus": "warning"
+ },
+ "edgeStyle": "solid",
+ "id": "3c2b3bd5a5424269e76186e16382eff51cd383ccc360154478782059a2aa7a80",
+ "source": "f891bcc76364f5421f0b36378f076a925fa460f7408d4cd36e17e68cc2fcd162",
+ "target": "82d72a9fde4cd0cd9da011281f8564a9c795864018e37dcf801a65a419ff3413",
+ "type": "edge"
+ }
+ ],
+ "graph": {
+ "id": "g1",
+ "type": "graph",
+ "layout": "Dagre"
+ }
+}
diff --git a/workspaces/kiali/plugins/kiali-react/dev/index.tsx b/workspaces/kiali/plugins/kiali-react/dev/index.tsx
new file mode 100644
index 000000000..909e641c9
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/dev/index.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { createDevApp } from '@backstage/dev-utils';
+import { Model } from '@patternfly/react-topology';
+import { getAllThemes } from '@redhat-developer/red-hat-developer-hub-theme';
+import { TrafficGraph } from '../src';
+import graphModel from './__data__/bookinfoGraphModel.json';
+
+createDevApp()
+ .registerPlugin()
+ .addThemes(getAllThemes())
+ .addPage({
+ element: ,
+ title: 'TrafficGraph',
+ path: '/kiali/trafficGraph',
+ });
diff --git a/workspaces/kiali/plugins/kiali-react/package.json b/workspaces/kiali/plugins/kiali-react/package.json
new file mode 100644
index 000000000..3bbec2bea
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@backstage-community/plugin-kiali-react",
+ "version": "0.1.0",
+ "license": "Apache-2.0",
+ "private": true,
+ "description": "Web library for the kiali plugin",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/community-plugins",
+ "directory": "workspaces/kiali/plugins/kiali-react"
+ },
+ "backstage": {
+ "role": "web-library",
+ "pluginId": "kiali",
+ "pluginPackages": [
+ "@backstage-community/plugin-kiali",
+ "@backstage-community/plugin-kiali-backend",
+ "@backstage-community/plugin-kiali-common",
+ "@backstage-community/plugin-kiali-react"
+ ]
+ },
+ "sideEffects": false,
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage-community/plugin-kiali-common": "workspace:^",
+ "@backstage/core-plugin-api": "^1.10.6",
+ "@material-ui/core": "^4.9.13",
+ "@patternfly/react-core": "^5.1.1",
+ "@patternfly/react-icons": "^5.1.1",
+ "@patternfly/react-topology": "5.4.1",
+ "typestyle": "^2.4.0"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.32.0",
+ "@backstage/dev-utils": "^1.1.9",
+ "@backstage/test-utils": "^1.7.7",
+ "@redhat-developer/red-hat-developer-hub-theme": "0.4.0",
+ "@testing-library/jest-dom": "^6.0.0",
+ "@testing-library/react": "^14.0.0",
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/workspaces/kiali/plugins/kiali-react/report.api.md b/workspaces/kiali/plugins/kiali-react/report.api.md
new file mode 100644
index 000000000..ec87191e1
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/report.api.md
@@ -0,0 +1,15 @@
+## API Report File for "@backstage-community/plugin-kiali-react"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { JSX as JSX_2 } from 'react/jsx-runtime';
+import { Model } from '@patternfly/react-topology';
+
+// Warning: (ae-missing-release-tag) "TrafficGraph" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const TrafficGraph: (props: { model: Model }) => JSX_2.Element;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfBadges.tsx b/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfBadges.tsx
new file mode 100644
index 000000000..e4af8ec82
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfBadges.tsx
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Badge, Tooltip, TooltipPosition } from '@patternfly/react-core';
+import { CSSProperties, default as React } from 'react';
+import { style } from 'typestyle';
+import { PFColors } from './PfColors';
+
+export type PFBadgeType = {
+ badge: string;
+ tt?: React.ReactFragment;
+ style?: React.CSSProperties;
+};
+
+// PF Badges used by Kiali, keep alphabetized
+// avoid duplicate badge letters, especially if they may appear on the same page
+export const PFBadges: { [key: string]: PFBadgeType } = Object.freeze({
+ App: {
+ badge: 'A',
+ tt: 'Application',
+ style: { backgroundColor: PFColors.Green500 },
+ } as PFBadgeType,
+ Adapter: { badge: 'A', tt: 'Adapter' } as PFBadgeType,
+ AttributeManifest: { badge: 'AM', tt: 'Attribute Manifest' } as PFBadgeType,
+ AuthorizationPolicy: {
+ badge: 'AP',
+ tt: 'Authorization Policy',
+ } as PFBadgeType,
+ Cluster: {
+ badge: 'C',
+ tt: 'Cluster',
+ style: { backgroundColor: PFColors.Blue300 },
+ } as PFBadgeType,
+ ClusterRBACConfig: {
+ badge: 'CRC',
+ tt: 'Cluster RBAC Configuration',
+ } as PFBadgeType,
+ Container: {
+ badge: 'C',
+ tt: 'Container',
+ style: { backgroundColor: PFColors.Blue300 },
+ } as PFBadgeType,
+ DestinationRule: { badge: 'DR', tt: 'Destination Rule' } as PFBadgeType,
+ EnvoyFilter: { badge: 'EF', tt: 'Envoy Filter' } as PFBadgeType,
+ ExternalService: { badge: 'ES', tt: 'External Service' } as PFBadgeType,
+ FaultInjectionAbort: {
+ badge: 'FI',
+ tt: 'Fault Injection: Abort',
+ style: { backgroundColor: PFColors.Purple500 },
+ } as PFBadgeType,
+ FaultInjectionDelay: {
+ badge: 'FI',
+ tt: 'Fault Injection: Delay',
+ style: { backgroundColor: PFColors.Purple500 },
+ } as PFBadgeType,
+ FederatedService: { badge: 'FS', tt: 'Federated Service' } as PFBadgeType,
+ Gateway: { badge: 'G', tt: 'Gateway' } as PFBadgeType,
+ HTTPRoute: { badge: 'HTTP', tt: 'HTTPRoute' } as PFBadgeType,
+ K8sGateway: { badge: 'G', tt: 'Gateway (K8s)' } as PFBadgeType,
+ K8sHTTPRoute: { badge: 'HTTP', tt: 'HTTPRoute (K8s)' } as PFBadgeType,
+ Handler: { badge: 'H', tt: 'Handler' },
+ Host: { badge: 'H', tt: 'Host' },
+ Instance: { badge: 'I', tt: 'Instance' },
+ MeshPolicy: { badge: 'MP', tt: 'Mesh Policy' } as PFBadgeType,
+ MirroredWorkload: {
+ badge: 'MI',
+ tt: 'Mirrored Workload',
+ style: { backgroundColor: PFColors.Purple500 },
+ } as PFBadgeType,
+ Namespace: {
+ badge: 'NS',
+ tt: 'Namespace',
+ style: { backgroundColor: PFColors.Green600 },
+ } as PFBadgeType,
+ Operation: { badge: 'O', tt: 'Operation' } as PFBadgeType,
+ PeerAuthentication: { badge: 'PA', tt: 'Peer Authentication' } as PFBadgeType,
+ Pod: {
+ badge: 'P',
+ tt: 'Pod',
+ style: { backgroundColor: PFColors.Cyan300 },
+ } as PFBadgeType,
+ Policy: { badge: 'P', tt: 'Policy' } as PFBadgeType,
+ RBACConfig: { badge: 'RC', tt: 'RBAC Configuration' } as PFBadgeType,
+ RequestAuthentication: {
+ badge: 'RA',
+ tt: 'Request Authentication',
+ } as PFBadgeType,
+ RequestRetry: {
+ badge: 'RR',
+ tt: 'Request Retry',
+ style: { backgroundColor: PFColors.Purple500 },
+ } as PFBadgeType,
+ RequestTimeout: {
+ badge: 'RT',
+ tt: 'Request Timeout',
+ style: { backgroundColor: PFColors.Purple500 },
+ } as PFBadgeType,
+ Rule: { badge: 'R', tt: 'Rule' } as PFBadgeType,
+ Service: {
+ badge: 'S',
+ tt: 'Service',
+ style: { backgroundColor: PFColors.LightGreen500 },
+ } as PFBadgeType,
+ ServiceEntry: { badge: 'SE', tt: 'Service Entry' } as PFBadgeType,
+ ServiceRole: { badge: 'SR', tt: 'Service Role' } as PFBadgeType,
+ ServiceRoleBinding: {
+ badge: 'SRB',
+ tt: 'Service Role Binding',
+ } as PFBadgeType,
+ Sidecar: { badge: 'SC', tt: 'Istio Sidecar Proxy' } as PFBadgeType,
+ WasmPlugin: { badge: 'WP', tt: 'Istio Wasm Plugin' } as PFBadgeType,
+ Telemetry: { badge: 'TM', tt: 'Istio Telemetry' } as PFBadgeType,
+ Template: { badge: 'T', tt: 'Template' } as PFBadgeType,
+ Unknown: { badge: 'U', tt: 'Unknown' } as PFBadgeType,
+ VirtualService: { badge: 'VS', tt: 'Virtual Service' } as PFBadgeType,
+ Waypoint: { badge: 'W', tt: 'Waypoint proxy' } as PFBadgeType,
+ Workload: {
+ badge: 'W',
+ tt: 'Workload',
+ style: { backgroundColor: PFColors.Blue500 },
+ } as PFBadgeType,
+ WorkloadEntry: { badge: 'WE', tt: 'Workload Entry' } as PFBadgeType,
+ WorkloadGroup: { badge: 'WG', tt: 'Workload Group' } as PFBadgeType,
+});
+
+// This is styled for consistency with OpenShift Console. See console: public/components/_resource.scss
+export const kialiBadge = style({
+ backgroundColor: PFColors.Badge,
+ color: PFColors.White,
+ borderRadius: '20px',
+ flexShrink: 0,
+ fontFamily: 'var(--pf-v5-global--FontFamily--text)',
+ fontSize: 'var(--kiali-global--font-size)',
+ lineHeight: '16px',
+ marginRight: '4px',
+ minWidth: '1.5em',
+ padding: '1px 4px',
+ textAlign: 'center',
+ whiteSpace: 'nowrap',
+});
+
+export const kialiBadgeSmall = style({
+ backgroundColor: PFColors.Badge,
+ color: PFColors.White,
+ borderRadius: '20px',
+ flexShrink: 0,
+ fontFamily: 'var(--pf-v5-global--FontFamily--text)',
+ fontSize: '12px',
+ lineHeight: '13px',
+ marginRight: '5px',
+ minWidth: '1.3em',
+ padding: '1px 3px',
+ textAlign: 'center',
+ whiteSpace: 'nowrap',
+});
+
+type PFBadgeProps = {
+ badge: PFBadgeType;
+ isRead?: boolean;
+ keyValue?: string;
+ position?: string; // default=auto
+ size?: 'global' | 'sm';
+ style?: CSSProperties;
+ tooltip?: React.ReactFragment;
+};
+
+export class PFBadge extends React.PureComponent {
+ render() {
+ const key = this.props.keyValue || `pfbadge-${this.props.badge.badge}`;
+ const ttKey = `tt-${key}`;
+ const BadgeStyle = { ...this.props.badge.style, ...this.props.style };
+ const tooltip = this.props.tooltip || this.props.badge.tt;
+ const className = this.props.size === 'sm' ? kialiBadgeSmall : kialiBadge;
+
+ const badge = (
+
+ {this.props.badge.badge}
+
+ );
+
+ return !tooltip ? (
+ badge
+ ) : (
+ {tooltip}>}
+ id={ttKey}
+ key={ttKey}
+ position={
+ (this.props.position as TooltipPosition) || TooltipPosition.auto
+ }
+ >
+ {badge}
+
+ );
+ }
+}
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfColors.tsx b/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfColors.tsx
new file mode 100644
index 000000000..f85c7403a
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/Pf/PfColors.tsx
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// PF colors, and moreover, use the defined color variables such that any changes made by PF are
+// picked up when the PF version is updated. The preferred, standard way, is in CSS styling. In
+// those cases we can directly let CSS resolve the PF var. So, whenever possible use the PFColors
+// enum below. In certain cases (like in cytoscape), we need the explicit hex value. In that case
+// we must actually get the computed value. We do this as soon as we get an initial document (in
+// StartupInitializer.tsx). In those cases use PFColorVals. Note that those values are not
+// available until they can be computed, so don't use them in constants or before they are
+// available.
+
+// Colors used by Kiali for CSS styling
+export enum PFColors {
+ Black100 = 'var(--pf-v5-global--palette--black-100)',
+ Black150 = 'var(--pf-v5-global--palette--black-150)',
+ Black200 = 'var(--pf-v5-global--palette--black-200)',
+ Black300 = 'var(--pf-v5-global--palette--black-300)',
+ Black400 = 'var(--pf-v5-global--palette--black-400)',
+ Black500 = 'var(--pf-v5-global--palette--black-500)',
+ Black600 = 'var(--pf-v5-global--palette--black-600)',
+ Black700 = 'var(--pf-v5-global--palette--black-700)',
+ Black800 = 'var(--pf-v5-global--palette--black-800)',
+ Black900 = 'var(--pf-v5-global--palette--black-900)',
+ Black1000 = 'var(--pf-v5-global--palette--black-1000)',
+ Blue50 = 'var(--pf-v5-global--palette--blue-50)',
+ Blue200 = 'var(--pf-v5-global--palette--blue-200)',
+ Blue300 = 'var(--pf-v5-global--palette--blue-300)',
+ Blue400 = 'var(--pf-v5-global--palette--blue-400)',
+ Blue500 = 'var(--pf-v5-global--palette--blue-500)',
+ Blue600 = 'var(--pf-v5-global--palette--blue-600)',
+ Cyan300 = 'var(--pf-v5-global--palette--cyan-300)',
+ Gold400 = 'var(--pf-v5-global--palette--gold-400)',
+ Green300 = 'var(--pf-v5-global--palette--green-300)',
+ Green400 = 'var(--pf-v5-global--palette--green-400)',
+ Green500 = 'var(--pf-v5-global--palette--green-500)',
+ Green600 = 'var(--pf-v5-global--palette--green-600)',
+ LightBlue400 = 'var(--pf-v5-global--palette--light-blue-400)',
+ LightGreen400 = 'var(--pf-v5-global--palette--light-green-400)',
+ LightGreen500 = 'var(--pf-v5-global--palette--light-green-500)',
+ Orange50 = 'var(--pf-v5-global--palette--orange-50)',
+ Orange400 = 'var(--pf-v5-global--palette--orange-400)',
+ Purple100 = 'var(--pf-v5-global--palette--purple-100)',
+ Purple200 = 'var(--pf-v5-global--palette--purple-200)',
+ Purple500 = 'var(--pf-v5-global--palette--purple-500)',
+ Red50 = 'var(--pf-v5-global--palette--red-50)',
+ Red100 = 'var(--pf-v5-global--palette--red-100)',
+ Red200 = 'var(--pf-v5-global--palette--red-200)',
+ Red500 = 'var(--pf-v5-global--palette--red-500)',
+ White = 'var(--pf-v5-global--palette--white)',
+
+ // semantic kiali colors
+ Active = 'var(--pf-v5-global--active-color--400)',
+ ActiveText = 'var(--pf-v5-global--primary-color--200)',
+ Badge = 'var(--pf-v5-global--palette--blue-300)',
+ Replay = 'var(--pf-v5-global--active-color--300)',
+ Link = 'var(--pf-v5-global--link--Color)',
+
+ // Health/Alert colors https://www.patternfly.org/v4/design-guidelines/styles/colors
+ Danger = 'var(--pf-v5-global--danger-color--100)',
+ Info = 'var(--pf-v5-global--info-color--100)',
+ InfoBackground = 'var(--pf-v5-global--info-color--200)',
+ Success = 'var(--pf-v5-global--success-color--100)',
+ SuccessBackground = 'var(--pf-v5-global--success-color--200)',
+ Warning = 'var(--pf-v5-global--warning-color--100)',
+
+ // chart-specific color values, for rates charts where 4xx is really Danger not Warning
+ ChartDanger = 'var(--pf-v5-global--danger-color--300)',
+ ChartOther = 'var(--pf-v5-global--palette-black-1000)',
+ ChartWarning = 'var(--pf-v5-global--danger-color--100)',
+
+ // PF background colors (compatible with dark mode)
+ BackgroundColor100 = 'var(--pf-v5-global--BackgroundColor--100)',
+ BackgroundColor150 = 'var(--pf-v5-global--BackgroundColor--150)',
+ BackgroundColor200 = 'var(--pf-v5-global--BackgroundColor--200)',
+
+ // PF standard colors (compatible with dark mode)
+ Color100 = 'var(--pf-v5-global--Color--100)',
+ Color200 = 'var(--pf-v5-global--Color--200)',
+ ColorLight100 = 'var(--pf-v5-global--Color--light-100)',
+ ColorLight200 = 'var(--pf-v5-global--Color--light-200)',
+ ColorLight300 = 'var(--pf-v5-global--Color--light-300)',
+
+ // PF border colors (compatible with dark mode)
+ BorderColor100 = 'var(--pf-v5-global--BorderColor--100)',
+ BorderColor200 = 'var(--pf-v5-global--BorderColor--200)',
+ BorderColor300 = 'var(--pf-v5-global--BorderColor--300)',
+ BorderColorLight100 = 'var(--pf-v5-global--BorderColor--light-100)',
+}
+
+// The hex string value of the PF CSS variable
+export type PFColorVal = string;
+
+// Color values used by Kiali outside of CSS (i.e. when we must have the actual hex value)
+export type PFColorValues = {
+ Black100: PFColorVal;
+ Black150: PFColorVal;
+ Black200: PFColorVal;
+ Black300: PFColorVal;
+ Black400: PFColorVal;
+ Black500: PFColorVal;
+ Black600: PFColorVal;
+ Black700: PFColorVal;
+ Black1000: PFColorVal;
+ Blue50: PFColorVal;
+ Blue300: PFColorVal;
+ Blue600: PFColorVal;
+ Red50: PFColorVal;
+ Orange50: PFColorVal;
+ Gold400: PFColorVal;
+ Green400: PFColorVal;
+ Purple200: PFColorVal;
+ White: PFColorVal;
+
+ // Health/Alert colors https://www.patternfly.org/v4/design-guidelines/styles/colors
+ Danger: PFColorVal;
+ Success: PFColorVal;
+ Warning: PFColorVal;
+
+ // PF colors (compatible with dark mode)
+ BackgroundColor100: PFColorVal;
+ BackgroundColor200: PFColorVal;
+
+ Color100: PFColorVal;
+ Color200: PFColorVal;
+
+ BorderColor100: PFColorVal;
+ BorderColor200: PFColorVal;
+ BorderColor300: PFColorVal;
+};
+
+export let PFColorVals: PFColorValues;
+
+/*
+ Extract color from var
+ Input : var(--pf-v5-global--palette--black-100)
+ Output: --pf-v5-global--palette--black-100
+
+ - In case there is not var then return the same input
+*/
+const getColor = (val: string) => {
+ return val.indexOf('var(') === 0 ? val.split('(').pop()!.split(')')[0] : val;
+};
+
+export const setPFColorVals = (element: Element) => {
+ PFColorVals = {
+ // color values used by kiali
+ Black100: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black100)),
+ Black150: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black150)),
+ Black200: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black200)),
+ Black300: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black300)),
+ Black400: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black400)),
+ Black500: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black500)),
+ Black600: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black600)),
+ Black700: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black700)),
+ Black1000: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Black1000)),
+ Blue50: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Blue50)),
+ Blue300: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Blue300)),
+ Blue600: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Blue600)),
+ Red50: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Red50)),
+ Orange50: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Orange50)),
+ Gold400: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Gold400)),
+ Green400: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Green400)),
+ Purple200: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Purple200)),
+ White: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.White)),
+
+ // status color values used by kiali
+ Danger: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Danger)),
+ Success: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Success)),
+ Warning: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Warning)),
+
+ // PF colors (compatible with dark mode)
+ BackgroundColor100: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.BackgroundColor100)),
+ BackgroundColor200: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.BackgroundColor200)),
+
+ Color100: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Color100)),
+ Color200: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.Color200)),
+
+ BorderColor100: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.BorderColor100)),
+ BorderColor200: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.BorderColor200)),
+ BorderColor300: window
+ .getComputedStyle(element)
+ .getPropertyValue(getColor(PFColors.BorderColor300)),
+ };
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/KialiLayoutFactory.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/KialiLayoutFactory.tsx
new file mode 100644
index 000000000..5b0a28a07
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/KialiLayoutFactory.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ ColaLayout,
+ Graph,
+ Layout,
+ LayoutFactory,
+} from '@patternfly/react-topology';
+import { KialiDagreLayout } from './layouts/KialiDagreLayout';
+
+export const KialiLayoutFactory: LayoutFactory = (
+ type: string,
+ graph: Graph,
+): Layout => {
+ switch (type) {
+ case 'Dagre':
+ return new KialiDagreLayout(graph, {
+ linkDistance: 40,
+ nodeDistance: 25,
+ marginx: undefined,
+ marginy: undefined,
+ ranker: 'network-simplex',
+ rankdir: 'LR',
+ });
+ default:
+ return new ColaLayout(graph, { layoutOnDrag: false });
+ }
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/TrafficGraph.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/TrafficGraph.tsx
new file mode 100644
index 000000000..9a0ed5cd8
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/TrafficGraph.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ action,
+ createTopologyControlButtons,
+ defaultControlButtonsOptions,
+ Model,
+ TopologyControlBar,
+ TopologyView,
+ Visualization,
+ VisualizationProvider,
+ VisualizationSurface,
+} from '@patternfly/react-topology';
+import { useEffect, useState } from 'react';
+import { KialiComponentFactory } from './factories/KialiComponentFactory';
+import { KialiLayoutFactory } from './factories/KialiLayoutFactory';
+
+const getVisualization = (): Visualization => {
+ const vis = new Visualization();
+
+ vis.registerLayoutFactory(KialiLayoutFactory);
+ vis.registerComponentFactory(KialiComponentFactory);
+ vis.setFitToScreenOnLayout(true);
+
+ return vis;
+};
+
+export const TrafficGraph = (props: { model: Model }) => {
+ const [controller] = useState(getVisualization());
+ useEffect(() => {
+ controller.fromModel(props.model, false);
+ }, [props.model, controller]);
+
+ return (
+ {
+ controller.getGraph().scaleBy(4 / 3);
+ }),
+ zoomOutCallback: action(() => {
+ controller.getGraph().scaleBy(0.75);
+ }),
+ fitToScreenCallback: action(() => {
+ controller.getGraph().fit(80);
+ }),
+ resetViewCallback: action(() => {
+ controller.getGraph().reset();
+ controller.getGraph().layout();
+ }),
+ legend: false,
+ zoomInAriaLabel: '',
+ zoomOutAriaLabel: '',
+ fitToScreenAriaLabel: '',
+ resetViewAriaLabel: '',
+ zoomInTip: '',
+ zoomOutTip: '',
+ fitToScreenTip: '',
+ resetViewTip: '',
+ })}
+ />
+ }
+ >
+
+
+
+
+ );
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiComponentFactory.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiComponentFactory.tsx
new file mode 100644
index 000000000..5c176a166
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiComponentFactory.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ ComponentFactory,
+ DefaultGroup,
+ GraphComponent,
+ ModelKind,
+ withPanZoom,
+ withSelection,
+} from '@patternfly/react-topology';
+import { KialiEdge } from '../styles/KialiEdge';
+import { KialiNode } from '../styles/KialiNode';
+
+export const KialiComponentFactory: ComponentFactory = (
+ kind: ModelKind,
+ type: string,
+) => {
+ switch (type) {
+ case 'group':
+ return DefaultGroup;
+ default:
+ switch (kind) {
+ case ModelKind.graph:
+ return withPanZoom()(GraphComponent);
+ case ModelKind.node:
+ return KialiNode as any;
+ case ModelKind.edge:
+ return withSelection({ multiSelect: false, controlled: false })(
+ KialiEdge as any,
+ );
+ default:
+ return undefined;
+ }
+ }
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiLayoutFactory.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiLayoutFactory.tsx
new file mode 100644
index 000000000..df39e9da1
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/factories/KialiLayoutFactory.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ ColaLayout,
+ Graph,
+ Layout,
+ LayoutFactory,
+} from '@patternfly/react-topology';
+import { KialiDagreLayout } from '../layouts/KialiDagreLayout';
+
+export const KialiLayoutFactory: LayoutFactory = (
+ type: string,
+ graph: Graph,
+): Layout => {
+ switch (type) {
+ case 'Dagre':
+ return new KialiDagreLayout(graph, {
+ linkDistance: 40,
+ nodeDistance: 25,
+ marginx: undefined,
+ marginy: undefined,
+ ranker: 'network-simplex',
+ rankdir: 'LR',
+ });
+ default:
+ return new ColaLayout(graph, { layoutOnDrag: false });
+ }
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/index.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/index.ts
new file mode 100644
index 000000000..3c35622e0
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './TrafficGraph';
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/layouts/KialiDagreLayout.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/layouts/KialiDagreLayout.ts
new file mode 100644
index 000000000..2c1b58300
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/layouts/KialiDagreLayout.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { DagreLayout } from '@patternfly/react-topology';
+
+export class KialiDagreLayout extends DagreLayout {
+ override updateEdgeBendpoints() {
+ // Your implementation here
+ }
+}
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiEdge.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiEdge.tsx
new file mode 100644
index 000000000..95ea709c4
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiEdge.tsx
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ DefaultEdge,
+ Edge,
+ observer,
+ ScaleDetailsLevel,
+ WithSelectionProps,
+} from '@patternfly/react-topology';
+import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel';
+import { default as React } from 'react';
+import { classes, style } from 'typestyle';
+import { PFColors } from '../../../components/Pf/PfColors';
+
+// This is our styled edge component registered in stylesComponentFactory.tsx. It is responsible for adding customizations that then get passed down to DefaultEdge. The current customizations:
+// data.pathStyle?: React.CSSProperties // additional CSS stylings for the edge/path (not the endpoint).
+// data.isFind?: boolean // adds graph-find overlay
+// data.isUnhighlighted?: boolean // adds unhighlight effects
+// data.hasSpans?: Span[] // adds trace overlay
+// add showTag prop and show scaled tag on hover (when showTag is false)
+// support [lock] icons on edge tags
+
+const ColorFind = PFColors.Gold400;
+const ColorSpan = PFColors.Purple200;
+const OverlayOpacity = 0.3;
+const OverlayWidth = 30;
+
+type StyleEdgeProps = {
+ element: Edge;
+} & WithSelectionProps;
+
+const tagClass = style({
+ fontFamily: 'Verdana,Arial,Helvetica,sans-serif,pficon',
+});
+
+const StyleEdgeComponent: React.FC = ({ element, ...rest }) => {
+ const data = element.getData();
+ const detailsLevel = useDetailsLevel();
+
+ const cssClasses: string[] = [];
+
+ // Change edge color according to the pathStyle
+ const edgeClass = style({
+ $nest: {
+ '& .pf-topology__edge__link': data.pathStyle,
+ },
+ });
+ cssClasses.push(edgeClass);
+
+ const edgeHoverClass = style({
+ $nest: {
+ '& .pf-topology__edge.pf-m-hover': {
+ $nest: {
+ '& .pf-topology__edge__link, & .pf-topology-connector-arrow':
+ data.pathStyle,
+ },
+ },
+ },
+ });
+ cssClasses.push(edgeHoverClass);
+
+ // Change connector color according to the pathStyle
+ const connectorClass = style({
+ $nest: {
+ '& .pf-topology-connector-arrow': {
+ stroke: data.pathStyle.stroke,
+ fill: data.pathStyle.stroke,
+ },
+ },
+ });
+ cssClasses.push(connectorClass);
+
+ const edgeConnectorArrowHoverStyles = style({
+ $nest: {
+ '& .pf-topology__edge.pf-m-hover': {
+ $nest: {
+ '& .pf-topology-connector-arrow': {
+ stroke: data.pathStyle.stroke,
+ fill: data.pathStyle.stroke,
+ },
+ },
+ },
+ },
+ });
+ cssClasses.push(edgeConnectorArrowHoverStyles);
+
+ // If has spans, add the span overlay
+ if (data.hasSpans) {
+ const spansClass = style({
+ $nest: {
+ '& .pf-topology__edge__background': {
+ strokeWidth: OverlayWidth,
+ stroke: ColorSpan,
+ strokeOpacity: OverlayOpacity,
+ },
+ },
+ });
+ cssClasses.push(spansClass);
+ // If isHighlighted, add the highlight overlay
+ } else if (data.isFind) {
+ const findClass = style({
+ $nest: {
+ '& .pf-topology__edge__background': {
+ strokeWidth: OverlayWidth,
+ stroke: ColorFind,
+ strokeOpacity: OverlayOpacity,
+ },
+ },
+ });
+ cssClasses.push(findClass);
+ }
+
+ // Set animation duration velocity
+ if (data.animationDuration) {
+ const animationClass = style({
+ $nest: {
+ '& .pf-topology__edge__link': {
+ animationDuration: `${data.animationDuration}s`,
+ },
+ },
+ });
+ cssClasses.push(animationClass);
+ }
+
+ // Set the path style when unhighlighted (opacity)
+ let opacity = 1;
+ if (data.isUnhighlighted) {
+ opacity = 0.1;
+ }
+
+ const passedData = React.useMemo(() => {
+ const newData = { ...data };
+ if (detailsLevel !== ScaleDetailsLevel.high) {
+ newData.showTag = false;
+ }
+ Object.keys(newData).forEach(key => {
+ if (newData[key] === undefined) {
+ delete newData[key];
+ }
+ });
+ return newData;
+ }, [data, detailsLevel]);
+
+ return (
+
+
+
+ );
+};
+
+export const KialiEdge = observer(StyleEdgeComponent);
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiGroup.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiGroup.tsx
new file mode 100644
index 000000000..5e1828d71
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiGroup.tsx
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { CubesIcon } from '@patternfly/react-icons';
+import {
+ DefaultGroup,
+ Node,
+ ScaleDetailsLevel,
+ ShapeProps,
+ WithSelectionProps,
+} from '@patternfly/react-topology';
+import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel';
+import { default as React } from 'react';
+import { PFColors } from '../../../components/Pf/PfColors';
+
+const ICON_PADDING = 20;
+
+export enum DataTypes {
+ Default,
+}
+
+type StyleGroupProps = {
+ element: Node;
+ collapsible: boolean;
+ collapsedWidth?: number;
+ collapsedHeight?: number;
+ onCollapseChange?: (group: Node, collapsed: boolean) => void;
+ getCollapsedShape?: (node: Node) => React.FC;
+ collapsedShadowOffset?: number; // defaults to 10
+} & WithSelectionProps;
+
+export function KialiGroup({
+ element,
+ collapsedWidth = 75,
+ collapsedHeight = 75,
+ ...rest
+}: StyleGroupProps) {
+ const data = element.getData();
+ const detailsLevel = useDetailsLevel();
+
+ const passedData = React.useMemo(() => {
+ const newData = { ...data };
+ Object.keys(newData).forEach(key => {
+ if (newData[key] === undefined) {
+ delete newData[key];
+ }
+ });
+ return newData;
+ }, [data]);
+
+ if (data.isFocused) {
+ element.setData({ ...data, isFocused: false });
+ }
+
+ const renderIcon = (): React.ReactNode => {
+ const iconSize =
+ Math.min(collapsedWidth, collapsedHeight) - ICON_PADDING * 2;
+ const Component = CubesIcon;
+
+ return (
+
+
+
+ );
+ };
+
+ return (
+
+
+ {element.isCollapsed() ? renderIcon() : null}
+
+
+ );
+}
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiNode.tsx b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiNode.tsx
new file mode 100644
index 000000000..46ccd202e
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/styles/KialiNode.tsx
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { KeyIcon, TopologyIcon } from '@patternfly/react-icons';
+import {
+ DefaultNode,
+ getShapeComponent,
+ Node,
+ NodeShape,
+ observer,
+ ScaleDetailsLevel,
+ useHover,
+ WithSelectionProps,
+} from '@patternfly/react-topology';
+import useDetailsLevel from '@patternfly/react-topology/dist/esm/hooks/useDetailsLevel';
+import { default as React } from 'react';
+import { style } from 'typestyle';
+import { PFColors } from '../../../components/Pf/PfColors';
+
+// This is the registered Node component override that utilizes our customized Node.tsx component.
+
+type StyleNodeProps = {
+ element: Node;
+} & WithSelectionProps;
+
+const renderIcon = (element: Node): React.ReactNode => {
+ let Component: React.ComponentClass> | undefined;
+ const data = element.getData();
+ const isInaccessible = data.isInaccessible;
+ const isServiceEntry = data.isServiceEntry;
+ const isBox = data.isBox;
+ if (isInaccessible && !isServiceEntry && !isBox) {
+ Component = KeyIcon;
+ }
+ const isOutside = data.isOutside;
+ if (isOutside && !isBox) {
+ Component = TopologyIcon;
+ }
+
+ // this blurb taken from PFT demo StyleNode.tsx, not sure if it's required
+ // vv
+ const { width, height } = element.getDimensions();
+ const shape = element.getNodeShape();
+ const iconSize =
+ (shape === NodeShape.trapezoid ? width : Math.min(width, height)) -
+ (shape === NodeShape.stadium ? 5 : 20) * 2;
+ // ^^
+
+ return Component ? (
+
+
+
+ ) : (
+ <>>
+ );
+};
+
+const StyleNodeComponent: React.FC = ({ element, ...rest }) => {
+ const data = element.getData();
+ const detailsLevel = useDetailsLevel();
+ const [hover, hoverRef] = useHover();
+ const ShapeComponent = getShapeComponent(element);
+
+ const ColorFind = PFColors.Gold400;
+ const ColorSpan = PFColors.Purple200;
+ const OverlayOpacity = 0.3;
+ const OverlayWidth = 40;
+
+ const traceOverlayStyle = style({
+ strokeWidth: OverlayWidth,
+ stroke: ColorSpan,
+ strokeOpacity: OverlayOpacity,
+ });
+
+ const findOverlayStyle = style({
+ strokeWidth: OverlayWidth,
+ stroke: ColorFind,
+ strokeOpacity: OverlayOpacity,
+ });
+
+ // Set the path style when unhighlighted (opacity)
+ let opacity = 1;
+ if (data.isUnhighlighted) {
+ opacity = 0.1;
+ }
+
+ const passedData = React.useMemo(() => {
+ const newData = { ...data };
+ if (detailsLevel !== ScaleDetailsLevel.high) {
+ newData.tag = undefined;
+ }
+ Object.keys(newData).forEach(key => {
+ if (newData[key] === undefined) {
+ delete newData[key];
+ }
+ });
+ return newData;
+ }, [data, detailsLevel]);
+
+ const { width, height } = element.getDimensions();
+
+ return (
+
+ {data.hasSpans && (
+
+ )}
+ {data.isFind && (
+
+ )}
+
+ {(hover || detailsLevel !== ScaleDetailsLevel.low) &&
+ renderIcon(element)}
+
+
+ );
+};
+
+export const KialiNode = observer(StyleNodeComponent);
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/EdgeData.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/EdgeData.ts
new file mode 100644
index 000000000..55ffece83
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/EdgeData.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ DecoratedGraphEdgeData,
+ Span,
+} from '@backstage-community/plugin-kiali-common/types';
+import {
+ EdgeTerminalType,
+ GraphElement,
+ NodeStatus,
+} from '@patternfly/react-topology';
+
+export type EdgeData = DecoratedGraphEdgeData & {
+ endTerminalType: EdgeTerminalType;
+ hasSpans?: Span[];
+ isFind?: boolean;
+ isHighlighted?: boolean;
+ isSelected?: boolean;
+ isUnhighlighted?: boolean;
+ onHover?: (element: GraphElement, isMouseIn: boolean) => void;
+ pathStyle?: React.CSSProperties;
+ tag?: string;
+ tagStatus?: NodeStatus;
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/GraphPFSettings.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/GraphPFSettings.ts
new file mode 100644
index 000000000..8d040af6f
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/GraphPFSettings.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { Namespace } from '@backstage-community/plugin-kiali-common/types';
+import {
+ EdgeLabelMode,
+ GraphType,
+ TrafficRate,
+} from '@backstage-community/plugin-kiali-common/types';
+
+export type GraphPFSettings = {
+ activeNamespaces: Namespace[];
+ edgeLabels: EdgeLabelMode[];
+ graphType: GraphType;
+ showOutOfMesh: boolean;
+ showSecurity: boolean;
+ showVirtualServices: boolean;
+ trafficRates: TrafficRate[];
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeData.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeData.ts
new file mode 100644
index 000000000..b0b67b005
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeData.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ BoxByType,
+ DecoratedGraphNodeData,
+ DEGRADED,
+ FAILURE,
+ NodeType,
+} from '@backstage-community/plugin-kiali-common/types';
+import {
+ BadgeLocation,
+ GraphElement,
+ LabelPosition,
+ NodeShape,
+ NodeStatus,
+} from '@patternfly/react-topology';
+
+export type NodeData = DecoratedGraphNodeData & {
+ // These are node.data fields that have an impact on the PFT rendering of the node.
+ // TODO: Is there an actual type defined for these in PFT?
+ attachments?: React.ReactNode; // ie. decorators
+ badge?: string;
+ badgeBorderColor?: string;
+ badgeClassName?: string;
+ badgeColor?: string;
+ badgeLocation?: BadgeLocation;
+ badgeTextColor?: string;
+ column?: number;
+ component?: React.ReactNode;
+ icon?: React.ReactNode;
+ isFind?: boolean;
+ isHighlighted?: boolean;
+ isSelected?: boolean;
+ isUnhighlighted?: boolean;
+ labelIcon?: React.ReactNode;
+ labelIconClass?: string;
+ labelIconPadding?: number;
+ labelPosition?: LabelPosition;
+ marginX?: number;
+ onHover?: (element: GraphElement, isMouseIn: boolean) => void;
+ row?: number;
+ secondaryLabel?: string;
+ setLocation?: boolean;
+ showContextMenu?: boolean;
+ showStatusDecorator?: boolean;
+ statusDecoratorTooltip?: React.ReactNode;
+ truncateLength?: number;
+ x?: number;
+ y?: number;
+};
+
+export const getNodeStatus = (data: NodeData): NodeStatus => {
+ if ((data.isBox && data.isBox !== BoxByType.APP) || data.isIdle) {
+ return NodeStatus.default;
+ }
+
+ switch (data.healthStatus) {
+ case DEGRADED.name:
+ return NodeStatus.warning;
+ case FAILURE.name:
+ return NodeStatus.danger;
+ default:
+ return NodeStatus.success;
+ }
+};
+
+export const getNodeShape = (data: NodeData): NodeShape => {
+ switch (data.nodeType) {
+ case NodeType.AGGREGATE:
+ return NodeShape.hexagon;
+ case NodeType.APP:
+ return NodeShape.rect;
+ case NodeType.SERVICE:
+ return data.isServiceEntry ? NodeShape.trapezoid : NodeShape.rhombus;
+ case NodeType.WORKLOAD:
+ return NodeShape.circle;
+ default:
+ return NodeShape.ellipse;
+ }
+};
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeMap.ts b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeMap.ts
new file mode 100644
index 000000000..7b5310b6d
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/types/NodeMap.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { NodeModel } from '@patternfly/react-topology';
+
+export type NodeMap = Map;
diff --git a/workspaces/kiali/plugins/kiali-react/src/components/index.ts b/workspaces/kiali/plugins/kiali-react/src/components/index.ts
new file mode 100644
index 000000000..a6195151a
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/components/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// The index file in ./components/ is typically responsible for selecting
+// which components are public API and should be exported from the package.
+
+export * from './TrafficGraph';
diff --git a/workspaces/kiali/plugins/kiali-react/src/index.ts b/workspaces/kiali/plugins/kiali-react/src/index.ts
new file mode 100644
index 000000000..546e83d06
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './components';
diff --git a/workspaces/kiali/plugins/kiali-react/src/setupTests.ts b/workspaces/kiali/plugins/kiali-react/src/setupTests.ts
new file mode 100644
index 000000000..b57590b52
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/setupTests.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '@testing-library/jest-dom';
diff --git a/workspaces/kiali/plugins/kiali-react/src/styles/AceEditorStyle.ts b/workspaces/kiali/plugins/kiali-react/src/styles/AceEditorStyle.ts
new file mode 100644
index 000000000..470a360a6
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/styles/AceEditorStyle.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { style } from 'typestyle';
+import { NestedCSSProperties } from 'typestyle/lib/types';
+import { PFColors } from '../components/Pf/PfColors';
+
+/*
+ * 70px is the height of the bottom toolbar (save, reload and cancel buttons)
+ * 100px is the top margin of the yaml editor (Adjusted with RenderComponentScroll).
+ * So, substracting 170px from the tab content height.
+ */
+export const istioAceEditorStyle = style({
+ '--kiali-yaml-editor-height':
+ 'calc(var(--kiali-details-pages-tab-content-height) - 170px)',
+ position: 'relative',
+ minHeight: '200px',
+ border: `1px solid ${PFColors.BorderColor200}`,
+ fontSize: 'var(--kiali-global--font-size) !important',
+ $nest: {
+ '& div.ace_gutter-cell.ace_info': {
+ backgroundImage: 'none',
+ $nest: {
+ '&::before': {
+ content: `'\\E92b'`,
+ fontFamily: 'pficon',
+ left: '5px',
+ position: 'absolute',
+ },
+ },
+ },
+ },
+} as NestedCSSProperties);
+
+export const istioValidationErrorStyle = style({
+ position: 'absolute',
+ background: 'rgba(204, 0, 0, 0.5)',
+});
+
+export const istioValidationWarningStyle = style({
+ position: 'absolute',
+ background: 'rgba(236, 122, 8, 0.5)',
+});
+
+export const istioValidationInfoStyle = style({
+ position: 'absolute',
+ background: PFColors.ColorLight300,
+});
diff --git a/workspaces/kiali/plugins/kiali-react/src/styles/DropdownStyles.ts b/workspaces/kiali/plugins/kiali-react/src/styles/DropdownStyles.ts
new file mode 100644
index 000000000..e24205e61
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/styles/DropdownStyles.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { style } from 'typestyle';
+import { NestedCSSProperties } from 'typestyle/lib/types';
+import { PFColors } from '../components/Pf/PfColors';
+
+export const containerStyle = style({
+ overflow: 'auto',
+});
+
+// this emulates Select component .pf-v5-c-select__menu
+export const menuStyle = style({
+ fontSize: 'var(--kiali-global--font-size)',
+});
+
+// this emulates Select component .pf-v5-c-select__menu but w/o cursor manipulation
+export const menuEntryStyle = style({
+ display: 'inline-block',
+ width: '100%',
+});
+
+// this emulates Select component .pf-v5-c-select__menu-group-title but with less bottom padding to conserve space
+export const titleStyle = style({
+ padding: '0.5rem 1rem 0 1rem',
+ fontWeight: 700,
+ color: PFColors.Color200,
+});
+
+const itemStyle: NestedCSSProperties = {
+ alignItems: 'center',
+ whiteSpace: 'nowrap',
+ margin: 0,
+ padding: '0.375rem 1rem',
+ display: 'inline-block',
+};
+
+// this emulates Select component .pf-v5-c-select__menu-item but with less vertical padding to conserve space
+export const itemStyleWithoutInfo = style(itemStyle);
+
+// this emulates Select component .pf-v5-c-select__menu-item but with less vertical padding to conserve space
+export const itemStyleWithInfo = style({
+ ...itemStyle,
+ padding: '0.375rem 0 0.375rem 1rem',
+});
+
+export const infoStyle = style({
+ marginLeft: '0.375rem',
+});
+
+export const groupMenuStyle = style({
+ textAlign: 'left',
+});
+
+export const kebabToggleStyle = style({
+ paddingLeft: '0.5rem',
+ paddingRight: '0.5rem',
+});
diff --git a/workspaces/kiali/plugins/kiali-react/src/styles/HealthStyle.ts b/workspaces/kiali/plugins/kiali-react/src/styles/HealthStyle.ts
new file mode 100644
index 000000000..b808b7324
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/styles/HealthStyle.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { style } from 'typestyle';
+import { PFColors } from '../components/Pf/PfColors';
+
+export const healthIndicatorStyle = style({
+ $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/workspaces/kiali/plugins/kiali-react/src/styles/TabStyles.ts b/workspaces/kiali/plugins/kiali-react/src/styles/TabStyles.ts
new file mode 100644
index 000000000..b5270fcd4
--- /dev/null
+++ b/workspaces/kiali/plugins/kiali-react/src/styles/TabStyles.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { style } from 'typestyle';
+import { PFColors } from '../components/Pf/PfColors';
+
+export const basicTabStyle = style({
+ $nest: {
+ '& .pf-v5-c-tabs__list': {
+ marginLeft: '20px',
+ },
+
+ '& .pf-v5-c-tab-content': {
+ overflowY: 'auto',
+ height: '600px',
+ },
+ },
+});
+
+export const traceTabStyle = style({
+ $nest: {
+ '& .pf-v5-c-tabs__list': {
+ backgroundColor: PFColors.BackgroundColor100,
+ borderBottom: `1px solid ${PFColors.BorderColor100}`,
+ },
+ },
+});
diff --git a/workspaces/kiali/plugins/kiali/package.json b/workspaces/kiali/plugins/kiali/package.json
index db696b771..3b26d759c 100644
--- a/workspaces/kiali/plugins/kiali/package.json
+++ b/workspaces/kiali/plugins/kiali/package.json
@@ -16,7 +16,8 @@
"pluginPackages": [
"@backstage-community/plugin-kiali",
"@backstage-community/plugin-kiali-backend",
- "@backstage-community/plugin-kiali-common"
+ "@backstage-community/plugin-kiali-common",
+ "@backstage-community/plugin-kiali-react"
]
},
"sideEffects": false,
@@ -31,6 +32,7 @@
},
"dependencies": {
"@backstage-community/plugin-kiali-common": "workspace:^",
+ "@backstage-community/plugin-kiali-react": "workspace:^",
"@backstage/catalog-model": "^1.7.3",
"@backstage/core-components": "^0.17.1",
"@backstage/core-plugin-api": "^1.10.6",
@@ -90,6 +92,7 @@
"canvas": "^3.0.0",
"cross-fetch": "4.1.0",
"jest-canvas-mock": "2.5.2",
+ "msw": "1.3.5",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "^6.0.0",
diff --git a/workspaces/kiali/plugins/kiali/src/pages/TrafficGraph/TrafficGraphPage.tsx b/workspaces/kiali/plugins/kiali/src/pages/TrafficGraph/TrafficGraphPage.tsx
index 9c7a2c603..a981e74af 100644
--- a/workspaces/kiali/plugins/kiali/src/pages/TrafficGraph/TrafficGraphPage.tsx
+++ b/workspaces/kiali/plugins/kiali/src/pages/TrafficGraph/TrafficGraphPage.tsx
@@ -21,21 +21,12 @@ import {
GraphType,
TrafficRate,
} from '@backstage-community/plugin-kiali-common/types';
+import { TrafficGraph } from '@backstage-community/plugin-kiali-react';
import { Entity } from '@backstage/catalog-model';
import { Content, InfoCard } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { CircularProgress, useTheme } from '@material-ui/core';
-import {
- action,
- createTopologyControlButtons,
- defaultControlButtonsOptions,
- Model,
- TopologyControlBar,
- TopologyView,
- Visualization,
- VisualizationProvider,
- VisualizationSurface,
-} from '@patternfly/react-topology';
+import { Model, Visualization } from '@patternfly/react-topology';
import { default as React, useRef, useState } from 'react';
import { useAsyncFn, useDebounce } from 'react-use';
import { DefaultSecondaryMasthead } from '../../components/DefaultSecondaryMasthead/DefaultSecondaryMasthead';
@@ -241,41 +232,7 @@ function TrafficGraphPage(props: { view?: string; entity?: Entity }) {
onRefresh={refresh}
/>
)}
- {
- controller.getGraph().scaleBy(4 / 3);
- }),
- zoomOutCallback: action(() => {
- controller.getGraph().scaleBy(0.75);
- }),
- fitToScreenCallback: action(() => {
- controller.getGraph().fit(80);
- }),
- resetViewCallback: action(() => {
- controller.getGraph().reset();
- controller.getGraph().layout();
- }),
- legend: false,
- zoomInAriaLabel: '',
- zoomOutAriaLabel: '',
- fitToScreenAriaLabel: '',
- resetViewAriaLabel: '',
- zoomInTip: '',
- zoomOutTip: '',
- fitToScreenTip: '',
- resetViewTip: '',
- })}
- />
- }
- >
-
-
-
-
+
>
)}
diff --git a/workspaces/kiali/yarn.lock b/workspaces/kiali/yarn.lock
index 3f0e84466..72d7855d2 100644
--- a/workspaces/kiali/yarn.lock
+++ b/workspaces/kiali/yarn.lock
@@ -2790,11 +2790,35 @@ __metadata:
languageName: unknown
linkType: soft
+"@backstage-community/plugin-kiali-react@workspace:^, @backstage-community/plugin-kiali-react@workspace:plugins/kiali-react":
+ version: 0.0.0-use.local
+ resolution: "@backstage-community/plugin-kiali-react@workspace:plugins/kiali-react"
+ dependencies:
+ "@backstage-community/plugin-kiali-common": "workspace:^"
+ "@backstage/cli": "npm:^0.32.0"
+ "@backstage/core-plugin-api": "npm:^1.10.6"
+ "@backstage/dev-utils": "npm:^1.1.9"
+ "@backstage/test-utils": "npm:^1.7.7"
+ "@material-ui/core": "npm:^4.9.13"
+ "@patternfly/react-core": "npm:^5.1.1"
+ "@patternfly/react-icons": "npm:^5.1.1"
+ "@patternfly/react-topology": "npm:5.4.1"
+ "@redhat-developer/red-hat-developer-hub-theme": "npm:0.4.0"
+ "@testing-library/jest-dom": "npm:^6.0.0"
+ "@testing-library/react": "npm:^14.0.0"
+ react: "npm:^16.13.1 || ^17.0.0 || ^18.0.0"
+ typestyle: "npm:^2.4.0"
+ peerDependencies:
+ react: ^16.13.1 || ^17.0.0 || ^18.0.0
+ languageName: unknown
+ linkType: soft
+
"@backstage-community/plugin-kiali@workspace:^, @backstage-community/plugin-kiali@workspace:plugins/kiali":
version: 0.0.0-use.local
resolution: "@backstage-community/plugin-kiali@workspace:plugins/kiali"
dependencies:
"@backstage-community/plugin-kiali-common": "workspace:^"
+ "@backstage-community/plugin-kiali-react": "workspace:^"
"@backstage/catalog-model": "npm:^1.7.3"
"@backstage/cli": "npm:^0.32.0"
"@backstage/core-components": "npm:^0.17.1"
@@ -2836,6 +2860,7 @@ __metadata:
lodash: "npm:^4.17.21"
micro-memoize: "npm:4.1.3"
moment: "npm:^2.29.4"
+ msw: "npm:1.3.5"
prop-types: "npm:^15.8.1"
react: "npm:^16.13.1 || ^17.0.0 || ^18.0.0"
react-ace: "npm:9.5.0"