@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.
Files changed (31) hide show
  1. package/README.md +275 -0
  2. package/dist/api/KwirthMetricsClient.esm.js +74 -0
  3. package/dist/api/KwirthMetricsClient.esm.js.map +1 -0
  4. package/dist/api/types.esm.js +8 -0
  5. package/dist/api/types.esm.js.map +1 -0
  6. package/dist/assets/kwirthmetrics-component-not-found.svg +29 -0
  7. package/dist/assets/kwirthmetrics-logo.svg +15 -0
  8. package/dist/components/ClusterList/ClusterList.esm.js +30 -0
  9. package/dist/components/ClusterList/ClusterList.esm.js.map +1 -0
  10. package/dist/components/ComponentNotFound/ComponentNotFound.esm.js +60 -0
  11. package/dist/components/ComponentNotFound/ComponentNotFound.esm.js.map +1 -0
  12. package/dist/components/EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js +526 -0
  13. package/dist/components/EntityKwirthMetricsContent/EntityKwirthMetricsContent.esm.js.map +1 -0
  14. package/dist/components/EntityKwirthMetricsContent/index.esm.js +2 -0
  15. package/dist/components/EntityKwirthMetricsContent/index.esm.js.map +1 -0
  16. package/dist/components/ObjectSelector/ObjectSelector.esm.js +82 -0
  17. package/dist/components/ObjectSelector/ObjectSelector.esm.js.map +1 -0
  18. package/dist/components/Options/Options.esm.js +28 -0
  19. package/dist/components/Options/Options.esm.js.map +1 -0
  20. package/dist/components/ShowError/ShowError.esm.js +20 -0
  21. package/dist/components/ShowError/ShowError.esm.js.map +1 -0
  22. package/dist/components/StatusLog/StatusLog.esm.js +10 -0
  23. package/dist/components/StatusLog/StatusLog.esm.js.map +1 -0
  24. package/dist/index.d.ts +40 -0
  25. package/dist/index.esm.js +4 -0
  26. package/dist/index.esm.js.map +1 -0
  27. package/dist/plugin.esm.js +33 -0
  28. package/dist/plugin.esm.js.map +1 -0
  29. package/dist/routes.esm.js +8 -0
  30. package/dist/routes.esm.js.map +1 -0
  31. 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