@jfvilas/plugin-kwirth-metrics 0.12.5
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 +275 -0
- package/dist/api/KwirthMetricsClient.esm.js +74 -0
- package/dist/api/KwirthMetricsClient.esm.js.map +1 -0
- package/dist/api/types.esm.js +8 -0
- package/dist/api/types.esm.js.map +1 -0
- package/dist/assets/kwirthmetrics-component-not-found.svg +29 -0
- package/dist/assets/kwirthmetrics-logo.svg +15 -0
- package/dist/components/ClusterList/ClusterList.esm.js +30 -0
- package/dist/components/ClusterList/ClusterList.esm.js.map +1 -0
- package/dist/components/ComponentNotFound/ComponentNotFound.esm.js +60 -0
- package/dist/components/ComponentNotFound/ComponentNotFound.esm.js.map +1 -0
- package/dist/components/EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js +526 -0
- package/dist/components/EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js.map +1 -0
- package/dist/components/EntityKwirthMetricsContent/index.esm.js +2 -0
- package/dist/components/EntityKwirthMetricsContent/index.esm.js.map +1 -0
- package/dist/components/ObjectSelector/ObjectSelector.esm.js +82 -0
- package/dist/components/ObjectSelector/ObjectSelector.esm.js.map +1 -0
- package/dist/components/Options/Options.esm.js +28 -0
- package/dist/components/Options/Options.esm.js.map +1 -0
- package/dist/components/ShowError/ShowError.esm.js +20 -0
- package/dist/components/ShowError/ShowError.esm.js.map +1 -0
- package/dist/components/StatusLog/StatusLog.esm.js +10 -0
- package/dist/components/StatusLog/StatusLog.esm.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +33 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import React, { useState, useRef } from 'react';
|
|
2
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
3
|
+
import { Progress, WarningPanel } from '@backstage/core-components';
|
|
4
|
+
import { useApi, alertApiRef } from '@backstage/core-plugin-api';
|
|
5
|
+
import { isKwirthAvailable, ANNOTATION_KWIRTH_LOCATION } from '@jfvilas/plugin-kwirth-common';
|
|
6
|
+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
|
7
|
+
import { kwirthMetricsApiRef } from '../../api/types.esm.js';
|
|
8
|
+
import { SignalMessageLevelEnum, InstanceConfigScopeEnum, InstanceMessageTypeEnum, OpsCommandEnum, accessKeySerialize, InstanceMessageChannelEnum, InstanceMessageFlowEnum, InstanceMessageActionEnum, MetricsConfigModeEnum, InstanceConfigViewEnum, InstanceConfigObjectEnum } from '@jfvilas/kwirth-common';
|
|
9
|
+
import { ComponentNotFound, ErrorType } from '../ComponentNotFound/ComponentNotFound.esm.js';
|
|
10
|
+
import { Options } from '../Options/Options.esm.js';
|
|
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';
|
|
19
|
+
import PlayIcon from '@material-ui/icons/PlayArrow';
|
|
20
|
+
import PauseIcon from '@material-ui/icons/Pause';
|
|
21
|
+
import StopIcon from '@material-ui/icons/Stop';
|
|
22
|
+
import InfoIcon from '@material-ui/icons/Info';
|
|
23
|
+
import WarningIcon from '@material-ui/icons/Warning';
|
|
24
|
+
import ErrorIcon from '@material-ui/icons/Error';
|
|
25
|
+
import RefreshIcon from '@material-ui/icons/Refresh';
|
|
26
|
+
import KwirthMetricsLogo from '../../assets/kwirthmetrics-logo.svg';
|
|
27
|
+
import { BarChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, AreaChart, Area, LineChart, Line, ResponsiveContainer } from 'recharts';
|
|
28
|
+
|
|
29
|
+
const EntityKwirthMetricsContent = (props) => {
|
|
30
|
+
const kwirthMetricsApi = useApi(kwirthMetricsApiRef);
|
|
31
|
+
const alertApi = useApi(alertApiRef);
|
|
32
|
+
const { entity } = useEntity();
|
|
33
|
+
const [clusterValidPods, setClusterValidPods] = useState([]);
|
|
34
|
+
const [selectedClusterName, setSelectedClusterName] = useState("");
|
|
35
|
+
const [selectedNamespaces, setSelectedNamespaces] = useState([]);
|
|
36
|
+
const [selectedPodNames, setSelectedPodNames] = useState([]);
|
|
37
|
+
const [selectedContainerNames, setSelectedContainerNames] = useState([]);
|
|
38
|
+
const [selectedMetrics, setSelectedMetrics] = useState([]);
|
|
39
|
+
const [showError, setShowError] = useState("");
|
|
40
|
+
const [started, setStarted] = useState(false);
|
|
41
|
+
const [stopped, setStopped] = useState(true);
|
|
42
|
+
const paused = useRef(false);
|
|
43
|
+
const [metricsMessages, setMetricsMessages] = useState([]);
|
|
44
|
+
const [statusMessages, setStatusMessages] = useState([]);
|
|
45
|
+
const [websocket, setWebsocket] = useState();
|
|
46
|
+
const [instance, setInstance] = useState();
|
|
47
|
+
const kwirthMetricsOptionsRef = useRef({ depth: 10, width: 3, interval: 10, chart: "area", aggregate: false, merge: false, stack: false });
|
|
48
|
+
const [showStatusDialog, setShowStatusDialog] = useState(false);
|
|
49
|
+
const [statusLevel, setStatusLevel] = useState(SignalMessageLevelEnum.INFO);
|
|
50
|
+
const [backendVersion, setBackendVersion] = useState("");
|
|
51
|
+
const [_refresh, setRefresh] = useState(0);
|
|
52
|
+
const [allMetrics, setAllMetrics] = useState(
|
|
53
|
+
[
|
|
54
|
+
{ metric: "kwirth_container_memory_percentage", help: "", eval: "", type: "counter" },
|
|
55
|
+
{ metric: "kwirth_container_cpu_percentage", help: "", eval: "", type: "counter" },
|
|
56
|
+
{ metric: "kwirth_container_transmit_percentage", help: "", eval: "", type: "counter" },
|
|
57
|
+
{ metric: "kwirth_container_receive_percentage", help: "", eval: "", type: "counter" },
|
|
58
|
+
{ metric: "kwirth_container_transmit_mbps", help: "", eval: "", type: "counter" },
|
|
59
|
+
{ metric: "kwirth_container_receive_mbps", help: "", eval: "", type: "counter" }
|
|
60
|
+
]
|
|
61
|
+
);
|
|
62
|
+
const { loading, error } = useAsync(async () => {
|
|
63
|
+
if (backendVersion === "") setBackendVersion(await kwirthMetricsApi.getVersion());
|
|
64
|
+
let reqScopes = [InstanceConfigScopeEnum.STREAM];
|
|
65
|
+
if (props.enableRestart) reqScopes.push(InstanceConfigScopeEnum.RESTART);
|
|
66
|
+
let data = await kwirthMetricsApi.requestAccess(entity, "metrics", reqScopes);
|
|
67
|
+
setClusterValidPods(data);
|
|
68
|
+
});
|
|
69
|
+
const colours = [
|
|
70
|
+
"#6e5bb8",
|
|
71
|
+
// morado oscuro
|
|
72
|
+
"#4a9076",
|
|
73
|
+
// verde oscuro
|
|
74
|
+
"#b56c52",
|
|
75
|
+
// naranja oscuro
|
|
76
|
+
"#7f6b97",
|
|
77
|
+
// color lavanda oscuro
|
|
78
|
+
"#b0528f",
|
|
79
|
+
// rosa oscuro
|
|
80
|
+
"#b0b052",
|
|
81
|
+
// amarillo oscuro
|
|
82
|
+
"#b05252",
|
|
83
|
+
// rojo oscuro
|
|
84
|
+
"#5285b0",
|
|
85
|
+
// azul oscuro
|
|
86
|
+
"#a38ad6",
|
|
87
|
+
// morado pastel
|
|
88
|
+
"#89c1a0",
|
|
89
|
+
// verde pastel
|
|
90
|
+
"#e4a28a",
|
|
91
|
+
// naranja pastel
|
|
92
|
+
"#b09dbd",
|
|
93
|
+
// lavanda pastel
|
|
94
|
+
"#e2a4c6",
|
|
95
|
+
// rosa pastel
|
|
96
|
+
"#c5c89e",
|
|
97
|
+
// amarillo pastel
|
|
98
|
+
"#e2a4a4",
|
|
99
|
+
// rojo pastel
|
|
100
|
+
"#90b7e2",
|
|
101
|
+
// azul pastel
|
|
102
|
+
"#f8d5e1",
|
|
103
|
+
// rosa claro pastel
|
|
104
|
+
"#b2d7f0",
|
|
105
|
+
// azul muy claro pastel
|
|
106
|
+
"#f7e1b5",
|
|
107
|
+
// amarillo muy claro pastel
|
|
108
|
+
"#d0f0c0",
|
|
109
|
+
// verde muy claro pastel
|
|
110
|
+
"#f5b0a1",
|
|
111
|
+
// coral pastel
|
|
112
|
+
"#d8a7db",
|
|
113
|
+
// lavanda muy claro pastel
|
|
114
|
+
"#f4c2c2",
|
|
115
|
+
// rosa suave pastel
|
|
116
|
+
"#e6c7b9",
|
|
117
|
+
// marron claro pastel
|
|
118
|
+
"#f0e2b6",
|
|
119
|
+
// crema pastel
|
|
120
|
+
"#a7c7e7",
|
|
121
|
+
// azul palido pastel
|
|
122
|
+
"#f5e6a5",
|
|
123
|
+
// amarillo palido pastel
|
|
124
|
+
"#e3c8f5",
|
|
125
|
+
// lilas pastel
|
|
126
|
+
"#d0c4e8",
|
|
127
|
+
// lila palido pastel
|
|
128
|
+
"#b8d8b8",
|
|
129
|
+
// verde claro pastel
|
|
130
|
+
"#d2ebfa",
|
|
131
|
+
// azul muy claro pastel
|
|
132
|
+
"#f1c1d2"
|
|
133
|
+
// rosa bebe pastel
|
|
134
|
+
];
|
|
135
|
+
const clickStart = () => {
|
|
136
|
+
if (!paused.current) {
|
|
137
|
+
setMetricsMessages([]);
|
|
138
|
+
setStarted(true);
|
|
139
|
+
paused.current = false;
|
|
140
|
+
setStopped(false);
|
|
141
|
+
startMetricsViewer();
|
|
142
|
+
} else {
|
|
143
|
+
paused.current = false;
|
|
144
|
+
setStarted(true);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const clickPause = () => {
|
|
148
|
+
setStarted(false);
|
|
149
|
+
paused.current = true;
|
|
150
|
+
};
|
|
151
|
+
const clickStop = () => {
|
|
152
|
+
setStarted(false);
|
|
153
|
+
setStopped(true);
|
|
154
|
+
paused.current = false;
|
|
155
|
+
stopMetricsViewer();
|
|
156
|
+
};
|
|
157
|
+
const onSelectCluster = async (clusterName) => {
|
|
158
|
+
if (clusterName) {
|
|
159
|
+
setSelectedClusterName(clusterName);
|
|
160
|
+
setSelectedNamespaces([]);
|
|
161
|
+
setSelectedPodNames([]);
|
|
162
|
+
setSelectedContainerNames([]);
|
|
163
|
+
setMetricsMessages([]);
|
|
164
|
+
setStatusMessages([]);
|
|
165
|
+
clickStop();
|
|
166
|
+
let cluster = clusterValidPods.find((cluster2) => cluster2.name === clusterName);
|
|
167
|
+
if (cluster && cluster.metrics) {
|
|
168
|
+
cluster.metrics.sort((a, b) => a.metric.startsWith("kwirth") ? -1 : 1);
|
|
169
|
+
setAllMetrics(cluster.metrics);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const processMetricsMessage = (wsEvent) => {
|
|
174
|
+
let instanceMessage = JSON.parse(wsEvent.data);
|
|
175
|
+
switch (instanceMessage.type) {
|
|
176
|
+
case InstanceMessageTypeEnum.DATA:
|
|
177
|
+
let metricsMessage = instanceMessage;
|
|
178
|
+
if (metricsMessage.timestamp === 0) {
|
|
179
|
+
metricsMessage.timestamp = Date.now();
|
|
180
|
+
setMetricsMessages([metricsMessage]);
|
|
181
|
+
} else {
|
|
182
|
+
setMetricsMessages((prev) => {
|
|
183
|
+
while (prev.length > kwirthMetricsOptionsRef.current.depth) {
|
|
184
|
+
prev.splice(0, 1);
|
|
185
|
+
}
|
|
186
|
+
if (paused.current)
|
|
187
|
+
return prev;
|
|
188
|
+
else
|
|
189
|
+
return [...prev, metricsMessage];
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case InstanceMessageTypeEnum.SIGNAL:
|
|
194
|
+
if (instanceMessage.flow === InstanceMessageFlowEnum.RESPONSE && instanceMessage.action === InstanceMessageActionEnum.START) {
|
|
195
|
+
if (instanceMessage.instance !== "")
|
|
196
|
+
setInstance(instanceMessage.instance);
|
|
197
|
+
else {
|
|
198
|
+
let signalMessage = instanceMessage;
|
|
199
|
+
alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
let signalMessage = instanceMessage;
|
|
203
|
+
addMessage(signalMessage.level, signalMessage.text);
|
|
204
|
+
switch (signalMessage.level) {
|
|
205
|
+
case SignalMessageLevelEnum.INFO:
|
|
206
|
+
alertApi.post({ message: signalMessage.text, severity: "info", display: "transient" });
|
|
207
|
+
break;
|
|
208
|
+
case SignalMessageLevelEnum.WARNING:
|
|
209
|
+
alertApi.post({ message: signalMessage.text, severity: "warning", display: "transient" });
|
|
210
|
+
break;
|
|
211
|
+
case SignalMessageLevelEnum.ERROR:
|
|
212
|
+
alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" });
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
alertApi.post({ message: signalMessage.text, severity: "success", display: "transient" });
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Invalid message type received: " + instanceMessage.type);
|
|
222
|
+
alertApi.post({ message: "Invalid message type received: " + instanceMessage.type, severity: "error", display: "transient" });
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const websocketOnChunk = (wsEvent) => {
|
|
227
|
+
let instanceMessage;
|
|
228
|
+
try {
|
|
229
|
+
instanceMessage = JSON.parse(wsEvent.data);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.log(err);
|
|
232
|
+
console.log(wsEvent.data);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
switch (instanceMessage.channel) {
|
|
236
|
+
case InstanceMessageChannelEnum.METRICS:
|
|
237
|
+
processMetricsMessage(wsEvent);
|
|
238
|
+
break;
|
|
239
|
+
case InstanceMessageChannelEnum.OPS:
|
|
240
|
+
let opsMessage = instanceMessage;
|
|
241
|
+
if (opsMessage.data?.data)
|
|
242
|
+
addMessage(SignalMessageLevelEnum.WARNING, "Operations message: " + opsMessage.data.data);
|
|
243
|
+
else
|
|
244
|
+
addMessage(SignalMessageLevelEnum.WARNING, "Operations message: " + JSON.stringify(opsMessage));
|
|
245
|
+
break;
|
|
246
|
+
default:
|
|
247
|
+
console.log("Invalid channel in message: ", instanceMessage);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const websocketOnOpen = (ws) => {
|
|
252
|
+
let cluster = clusterValidPods.find((cluster2) => cluster2.name === selectedClusterName);
|
|
253
|
+
if (!cluster) {
|
|
254
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Cluster not found");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
let pods = cluster.data.filter((p2) => selectedNamespaces.includes(p2.namespace));
|
|
258
|
+
if (!pods || pods.length === 0) {
|
|
259
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Pod not found");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
console.log(`WS connected`);
|
|
263
|
+
let accessKey = cluster.accessKeys.get(InstanceConfigScopeEnum.STREAM);
|
|
264
|
+
if (accessKey) {
|
|
265
|
+
let containers = [];
|
|
266
|
+
if (selectedContainerNames.length > 0) {
|
|
267
|
+
for (var p of selectedPodNames) {
|
|
268
|
+
for (var c of selectedContainerNames) {
|
|
269
|
+
containers.push(p + "+" + c);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
var iConfig = {
|
|
274
|
+
channel: InstanceMessageChannelEnum.METRICS,
|
|
275
|
+
objects: InstanceConfigObjectEnum.PODS,
|
|
276
|
+
action: InstanceMessageActionEnum.START,
|
|
277
|
+
flow: InstanceMessageFlowEnum.REQUEST,
|
|
278
|
+
instance: "",
|
|
279
|
+
accessKey: accessKeySerialize(accessKey),
|
|
280
|
+
scope: InstanceConfigScopeEnum.STREAM,
|
|
281
|
+
view: selectedContainerNames.length > 0 ? InstanceConfigViewEnum.CONTAINER : InstanceConfigViewEnum.POD,
|
|
282
|
+
namespace: selectedNamespaces.join(","),
|
|
283
|
+
group: "",
|
|
284
|
+
pod: selectedPodNames.join(","),
|
|
285
|
+
container: containers.join(","),
|
|
286
|
+
data: {
|
|
287
|
+
mode: MetricsConfigModeEnum.STREAM,
|
|
288
|
+
aggregate: kwirthMetricsOptionsRef.current.aggregate,
|
|
289
|
+
metrics: selectedMetrics,
|
|
290
|
+
interval: kwirthMetricsOptionsRef.current.interval
|
|
291
|
+
},
|
|
292
|
+
type: InstanceMessageTypeEnum.SIGNAL
|
|
293
|
+
};
|
|
294
|
+
ws.send(JSON.stringify(iConfig));
|
|
295
|
+
} else {
|
|
296
|
+
addMessage(SignalMessageLevelEnum.ERROR, "AccessKey has not been obtained");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
const startMetricsViewer = () => {
|
|
301
|
+
let cluster = clusterValidPods.find((cluster2) => cluster2.name === selectedClusterName);
|
|
302
|
+
if (!cluster) {
|
|
303
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Cluster not found");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
let ws = new WebSocket(cluster.url);
|
|
308
|
+
ws.onopen = () => websocketOnOpen(ws);
|
|
309
|
+
ws.onmessage = (event) => websocketOnChunk(event);
|
|
310
|
+
ws.onclose = (event) => websocketOnClose(event);
|
|
311
|
+
setWebsocket(ws);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Error starting websocket");
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const websocketOnClose = (_event) => {
|
|
317
|
+
console.log(`WS disconnected from remote`);
|
|
318
|
+
setStarted(false);
|
|
319
|
+
paused.current = false;
|
|
320
|
+
setStopped(true);
|
|
321
|
+
};
|
|
322
|
+
const stopMetricsViewer = () => {
|
|
323
|
+
websocket?.close();
|
|
324
|
+
};
|
|
325
|
+
const onChangeOptions = (options) => {
|
|
326
|
+
kwirthMetricsOptionsRef.current = options;
|
|
327
|
+
setRefresh(Math.random());
|
|
328
|
+
};
|
|
329
|
+
const actionButtons = () => {
|
|
330
|
+
let hasStreamKey = false, hasRestartKey = false;
|
|
331
|
+
let cluster = clusterValidPods.find((cluster2) => cluster2.name === selectedClusterName);
|
|
332
|
+
if (cluster) {
|
|
333
|
+
hasStreamKey = Boolean(cluster.accessKeys.has(InstanceConfigScopeEnum.STREAM));
|
|
334
|
+
hasRestartKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART));
|
|
335
|
+
}
|
|
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: clickStop, title: "Stop", disabled: stopped || selectedPodNames.length === 0 }, /* @__PURE__ */ React.createElement(StopIcon, null)));
|
|
337
|
+
};
|
|
338
|
+
const onMetricsChange = (event) => {
|
|
339
|
+
setSelectedMetrics(event.target.value);
|
|
340
|
+
};
|
|
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
|
+
const statusButtons = (title) => {
|
|
348
|
+
const show = (level) => {
|
|
349
|
+
setShowStatusDialog(true);
|
|
350
|
+
setStatusLevel(level);
|
|
351
|
+
};
|
|
352
|
+
return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, 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" } }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, metricsSelector()));
|
|
353
|
+
};
|
|
354
|
+
const statusClear = (level) => {
|
|
355
|
+
setStatusMessages(statusMessages.filter((m) => m.level !== level));
|
|
356
|
+
setShowStatusDialog(false);
|
|
357
|
+
};
|
|
358
|
+
const mergeSeries = (names, series) => {
|
|
359
|
+
if (!names || names.length === 0) return [];
|
|
360
|
+
var resultSeries = [];
|
|
361
|
+
for (var i = 0; i < series[0].length; i++) {
|
|
362
|
+
var item = {};
|
|
363
|
+
for (var j = 0; j < series.length; j++) {
|
|
364
|
+
if (series[j][i]) {
|
|
365
|
+
item["timestamp"] = series[0][i].timestamp;
|
|
366
|
+
item[names[j]] = series[j][i].value;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
resultSeries.push(item);
|
|
370
|
+
}
|
|
371
|
+
return resultSeries;
|
|
372
|
+
};
|
|
373
|
+
const addChart = (options, metric, names, series, colour) => {
|
|
374
|
+
var result = /* @__PURE__ */ React.createElement(React.Fragment, null);
|
|
375
|
+
var mergedSeries = mergeSeries(names, series);
|
|
376
|
+
const renderLabel = (data) => {
|
|
377
|
+
var values = series.map((s) => s[data.index]);
|
|
378
|
+
var total = values.reduce((acc, value) => acc + value.value, 0);
|
|
379
|
+
return /* @__PURE__ */ React.createElement("text", { x: data.x + data.width / 3.5, y: data.y - 10 }, total.toPrecision(3).replace(/0+$/, ""));
|
|
380
|
+
};
|
|
381
|
+
let height = 300;
|
|
382
|
+
switch (options.chart) {
|
|
383
|
+
case "value":
|
|
384
|
+
height = 40 + series.length * 80;
|
|
385
|
+
result = /* @__PURE__ */ React.createElement(Grid, { direction: "row" }, /* @__PURE__ */ React.createElement(Typography, null, series.map((serie, index) => {
|
|
386
|
+
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
|
+
})));
|
|
388
|
+
break;
|
|
389
|
+
case "line":
|
|
390
|
+
result = /* @__PURE__ */ React.createElement(LineChart, { data: mergedSeries }, /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(XAxis, { dataKey: "timestamp", fontSize: 8 }), /* @__PURE__ */ React.createElement(YAxis, null), /* @__PURE__ */ React.createElement(Tooltip, null), /* @__PURE__ */ React.createElement(Legend, null), series.map((_serie, index) => /* @__PURE__ */ React.createElement(Line, { key: index, name: names[index], type: "monotone", dataKey: names[index], stroke: series.length === 1 ? colour : colours[index], activeDot: { r: 8 } })));
|
|
391
|
+
break;
|
|
392
|
+
case "area":
|
|
393
|
+
result = /* @__PURE__ */ React.createElement(AreaChart, { data: mergedSeries }, /* @__PURE__ */ React.createElement("defs", null, series.map((_serie, index) => {
|
|
394
|
+
return /* @__PURE__ */ React.createElement("linearGradient", { key: index, id: `color${series.length === 1 ? colour : colours[index]}`, x1: "0", y1: "0", x2: "0", y2: "1" }, /* @__PURE__ */ React.createElement("stop", { offset: "7%", stopColor: series.length === 1 ? colour : colours[index], stopOpacity: 0.8 }), /* @__PURE__ */ React.createElement("stop", { offset: "93%", stopColor: series.length === 1 ? colour : colours[index], stopOpacity: 0 }));
|
|
395
|
+
})), /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(XAxis, { dataKey: "timestamp", fontSize: 8 }), /* @__PURE__ */ React.createElement(YAxis, null), /* @__PURE__ */ React.createElement(Tooltip, null), /* @__PURE__ */ React.createElement(Legend, null), series.map((_serie, index) => /* @__PURE__ */ React.createElement(Area, { key: index, name: names[index], type: "monotone", ...options.stack ? { stackId: "1" } : {}, dataKey: names[index], stroke: series.length === 1 ? colour : colours[index], fill: `url(#color${series.length === 1 ? colour : colours[index]})` })));
|
|
396
|
+
break;
|
|
397
|
+
case "bar":
|
|
398
|
+
result = /* @__PURE__ */ React.createElement(BarChart, { data: mergedSeries }, /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(XAxis, { dataKey: "timestamp", fontSize: 8 }), /* @__PURE__ */ React.createElement(YAxis, null), /* @__PURE__ */ React.createElement(Tooltip, null), /* @__PURE__ */ React.createElement(Legend, null), series.map(
|
|
399
|
+
(_serie, index) => /* @__PURE__ */ React.createElement(Bar, { name: names[index], ...options.stack ? { stackId: "1" } : {}, dataKey: names[index], stroke: series.length === 1 ? colour : colours[index], fill: series.length === 1 ? colour : colours[index] }, index === series.length - 1 && series.length > 1 ? /* @__PURE__ */ React.createElement(LabelList, { dataKey: names[index], position: "insideTop", content: renderLabel }) : null)
|
|
400
|
+
));
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
return /* @__PURE__ */ React.createElement(Grid, { direction: "column", style: { width: "100%", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Typography, { align: "center" }, metric), /* @__PURE__ */ React.createElement(ResponsiveContainer, { height, key: metric + JSON.stringify(names) }, result));
|
|
404
|
+
};
|
|
405
|
+
const showMetrics = (options) => {
|
|
406
|
+
if (!metricsMessages || metricsMessages.length === 0) {
|
|
407
|
+
if (selectedNamespaces.length === 0)
|
|
408
|
+
return /* @__PURE__ */ React.createElement(Typography, null, "Select namespace chip on top.");
|
|
409
|
+
else
|
|
410
|
+
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
|
+
}
|
|
412
|
+
let data = /* @__PURE__ */ new Map();
|
|
413
|
+
for (let metricsMessage of metricsMessages) {
|
|
414
|
+
let ts = new Date(metricsMessage.timestamp);
|
|
415
|
+
let timestamp = `${ts.getHours().toString().padStart(2, "0")}:${ts.getMinutes().toString().padStart(2, "0")}:${ts.getSeconds().toString().padStart(2, "0")}`;
|
|
416
|
+
for (var i = 0; i < metricsMessage.assets.length; i++) {
|
|
417
|
+
var assetName = metricsMessage.assets[i].assetName;
|
|
418
|
+
for (var metrics of metricsMessage.assets[i].values) {
|
|
419
|
+
if (!data.has(assetName)) data.set(assetName, /* @__PURE__ */ new Map());
|
|
420
|
+
if (!data.get(assetName)?.has(metrics.metricName)) data.get(assetName)?.set(metrics.metricName, []);
|
|
421
|
+
data.get(assetName)?.get(metrics.metricName)?.push({ timestamp, value: metrics.metricValue });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
let allCharts = [];
|
|
426
|
+
if (options.merge) {
|
|
427
|
+
var assetNames = Array.from(data.keys());
|
|
428
|
+
var firstAsset = assetNames[0];
|
|
429
|
+
var allMetrics2 = Array.from(new Set(data.get(firstAsset).keys()));
|
|
430
|
+
for (let metric of allMetrics2) {
|
|
431
|
+
var series = assetNames.map((an) => {
|
|
432
|
+
return data.get(an).get(metric);
|
|
433
|
+
});
|
|
434
|
+
allCharts.push(/* @__PURE__ */ React.createElement(React.Fragment, null, addChart(options, metric, assetNames, series, "")));
|
|
435
|
+
}
|
|
436
|
+
let rows = [];
|
|
437
|
+
for (let i2 = 0; i2 < allCharts.length; i2 += options.width) {
|
|
438
|
+
rows.push(allCharts.slice(i2, i2 + options.width));
|
|
439
|
+
}
|
|
440
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, rows.map((row, index) => /* @__PURE__ */ React.createElement("div", { key: index, style: { display: "flex", justifyContent: "space-around" } }, row)));
|
|
441
|
+
} else {
|
|
442
|
+
let allCharts2 = Array.from(data.keys()).map((asset, index) => {
|
|
443
|
+
return Array.from(data.get(asset)?.keys()).map((metric) => {
|
|
444
|
+
var serie = data.get(asset)?.get(metric);
|
|
445
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, addChart(options, metric, [asset], [serie], colours[index]));
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
let rows = [];
|
|
449
|
+
for (var resultAsset of allCharts2) {
|
|
450
|
+
for (let i2 = 0; i2 < resultAsset.length; i2 += options.width) {
|
|
451
|
+
rows.push(resultAsset.slice(i2, i2 + options.width));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, rows.map((row, index) => /* @__PURE__ */ React.createElement("div", { key: index, style: { display: "flex", justifyContent: "space-around" } }, row)));
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const onSelectObject = (namespaces, podNames, containerNames) => {
|
|
458
|
+
setSelectedNamespaces(namespaces);
|
|
459
|
+
setSelectedPodNames(podNames);
|
|
460
|
+
setSelectedContainerNames(containerNames);
|
|
461
|
+
};
|
|
462
|
+
const addMessage = (level, text) => {
|
|
463
|
+
setStatusMessages((prev) => [...prev, {
|
|
464
|
+
level,
|
|
465
|
+
text,
|
|
466
|
+
type: InstanceMessageTypeEnum.SIGNAL
|
|
467
|
+
}]);
|
|
468
|
+
};
|
|
469
|
+
const onClickRestart = () => {
|
|
470
|
+
let cluster = clusterValidPods.find((cluster2) => cluster2.name === selectedClusterName);
|
|
471
|
+
if (!cluster) {
|
|
472
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
let restartKey = cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART);
|
|
476
|
+
if (!restartKey) {
|
|
477
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No access key present");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (!instance) {
|
|
481
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No instance has been established");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
let pods = cluster.data.filter((pod) => selectedNamespaces.includes(pod.namespace));
|
|
485
|
+
for (let pod of pods) {
|
|
486
|
+
let om = {
|
|
487
|
+
msgtype: "opsmessage",
|
|
488
|
+
action: InstanceMessageActionEnum.COMMAND,
|
|
489
|
+
flow: InstanceMessageFlowEnum.IMMEDIATE,
|
|
490
|
+
type: InstanceMessageTypeEnum.DATA,
|
|
491
|
+
channel: InstanceMessageChannelEnum.OPS,
|
|
492
|
+
instance: "",
|
|
493
|
+
id: "1",
|
|
494
|
+
accessKey: accessKeySerialize(restartKey),
|
|
495
|
+
command: OpsCommandEnum.RESTARTPOD,
|
|
496
|
+
namespace: pod.namespace,
|
|
497
|
+
group: "",
|
|
498
|
+
pod: pod.name,
|
|
499
|
+
container: ""
|
|
500
|
+
};
|
|
501
|
+
let rm = {
|
|
502
|
+
msgtype: "routemessage",
|
|
503
|
+
accessKey: accessKeySerialize(restartKey),
|
|
504
|
+
destChannel: InstanceMessageChannelEnum.OPS,
|
|
505
|
+
action: InstanceMessageActionEnum.ROUTE,
|
|
506
|
+
flow: InstanceMessageFlowEnum.IMMEDIATE,
|
|
507
|
+
type: InstanceMessageTypeEnum.SIGNAL,
|
|
508
|
+
channel: InstanceMessageChannelEnum.METRICS,
|
|
509
|
+
instance,
|
|
510
|
+
data: om
|
|
511
|
+
};
|
|
512
|
+
websocket?.send(JSON.stringify(rm));
|
|
513
|
+
}
|
|
514
|
+
};
|
|
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_KWIRTH_LOCATION }), isKwirthAvailable(entity) && !loading && clusterValidPods && clusterValidPods.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && clusterValidPods && clusterValidPods.length > 0 && clusterValidPods.reduce((sum, cluster) => sum + cluster.data.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && clusterValidPods && clusterValidPods.length > 0 && clusterValidPods.reduce((sum, cluster) => sum + cluster.data.length, 0) > 0 && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex" } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "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: clusterValidPods, selectedClusterName, onSelect: onSelectCluster }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(Options, { options: kwirthMetricsOptionsRef.current, selectedNamespaces, selectedPodNames, selectedContainerNames, onChange: onChangeOptions, disabled: selectedNamespaces.length === 0 || paused.current }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, p: 1 } }, !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(
|
|
516
|
+
CardHeader,
|
|
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 }));
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
export { EntityKwirthMetricsContent };
|
|
526
|
+
//# sourceMappingURL=EntityKwirthMetricsContent.esm.js.map
|