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"