ydb-embedded-ui 5.3.0 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/components/MetricChart/MetricChart.js +9 -4
- package/dist/components/MetricChart/colors.d.ts +2 -0
- package/dist/components/MetricChart/colors.js +21 -0
- package/dist/components/MetricChart/getDefaultDataFormatter.js +9 -0
- package/dist/components/MetricChart/types.d.ts +7 -2
- package/dist/containers/PDisk/PDisk.js +22 -4
- package/dist/containers/PDisk/PDisk.scss +1 -0
- package/dist/containers/PDisk/i18n/en.json +3 -1
- package/dist/containers/PDisk/i18n/index.d.ts +1 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/cpuDashboardConfig.js +11 -6
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +1 -1
- package/dist/services/api.d.ts +1 -0
- package/dist/services/api.js +13 -0
- package/package.json +3 -2
@@ -3,7 +3,6 @@ import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
3
3
|
import { YagrPlugin } from '@gravity-ui/chartkit/yagr';
|
4
4
|
import ChartKit, { settings } from '@gravity-ui/chartkit';
|
5
5
|
import { useAutofetcher } from '../../utils/hooks';
|
6
|
-
import { COLORS } from '../../utils/versions';
|
7
6
|
import { cn } from '../../utils/cn';
|
8
7
|
import { Loader } from '../Loader';
|
9
8
|
import { ResponseError } from '../Errors/ResponseError';
|
@@ -11,6 +10,7 @@ import { convertResponse } from './convertReponse';
|
|
11
10
|
import { getDefaultDataFormatter } from './getDefaultDataFormatter';
|
12
11
|
import { getChartData } from './getChartData';
|
13
12
|
import { chartReducer, initialChartState, setChartData, setChartDataLoading, setChartDataWasNotLoaded, setChartError, } from './reducer';
|
13
|
+
import { colorToRGBA, colors } from './colors';
|
14
14
|
import i18n from './i18n';
|
15
15
|
import './MetricChart.scss';
|
16
16
|
const b = cn('ydb-metric-chart');
|
@@ -20,12 +20,16 @@ const prepareWidgetData = (data, options = {}) => {
|
|
20
20
|
const defaultDataFormatter = getDefaultDataFormatter(dataType);
|
21
21
|
const isDataEmpty = !data.metrics.length;
|
22
22
|
const graphs = data.metrics.map((metric, index) => {
|
23
|
+
const lineColor = metric.color || colors[index];
|
24
|
+
const color = colorToRGBA(lineColor, 0.1);
|
23
25
|
return {
|
24
26
|
id: metric.target,
|
25
27
|
name: metric.title || metric.target,
|
26
|
-
color: metric.color || COLORS[index],
|
27
28
|
data: metric.data,
|
28
29
|
formatter: defaultDataFormatter,
|
30
|
+
lineColor,
|
31
|
+
color,
|
32
|
+
legendColorKey: 'lineColor',
|
29
33
|
};
|
30
34
|
});
|
31
35
|
return {
|
@@ -41,7 +45,8 @@ const prepareWidgetData = (data, options = {}) => {
|
|
41
45
|
padding: isDataEmpty ? [10, 0, 10, 0] : undefined,
|
42
46
|
},
|
43
47
|
series: {
|
44
|
-
type: '
|
48
|
+
type: 'area',
|
49
|
+
lineWidth: 1.5,
|
45
50
|
},
|
46
51
|
select: {
|
47
52
|
zoom: false,
|
@@ -128,7 +133,7 @@ export const MetricChart = ({ database, title, metrics, timeFrame = '1h', autore
|
|
128
133
|
}
|
129
134
|
dispatch(setChartError(err));
|
130
135
|
}
|
131
|
-
}, [metrics, timeFrame, width]);
|
136
|
+
}, [database, metrics, timeFrame, width]);
|
132
137
|
useAutofetcher(fetchChartData, [fetchChartData], autorefresh);
|
133
138
|
const convertedData = prepareWidgetData(data, chartOptions);
|
134
139
|
const renderContent = () => {
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { colord } from 'colord';
|
2
|
+
// Grafana classic palette
|
3
|
+
export const colors = [
|
4
|
+
'#7EB26D',
|
5
|
+
'#EAB839',
|
6
|
+
'#6ED0E0',
|
7
|
+
'#EF843C',
|
8
|
+
'#E24D42',
|
9
|
+
'#1F78C1',
|
10
|
+
'#BA43A9',
|
11
|
+
'#705DA0',
|
12
|
+
'#508642',
|
13
|
+
'#CCA300', // 9: dark sand
|
14
|
+
];
|
15
|
+
export function colorToRGBA(initialColor, opacity) {
|
16
|
+
const color = colord(initialColor);
|
17
|
+
if (!color.isValid()) {
|
18
|
+
throw new Error('Invalid color is passed');
|
19
|
+
}
|
20
|
+
return color.alpha(opacity).toRgbString();
|
21
|
+
}
|
@@ -11,6 +11,9 @@ export const getDefaultDataFormatter = (dataType) => {
|
|
11
11
|
case 'size': {
|
12
12
|
return formatChartValueToSize;
|
13
13
|
}
|
14
|
+
case 'percent': {
|
15
|
+
return formatChartValueToPercent;
|
16
|
+
}
|
14
17
|
default:
|
15
18
|
return undefined;
|
16
19
|
}
|
@@ -29,6 +32,12 @@ function formatChartValueToSize(value) {
|
|
29
32
|
}
|
30
33
|
return formatBytes({ value: convertToNumber(value), precision: 3 });
|
31
34
|
}
|
35
|
+
function formatChartValueToPercent(value) {
|
36
|
+
if (value === null) {
|
37
|
+
return EMPTY_DATA_PLACEHOLDER;
|
38
|
+
}
|
39
|
+
return Math.round(convertToNumber(value) * 100) + '%';
|
40
|
+
}
|
32
41
|
// Numeric values expected, not numeric value should be displayd as 0
|
33
42
|
function convertToNumber(value) {
|
34
43
|
if (isNumeric(value)) {
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
import type { PoolName } from '../../types/api/nodes';
|
2
|
+
type Percentile = 'p50' | 'p75' | 'p90' | 'p99';
|
3
|
+
type QueriesLatenciesMetric = `queries.latencies.${Percentile}`;
|
4
|
+
type PoolUsageMetric = `resources.cpu.${PoolName}.usage`;
|
5
|
+
export type Metric = 'queries.requests' | 'resources.memory.used_bytes' | 'resources.storage.used_bytes' | 'resources.cpu.usage' | PoolUsageMetric | QueriesLatenciesMetric;
|
2
6
|
export interface MetricDescription {
|
3
7
|
target: Metric;
|
4
8
|
title?: string;
|
@@ -12,9 +16,10 @@ export interface PreparedMetricsData {
|
|
12
16
|
metrics: PreparedMetric[];
|
13
17
|
}
|
14
18
|
export type ChartValue = number | string | null;
|
15
|
-
export type ChartDataType = 'ms' | 'size';
|
19
|
+
export type ChartDataType = 'ms' | 'size' | 'percent';
|
16
20
|
export interface ChartOptions {
|
17
21
|
dataType?: ChartDataType;
|
18
22
|
}
|
19
23
|
export type ChartDataStatus = 'loading' | 'success' | 'error';
|
20
24
|
export type OnChartDataStatusChange = (newStatus: ChartDataStatus) => void;
|
25
|
+
export {};
|
@@ -2,6 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect } from 'react';
|
3
3
|
import { StringParam, useQueryParams } from 'use-query-params';
|
4
4
|
import { Helmet } from 'react-helmet-async';
|
5
|
+
import { Icon } from '@gravity-ui/uikit';
|
6
|
+
import ArrowRotateLeftIcon from '@gravity-ui/icons/svgs/arrow-rotate-left.svg';
|
5
7
|
import { getPDiskData, getPDiskStorage, setPDiskDataWasNotLoaded, } from '../../store/reducers/pdisk/pdisk';
|
6
8
|
import { setHeaderBreadcrumbs } from '../../store/reducers/header/header';
|
7
9
|
import { getNodesList, selectNodesMap } from '../../store/reducers/nodesList';
|
@@ -11,6 +13,7 @@ import { PageMeta } from '../../components/PageMeta/PageMeta';
|
|
11
13
|
import { StatusIcon } from '../../components/StatusIcon/StatusIcon';
|
12
14
|
import { PDiskInfo } from '../../components/PDiskInfo/PDiskInfo';
|
13
15
|
import { InfoViewerSkeleton } from '../../components/InfoViewerSkeleton/InfoViewerSkeleton';
|
16
|
+
import { ButtonWithConfirmDialog } from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog';
|
14
17
|
import { PDiskGroups } from './PDiskGroups';
|
15
18
|
import { pdiskPageCn } from './shared';
|
16
19
|
import { pDiskPageKeyset } from './i18n';
|
@@ -30,16 +33,28 @@ export function PDisk() {
|
|
30
33
|
useEffect(() => {
|
31
34
|
dispatch(getNodesList());
|
32
35
|
}, [dispatch]);
|
33
|
-
const fetchData = useCallback((isBackground) => {
|
36
|
+
const fetchData = useCallback(async (isBackground) => {
|
34
37
|
if (!isBackground) {
|
35
38
|
dispatch(setPDiskDataWasNotLoaded());
|
36
39
|
}
|
37
40
|
if (nodeId && pDiskId) {
|
38
|
-
|
39
|
-
|
41
|
+
return Promise.all([
|
42
|
+
dispatch(getPDiskData({ nodeId, pDiskId })),
|
43
|
+
dispatch(getPDiskStorage({ nodeId, pDiskId })),
|
44
|
+
]);
|
40
45
|
}
|
46
|
+
return undefined;
|
41
47
|
}, [dispatch, nodeId, pDiskId]);
|
42
48
|
useAutofetcher(fetchData, [fetchData], true);
|
49
|
+
const handleRestart = async () => {
|
50
|
+
if (nodeId && pDiskId) {
|
51
|
+
return window.api.restartPDisk(nodeId, pDiskId);
|
52
|
+
}
|
53
|
+
return undefined;
|
54
|
+
};
|
55
|
+
const handleAfterRestart = async () => {
|
56
|
+
return fetchData(true);
|
57
|
+
};
|
43
58
|
const renderHelmet = () => {
|
44
59
|
const pDiskPagePart = pDiskId
|
45
60
|
? `${pDiskPageKeyset('pdisk')} ${pDiskId}`
|
@@ -55,6 +70,9 @@ export function PDisk() {
|
|
55
70
|
const renderPageTitle = () => {
|
56
71
|
return (_jsxs("div", Object.assign({ className: pdiskPageCn('title') }, { children: [_jsx("span", Object.assign({ className: pdiskPageCn('title__prefix') }, { children: pDiskPageKeyset('pdisk') })), _jsx(StatusIcon, { status: getSeverityColor(Severity), size: "s" }), pDiskId] })));
|
57
72
|
};
|
73
|
+
const renderControls = () => {
|
74
|
+
return (_jsx("div", Object.assign({ className: pdiskPageCn('controls') }, { children: _jsxs(ButtonWithConfirmDialog, Object.assign({ onConfirmAction: handleRestart, onConfirmActionSuccess: handleAfterRestart, buttonDisabled: !nodeId || !pDiskId, buttonView: "normal", dialogContent: pDiskPageKeyset('restart-pdisk-dialog') }, { children: [_jsx(Icon, { data: ArrowRotateLeftIcon }), pDiskPageKeyset('restart-pdisk-button')] })) })));
|
75
|
+
};
|
58
76
|
const renderInfo = () => {
|
59
77
|
if (pDiskLoading && !pDiskWasLoaded) {
|
60
78
|
return _jsx(InfoViewerSkeleton, { className: pdiskPageCn('info'), rows: 10 });
|
@@ -64,5 +82,5 @@ export function PDisk() {
|
|
64
82
|
const renderGroupsTable = () => {
|
65
83
|
return (_jsx(PDiskGroups, { data: groupsData, nodesMap: nodesMap, loading: groupsLoading && !groupsWasLoaded }));
|
66
84
|
};
|
67
|
-
return (_jsxs("div", Object.assign({ className: pdiskPageCn(null) }, { children: [renderHelmet(), renderPageMeta(), renderPageTitle(), renderInfo(), renderGroupsTable()] })));
|
85
|
+
return (_jsxs("div", Object.assign({ className: pdiskPageCn(null) }, { children: [renderHelmet(), renderPageMeta(), renderPageTitle(), renderControls(), renderInfo(), renderGroupsTable()] })));
|
68
86
|
}
|
@@ -1 +1 @@
|
|
1
|
-
export declare const pDiskPageKeyset: (key: "groups" | "node" | "pdisk" | "fqdn", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
1
|
+
export declare const pDiskPageKeyset: (key: "groups" | "node" | "pdisk" | "fqdn" | "restart-pdisk-button" | "restart-pdisk-dialog", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
@@ -1,12 +1,17 @@
|
|
1
1
|
import i18n from '../i18n';
|
2
|
+
const pools = ['IC', 'IO', 'Batch', 'User', 'System'];
|
3
|
+
const getPoolMetricConfig = (poolName) => {
|
4
|
+
return {
|
5
|
+
target: `resources.cpu.${poolName}.usage`,
|
6
|
+
title: poolName,
|
7
|
+
};
|
8
|
+
};
|
2
9
|
export const cpuDashboardConfig = [
|
3
10
|
{
|
4
11
|
title: i18n('charts.cpu-usage'),
|
5
|
-
metrics:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
},
|
10
|
-
],
|
12
|
+
metrics: pools.map(getPoolMetricConfig),
|
13
|
+
options: {
|
14
|
+
dataType: 'percent',
|
15
|
+
},
|
11
16
|
},
|
12
17
|
];
|
@@ -18,7 +18,7 @@
|
|
18
18
|
"by-size": "by size",
|
19
19
|
"charts.queries-per-second": "Queries per second",
|
20
20
|
"charts.transaction-latency": "Transactions latencies {{percentile}}",
|
21
|
-
"charts.cpu-usage": "CPU
|
21
|
+
"charts.cpu-usage": "CPU usage by pool",
|
22
22
|
"charts.storage-usage": "Tablet storage usage",
|
23
23
|
"charts.memory-usage": "Memory usage",
|
24
24
|
"storage.tablet-storage-title": "Tablet storage",
|
package/dist/services/api.d.ts
CHANGED
@@ -93,6 +93,7 @@ export declare class YdbEmbeddedAPI extends AxiosWrapper {
|
|
93
93
|
getExplainQueryAst(query: string, database: string): Promise<import("../types/api/query").ExplainQueryResponse>;
|
94
94
|
getHotKeys(path: string, enableSampling: boolean, { concurrentId }?: AxiosOptions): Promise<JsonHotKeysResponse>;
|
95
95
|
getHealthcheckInfo(database: string, { concurrentId }?: AxiosOptions): Promise<HealthCheckAPIResponse>;
|
96
|
+
restartPDisk(nodeId: number | string, pDiskId: number | string): Promise<any>;
|
96
97
|
killTablet(id?: string): Promise<string>;
|
97
98
|
stopTablet(id?: string, hiveId?: string): Promise<string>;
|
98
99
|
resumeTablet(id?: string, hiveId?: string): Promise<string>;
|
package/dist/services/api.js
CHANGED
@@ -2,6 +2,7 @@ import { __rest } from "tslib";
|
|
2
2
|
import AxiosWrapper from '@gravity-ui/axios-wrapper';
|
3
3
|
import { backend as BACKEND, metaBackend as META_BACKEND } from '../store';
|
4
4
|
import { prepareSortValue } from '../utils/filters';
|
5
|
+
import { createPDiskDeveloperUILink } from '../utils/developerUI/developerUI';
|
5
6
|
import { BINARY_DATA_IN_PLAIN_TEXT_DISPLAY } from '../utils/constants';
|
6
7
|
import { parseMetaCluster } from './parsers/parseMetaCluster';
|
7
8
|
import { parseMetaTenants } from './parsers/parseMetaTenants';
|
@@ -207,6 +208,18 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
207
208
|
getHealthcheckInfo(database, { concurrentId } = {}) {
|
208
209
|
return this.get(this.getPath('/viewer/json/healthcheck?merge_records=true'), { tenant: database }, { concurrentId });
|
209
210
|
}
|
211
|
+
restartPDisk(nodeId, pDiskId) {
|
212
|
+
const pDiskPath = createPDiskDeveloperUILink({
|
213
|
+
nodeId,
|
214
|
+
pDiskId,
|
215
|
+
host: this.getPath(''),
|
216
|
+
});
|
217
|
+
return this.post(pDiskPath, 'restartPDisk=', {}, {
|
218
|
+
headers: {
|
219
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
220
|
+
},
|
221
|
+
});
|
222
|
+
}
|
210
223
|
killTablet(id) {
|
211
224
|
return this.get(this.getPath(`/tablets?KillTabletID=${id}`), {});
|
212
225
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.4.0",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -11,7 +11,7 @@
|
|
11
11
|
},
|
12
12
|
"dependencies": {
|
13
13
|
"@gravity-ui/axios-wrapper": "^1.4.1",
|
14
|
-
"@gravity-ui/chartkit": "^4.
|
14
|
+
"@gravity-ui/chartkit": "^4.23.0",
|
15
15
|
"@gravity-ui/components": "^2.12.0",
|
16
16
|
"@gravity-ui/date-utils": "^1.4.2",
|
17
17
|
"@gravity-ui/i18n": "^1.2.0",
|
@@ -24,6 +24,7 @@
|
|
24
24
|
"@reduxjs/toolkit": "^2.2.1",
|
25
25
|
"axios": "^1.6.7",
|
26
26
|
"bem-cn-lite": "^4.1.0",
|
27
|
+
"colord": "^2.9.3",
|
27
28
|
"copy-to-clipboard": "^3.3.3",
|
28
29
|
"crc-32": "^1.2.2",
|
29
30
|
"history": "^4.10.1",
|