andoncloud-prometheus-widget 1.3.18 → 1.3.19
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 +36 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +274 -97
- package/dist/index.js.map +1 -1
- package/package.json +31 -29
- package/dist/index.cjs +0 -628
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -42
package/dist/index.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { t as thumbnail_default } from "./thumbnail-B_DBfYD0.js";
|
|
2
2
|
import { registerTranslations } from "andoncloud-sdk";
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { generateTimeRange, getCurrentShift, getRecentShift, normalizeShifts, useGqlClients } from "andoncloud-dashboard-toolkit";
|
|
5
5
|
import { BaseWidget } from "andoncloud-widget-base";
|
|
6
6
|
import { useTranslation } from "react-i18next";
|
|
7
7
|
import { Clear } from "@mui/icons-material";
|
|
8
8
|
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
|
9
|
-
import { Box, Button, FormControl, FormHelperText, IconButton, InputLabel, MenuItem, Select, Stack, Tab, TextField } from "@mui/material";
|
|
9
|
+
import { Alert, Box, Button, Divider, FormControl, FormHelperText, IconButton, InputLabel, MenuItem, Select, Stack, Tab, TextField, Typography } from "@mui/material";
|
|
10
10
|
import { getIn } from "formik";
|
|
11
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import "chartjs-adapter-dayjs-4";
|
|
13
13
|
import { Line } from "react-chartjs-2";
|
|
14
14
|
import { CategoryScale, Chart, Filler, Legend, LineElement, LinearScale, PointElement, TimeScale, Tooltip } from "chart.js";
|
|
15
15
|
import dayjs from "dayjs";
|
|
16
|
-
import pick from "lodash.pick";
|
|
17
16
|
import { PrometheusDriver } from "prometheus-query";
|
|
18
17
|
import Color from "color";
|
|
19
18
|
import { satisfies } from "compare-versions";
|
|
@@ -26,51 +25,73 @@ const resources = {
|
|
|
26
25
|
"addAnotherDisplayedQueryParameter": "Add another displayed query parameter",
|
|
27
26
|
"addAnotherQuery": "Add another query",
|
|
28
27
|
"advanced": "Advanced",
|
|
28
|
+
"credentialsHelper": "Credentials are optional. Fill in only if the instance requires authentication.",
|
|
29
29
|
"currentShift": "Current shift",
|
|
30
30
|
"customTitle": "Custom title",
|
|
31
31
|
"displayedQueryParameter": "Displayed query parameter",
|
|
32
|
+
"displayName": "PromQL chart",
|
|
32
33
|
"endpointUrl": "Endpoint URL",
|
|
34
|
+
"errorAuth": "Could not authenticate to Prometheus. Check the username and password in widget settings.",
|
|
35
|
+
"errorForbidden": "Authenticated, but the Prometheus user has no permission to read these metrics.",
|
|
36
|
+
"errorUnreachable": "Could not reach Prometheus at the configured URL.",
|
|
37
|
+
"insecureWarning": "Credentials will be sent over an unencrypted connection (HTTP). Use HTTPS to protect them.",
|
|
33
38
|
"name": "Name",
|
|
39
|
+
"password": "Password",
|
|
34
40
|
"period": "Period",
|
|
35
41
|
"previousShift": "Previous shift",
|
|
36
42
|
"query": "Query",
|
|
43
|
+
"removeParam": "Remove parameter",
|
|
44
|
+
"removeQuery": "Remove query",
|
|
45
|
+
"sectionAuthorization": "Authorization",
|
|
46
|
+
"sectionData": "Data",
|
|
37
47
|
"settings": "Settings",
|
|
38
48
|
"thisFieldIsRequired": "This field is required",
|
|
39
|
-
"xAxisUnit": "X axis unit",
|
|
40
|
-
"displayName": "PromQL chart",
|
|
41
|
-
"titleQueries_one": "{{count}} query",
|
|
42
|
-
"titleQueries_other": "{{count}} queries",
|
|
43
49
|
"titleCurrentShift": "current shift",
|
|
44
50
|
"titlePreviousShift": "previous shift",
|
|
51
|
+
"titleQueries_one": "{{count}} query",
|
|
52
|
+
"titleQueries_other": "{{count}} queries",
|
|
53
|
+
"username": "Username",
|
|
54
|
+
"xAxisUnit": "X axis unit",
|
|
45
55
|
"yAxisUnit": "Y axis unit"
|
|
46
56
|
} } },
|
|
47
57
|
pl: { translation: { prometheusWidget: {
|
|
48
58
|
"addAnotherDisplayedQueryParameter": "Dodaj kolejny wyświetlany parametr zapytania",
|
|
49
59
|
"addAnotherQuery": "Dodaj kolejne zapytanie",
|
|
50
60
|
"advanced": "Zaawansowane",
|
|
61
|
+
"credentialsHelper": "Dane autoryzacyjne są opcjonalne. Wypełnij tylko jeśli instancja wymaga uwierzytelniania.",
|
|
51
62
|
"currentShift": "Obecna zmiana",
|
|
52
63
|
"customTitle": "Własny tytuł",
|
|
53
64
|
"displayedQueryParameter": "Wyświetlany parametr zapytania",
|
|
65
|
+
"displayName": "Wykres PromQL",
|
|
54
66
|
"endpointUrl": "Adres URL",
|
|
67
|
+
"errorAuth": "Nie można uwierzytelnić się w Prometheusie. Sprawdź nazwę użytkownika i hasło w ustawieniach widgetu.",
|
|
68
|
+
"errorForbidden": "Uwierzytelnienie powiodło się, ale użytkownik Prometheusa nie ma uprawnień do tych metryk.",
|
|
69
|
+
"errorUnreachable": "Nie można połączyć się z Prometheusem pod skonfigurowanym adresem.",
|
|
70
|
+
"insecureWarning": "Dane logowania zostaną wysłane nieszyfrowanym połączeniem (HTTP). Użyj HTTPS, aby je chronić.",
|
|
55
71
|
"name": "Nazwa",
|
|
72
|
+
"password": "Hasło",
|
|
56
73
|
"period": "Okres",
|
|
57
74
|
"previousShift": "Poprzednia zmiana",
|
|
58
75
|
"query": "Zapytanie",
|
|
76
|
+
"removeParam": "Usuń parametr",
|
|
77
|
+
"removeQuery": "Usuń zapytanie",
|
|
78
|
+
"sectionAuthorization": "Autoryzacja",
|
|
79
|
+
"sectionData": "Dane",
|
|
59
80
|
"settings": "Ustawienia",
|
|
60
81
|
"thisFieldIsRequired": "To pole jest wymagane",
|
|
61
|
-
"xAxisUnit": "Jednostka osi X",
|
|
62
|
-
"displayName": "Wykres PromQL",
|
|
63
|
-
"titleQueries_one": "{{count}} zapytanie",
|
|
64
|
-
"titleQueries_few": "{{count}} zapytania",
|
|
65
|
-
"titleQueries_many": "{{count}} zapytań",
|
|
66
82
|
"titleCurrentShift": "obecna zmiana",
|
|
67
83
|
"titlePreviousShift": "poprzednia zmiana",
|
|
84
|
+
"titleQueries_few": "{{count}} zapytania",
|
|
85
|
+
"titleQueries_many": "{{count}} zapytań",
|
|
86
|
+
"titleQueries_one": "{{count}} zapytanie",
|
|
87
|
+
"username": "Nazwa użytkownika",
|
|
88
|
+
"xAxisUnit": "Jednostka osi X",
|
|
68
89
|
"yAxisUnit": "Jednostka osi Y"
|
|
69
90
|
} } }
|
|
70
91
|
};
|
|
71
92
|
//#endregion
|
|
72
93
|
//#region src/version.ts
|
|
73
|
-
const LIBRARY_VERSION = "1.3.
|
|
94
|
+
const LIBRARY_VERSION = "1.3.19";
|
|
74
95
|
//#endregion
|
|
75
96
|
//#region src/types.ts
|
|
76
97
|
let QueriesPeriod = /* @__PURE__ */ function(QueriesPeriod) {
|
|
@@ -79,14 +100,87 @@ let QueriesPeriod = /* @__PURE__ */ function(QueriesPeriod) {
|
|
|
79
100
|
return QueriesPeriod;
|
|
80
101
|
}({});
|
|
81
102
|
//#endregion
|
|
103
|
+
//#region src/components/SettingsFormContent/utils.ts
|
|
104
|
+
const isInsecureEndpoint = (url) => /^http:\/\//i.test(url);
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/components/SettingsFormContent/styles.ts
|
|
107
|
+
const styles = {
|
|
108
|
+
sectionDivider: {
|
|
109
|
+
marginTop: 3,
|
|
110
|
+
marginBottom: 1,
|
|
111
|
+
color: (theme) => theme.palette.text.primary,
|
|
112
|
+
fontWeight: 600,
|
|
113
|
+
letterSpacing: "0.08em",
|
|
114
|
+
textTransform: "uppercase",
|
|
115
|
+
fontSize: "0.75rem",
|
|
116
|
+
"&::before, &::after": { borderColor: "rgba(255, 255, 255, 0.15)" }
|
|
117
|
+
},
|
|
118
|
+
formControl: { marginTop: 2 },
|
|
119
|
+
addButton: { marginTop: 2 },
|
|
120
|
+
credentialsHelper: {
|
|
121
|
+
marginTop: 1,
|
|
122
|
+
color: (theme) => theme.palette.text.primary
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/components/SettingsFormContent/CredentialsFields/index.tsx
|
|
127
|
+
const CredentialsFields = ({ formProps }) => {
|
|
128
|
+
const [passwordInput, setPasswordInput] = useState("");
|
|
129
|
+
const { t } = useTranslation();
|
|
130
|
+
const handlePasswordChange = (event) => {
|
|
131
|
+
setPasswordInput(event.target.value);
|
|
132
|
+
formProps.setFieldValue("password", event.target.value);
|
|
133
|
+
};
|
|
134
|
+
const showInsecureWarning = isInsecureEndpoint(formProps.values.endpointUrl);
|
|
135
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
136
|
+
/* @__PURE__ */ jsx(FormHelperText, {
|
|
137
|
+
sx: styles.credentialsHelper,
|
|
138
|
+
"data-testid": "prometheus.settings.credentials-helper",
|
|
139
|
+
children: t("prometheusWidget.credentialsHelper")
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ jsx(FormControl, {
|
|
142
|
+
sx: styles.formControl,
|
|
143
|
+
fullWidth: true,
|
|
144
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
145
|
+
name: "username",
|
|
146
|
+
label: t("prometheusWidget.username"),
|
|
147
|
+
value: formProps.values.username || "",
|
|
148
|
+
onChange: formProps.handleChange,
|
|
149
|
+
onBlur: formProps.handleBlur,
|
|
150
|
+
"data-testid": "prometheus.settings.username",
|
|
151
|
+
fullWidth: true
|
|
152
|
+
})
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsx(FormControl, {
|
|
155
|
+
sx: styles.formControl,
|
|
156
|
+
fullWidth: true,
|
|
157
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
158
|
+
name: "password",
|
|
159
|
+
type: "password",
|
|
160
|
+
label: t("prometheusWidget.password"),
|
|
161
|
+
value: passwordInput,
|
|
162
|
+
onChange: handlePasswordChange,
|
|
163
|
+
onBlur: formProps.handleBlur,
|
|
164
|
+
"data-testid": "prometheus.settings.password",
|
|
165
|
+
fullWidth: true
|
|
166
|
+
})
|
|
167
|
+
}),
|
|
168
|
+
showInsecureWarning && /* @__PURE__ */ jsx(Alert, {
|
|
169
|
+
severity: "warning",
|
|
170
|
+
sx: styles.formControl,
|
|
171
|
+
"data-testid": "prometheus.settings.insecure-warning",
|
|
172
|
+
children: t("prometheusWidget.insecureWarning")
|
|
173
|
+
})
|
|
174
|
+
] });
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
82
177
|
//#region src/components/SettingsFormContent/index.tsx
|
|
178
|
+
const emptyQueryItem = {
|
|
179
|
+
query: "",
|
|
180
|
+
displayName: ""
|
|
181
|
+
};
|
|
83
182
|
const SettingsFormContent = ({ formProps }) => {
|
|
84
183
|
const [selectedTab, setSelectedTab] = useState("settings");
|
|
85
|
-
const emptyQueryItem = {
|
|
86
|
-
query: "",
|
|
87
|
-
displayName: ""
|
|
88
|
-
};
|
|
89
|
-
const formControlStyles = { mt: 2 };
|
|
90
184
|
const { t } = useTranslation();
|
|
91
185
|
const setPrometheusQuery = (i, item) => {
|
|
92
186
|
const queries = [...formProps.values.prometheusQueries || [emptyQueryItem]];
|
|
@@ -121,7 +215,7 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
121
215
|
};
|
|
122
216
|
const mapPeriodToLabel = (period) => {
|
|
123
217
|
switch (period) {
|
|
124
|
-
case
|
|
218
|
+
case "PREVIOUS_SHIFT": return t("prometheusWidget.previousShift");
|
|
125
219
|
default: return t("prometheusWidget.currentShift");
|
|
126
220
|
}
|
|
127
221
|
};
|
|
@@ -145,7 +239,7 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
145
239
|
mb: 2,
|
|
146
240
|
children: [
|
|
147
241
|
/* @__PURE__ */ jsx(FormControl, {
|
|
148
|
-
sx:
|
|
242
|
+
sx: styles.formControl,
|
|
149
243
|
fullWidth: true,
|
|
150
244
|
children: /* @__PURE__ */ jsx(TextField, {
|
|
151
245
|
name: "endpointUrl",
|
|
@@ -159,8 +253,17 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
159
253
|
fullWidth: true
|
|
160
254
|
})
|
|
161
255
|
}),
|
|
256
|
+
/* @__PURE__ */ jsx(Divider, {
|
|
257
|
+
sx: styles.sectionDivider,
|
|
258
|
+
children: t("prometheusWidget.sectionAuthorization")
|
|
259
|
+
}),
|
|
260
|
+
/* @__PURE__ */ jsx(CredentialsFields, { formProps }),
|
|
261
|
+
/* @__PURE__ */ jsx(Divider, {
|
|
262
|
+
sx: styles.sectionDivider,
|
|
263
|
+
children: t("prometheusWidget.sectionData")
|
|
264
|
+
}),
|
|
162
265
|
/* @__PURE__ */ jsxs(FormControl, {
|
|
163
|
-
sx:
|
|
266
|
+
sx: styles.formControl,
|
|
164
267
|
fullWidth: true,
|
|
165
268
|
children: [
|
|
166
269
|
/* @__PURE__ */ jsx(InputLabel, {
|
|
@@ -171,16 +274,16 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
171
274
|
variant: "outlined",
|
|
172
275
|
name: "queriesPeriod",
|
|
173
276
|
labelId: "queries-period-label",
|
|
174
|
-
value: formProps.values.queriesPeriod ||
|
|
277
|
+
value: formProps.values.queriesPeriod || "CURRENT_SHIFT",
|
|
175
278
|
onChange: formProps.handleChange,
|
|
176
279
|
onBlur: formProps.handleBlur,
|
|
177
280
|
error: formProps.touched.queriesPeriod && Boolean(formProps.errors.queriesPeriod),
|
|
178
281
|
"data-testid": "prometheus.settings.period-select",
|
|
179
282
|
fullWidth: true,
|
|
180
|
-
children: Object.
|
|
181
|
-
value
|
|
182
|
-
children: mapPeriodToLabel(
|
|
183
|
-
},
|
|
283
|
+
children: Object.values(QueriesPeriod).map((value) => /* @__PURE__ */ jsx(MenuItem, {
|
|
284
|
+
value,
|
|
285
|
+
children: mapPeriodToLabel(value)
|
|
286
|
+
}, value))
|
|
184
287
|
}),
|
|
185
288
|
/* @__PURE__ */ jsx(FormHelperText, {
|
|
186
289
|
error: true,
|
|
@@ -189,7 +292,7 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
189
292
|
]
|
|
190
293
|
}),
|
|
191
294
|
(formProps.values.prometheusQueries || [emptyQueryItem]).map(({ query, displayName }, i) => /* @__PURE__ */ jsx(FormControl, {
|
|
192
|
-
sx:
|
|
295
|
+
sx: styles.formControl,
|
|
193
296
|
fullWidth: true,
|
|
194
297
|
children: /* @__PURE__ */ jsxs(Stack, {
|
|
195
298
|
direction: "row",
|
|
@@ -221,6 +324,7 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
221
324
|
}),
|
|
222
325
|
/* @__PURE__ */ jsx(IconButton, {
|
|
223
326
|
color: "error",
|
|
327
|
+
"aria-label": t("prometheusWidget.removeQuery"),
|
|
224
328
|
onClick: () => removeQueryField(i),
|
|
225
329
|
"data-testid": `prometheus.settings.remove-query-${i}`,
|
|
226
330
|
children: /* @__PURE__ */ jsx(Clear, {})
|
|
@@ -231,13 +335,13 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
231
335
|
/* @__PURE__ */ jsx(Button, {
|
|
232
336
|
color: "secondary",
|
|
233
337
|
onClick: addQueryField,
|
|
234
|
-
sx:
|
|
338
|
+
sx: styles.addButton,
|
|
235
339
|
"data-testid": "prometheus.settings.add-query",
|
|
236
340
|
fullWidth: true,
|
|
237
341
|
children: t("prometheusWidget.addAnotherQuery")
|
|
238
342
|
}),
|
|
239
343
|
(formProps.values.displayedQueryParams || [""]).map((param, i) => /* @__PURE__ */ jsx(FormControl, {
|
|
240
|
-
sx:
|
|
344
|
+
sx: styles.formControl,
|
|
241
345
|
fullWidth: true,
|
|
242
346
|
children: /* @__PURE__ */ jsxs(Stack, {
|
|
243
347
|
direction: "row",
|
|
@@ -246,12 +350,13 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
246
350
|
label: t("prometheusWidget.displayedQueryParameter"),
|
|
247
351
|
value: param,
|
|
248
352
|
onChange: (e) => setDisplayedQueryParam(i, e.target.value),
|
|
249
|
-
error: formProps.touched.displayedQueryParams
|
|
250
|
-
helperText: formProps.touched.displayedQueryParams
|
|
353
|
+
error: Boolean(Array.isArray(formProps.touched.displayedQueryParams) && formProps.touched.displayedQueryParams[i] && getIn(formProps.errors, `displayedQueryParams.[${i}]`)),
|
|
354
|
+
helperText: Array.isArray(formProps.touched.displayedQueryParams) && formProps.touched.displayedQueryParams[i] ? getIn(formProps.errors, `displayedQueryParams.[${i}]`) : void 0,
|
|
251
355
|
"data-testid": `prometheus.settings.param-input-${i}`,
|
|
252
356
|
fullWidth: true
|
|
253
357
|
}), /* @__PURE__ */ jsx(IconButton, {
|
|
254
358
|
color: "error",
|
|
359
|
+
"aria-label": t("prometheusWidget.removeParam"),
|
|
255
360
|
onClick: () => removeDisplayedQueryParam(i),
|
|
256
361
|
"data-testid": `prometheus.settings.remove-param-${i}`,
|
|
257
362
|
children: /* @__PURE__ */ jsx(Clear, {})
|
|
@@ -261,13 +366,13 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
261
366
|
/* @__PURE__ */ jsx(Button, {
|
|
262
367
|
color: "secondary",
|
|
263
368
|
onClick: addDisplayedQueryParam,
|
|
264
|
-
sx:
|
|
369
|
+
sx: styles.addButton,
|
|
265
370
|
"data-testid": "prometheus.settings.add-param",
|
|
266
371
|
fullWidth: true,
|
|
267
372
|
children: t("prometheusWidget.addAnotherDisplayedQueryParameter")
|
|
268
373
|
}),
|
|
269
374
|
/* @__PURE__ */ jsx(FormControl, {
|
|
270
|
-
sx:
|
|
375
|
+
sx: styles.formControl,
|
|
271
376
|
fullWidth: true,
|
|
272
377
|
children: /* @__PURE__ */ jsx(TextField, {
|
|
273
378
|
name: "xAxisUnit",
|
|
@@ -282,7 +387,7 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
282
387
|
})
|
|
283
388
|
}),
|
|
284
389
|
/* @__PURE__ */ jsx(FormControl, {
|
|
285
|
-
sx:
|
|
390
|
+
sx: styles.formControl,
|
|
286
391
|
fullWidth: true,
|
|
287
392
|
children: /* @__PURE__ */ jsx(TextField, {
|
|
288
393
|
name: "yAxisUnit",
|
|
@@ -315,13 +420,21 @@ const SettingsFormContent = ({ formProps }) => {
|
|
|
315
420
|
};
|
|
316
421
|
//#endregion
|
|
317
422
|
//#region src/helpers.ts
|
|
423
|
+
const classifyFetchError = (err) => {
|
|
424
|
+
const e = err;
|
|
425
|
+
const status = e?.httpStatus ?? e?.response?.status;
|
|
426
|
+
if (status === 401) return "auth";
|
|
427
|
+
if (status === 403) return "forbidden";
|
|
428
|
+
return "unreachable";
|
|
429
|
+
};
|
|
430
|
+
const pickKeys = (obj, keys) => Object.fromEntries(keys.filter((k) => k in obj).map((k) => [k, obj[k]]));
|
|
318
431
|
const getQueriesTimeRange = (period, data) => {
|
|
319
432
|
const currentDate = dayjs();
|
|
320
433
|
const normalizedShifts = normalizeShifts(data?.shifts, currentDate);
|
|
321
434
|
const currentShift = getCurrentShift(currentDate, normalizedShifts);
|
|
322
435
|
const recentShift = getRecentShift(currentDate, normalizedShifts);
|
|
323
436
|
switch (period) {
|
|
324
|
-
case
|
|
437
|
+
case "CURRENT_SHIFT":
|
|
325
438
|
if (currentShift) return {
|
|
326
439
|
startedAt: currentShift.startedAt.toDate(),
|
|
327
440
|
finishedAt: currentShift.finishedAt.toDate()
|
|
@@ -331,7 +444,7 @@ const getQueriesTimeRange = (period, data) => {
|
|
|
331
444
|
finishedAt: recentShift.finishedAt.toDate()
|
|
332
445
|
};
|
|
333
446
|
return null;
|
|
334
|
-
case
|
|
447
|
+
case "PREVIOUS_SHIFT":
|
|
335
448
|
if (recentShift) return {
|
|
336
449
|
startedAt: recentShift.startedAt.toDate(),
|
|
337
450
|
finishedAt: recentShift.finishedAt.toDate()
|
|
@@ -341,6 +454,21 @@ const getQueriesTimeRange = (period, data) => {
|
|
|
341
454
|
}
|
|
342
455
|
};
|
|
343
456
|
//#endregion
|
|
457
|
+
//#region src/components/PrometheusChart/constants.ts
|
|
458
|
+
const COLORS = [
|
|
459
|
+
"#36A2EB",
|
|
460
|
+
"#FF6384",
|
|
461
|
+
"#4BC0C0",
|
|
462
|
+
"#FF9F40",
|
|
463
|
+
"#9966FF",
|
|
464
|
+
"#FFCD56",
|
|
465
|
+
"#C9CBCF",
|
|
466
|
+
"#7BC8A4",
|
|
467
|
+
"#E7E9ED",
|
|
468
|
+
"#FF6B6B"
|
|
469
|
+
];
|
|
470
|
+
const DEFAULT_FONT_COLOR = "rgba(255, 255, 255, 0.75)";
|
|
471
|
+
//#endregion
|
|
344
472
|
//#region src/components/PrometheusChart/GradientPlugin.ts
|
|
345
473
|
const createGradient = (ctx, area) => {
|
|
346
474
|
return ctx.createLinearGradient(0, area.bottom, 0, area.top);
|
|
@@ -368,31 +496,49 @@ const GradientPlugin = {
|
|
|
368
496
|
//#region src/components/PrometheusChart/index.tsx
|
|
369
497
|
Chart.register(CategoryScale, LinearScale, TimeScale, PointElement, LineElement, Filler, Legend, Tooltip);
|
|
370
498
|
Chart.defaults.font.size = 12;
|
|
371
|
-
Chart.defaults.color =
|
|
372
|
-
const
|
|
373
|
-
"
|
|
374
|
-
"
|
|
375
|
-
"
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"
|
|
379
|
-
"
|
|
380
|
-
"
|
|
381
|
-
"
|
|
382
|
-
"
|
|
383
|
-
|
|
499
|
+
Chart.defaults.color = DEFAULT_FONT_COLOR;
|
|
500
|
+
const errorTranslationKey = {
|
|
501
|
+
auth: "prometheusWidget.errorAuth",
|
|
502
|
+
forbidden: "prometheusWidget.errorForbidden",
|
|
503
|
+
unreachable: "prometheusWidget.errorUnreachable"
|
|
504
|
+
};
|
|
505
|
+
const errorMessageStyles = {
|
|
506
|
+
width: "100%",
|
|
507
|
+
height: "100%",
|
|
508
|
+
display: "flex",
|
|
509
|
+
alignItems: "center",
|
|
510
|
+
justifyContent: "center",
|
|
511
|
+
textAlign: "center",
|
|
512
|
+
padding: 2
|
|
513
|
+
};
|
|
384
514
|
const PrometheusChart = ({ data, settings }) => {
|
|
385
515
|
const chart = useRef(null);
|
|
516
|
+
const hiddenSeriesRef = useRef({});
|
|
517
|
+
const fetchGenRef = useRef(0);
|
|
386
518
|
const [chartData, setChartData] = useState({
|
|
387
519
|
labels: [],
|
|
388
520
|
datasets: []
|
|
389
521
|
});
|
|
390
522
|
const [queriesTimeRange, setQueriesTimeRange] = useState(null);
|
|
391
523
|
const [timeLabels, setTimeLabels] = useState([]);
|
|
392
|
-
const
|
|
524
|
+
const [error, setError] = useState(null);
|
|
525
|
+
const driver = useMemo(() => {
|
|
526
|
+
const options = { endpoint: settings.endpointUrl };
|
|
527
|
+
if (settings.username && settings.password) options.auth = {
|
|
528
|
+
username: settings.username,
|
|
529
|
+
password: settings.password
|
|
530
|
+
};
|
|
531
|
+
return new PrometheusDriver(options);
|
|
532
|
+
}, [
|
|
533
|
+
settings.endpointUrl,
|
|
534
|
+
settings.username,
|
|
535
|
+
settings.password
|
|
536
|
+
]);
|
|
537
|
+
const { t } = useTranslation();
|
|
393
538
|
const fetchData = useCallback(async (timeRange) => {
|
|
394
539
|
if (!settings.endpointUrl || !settings.prometheusQueries?.length) return;
|
|
395
|
-
|
|
540
|
+
fetchGenRef.current += 1;
|
|
541
|
+
const gen = fetchGenRef.current;
|
|
396
542
|
const start = new Date(timeRange.startedAt);
|
|
397
543
|
const end = timeRange.finishedAt ? new Date(timeRange.finishedAt) : /* @__PURE__ */ new Date();
|
|
398
544
|
const step = 30;
|
|
@@ -400,39 +546,51 @@ const PrometheusChart = ({ data, settings }) => {
|
|
|
400
546
|
const ds = chart.current.data.datasets[i];
|
|
401
547
|
if (ds.label) hiddenSeriesRef.current[ds.label] = !chart.current.isDatasetVisible(i);
|
|
402
548
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
549
|
+
try {
|
|
550
|
+
const queries = settings.prometheusQueries.map(({ query }) => query);
|
|
551
|
+
const results = await Promise.all(queries.map((query) => driver.rangeQuery(query, start, end, step)));
|
|
552
|
+
if (gen !== fetchGenRef.current) return;
|
|
553
|
+
let seriesIndex = 0;
|
|
554
|
+
setChartData({
|
|
555
|
+
labels: [],
|
|
556
|
+
datasets: results.flatMap((result, queryIndex) => result.result.map((serie) => {
|
|
557
|
+
const idx = seriesIndex++;
|
|
558
|
+
const queryConfig = settings.prometheusQueries[queryIndex];
|
|
559
|
+
const metricLabels = serie.metric.labels || {};
|
|
560
|
+
const filteredValues = Object.values(pickKeys(metricLabels, settings.displayedQueryParams)).join(" - ");
|
|
561
|
+
let label;
|
|
562
|
+
if (queryConfig?.displayName) label = filteredValues ? `${queryConfig.displayName} - ${filteredValues}` : queryConfig.displayName;
|
|
563
|
+
else {
|
|
564
|
+
const metricName = serie.metric.name || queryConfig?.query || "";
|
|
565
|
+
label = filteredValues ? `${metricName} ${JSON.stringify(pickKeys(metricLabels, settings.displayedQueryParams))}` : metricName;
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
label,
|
|
569
|
+
data: serie.values.map((v) => ({
|
|
570
|
+
x: v.time,
|
|
571
|
+
y: v.value
|
|
572
|
+
})),
|
|
573
|
+
fill: true,
|
|
574
|
+
tension: .4,
|
|
575
|
+
borderColor: COLORS[idx % COLORS.length],
|
|
576
|
+
backgroundColor: COLORS[idx % COLORS.length],
|
|
577
|
+
borderWidth: 3,
|
|
578
|
+
pointRadius: 0,
|
|
579
|
+
hidden: hiddenSeriesRef.current[label] || false
|
|
580
|
+
};
|
|
581
|
+
}))
|
|
582
|
+
});
|
|
583
|
+
setError(null);
|
|
584
|
+
} catch (err) {
|
|
585
|
+
if (gen !== fetchGenRef.current) return;
|
|
586
|
+
setError(classifyFetchError(err));
|
|
587
|
+
}
|
|
588
|
+
}, [
|
|
589
|
+
settings.endpointUrl,
|
|
590
|
+
settings.prometheusQueries,
|
|
591
|
+
settings.displayedQueryParams,
|
|
592
|
+
driver
|
|
593
|
+
]);
|
|
436
594
|
useEffect(() => {
|
|
437
595
|
if (queriesTimeRange) {
|
|
438
596
|
setTimeLabels(generateTimeRange(dayjs(queriesTimeRange.startedAt), dayjs(queriesTimeRange.finishedAt), "minutes", 15).map((d) => d.toDate()));
|
|
@@ -441,7 +599,7 @@ const PrometheusChart = ({ data, settings }) => {
|
|
|
441
599
|
}, [queriesTimeRange, fetchData]);
|
|
442
600
|
useEffect(() => {
|
|
443
601
|
setQueriesTimeRange(getQueriesTimeRange(settings.queriesPeriod, data));
|
|
444
|
-
if (settings?.queriesPeriod ===
|
|
602
|
+
if (settings?.queriesPeriod === "CURRENT_SHIFT") {
|
|
445
603
|
const intervalId = setInterval(() => {
|
|
446
604
|
setQueriesTimeRange(getQueriesTimeRange(settings.queriesPeriod, data));
|
|
447
605
|
}, 3e4);
|
|
@@ -451,6 +609,15 @@ const PrometheusChart = ({ data, settings }) => {
|
|
|
451
609
|
}
|
|
452
610
|
return () => {};
|
|
453
611
|
}, [data, settings]);
|
|
612
|
+
if (error) return /* @__PURE__ */ jsx(Box, {
|
|
613
|
+
sx: errorMessageStyles,
|
|
614
|
+
"data-testid": `prometheus-widget.error.${error}`,
|
|
615
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
616
|
+
variant: "body2",
|
|
617
|
+
color: "text.primary",
|
|
618
|
+
children: t(errorTranslationKey[error])
|
|
619
|
+
})
|
|
620
|
+
});
|
|
454
621
|
return queriesTimeRange && /* @__PURE__ */ jsx(Line, {
|
|
455
622
|
ref: chart,
|
|
456
623
|
data: chartData,
|
|
@@ -506,13 +673,16 @@ const WidgetView = ({ data, settings }) => {
|
|
|
506
673
|
};
|
|
507
674
|
//#endregion
|
|
508
675
|
//#region src/components/Widget/utils.ts
|
|
676
|
+
const isLegacyQuery = (query) => typeof query === "string";
|
|
509
677
|
const getSettingsFormProps = (t) => {
|
|
510
678
|
yup.setLocale({ mixed: { required: t("prometheusWidget.thisFieldIsRequired") } });
|
|
511
679
|
return {
|
|
512
680
|
initialValues: {
|
|
513
681
|
customTitle: "",
|
|
514
682
|
endpointUrl: "",
|
|
515
|
-
|
|
683
|
+
username: "",
|
|
684
|
+
password: "",
|
|
685
|
+
queriesPeriod: "CURRENT_SHIFT",
|
|
516
686
|
prometheusQueries: [],
|
|
517
687
|
displayedQueryParams: [],
|
|
518
688
|
xAxisUnit: "",
|
|
@@ -520,6 +690,8 @@ const getSettingsFormProps = (t) => {
|
|
|
520
690
|
},
|
|
521
691
|
validationSchema: yup.object({
|
|
522
692
|
endpointUrl: yup.string().required(),
|
|
693
|
+
username: yup.string(),
|
|
694
|
+
password: yup.string(),
|
|
523
695
|
queriesPeriod: yup.string().required(),
|
|
524
696
|
prometheusQueries: yup.array().of(yup.object().shape({
|
|
525
697
|
query: yup.string().required(),
|
|
@@ -528,22 +700,26 @@ const getSettingsFormProps = (t) => {
|
|
|
528
700
|
displayedQueryParams: yup.array().of(yup.string().required()),
|
|
529
701
|
xAxisUnit: yup.string().required(),
|
|
530
702
|
yAxisUnit: yup.string().required()
|
|
531
|
-
})
|
|
703
|
+
}),
|
|
704
|
+
onSubmit: () => {}
|
|
532
705
|
};
|
|
533
706
|
};
|
|
534
707
|
const migrateSettings = (settings) => {
|
|
535
|
-
if (!satisfies(settings.version || "1.0.0", ">=1.2.5"))
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
708
|
+
if (!satisfies(settings.version || "1.0.0", ">=1.2.5")) {
|
|
709
|
+
const queries = settings.prometheusQueries;
|
|
710
|
+
return {
|
|
711
|
+
...settings,
|
|
712
|
+
prometheusQueries: queries.map((query) => isLegacyQuery(query) ? {
|
|
713
|
+
query,
|
|
714
|
+
displayName: ""
|
|
715
|
+
} : query)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
542
718
|
return settings;
|
|
543
719
|
};
|
|
544
720
|
//#endregion
|
|
545
721
|
//#region src/components/Widget/index.tsx
|
|
546
|
-
const Widget = ({ url, wsUrl, lang, data, ...widgetProps }) => {
|
|
722
|
+
const Widget = ({ url, wsUrl, lang, data, sidePanelOpened = false, updateSidePanelProps = () => {}, ...widgetProps }) => {
|
|
547
723
|
const { graphqlSdk, gqlWsClient } = useGqlClients({
|
|
548
724
|
url,
|
|
549
725
|
wsUrl,
|
|
@@ -558,10 +734,11 @@ const Widget = ({ url, wsUrl, lang, data, ...widgetProps }) => {
|
|
|
558
734
|
}, [data, graphqlSdk]);
|
|
559
735
|
return /* @__PURE__ */ jsx(BaseWidget, {
|
|
560
736
|
...widgetProps,
|
|
561
|
-
url,
|
|
562
737
|
lang,
|
|
563
738
|
locales: resources,
|
|
564
739
|
data: widgetData,
|
|
740
|
+
sidePanelOpened,
|
|
741
|
+
updateSidePanelProps,
|
|
565
742
|
gqlClients: {
|
|
566
743
|
graphqlSdk,
|
|
567
744
|
gqlWsClient
|