@jfvilas/plugin-kwirth-metrics 0.12.7 → 0.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -12
- package/dist/api/KwirthMetricsClient.esm.js +4 -57
- package/dist/api/KwirthMetricsClient.esm.js.map +1 -1
- package/dist/api/types.esm.js.map +1 -1
- package/dist/components/{EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js → EntityKwirthMetricsContent.esm.js} +83 -64
- package/dist/components/EntityKwirthMetricsContent.esm.js.map +1 -0
- package/dist/components/{Options/Options.esm.js → Options.esm.js} +1 -1
- package/dist/components/Options.esm.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +1 -1
- package/dist/plugin.esm.js.map +1 -1
- package/dist/routes.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/kwirthmetrics-component-not-found.svg +0 -29
- package/dist/components/ClusterList/ClusterList.esm.js +0 -30
- package/dist/components/ClusterList/ClusterList.esm.js.map +0 -1
- package/dist/components/ComponentNotFound/ComponentNotFound.esm.js +0 -63
- package/dist/components/ComponentNotFound/ComponentNotFound.esm.js.map +0 -1
- package/dist/components/EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js.map +0 -1
- package/dist/components/EntityKwirthMetricsContent/index.esm.js +0 -2
- package/dist/components/EntityKwirthMetricsContent/index.esm.js.map +0 -1
- package/dist/components/ObjectSelector/ObjectSelector.esm.js +0 -82
- package/dist/components/ObjectSelector/ObjectSelector.esm.js.map +0 -1
- package/dist/components/Options/Options.esm.js.map +0 -1
- package/dist/components/ShowError/ShowError.esm.js +0 -20
- package/dist/components/ShowError/ShowError.esm.js.map +0 -1
- package/dist/components/StatusLog/StatusLog.esm.js +0 -10
- package/dist/components/StatusLog/StatusLog.esm.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,14 +3,15 @@ This package is a Backstage plugin for **showing real-time streamed Kubernetes o
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
## Version compatibility
|
|
6
|
-
|
|
6
|
+
KwirthMetrics is compatible with Kwirth core server versions according to following table.
|
|
7
7
|
|
|
8
8
|
| Plugin Kwirth version | Kwirth version |
|
|
9
9
|
|-|-|
|
|
10
|
+
|0.13.0|0.4.131|
|
|
11
|
+
|0.12.8|0.4.45|
|
|
10
12
|
|0.12.5|0.4.20|
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
|
|
14
15
|
## What for?
|
|
15
16
|
This Backstage plugin allows you viewing **Kubernetes metrics** in real time that are linked to your Backstage entities. It's very important to understand that for this plugin to work you need to install Kwirth on your Kubernetes cluster (aside from Kwirth Backstage backend plugin), that is, this plugin is just another frontend for [Kwirth](https://jfvilas.github.io/kwirth).
|
|
16
17
|
|
|
@@ -38,14 +39,15 @@ This frontend plugin **includes just the visualization of metrics** charts. All
|
|
|
38
39
|
The ability to restart pods is also configured in the app-config (YAML, env or whatever), and **restartig permissions are set independently than chart streaming permissions**.
|
|
39
40
|
The backend plugin is the only responsible for configuration and permissionism, all the capabilities related with showing charts are implemented in the frontend plugin, which is in charge of establishing the connections to the corresponding Kwirth instances (running inside your Kubernetes clusters).
|
|
40
41
|
|
|
41
|
-
|
|
42
42
|
## How does it work?
|
|
43
43
|
Let's explain this by following a user working sequence:
|
|
44
44
|
|
|
45
45
|
1. A Backstage user searchs for an entity in the Backstage.
|
|
46
46
|
2. In the entity page there will be a new tab named 'KWIRTHMETRICS'.
|
|
47
47
|
3. When the user clicks on KWIRTHMETRICS the frontend plugin sends a request to the backend plugin asking for metrics information on several Kubernetes clusters.
|
|
48
|
-
4.
|
|
48
|
+
4. Next step is to identify the Kubernetes objects that match requested entity. As well as it occurs with other Backstage Kwirth plugins, Kwirth implements two strategies for getting the listo of kubernetes objects that match:
|
|
49
|
+
- *Option 1*. Your catalog-info contains an annotation of this type: **backstage.io/kubernetes-id**. In this case, the Backstage Kwirth backend plugin sends requests to the Kwirth instances that are running inside all the clusters previously added to Backstage (via app-config YAML). These requests ask for the following: *'Tell me all the pods that are labeled with the kubernetes-id label and do correspond with the entity I'm looking for'*. In response to this query, each Kwirth instance answers with a list of pods and the namespaces where they are running.
|
|
50
|
+
- *Option 2*. Your catalog-info contains an annotation of this type: **backstage.io/kubernetes-label-selector**. In this case, the Backstage Kwirth backend plugin sends requests to the Kwirth instances that are running inside all the clusters previously added to Backstage (via app-config YAML). These requests ask for the following: *'Tell me all the pods whose labels match with the kubernetes-label-selector label selector*. In response to this query, each Kwirth instance answers with a list of pods and the namespaces where they are running.
|
|
49
51
|
5. The backend plugin checks the permissions of the connected user and prunes the pods list removing the ones that the user has not access to.
|
|
50
52
|
6. With the final pod list, the backend plugin sends requests to the Kwirth instances on the clusters asking for specific API Keys for streaming metrics and/or restarting Kubernetes objects.
|
|
51
53
|
7. The backend plugin then asks for a metrics list, that is, a list of the metrics the Kubernetes cluster can offer (the ones managed by the kubelet)
|
|
@@ -63,7 +65,7 @@ If everyting is correctly configured and tagged, the user should see a list of c
|
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
67
|
# From your Backstage root directory
|
|
66
|
-
yarn --cwd packages/app add @jfvilas/plugin-kwirth-metrics @jfvilas/plugin-kwirth-common @jfvilas/kwirth-common
|
|
68
|
+
yarn --cwd packages/app add @jfvilas/plugin-kwirth-metrics @jfvilas/plugin-kwirth-common @jfvilas/plugin-kwirth-frontend @jfvilas/kwirth-common
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
3. Make sure the [Kwirth backend plugin](https://www.npmjs.com/package/@jfvilas/plugin-kwirth-backend#configure) is installed and configured.
|
|
@@ -75,13 +77,14 @@ If everyting is correctly configured and tagged, the user should see a list of c
|
|
|
75
77
|
1. Add the KwirthMetrics plugin as a tab in your Entity pages:
|
|
76
78
|
|
|
77
79
|
Firstly, import the plugin module.
|
|
80
|
+
|
|
78
81
|
```typescript
|
|
79
82
|
// In packages/app/src/components/catalog/EntityPage.tsx
|
|
80
83
|
import { EntityKwirthMetricsContent, isKwirthAvailable } from '@jfvilas/plugin-kwirth-metrics';
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
Then, add a tab to your EntityPage (the 'if' is optional, you can keep the 'KwirthMetrics' tab always visible if you prefer to do it that way).
|
|
84
|
-
|
|
87
|
+
```jsx
|
|
85
88
|
// Note: Add to any other Pages as well (e.g. defaultEntityPage or webSiteEntityPage, for example)
|
|
86
89
|
const serviceEntityPage = (
|
|
87
90
|
<EntityLayout>
|
|
@@ -92,16 +95,45 @@ If everyting is correctly configured and tagged, the user should see a list of c
|
|
|
92
95
|
</EntityLayout>
|
|
93
96
|
)
|
|
94
97
|
```
|
|
98
|
+
You can setup some default *viewing* options on the `EntityKwirthMetricsContent` component, so, when the entity loads the default options will be set. These options are:
|
|
99
|
+
- `depth`
|
|
100
|
+
- `width`
|
|
101
|
+
- `interval`
|
|
102
|
+
- `chart`
|
|
103
|
+
(The meaning of these properties are explained at the end of this document)
|
|
104
|
+
|
|
105
|
+
If you want to setup a long-running histogram as a default chart for your entities you should setup your entity page like this:
|
|
106
|
+
```jsx
|
|
107
|
+
...
|
|
108
|
+
<EntityLayout.Route if={isKwirthAvailable} path="/kwirthmetrics" title="KwirthMetrics">
|
|
109
|
+
<EntityKwirthMetricsContent allMetrics={true} enableRestart={false} depth={100} chart={'bar'}/>
|
|
110
|
+
</EntityLayout.Route>
|
|
111
|
+
...
|
|
112
|
+
```
|
|
95
113
|
|
|
96
|
-
2.
|
|
114
|
+
2. Label your catalog-info according to one of these two startegies:
|
|
115
|
+
|
|
116
|
+
- **Strategy 1: one-to-one**. Add `backstage.io/kubernetes-id` annotation to your `catalog-info.yaml` for the entities deployed to Kubernetes you want to work with on Backstage. This is the same annotation that the Kubernetes core plugin uses, so, maybe you already have added it to your components. Exmaple:
|
|
97
117
|
|
|
98
118
|
```yaml
|
|
99
119
|
metadata:
|
|
100
120
|
annotations:
|
|
101
|
-
backstage.io/kubernetes-id:
|
|
121
|
+
backstage.io/kubernetes-id: entity001
|
|
102
122
|
```
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
- **Strategy 2: use selectors**. Add `backstage.io/kubernetes-label-selector` annotation to your `catalog-info.yaml` for the entities you want to work with. This is the same annotation that the Kubernetes core plugin uses, so, maybe you already have added it to your components. The label selector value follows Kubernetes [label selector semantics](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). Example:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
metadata:
|
|
128
|
+
annotations:
|
|
129
|
+
backstage.io/kubernetes-id: 'app=core,artifact=backend'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
3. Add proper **labels** to your Kubernetes objects so Backstage can *link* forward and backward the Backstage entities with the Kubernetes objects. To do this, you need to add `labels` to your Kubernetes YAML objects (please, don't get confused: **annotations in Backstage YAML, labels in Kubernetes YAML**).
|
|
133
|
+
|
|
134
|
+
- ***VERY IMPORTANT NOTE:*** If you opted for using label selectors **you have nothing new to add to your pods**.
|
|
135
|
+
|
|
136
|
+
- If you use labels (no label selectors), please note that the kubernetes-id label is **on the deployment** and on the **spec pod template** also. This is an example of a typical Kubernetes deployment with the required label:
|
|
105
137
|
|
|
106
138
|
```yaml
|
|
107
139
|
apiVersion: apps/v1
|
|
@@ -127,9 +159,6 @@ If everyting is correctly configured and tagged, the user should see a list of c
|
|
|
127
159
|
...
|
|
128
160
|
```
|
|
129
161
|
|
|
130
|
-
Please note that the kubernetes-id label is **on the deployment** and on the **spec pod template** also.
|
|
131
|
-
|
|
132
|
-
|
|
133
162
|
## Ready, set, go!
|
|
134
163
|
If you performed all these steps you would see a 'KwirthMetrics' tab in your **Entity Page**, like this one:
|
|
135
164
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getVersion, getResources, requestAccess } from '@jfvilas/plugin-kwirth-common';
|
|
1
|
+
import { getInfo, getVersion, getResources, requestAccess } from '@jfvilas/plugin-kwirth-common';
|
|
2
2
|
|
|
3
3
|
class KwirthMetricsClient {
|
|
4
4
|
discoveryApi;
|
|
@@ -7,71 +7,18 @@ class KwirthMetricsClient {
|
|
|
7
7
|
this.discoveryApi = options.discoveryApi;
|
|
8
8
|
this.fetchApi = options.fetchApi;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
async getInfo() {
|
|
11
|
+
return getInfo(this.discoveryApi, this.fetchApi);
|
|
12
|
+
}
|
|
11
13
|
async getVersion() {
|
|
12
14
|
return getVersion(this.discoveryApi, this.fetchApi);
|
|
13
15
|
}
|
|
14
|
-
// move to common
|
|
15
|
-
// async getVersion(): Promise<string> {
|
|
16
|
-
// try {
|
|
17
|
-
// const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
|
|
18
|
-
// const targetUrl = `${baseUrl}/version`
|
|
19
|
-
// const result = await this.fetchApi.fetch(targetUrl)
|
|
20
|
-
// const data = await result.json()
|
|
21
|
-
// if (!result.ok) {
|
|
22
|
-
// throw new Error(`getVersion error: not ok`)
|
|
23
|
-
// }
|
|
24
|
-
// return data.version
|
|
25
|
-
// }
|
|
26
|
-
// catch (err) {
|
|
27
|
-
// throw new Error(`getVersion error: ${err}`)
|
|
28
|
-
// }
|
|
29
|
-
// }
|
|
30
|
-
// async getResources(entity:Entity): Promise<ClusterValidPods> {
|
|
31
|
-
// try {
|
|
32
|
-
// const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
|
|
33
|
-
// const targetUrl = `${baseUrl}/start`
|
|
34
|
-
// var payload=JSON.stringify(entity)
|
|
35
|
-
// const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})
|
|
36
|
-
// const data = await result.json() as ClusterValidPods
|
|
37
|
-
// if (!result.ok) {
|
|
38
|
-
// throw new Error(`getResources error: not ok`)
|
|
39
|
-
// }
|
|
40
|
-
// return data
|
|
41
|
-
// }
|
|
42
|
-
// catch (err) {
|
|
43
|
-
// throw new Error(`getResources error: ${err}`)
|
|
44
|
-
// }
|
|
45
|
-
// }
|
|
46
16
|
async getResources(entity) {
|
|
47
17
|
return getResources(this.discoveryApi, this.fetchApi, entity);
|
|
48
18
|
}
|
|
49
19
|
async requestAccess(entity, channel, scopes) {
|
|
50
20
|
return requestAccess(this.discoveryApi, this.fetchApi, entity, channel, scopes);
|
|
51
21
|
}
|
|
52
|
-
//+++ move to backstage-common
|
|
53
|
-
// async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {
|
|
54
|
-
// try {
|
|
55
|
-
// const baseUrl = await this.discoveryApi.getBaseUrl('kwirth')
|
|
56
|
-
// var targetUrl:URL= new URL (`${baseUrl}/access`)
|
|
57
|
-
// targetUrl.searchParams.append('scopes',scopes.join(','))
|
|
58
|
-
// targetUrl.searchParams.append('channel',channel)
|
|
59
|
-
// var payload=JSON.stringify(entity)
|
|
60
|
-
// const result = await this.fetchApi.fetch(targetUrl, {method:'POST', body:payload, headers:{'Content-Type':'application/json'}})
|
|
61
|
-
// const data = await result.json() as ClusterValidPods[]
|
|
62
|
-
// // we reconstruct the 'Map' from string of arrays
|
|
63
|
-
// for (var c of data) {
|
|
64
|
-
// c.accessKeys = new Map(JSON.parse(((c as any).accessKeys)))
|
|
65
|
-
// }
|
|
66
|
-
// if (!result.ok) {
|
|
67
|
-
// throw new Error(`requestAccess error: not ok`)
|
|
68
|
-
// }
|
|
69
|
-
// return data
|
|
70
|
-
// }
|
|
71
|
-
// catch (err) {
|
|
72
|
-
// throw new Error(`requestAccess error: ${err}`)
|
|
73
|
-
// }
|
|
74
|
-
// }
|
|
75
22
|
}
|
|
76
23
|
|
|
77
24
|
export { KwirthMetricsClient };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KwirthMetricsClient.esm.js","sources":["../../src/api/KwirthMetricsClient.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'\r\nimport { KwirthMetricsApi } from './types'\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { ClusterValidPods, getResources, getVersion, requestAccess } from '@jfvilas/plugin-kwirth-common'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\nexport interface KwirthMetricsClientOptions {\r\n discoveryApi: DiscoveryApi\r\n fetchApi: FetchApi\r\n}\r\n\r\nexport class KwirthMetricsClient implements KwirthMetricsApi {\r\n private readonly discoveryApi: DiscoveryApi\r\n private readonly fetchApi: FetchApi\r\n\r\n\r\n constructor(options: KwirthMetricsClientOptions) {\r\n this.discoveryApi = options.discoveryApi\r\n this.fetchApi = options.fetchApi\r\n }\r\n\r\n
|
|
1
|
+
{"version":3,"file":"KwirthMetricsClient.esm.js","sources":["../../src/api/KwirthMetricsClient.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'\r\nimport { KwirthMetricsApi } from './types'\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { ClusterValidPods, getInfo, getResources, getVersion, IBackendInfo, requestAccess } from '@jfvilas/plugin-kwirth-common'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\n\r\nexport interface KwirthMetricsClientOptions {\r\n discoveryApi: DiscoveryApi\r\n fetchApi: FetchApi\r\n}\r\n\r\nexport class KwirthMetricsClient implements KwirthMetricsApi {\r\n private readonly discoveryApi: DiscoveryApi\r\n private readonly fetchApi: FetchApi\r\n\r\n\r\n constructor(options: KwirthMetricsClientOptions) {\r\n this.discoveryApi = options.discoveryApi\r\n this.fetchApi = options.fetchApi\r\n }\r\n\r\n async getInfo() : Promise<IBackendInfo> {\r\n return getInfo(this.discoveryApi, this.fetchApi)\r\n }\r\n\r\n async getVersion() : Promise<string> {\r\n return getVersion(this.discoveryApi, this.fetchApi)\r\n }\r\n\r\n async getResources(entity:Entity): Promise<ClusterValidPods> {\r\n return getResources(this.discoveryApi, this.fetchApi, entity)\r\n }\r\n\r\n async requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]> {\r\n return requestAccess(this.discoveryApi, this.fetchApi, entity, channel, scopes)\r\n }\r\n\r\n}\r\n"],"names":[],"mappings":";;AA0BO,MAAM,mBAAA,CAAgD;AAAA,EACxC,YAAA;AAAA,EACA,QAAA;AAAA,EAGjB,YAAY,OAAA,EAAqC;AAC7C,IAAA,IAAA,CAAK,eAAe,OAAA,CAAQ,YAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AAAA,EAC5B;AAAA,EAEA,MAAM,OAAA,GAAkC;AACpC,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,QAAQ,CAAA;AAAA,EACnD;AAAA,EAEA,MAAM,UAAA,GAA+B;AACjC,IAAA,OAAO,UAAA,CAAW,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,QAAQ,CAAA;AAAA,EACtD;AAAA,EAEA,MAAM,aAAa,MAAA,EAA0C;AACzD,IAAA,OAAO,YAAA,CAAa,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,EAChE;AAAA,EAEA,MAAM,aAAA,CAAc,MAAA,EAAe,OAAA,EAAgB,MAAA,EAA+D;AAC9G,IAAA,OAAO,cAAc,IAAA,CAAK,YAAA,EAAc,KAAK,QAAA,EAAU,MAAA,EAAQ,SAAS,MAAM,CAAA;AAAA,EAClF;AAEJ;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.esm.js","sources":["../../src/api/types.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { createApiRef } from '@backstage/core-plugin-api'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\nimport { ClusterValidPods } from '@jfvilas/plugin-kwirth-common'\r\n\r\nexport interface KwirthMetricsApi {\r\n requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]>\r\n getResources(entity:Entity): Promise<ClusterValidPods>\r\n getVersion(): Promise<string>\r\n}\r\n\r\nexport const kwirthMetricsApiRef = createApiRef<KwirthMetricsApi>({\r\n id: 'plugin.kwirthmetrics.api',\r\n})\r\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"types.esm.js","sources":["../../src/api/types.ts"],"sourcesContent":["/*\r\nCopyright 2024 Julio Fernandez\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n*/\r\nimport { Entity } from '@backstage/catalog-model'\r\nimport { createApiRef } from '@backstage/core-plugin-api'\r\nimport { InstanceConfigScopeEnum } from '@jfvilas/kwirth-common'\r\nimport { ClusterValidPods, IBackendInfo } from '@jfvilas/plugin-kwirth-common'\r\n\r\nexport interface KwirthMetricsApi {\r\n requestAccess(entity:Entity, channel:string, scopes:InstanceConfigScopeEnum[]): Promise<ClusterValidPods[]>\r\n getResources(entity:Entity): Promise<ClusterValidPods>\r\n getVersion(): Promise<string>\r\n getInfo(): Promise<IBackendInfo>\r\n}\r\n\r\nexport const kwirthMetricsApiRef = createApiRef<KwirthMetricsApi>({\r\n id: 'plugin.kwirthmetrics.api',\r\n})\r\n"],"names":[],"mappings":";;AA2BO,MAAM,sBAAsB,YAAA,CAA+B;AAAA,EAChE,EAAA,EAAI;AACN,CAAC;;;;"}
|
|
@@ -2,20 +2,12 @@ import React, { useState, useRef } from 'react';
|
|
|
2
2
|
import useAsync from 'react-use/esm/useAsync';
|
|
3
3
|
import { Progress, WarningPanel } from '@backstage/core-components';
|
|
4
4
|
import { useApi, alertApiRef } from '@backstage/core-plugin-api';
|
|
5
|
-
import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR } from '@jfvilas/plugin-kwirth-common';
|
|
5
|
+
import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR, getPodList, getContainerList } from '@jfvilas/plugin-kwirth-common';
|
|
6
6
|
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
|
7
|
-
import { kwirthMetricsApiRef } from '
|
|
7
|
+
import { kwirthMetricsApiRef } from '../api/types.esm.js';
|
|
8
8
|
import { SignalMessageLevelEnum, InstanceConfigScopeEnum, InstanceMessageTypeEnum, OpsCommandEnum, accessKeySerialize, InstanceMessageChannelEnum, InstanceMessageFlowEnum, InstanceMessageActionEnum, MetricsConfigModeEnum, InstanceConfigViewEnum, InstanceConfigObjectEnum } from '@jfvilas/kwirth-common';
|
|
9
|
-
import { ComponentNotFound, ErrorType } from '
|
|
10
|
-
import {
|
|
11
|
-
import { ClusterList } from '../ClusterList/ClusterList.esm.js';
|
|
12
|
-
import { ObjectSelector } from '../ObjectSelector/ObjectSelector.esm.js';
|
|
13
|
-
import { ShowError } from '../ShowError/ShowError.esm.js';
|
|
14
|
-
import { StatusLog } from '../StatusLog/StatusLog.esm.js';
|
|
15
|
-
import { Box, Grid, Card, CardHeader, CardContent, FormControl, Select, MenuItem, Checkbox } from '@material-ui/core';
|
|
16
|
-
import Divider from '@material-ui/core/Divider';
|
|
17
|
-
import IconButton from '@material-ui/core/IconButton';
|
|
18
|
-
import Typography from '@material-ui/core/Typography';
|
|
9
|
+
import { ShowError, ComponentNotFound, ErrorType, ClusterList, KwirthNews, ObjectSelector, StatusLog } from '@jfvilas/plugin-kwirth-frontend';
|
|
10
|
+
import { Box, Grid, Card, CardHeader, Typography, FormControl, Select, MenuItem, Checkbox, Divider, CardContent, IconButton, Tooltip as Tooltip$1 } from '@material-ui/core';
|
|
19
11
|
import PlayIcon from '@material-ui/icons/PlayArrow';
|
|
20
12
|
import PauseIcon from '@material-ui/icons/Pause';
|
|
21
13
|
import StopIcon from '@material-ui/icons/Stop';
|
|
@@ -23,14 +15,15 @@ import InfoIcon from '@material-ui/icons/Info';
|
|
|
23
15
|
import WarningIcon from '@material-ui/icons/Warning';
|
|
24
16
|
import ErrorIcon from '@material-ui/icons/Error';
|
|
25
17
|
import RefreshIcon from '@material-ui/icons/Refresh';
|
|
26
|
-
import KwirthMetricsLogo from '
|
|
18
|
+
import KwirthMetricsLogo from '../assets/kwirthmetrics-logo.svg';
|
|
27
19
|
import { BarChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, AreaChart, Area, LineChart, Line, ResponsiveContainer } from 'recharts';
|
|
20
|
+
import { Options } from './Options.esm.js';
|
|
28
21
|
|
|
29
22
|
const EntityKwirthMetricsContent = (props) => {
|
|
30
23
|
const kwirthMetricsApi = useApi(kwirthMetricsApiRef);
|
|
31
24
|
const alertApi = useApi(alertApiRef);
|
|
32
25
|
const { entity } = useEntity();
|
|
33
|
-
const [
|
|
26
|
+
const [validClusters, setValidClusters] = useState([]);
|
|
34
27
|
const [selectedClusterName, setSelectedClusterName] = useState("");
|
|
35
28
|
const [selectedNamespaces, setSelectedNamespaces] = useState([]);
|
|
36
29
|
const [selectedPodNames, setSelectedPodNames] = useState([]);
|
|
@@ -44,10 +37,19 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
44
37
|
const [statusMessages, setStatusMessages] = useState([]);
|
|
45
38
|
const [websocket, setWebsocket] = useState();
|
|
46
39
|
const [instance, setInstance] = useState();
|
|
47
|
-
const kwirthMetricsOptionsRef = useRef({
|
|
40
|
+
const kwirthMetricsOptionsRef = useRef({
|
|
41
|
+
depth: props.depth !== void 0 ? props.depth : 10,
|
|
42
|
+
width: props.width !== void 0 ? props.width : 3,
|
|
43
|
+
interval: props.interval !== void 0 ? props.interval : 15,
|
|
44
|
+
chart: props.chart !== void 0 ? props.chart : "area",
|
|
45
|
+
aggregate: false,
|
|
46
|
+
merge: false,
|
|
47
|
+
stack: false
|
|
48
|
+
});
|
|
48
49
|
const [showStatusDialog, setShowStatusDialog] = useState(false);
|
|
49
50
|
const [statusLevel, setStatusLevel] = useState(SignalMessageLevelEnum.INFO);
|
|
50
51
|
const [backendVersion, setBackendVersion] = useState("");
|
|
52
|
+
const [backendInfo, setBackendInfo] = useState();
|
|
51
53
|
const [_refresh, setRefresh] = useState(0);
|
|
52
54
|
const [allMetrics, setAllMetrics] = useState(
|
|
53
55
|
[
|
|
@@ -61,10 +63,11 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
61
63
|
);
|
|
62
64
|
const { loading, error } = useAsync(async () => {
|
|
63
65
|
if (backendVersion === "") setBackendVersion(await kwirthMetricsApi.getVersion());
|
|
66
|
+
if (!backendInfo) setBackendInfo(await kwirthMetricsApi.getInfo());
|
|
64
67
|
let reqScopes = [InstanceConfigScopeEnum.STREAM];
|
|
65
68
|
if (props.enableRestart) reqScopes.push(InstanceConfigScopeEnum.RESTART);
|
|
66
69
|
let data = await kwirthMetricsApi.requestAccess(entity, "metrics", reqScopes);
|
|
67
|
-
|
|
70
|
+
setValidClusters(data);
|
|
68
71
|
});
|
|
69
72
|
const colours = [
|
|
70
73
|
"#6e5bb8",
|
|
@@ -148,25 +151,32 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
148
151
|
setStarted(false);
|
|
149
152
|
paused.current = true;
|
|
150
153
|
};
|
|
151
|
-
const
|
|
154
|
+
const onClickStop = () => {
|
|
152
155
|
setStarted(false);
|
|
153
156
|
setStopped(true);
|
|
154
157
|
paused.current = false;
|
|
155
158
|
stopMetricsViewer();
|
|
156
159
|
};
|
|
157
160
|
const onSelectCluster = async (clusterName) => {
|
|
161
|
+
if (started) onClickStop();
|
|
158
162
|
if (clusterName) {
|
|
159
163
|
setSelectedClusterName(clusterName);
|
|
160
|
-
setSelectedNamespaces([]);
|
|
161
164
|
setSelectedPodNames([]);
|
|
162
165
|
setSelectedContainerNames([]);
|
|
163
|
-
setMetricsMessages([]);
|
|
164
166
|
setStatusMessages([]);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
cluster.metrics.sort((a, b) => a.metric.startsWith("kwirth") ? -1 : 1);
|
|
167
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === clusterName);
|
|
168
|
+
if (cluster && cluster.pods && cluster.metrics) {
|
|
169
|
+
cluster.metrics.sort((a, _b) => a.metric.startsWith("kwirth") ? -1 : 1);
|
|
169
170
|
setAllMetrics(cluster.metrics);
|
|
171
|
+
let validNamespaces = Array.from(new Set(cluster.pods.map((pod) => pod.namespace)));
|
|
172
|
+
if (validNamespaces.length === 1) {
|
|
173
|
+
setSelectedNamespaces(validNamespaces);
|
|
174
|
+
let podList = getPodList(cluster.pods, validNamespaces);
|
|
175
|
+
setSelectedPodNames(podList.map((pod) => pod.name));
|
|
176
|
+
setSelectedContainerNames(getContainerList(cluster.pods, validNamespaces, podList.map((pod) => pod.name)));
|
|
177
|
+
} else {
|
|
178
|
+
setSelectedNamespaces([]);
|
|
179
|
+
}
|
|
170
180
|
}
|
|
171
181
|
}
|
|
172
182
|
};
|
|
@@ -196,24 +206,28 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
196
206
|
setInstance(instanceMessage.instance);
|
|
197
207
|
else {
|
|
198
208
|
let signalMessage = instanceMessage;
|
|
199
|
-
|
|
209
|
+
if (signalMessage.text) {
|
|
210
|
+
alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
|
|
211
|
+
}
|
|
200
212
|
}
|
|
201
213
|
} else {
|
|
202
214
|
let signalMessage = instanceMessage;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
if (signalMessage.text) {
|
|
216
|
+
addMessage(signalMessage.level, signalMessage.text);
|
|
217
|
+
switch (signalMessage.level) {
|
|
218
|
+
case SignalMessageLevelEnum.INFO:
|
|
219
|
+
alertApi.post({ message: signalMessage.text, severity: "info", display: "transient" });
|
|
220
|
+
break;
|
|
221
|
+
case SignalMessageLevelEnum.WARNING:
|
|
222
|
+
alertApi.post({ message: signalMessage.text, severity: "warning", display: "transient" });
|
|
223
|
+
break;
|
|
224
|
+
case SignalMessageLevelEnum.ERROR:
|
|
225
|
+
alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
alertApi.post({ message: signalMessage.text, severity: "success", display: "transient" });
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
217
231
|
}
|
|
218
232
|
}
|
|
219
233
|
break;
|
|
@@ -249,12 +263,12 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
249
263
|
}
|
|
250
264
|
};
|
|
251
265
|
const websocketOnOpen = (ws) => {
|
|
252
|
-
let cluster =
|
|
266
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
253
267
|
if (!cluster) {
|
|
254
268
|
addMessage(SignalMessageLevelEnum.ERROR, "Cluster not found");
|
|
255
269
|
return;
|
|
256
270
|
}
|
|
257
|
-
let pods = cluster.
|
|
271
|
+
let pods = cluster.pods.filter((p2) => selectedNamespaces.includes(p2.namespace));
|
|
258
272
|
if (!pods || pods.length === 0) {
|
|
259
273
|
addMessage(SignalMessageLevelEnum.ERROR, "Pod not found");
|
|
260
274
|
return;
|
|
@@ -298,7 +312,7 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
298
312
|
}
|
|
299
313
|
};
|
|
300
314
|
const startMetricsViewer = () => {
|
|
301
|
-
let cluster =
|
|
315
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
302
316
|
if (!cluster) {
|
|
303
317
|
addMessage(SignalMessageLevelEnum.ERROR, "Cluster not found");
|
|
304
318
|
return;
|
|
@@ -328,28 +342,25 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
328
342
|
};
|
|
329
343
|
const actionButtons = () => {
|
|
330
344
|
let hasStreamKey = false, hasRestartKey = false;
|
|
331
|
-
let cluster =
|
|
345
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
332
346
|
if (cluster) {
|
|
333
347
|
hasStreamKey = Boolean(cluster.accessKeys.has(InstanceConfigScopeEnum.STREAM));
|
|
334
348
|
hasRestartKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART));
|
|
335
349
|
}
|
|
336
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, props.enableRestart && /* @__PURE__ */ React.createElement(IconButton, { title: "Restart", onClick: onClickRestart, disabled: selectedPodNames.length === 0 || !hasRestartKey || !websocket || !started }, /* @__PURE__ */ React.createElement(RefreshIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: clickStart, title: "Play", disabled: started || !paused || selectedPodNames.length === 0 || selectedMetrics.length == 0 || !hasStreamKey }, /* @__PURE__ */ React.createElement(PlayIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: clickPause, title: "Pause", disabled: !(started && !paused.current && selectedPodNames.length === 0) }, /* @__PURE__ */ React.createElement(PauseIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick:
|
|
350
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, props.enableRestart && /* @__PURE__ */ React.createElement(IconButton, { title: "Restart", onClick: onClickRestart, disabled: selectedPodNames.length === 0 || !hasRestartKey || !websocket || !started }, /* @__PURE__ */ React.createElement(RefreshIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: clickStart, title: "Play", disabled: started || !paused || selectedPodNames.length === 0 || selectedMetrics.length == 0 || !hasStreamKey }, /* @__PURE__ */ React.createElement(PlayIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: clickPause, title: "Pause", disabled: !(started && !paused.current && selectedPodNames.length === 0) }, /* @__PURE__ */ React.createElement(PauseIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: onClickStop, title: "Stop", disabled: stopped || selectedPodNames.length === 0 }, /* @__PURE__ */ React.createElement(StopIcon, null)));
|
|
337
351
|
};
|
|
338
352
|
const onMetricsChange = (event) => {
|
|
339
353
|
setSelectedMetrics(event.target.value);
|
|
340
354
|
};
|
|
341
|
-
const metricsSelector = () => {
|
|
342
|
-
let disabled = selectedClusterName === "" || selectedNamespaces.length === 0;
|
|
343
|
-
return /* @__PURE__ */ React.createElement(FormControl, { style: { marginLeft: 16, width: "300px" }, size: "small" }, /* @__PURE__ */ React.createElement(Select, { value: selectedMetrics, MenuProps: { variant: "menu" }, multiple: true, onChange: onMetricsChange, renderValue: (selected) => selected.join(", "), disabled: disabled || started }, allMetrics.map(
|
|
344
|
-
(m) => /* @__PURE__ */ React.createElement(MenuItem, { key: m.metric, value: m.metric, style: { marginTop: "-6px", marginBottom: "-6px" } }, /* @__PURE__ */ React.createElement(Checkbox, { checked: selectedMetrics.includes(m.metric), style: { marginTop: "-6px", marginBottom: "-6px" } }), /* @__PURE__ */ React.createElement(Typography, { style: { marginTop: "-6px", marginBottom: "-6px" } }, m.metric))
|
|
345
|
-
)));
|
|
346
|
-
};
|
|
347
355
|
const statusButtons = (title) => {
|
|
348
356
|
const show = (level) => {
|
|
349
357
|
setShowStatusDialog(true);
|
|
350
358
|
setStatusLevel(level);
|
|
351
359
|
};
|
|
352
|
-
|
|
360
|
+
const prepareText = (txt) => {
|
|
361
|
+
return txt ? txt.length > 25 ? txt.substring(0, 25) + "..." : txt : "N/A";
|
|
362
|
+
};
|
|
363
|
+
return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, prepareText(title))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { marginTop: "-8px" } }, /* @__PURE__ */ React.createElement(IconButton, { title: "info", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO), onClick: () => show(SignalMessageLevelEnum.INFO) }, /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO) ? "blue" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "warning", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING), onClick: () => show(SignalMessageLevelEnum.WARNING), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING) ? "gold" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "error", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR), onClick: () => show(SignalMessageLevelEnum.ERROR), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR) ? "red" : "#BDBDBD" } }))));
|
|
353
364
|
};
|
|
354
365
|
const statusClear = (level) => {
|
|
355
366
|
setStatusMessages(statusMessages.filter((m) => m.level !== level));
|
|
@@ -382,7 +393,7 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
382
393
|
switch (options.chart) {
|
|
383
394
|
case "value":
|
|
384
395
|
height = 40 + series.length * 80;
|
|
385
|
-
result = /* @__PURE__ */ React.createElement(Grid,
|
|
396
|
+
result = /* @__PURE__ */ React.createElement(Grid, null, /* @__PURE__ */ React.createElement(Typography, null, series.map((serie, index) => {
|
|
386
397
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, null, serie[serie.length - 1].value), /* @__PURE__ */ React.createElement(Typography, null, names[index]));
|
|
387
398
|
})));
|
|
388
399
|
break;
|
|
@@ -400,7 +411,16 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
400
411
|
));
|
|
401
412
|
break;
|
|
402
413
|
}
|
|
403
|
-
|
|
414
|
+
let title = metric.metric.replaceAll("_", " ");
|
|
415
|
+
title = title[0].toLocaleUpperCase() + title.substring(1);
|
|
416
|
+
title = title.replaceAll("cpu", "CPU");
|
|
417
|
+
title = title.replaceAll(" fs ", " FS ");
|
|
418
|
+
title = title.replaceAll(" io ", " IO ");
|
|
419
|
+
title = title.replaceAll("oom", "OOM");
|
|
420
|
+
title = title.replaceAll("nvm", "NVM");
|
|
421
|
+
title = title.replaceAll("rss", "RSS");
|
|
422
|
+
title = title.replaceAll("failcnt", "fail count");
|
|
423
|
+
return /* @__PURE__ */ React.createElement(Grid, { style: { width: "100%", marginBottom: 32 } }, /* @__PURE__ */ React.createElement(Tooltip$1, { title: /* @__PURE__ */ React.createElement(Typography, { style: { fontSize: 12 } }, /* @__PURE__ */ React.createElement("b", null, metric.metric), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null), metric.help) }, /* @__PURE__ */ React.createElement(Typography, { align: "center" }, title)), /* @__PURE__ */ React.createElement(ResponsiveContainer, { height, key: metric + JSON.stringify(names) }, result));
|
|
404
424
|
};
|
|
405
425
|
const showMetrics = (options) => {
|
|
406
426
|
if (!metricsMessages || metricsMessages.length === 0) {
|
|
@@ -409,6 +429,8 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
409
429
|
else
|
|
410
430
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, started ? /* @__PURE__ */ React.createElement(Typography, null, "Waiting for first data, be patient...") : /* @__PURE__ */ React.createElement(Typography, null, "Configure ", /* @__PURE__ */ React.createElement("b", null, "chart options"), ", select some ", /* @__PURE__ */ React.createElement("b", null, "metrics on top"), ", and ", /* @__PURE__ */ React.createElement("b", null, "press PLAY"), " on top-right button to start viewing."));
|
|
411
431
|
}
|
|
432
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
433
|
+
if (!cluster) return;
|
|
412
434
|
let data = /* @__PURE__ */ new Map();
|
|
413
435
|
for (let metricsMessage of metricsMessages) {
|
|
414
436
|
let ts = new Date(metricsMessage.timestamp);
|
|
@@ -428,10 +450,11 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
428
450
|
var firstAsset = assetNames[0];
|
|
429
451
|
var allMetrics2 = Array.from(new Set(data.get(firstAsset).keys()));
|
|
430
452
|
for (let metric of allMetrics2) {
|
|
453
|
+
let metricDefinition = cluster.metrics?.find((m) => m.metric === metric);
|
|
431
454
|
var series = assetNames.map((an) => {
|
|
432
455
|
return data.get(an).get(metric);
|
|
433
456
|
});
|
|
434
|
-
allCharts.push(/* @__PURE__ */ React.createElement(React.Fragment, null, addChart(options,
|
|
457
|
+
allCharts.push(/* @__PURE__ */ React.createElement(React.Fragment, null, addChart(options, metricDefinition, assetNames, series, "")));
|
|
435
458
|
}
|
|
436
459
|
let rows = [];
|
|
437
460
|
for (let i2 = 0; i2 < allCharts.length; i2 += options.width) {
|
|
@@ -442,7 +465,8 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
442
465
|
let allCharts2 = Array.from(data.keys()).map((asset, index) => {
|
|
443
466
|
return Array.from(data.get(asset)?.keys()).map((metric) => {
|
|
444
467
|
var serie = data.get(asset)?.get(metric);
|
|
445
|
-
|
|
468
|
+
let metricDefinition = cluster.metrics?.find((m) => m.metric === metric);
|
|
469
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, addChart(options, metricDefinition, [asset], [serie], colours[index]));
|
|
446
470
|
});
|
|
447
471
|
});
|
|
448
472
|
let rows = [];
|
|
@@ -467,7 +491,7 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
467
491
|
}]);
|
|
468
492
|
};
|
|
469
493
|
const onClickRestart = () => {
|
|
470
|
-
let cluster =
|
|
494
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
471
495
|
if (!cluster) {
|
|
472
496
|
addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
|
|
473
497
|
return;
|
|
@@ -481,7 +505,7 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
481
505
|
addMessage(SignalMessageLevelEnum.ERROR, "No instance has been established");
|
|
482
506
|
return;
|
|
483
507
|
}
|
|
484
|
-
let pods = cluster.
|
|
508
|
+
let pods = cluster.pods.filter((pod) => selectedNamespaces.includes(pod.namespace));
|
|
485
509
|
for (let pod of pods) {
|
|
486
510
|
let om = {
|
|
487
511
|
msgtype: "opsmessage",
|
|
@@ -512,14 +536,9 @@ const EntityKwirthMetricsContent = (props) => {
|
|
|
512
536
|
websocket?.send(JSON.stringify(rm));
|
|
513
537
|
}
|
|
514
538
|
};
|
|
515
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, showError !== "" && /* @__PURE__ */ React.createElement(ShowError, { message: showError, onClose: () => setShowError("") }), loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/kwirth", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading &&
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
title: statusButtons(selectedClusterName),
|
|
519
|
-
style: { marginTop: -4, marginBottom: 4, flexShrink: 0 },
|
|
520
|
-
action: actionButtons()
|
|
521
|
-
}
|
|
522
|
-
), /* @__PURE__ */ React.createElement(Typography, { style: { marginLeft: 16, marginBottom: 4 } }, /* @__PURE__ */ React.createElement(ObjectSelector, { cluster: clusterValidPods.find((cluster) => cluster.name === selectedClusterName), onSelect: onSelectObject, disabled: selectedClusterName === "" || started || paused.current, selectedNamespaces, selectedPodNames, selectedContainerNames, scope: InstanceConfigScopeEnum.STREAM })), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, { style: { overflow: "auto" } }, showMetrics(kwirthMetricsOptionsRef.current)))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear }));
|
|
539
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, showError !== "" && /* @__PURE__ */ React.createElement(ShowError, { message: showError, onClose: () => setShowError("") }), loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/kwirth", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) > 0 && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex" } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "200px", maxWidth: "200px" } }, /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(ClusterList, { resources: validClusters, selectedClusterName, onSelect: onSelectCluster }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(Options, { metricsOptions: kwirthMetricsOptionsRef.current, selectedNamespaces, selectedPodNames, selectedContainerNames, onChange: onChangeOptions, disabled: selectedNamespaces.length === 0 || paused.current }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(KwirthNews, { latestVersions: backendInfo, backendVersion }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, p: 1, marginLeft: "8px" } }, !selectedClusterName && /* @__PURE__ */ React.createElement("img", { src: KwirthMetricsLogo, alt: "No cluster selected", style: { left: "40%", marginTop: "10%", width: "20%", position: "relative" } }), selectedClusterName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { style: { marginTop: -8 } }, /* @__PURE__ */ React.createElement(CardHeader, { title: statusButtons(selectedClusterName), style: { marginTop: -4, marginBottom: 4, flexShrink: 0 }, action: actionButtons() }), /* @__PURE__ */ React.createElement(Grid, { container: true, style: { alignItems: "end" } }, /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "66%" } }, /* @__PURE__ */ React.createElement(Typography, { style: { marginLeft: 14 } }, /* @__PURE__ */ React.createElement(ObjectSelector, { cluster: validClusters.find((cluster) => cluster.name === selectedClusterName), onSelect: onSelectObject, disabled: selectedClusterName === "" || started || paused.current, selectedNamespaces, selectedPodNames, selectedContainerNames, scope: InstanceConfigScopeEnum.STREAM }))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "33%", marginLeft: 0, marginBottom: 6, maxWidth: "33%" } }, /* @__PURE__ */ React.createElement(FormControl, { style: { width: "100%" } }, /* @__PURE__ */ React.createElement(Select, { value: selectedMetrics, MenuProps: { variant: "menu" }, multiple: true, onChange: onMetricsChange, renderValue: (selected) => selected.join(", ").substring(0, 40) + "...", disabled: selectedClusterName === "" || selectedNamespaces.length === 0 || started }, allMetrics.map(
|
|
540
|
+
(m) => /* @__PURE__ */ React.createElement(MenuItem, { key: m.metric, value: m.metric, style: { marginTop: "-8px", marginBottom: "-8px" } }, /* @__PURE__ */ React.createElement(Checkbox, { checked: selectedMetrics.includes(m.metric), style: { marginTop: "-8px", marginBottom: "-8px" } }), /* @__PURE__ */ React.createElement(Typography, { style: { marginTop: "-8px", marginBottom: "-8px" } }, m.metric))
|
|
541
|
+
))))), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, { style: { overflow: "auto" } }, showMetrics(kwirthMetricsOptionsRef.current)))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear }));
|
|
523
542
|
};
|
|
524
543
|
|
|
525
544
|
export { EntityKwirthMetricsContent };
|