ydb-embedded-ui 3.2.2 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +27 -0
- package/dist/components/InfoViewer/InfoViewer.scss +10 -0
- package/dist/components/InfoViewer/InfoViewer.tsx +12 -2
- package/dist/components/InfoViewer/formatters/cdcStream.ts +10 -0
- package/dist/components/InfoViewer/formatters/index.ts +3 -0
- package/dist/components/InfoViewer/formatters/pqGroup.ts +51 -0
- package/dist/components/InfoViewer/formatters/schema.ts +1 -29
- package/dist/components/InfoViewer/formatters/topicStats.tsx +50 -0
- package/dist/components/InfoViewer/schemaInfo/index.ts +0 -2
- package/dist/components/InfoViewer/utils.ts +15 -0
- package/dist/components/InternalLink/InternalLink.tsx +17 -0
- package/dist/components/InternalLink/index.ts +1 -0
- package/dist/components/Tablet/Tablet.js +1 -1
- package/dist/components/TabletsStatistic/TabletsStatistic.tsx +1 -1
- package/dist/components/VerticalBars/VerticalBars.scss +15 -0
- package/dist/components/VerticalBars/VerticalBars.tsx +38 -0
- package/dist/components/VerticalBars/index.ts +1 -0
- package/dist/containers/App/App.js +0 -11
- package/dist/containers/App/App.scss +0 -1
- package/dist/containers/Cluster/Cluster.tsx +2 -2
- package/dist/containers/Nodes/Nodes.scss +5 -1
- package/dist/containers/Nodes/Nodes.tsx +196 -0
- package/dist/containers/{App → Nodes}/NodesTable.scss +2 -2
- package/dist/{utils/getNodesColumns.js → containers/Nodes/getNodesColumns.tsx} +60 -35
- package/dist/containers/Nodes/i18n/en.json +3 -0
- package/dist/containers/Nodes/i18n/index.ts +11 -0
- package/dist/containers/Nodes/i18n/ru.json +3 -0
- package/dist/containers/Nodes/index.ts +1 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +14 -20
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +32 -16
- package/dist/containers/Storage/DiskStateProgressBar/index.ts +1 -0
- package/dist/containers/Storage/{Pdisk/Pdisk.scss → PDisk/PDisk.scss} +15 -4
- package/dist/containers/Storage/PDisk/PDisk.tsx +145 -0
- package/dist/containers/Storage/PDisk/__tests__/colors.tsx +37 -0
- package/dist/containers/Storage/PDisk/index.ts +1 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.scss +3 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +82 -0
- package/dist/containers/Storage/PDiskPopup/index.ts +1 -0
- package/dist/containers/Storage/Storage.js +1 -1
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +10 -10
- package/dist/containers/Storage/StorageNodes/StorageNodes.scss +1 -0
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +7 -4
- package/dist/containers/Storage/VDisk/VDisk.scss +7 -0
- package/dist/containers/Storage/VDisk/VDisk.tsx +148 -0
- package/dist/containers/Storage/VDisk/__tests__/colors.tsx +209 -0
- package/dist/containers/Storage/VDisk/index.ts +1 -0
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.scss +14 -0
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +134 -0
- package/dist/containers/Storage/VDiskPopup/index.ts +1 -0
- package/dist/containers/Storage/utils/constants.ts +2 -9
- package/dist/containers/TabletsFilters/TabletsFilters.js +10 -6
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +2 -1
- package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +2 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -4
- package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +69 -0
- package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +18 -16
- package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +37 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +30 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +94 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +3 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +3 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/utils/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +42 -0
- package/dist/containers/Tenant/utils/schema.ts +19 -0
- package/dist/containers/Tenants/Tenants.js +2 -1
- package/dist/containers/UserSettings/UserSettings.tsx +18 -10
- package/dist/services/api.d.ts +8 -1
- package/dist/services/api.js +27 -8
- package/dist/store/reducers/index.ts +3 -1
- package/dist/store/reducers/nodes.ts +148 -14
- package/dist/store/reducers/{clusterNodes.js → nodesList.js} +0 -41
- package/dist/store/reducers/settings.js +10 -4
- package/dist/store/reducers/storage.js +24 -13
- package/dist/store/reducers/tenant.js +5 -4
- package/dist/store/reducers/tooltip.ts +1 -1
- package/dist/store/reducers/topic.ts +52 -0
- package/dist/styles/mixins.scss +19 -11
- package/dist/types/api/common.ts +5 -0
- package/dist/types/api/compute.ts +1 -1
- package/dist/types/api/consumer.ts +12 -10
- package/dist/types/api/nodes.ts +2 -0
- package/dist/types/api/pdisk.ts +1 -0
- package/dist/types/api/schema.ts +3 -3
- package/dist/types/api/topic.ts +5 -4
- package/dist/types/api/vdisk.ts +2 -1
- package/dist/types/store/nodes.ts +56 -6
- package/dist/types/store/tooltip.ts +1 -1
- package/dist/types/store/topic.ts +21 -0
- package/dist/utils/constants.ts +5 -1
- package/dist/utils/i18n/i18n.ts +10 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/timeParsers/__test__/formatDuration.test.ts +50 -0
- package/dist/utils/timeParsers/__test__/protobuf.test.ts +74 -0
- package/dist/utils/timeParsers/formatDuration.ts +46 -0
- package/dist/utils/timeParsers/i18n/en.json +7 -0
- package/dist/utils/timeParsers/i18n/index.ts +11 -0
- package/dist/utils/timeParsers/i18n/ru.json +7 -0
- package/dist/utils/timeParsers/index.ts +2 -0
- package/dist/utils/timeParsers/protobuf.ts +36 -0
- package/package.json +1 -1
- package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +0 -48
- package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +0 -30
- package/dist/components/InternalLink/InternalLink.js +0 -23
- package/dist/containers/Nodes/Nodes.js +0 -213
- package/dist/containers/NodesViewer/NodesViewer.js +0 -163
- package/dist/containers/NodesViewer/NodesViewer.scss +0 -66
- package/dist/containers/Storage/Pdisk/Pdisk.tsx +0 -153
- package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +0 -41
- package/dist/containers/Storage/Vdisk/Vdisk.js +0 -275
- package/dist/containers/Storage/Vdisk/Vdisk.scss +0 -22
- package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +0 -163
- package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +0 -139
- package/dist/containers/Tenant/Diagnostics/Compute/Compute.scss +0 -14
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.3.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.3...v3.3.0) (2023-01-30)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Nodes:** use /viewer/json/nodes endpoint ([226cc70](https://github.com/ydb-platform/ydb-embedded-ui/commit/226cc70dcb89262890856b4d0cb03eac0675256d))
|
9
|
+
* **Overview:** display topic stats for topics and streams ([08e9fe0](https://github.com/ydb-platform/ydb-embedded-ui/commit/08e9fe0ee379715229474322a03ec668e26bdb9b))
|
10
|
+
* **Storage:** display vdisks over pdisks ([bb5d1fa](https://github.com/ydb-platform/ydb-embedded-ui/commit/bb5d1fa5ae2953ca30b13df45340b7a1a63056cb))
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* add duration formatter ([e325d98](https://github.com/ydb-platform/ydb-embedded-ui/commit/e325d98845d29dea208debdfcb88d330c1d6daee))
|
16
|
+
* add protobuf time formatters ([c74cd9d](https://github.com/ydb-platform/ydb-embedded-ui/commit/c74cd9d0949674414ba2c9754e3dcc5c2be622a5))
|
17
|
+
* add verticalBars component ([053ffa8](https://github.com/ydb-platform/ydb-embedded-ui/commit/053ffa8fd460f89f4296a96fcf46a9267ac4cae3))
|
18
|
+
* **PDisk:** grey color for unknown state ([54f7e15](https://github.com/ydb-platform/ydb-embedded-ui/commit/54f7e159aaddd932ccecddfb10265ee596fed1e2))
|
19
|
+
* **Storage:** request only static nodes ([e91e136](https://github.com/ydb-platform/ydb-embedded-ui/commit/e91e136d7c72bea694c7a282c83d577cc60e5386))
|
20
|
+
* **Topic:** add reducer for describe_topic ([7c61dc9](https://github.com/ydb-platform/ydb-embedded-ui/commit/7c61dc906452df2e1a77a2ff602916f6ea785df5))
|
21
|
+
* update PDisks and VDisks tests ([3bf660e](https://github.com/ydb-platform/ydb-embedded-ui/commit/3bf660e41d92e1a32444872c5fb9d47209bef8b5))
|
22
|
+
|
23
|
+
## [3.2.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.2...v3.2.3) (2023-01-16)
|
24
|
+
|
25
|
+
|
26
|
+
### Bug Fixes
|
27
|
+
|
28
|
+
* fix crash on invalid search query ([4d6f551](https://github.com/ydb-platform/ydb-embedded-ui/commit/4d6f551fa4348a05ca3d8d2d6bd8b52ccb6310ee))
|
29
|
+
|
3
30
|
## [3.2.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.1...v3.2.2) (2023-01-13)
|
4
31
|
|
5
32
|
|
@@ -14,11 +14,19 @@ interface InfoViewerProps {
|
|
14
14
|
dots?: boolean;
|
15
15
|
size?: 's';
|
16
16
|
className?: string;
|
17
|
+
multilineLabels?: boolean;
|
17
18
|
}
|
18
19
|
|
19
20
|
const b = cn('info-viewer');
|
20
21
|
|
21
|
-
const InfoViewer = ({
|
22
|
+
const InfoViewer = ({
|
23
|
+
title,
|
24
|
+
info,
|
25
|
+
dots = true,
|
26
|
+
size,
|
27
|
+
className,
|
28
|
+
multilineLabels,
|
29
|
+
}: InfoViewerProps) => (
|
22
30
|
<div className={b({size}, className)}>
|
23
31
|
{title && <div className={b('title')}>{title}</div>}
|
24
32
|
{info && info.length > 0 ? (
|
@@ -26,7 +34,9 @@ const InfoViewer = ({title, info, dots = true, size, className}: InfoViewerProps
|
|
26
34
|
{info.map((data, infoIndex) => (
|
27
35
|
<div className={b('row')} key={data.label + infoIndex}>
|
28
36
|
<div className={b('label')}>
|
29
|
-
{
|
37
|
+
<div className={b('label-text', {multiline: multilineLabels})}>
|
38
|
+
{data.label}
|
39
|
+
</div>
|
30
40
|
{dots && <div className={b('dots')} />}
|
31
41
|
</div>
|
32
42
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import {TCdcStreamDescription} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {createInfoFormatter} from '../utils';
|
4
|
+
|
5
|
+
export const formatCdcStreamItem = createInfoFormatter<TCdcStreamDescription>({
|
6
|
+
values: {
|
7
|
+
Mode: (value) => value?.substring('ECdcStreamMode'.length),
|
8
|
+
Format: (value) => value?.substring('ECdcStreamFormat'.length),
|
9
|
+
},
|
10
|
+
});
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import {
|
2
|
+
EMeteringMode,
|
3
|
+
TPersQueueGroupDescription,
|
4
|
+
TPQPartitionConfig,
|
5
|
+
TPQTabletConfig,
|
6
|
+
} from '../../../types/api/schema';
|
7
|
+
import {formatBps, formatBytes, formatNumber} from '../../../utils';
|
8
|
+
import {HOUR_IN_SECONDS} from '../../../utils/constants';
|
9
|
+
|
10
|
+
import {createInfoFormatter} from '../utils';
|
11
|
+
|
12
|
+
const EMeteringModeToNames: Record<EMeteringMode, string> = {
|
13
|
+
[EMeteringMode.METERING_MODE_REQUEST_UNITS]: 'request-units',
|
14
|
+
[EMeteringMode.METERING_MODE_RESERVED_CAPACITY]: 'reserved-capacity',
|
15
|
+
};
|
16
|
+
|
17
|
+
export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
|
18
|
+
values: {
|
19
|
+
Partitions: (value) => formatNumber(value?.length || 0),
|
20
|
+
PQTabletConfig: (value) => {
|
21
|
+
const hours =
|
22
|
+
Math.round((value.PartitionConfig.LifetimeSeconds / HOUR_IN_SECONDS) * 100) / 100;
|
23
|
+
return `${formatNumber(hours)} hours`;
|
24
|
+
},
|
25
|
+
},
|
26
|
+
labels: {
|
27
|
+
Partitions: 'Partitions count',
|
28
|
+
PQTabletConfig: 'Retention',
|
29
|
+
},
|
30
|
+
});
|
31
|
+
|
32
|
+
export const formatPQTabletConfig = createInfoFormatter<TPQTabletConfig>({
|
33
|
+
values: {
|
34
|
+
Codecs: (value) => value && Object.values(value.Codecs || {}).join(', '),
|
35
|
+
MeteringMode: (value) => value && EMeteringModeToNames[value],
|
36
|
+
},
|
37
|
+
labels: {
|
38
|
+
MeteringMode: 'Metering mode',
|
39
|
+
},
|
40
|
+
});
|
41
|
+
|
42
|
+
export const formatPQPartitionConfig = createInfoFormatter<TPQPartitionConfig>({
|
43
|
+
values: {
|
44
|
+
StorageLimitBytes: formatBytes,
|
45
|
+
WriteSpeedInBytesPerSecond: formatBps,
|
46
|
+
},
|
47
|
+
labels: {
|
48
|
+
StorageLimitBytes: 'Retention storage',
|
49
|
+
WriteSpeedInBytesPerSecond: 'Partitions write speed',
|
50
|
+
},
|
51
|
+
});
|
@@ -1,10 +1,4 @@
|
|
1
|
-
import
|
2
|
-
TCdcStreamDescription,
|
3
|
-
TIndexDescription,
|
4
|
-
TPersQueueGroupDescription,
|
5
|
-
} from '../../../types/api/schema';
|
6
|
-
import {formatNumber} from '../../../utils';
|
7
|
-
import {HOUR_IN_SECONDS} from '../../../utils/constants';
|
1
|
+
import {TIndexDescription} from '../../../types/api/schema';
|
8
2
|
|
9
3
|
import {createInfoFormatter} from '../utils';
|
10
4
|
|
@@ -20,25 +14,3 @@ export const formatTableIndexItem = createInfoFormatter<TIndexDescription>({
|
|
20
14
|
DataColumnNames: 'Includes',
|
21
15
|
},
|
22
16
|
});
|
23
|
-
|
24
|
-
export const formatCdcStreamItem = createInfoFormatter<TCdcStreamDescription>({
|
25
|
-
values: {
|
26
|
-
Mode: (value) => value?.substring('ECdcStreamMode'.length),
|
27
|
-
Format: (value) => value?.substring('ECdcStreamFormat'.length),
|
28
|
-
},
|
29
|
-
});
|
30
|
-
|
31
|
-
export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
|
32
|
-
values: {
|
33
|
-
Partitions: (value) => formatNumber(value?.length || 0),
|
34
|
-
PQTabletConfig: (value) => {
|
35
|
-
const hours =
|
36
|
-
Math.round((value.PartitionConfig.LifetimeSeconds / HOUR_IN_SECONDS) * 100) / 100;
|
37
|
-
return `${formatNumber(hours)} hours`;
|
38
|
-
},
|
39
|
-
},
|
40
|
-
labels: {
|
41
|
-
Partitions: 'Partitions count',
|
42
|
-
PQTabletConfig: 'Retention',
|
43
|
-
},
|
44
|
-
});
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import type {MultipleWindowsStat} from '../../../types/api/consumer';
|
2
|
+
import type {TopicStats} from '../../../types/api/topic';
|
3
|
+
import {formatBytes} from '../../../utils';
|
4
|
+
import {DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS} from '../../../utils/constants';
|
5
|
+
|
6
|
+
import {
|
7
|
+
parseProtobufTimestampToMs,
|
8
|
+
parseProtobufDurationToMs,
|
9
|
+
formatDurationToShortTimeFormat,
|
10
|
+
} from '../../../utils/timeParsers';
|
11
|
+
|
12
|
+
import {VerticalBars} from '../../VerticalBars/VerticalBars';
|
13
|
+
|
14
|
+
import {createInfoFormatter} from '../utils';
|
15
|
+
|
16
|
+
export const prepareBytesWritten = (data?: MultipleWindowsStat) => {
|
17
|
+
return {
|
18
|
+
per_minute:
|
19
|
+
data && data.per_minute ? Math.floor(Number(data.per_minute) / MINUTE_IN_SECONDS) : 0,
|
20
|
+
per_hour: data && data.per_hour ? Math.floor(Number(data.per_hour) / HOUR_IN_SECONDS) : 0,
|
21
|
+
per_day: data && data.per_day ? Math.floor(Number(data.per_day) / DAY_IN_SECONDS) : 0,
|
22
|
+
};
|
23
|
+
};
|
24
|
+
|
25
|
+
export const formatTopicStats = createInfoFormatter<TopicStats>({
|
26
|
+
values: {
|
27
|
+
store_size_bytes: formatBytes,
|
28
|
+
min_last_write_time: (value) => {
|
29
|
+
if (!value) {
|
30
|
+
return formatDurationToShortTimeFormat(0);
|
31
|
+
}
|
32
|
+
|
33
|
+
const durationMs = Date.now() - parseProtobufTimestampToMs(value);
|
34
|
+
|
35
|
+
// Duration could be negative because of the difference between server and local time
|
36
|
+
// Usually it below 100ms, so it could be omitted
|
37
|
+
return formatDurationToShortTimeFormat(durationMs < 0 ? 0 : durationMs);
|
38
|
+
},
|
39
|
+
max_write_time_lag: (value) =>
|
40
|
+
formatDurationToShortTimeFormat(value ? parseProtobufDurationToMs(value) : 0),
|
41
|
+
bytes_written: (value) =>
|
42
|
+
value && <VerticalBars values={Object.values(prepareBytesWritten(value))} />,
|
43
|
+
},
|
44
|
+
labels: {
|
45
|
+
store_size_bytes: 'Store size',
|
46
|
+
min_last_write_time: 'Partitions max time since last write',
|
47
|
+
max_write_time_lag: 'Partitions max write time lag',
|
48
|
+
bytes_written: 'Average write speed',
|
49
|
+
},
|
50
|
+
});
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import type {ReactNode} from 'react';
|
2
2
|
|
3
|
+
import {InfoViewerItem} from './InfoViewer';
|
4
|
+
|
3
5
|
type LabelMap<T> = {
|
4
6
|
[label in keyof T]?: string;
|
5
7
|
};
|
@@ -40,3 +42,16 @@ export function createInfoFormatter<Shape extends Record<string, any>>({
|
|
40
42
|
value: formatValue(label, value, valueFormatters || {}, defaultValueFormatter),
|
41
43
|
});
|
42
44
|
}
|
45
|
+
|
46
|
+
export const formatObject = <Shape extends Record<string, any>>(
|
47
|
+
formatter: <Key extends keyof Shape>(value: Key, label: Shape[Key]) => InfoViewerItem,
|
48
|
+
obj?: Partial<Shape>,
|
49
|
+
): InfoViewerItem[] => {
|
50
|
+
if (!obj) {
|
51
|
+
return [];
|
52
|
+
}
|
53
|
+
|
54
|
+
return Object.entries(obj)
|
55
|
+
.map(([label, value]) => formatter(label, value))
|
56
|
+
.filter(({value}) => Boolean(value));
|
57
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import {Link, LinkProps} from 'react-router-dom';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
const bLink = cn('yc-link');
|
5
|
+
|
6
|
+
interface InternalLinkProps extends Omit<LinkProps, 'to'> {
|
7
|
+
to?: LinkProps['to'];
|
8
|
+
}
|
9
|
+
|
10
|
+
export const InternalLink = ({className, to, onClick, ...props}: InternalLinkProps) =>
|
11
|
+
to ? (
|
12
|
+
<Link to={to} onClick={onClick} className={bLink({view: 'normal'}, className)} {...props} />
|
13
|
+
) : (
|
14
|
+
<span className={className} onClick={onClick}>
|
15
|
+
{props.children}
|
16
|
+
</span>
|
17
|
+
);
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './InternalLink';
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
|
5
|
-
import InternalLink from '../InternalLink
|
5
|
+
import {InternalLink} from '../InternalLink';
|
6
6
|
|
7
7
|
import {getTabletLabel} from '../../utils/constants';
|
8
8
|
import routes, {createHref} from '../../routes';
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
import {useMemo} from 'react';
|
3
|
+
|
4
|
+
import './VerticalBars.scss';
|
5
|
+
|
6
|
+
const b = cn('ydb-bars');
|
7
|
+
|
8
|
+
const calculateValuesInPercents = (values: number[]) => {
|
9
|
+
const max = Math.max(...values);
|
10
|
+
|
11
|
+
if (!max) {
|
12
|
+
return values;
|
13
|
+
}
|
14
|
+
|
15
|
+
const res = [];
|
16
|
+
|
17
|
+
for (const value of values) {
|
18
|
+
res.push((value / max) * 100);
|
19
|
+
}
|
20
|
+
|
21
|
+
return res;
|
22
|
+
};
|
23
|
+
|
24
|
+
interface VerticalBarsProps {
|
25
|
+
values: number[];
|
26
|
+
}
|
27
|
+
|
28
|
+
export const VerticalBars = ({values}: VerticalBarsProps) => {
|
29
|
+
const preparedValues = useMemo(() => calculateValuesInPercents(values), [values]);
|
30
|
+
|
31
|
+
const getBars = () => {
|
32
|
+
return preparedValues.map((value, index) => {
|
33
|
+
return <div key={index} style={{height: `${value}%`}} className={b('value')} />;
|
34
|
+
});
|
35
|
+
};
|
36
|
+
|
37
|
+
return <div className={b()}>{getBars()}</div>;
|
38
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './VerticalBars';
|
@@ -2,10 +2,6 @@ import React from 'react';
|
|
2
2
|
import {connect} from 'react-redux';
|
3
3
|
import PropTypes from 'prop-types';
|
4
4
|
|
5
|
-
import {configure as configureUiKit} from '@gravity-ui/uikit';
|
6
|
-
import {configure as configureYdbUiComponents} from 'ydb-ui-components';
|
7
|
-
import {i18n, Lang} from '../../utils/i18n';
|
8
|
-
|
9
5
|
import ContentWrapper, {Content} from './Content';
|
10
6
|
import AsideNavigation from '../AsideNavigation/AsideNavigation';
|
11
7
|
|
@@ -24,13 +20,6 @@ class App extends React.Component {
|
|
24
20
|
children: PropTypes.node,
|
25
21
|
};
|
26
22
|
|
27
|
-
constructor(props) {
|
28
|
-
super(props);
|
29
|
-
i18n.setLang(Lang.En);
|
30
|
-
configureYdbUiComponents({lang: Lang.En});
|
31
|
-
configureUiKit({lang: Lang.En});
|
32
|
-
}
|
33
|
-
|
34
23
|
componentDidMount() {
|
35
24
|
const {isAuthenticated, getUser} = this.props;
|
36
25
|
if (isAuthenticated) {
|
@@ -2,7 +2,7 @@ import cn from 'bem-cn-lite';
|
|
2
2
|
//@ts-ignore
|
3
3
|
import Tenants from '../Tenants/Tenants';
|
4
4
|
//@ts-ignore
|
5
|
-
import Nodes from '../Nodes/Nodes';
|
5
|
+
import {Nodes} from '../Nodes/Nodes';
|
6
6
|
//@ts-ignore
|
7
7
|
import Storage from '../Storage/Storage';
|
8
8
|
import routes, {CLUSTER_PAGES} from '../../routes';
|
@@ -28,7 +28,7 @@ function Cluster(props: ClusterProps) {
|
|
28
28
|
return <Tenants {...props} />;
|
29
29
|
}
|
30
30
|
case CLUSTER_PAGES.nodes.id: {
|
31
|
-
return <Nodes {
|
31
|
+
return <Nodes additionalNodesInfo={props.additionalNodesInfo} />;
|
32
32
|
}
|
33
33
|
case CLUSTER_PAGES.storage.id: {
|
34
34
|
//@ts-ignore
|
@@ -0,0 +1,196 @@
|
|
1
|
+
import {useCallback, useEffect} from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
import {useDispatch} from 'react-redux';
|
4
|
+
|
5
|
+
import DataTable from '@yandex-cloud/react-data-table';
|
6
|
+
|
7
|
+
import {AccessDenied} from '../../components/Errors/403';
|
8
|
+
import {Illustration} from '../../components/Illustration';
|
9
|
+
import {Loader} from '../../components/Loader';
|
10
|
+
|
11
|
+
import {Search} from '../../components/Search';
|
12
|
+
import {ProblemFilter} from '../../components/ProblemFilter';
|
13
|
+
import {UptimeFilter} from '../../components/UptimeFIlter';
|
14
|
+
import {EntitiesCount} from '../../components/EntitiesCount';
|
15
|
+
|
16
|
+
import routes, {CLUSTER_PAGES, createHref} from '../../routes';
|
17
|
+
|
18
|
+
import {
|
19
|
+
ALL,
|
20
|
+
DEFAULT_TABLE_SETTINGS,
|
21
|
+
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
22
|
+
} from '../../utils/constants';
|
23
|
+
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
|
24
|
+
import {NodesUptimeFilterValues} from '../../utils/nodes';
|
25
|
+
|
26
|
+
import {setHeader} from '../../store/reducers/header';
|
27
|
+
import {
|
28
|
+
getNodes,
|
29
|
+
getFilteredPreparedNodesList,
|
30
|
+
setNodesUptimeFilter,
|
31
|
+
setSearchValue,
|
32
|
+
resetNodesState,
|
33
|
+
getComputeNodes,
|
34
|
+
} from '../../store/reducers/nodes';
|
35
|
+
import {changeFilter, getSettingValue} from '../../store/reducers/settings';
|
36
|
+
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
|
37
|
+
|
38
|
+
import {getNodesColumns} from './getNodesColumns';
|
39
|
+
|
40
|
+
import './Nodes.scss';
|
41
|
+
|
42
|
+
import i18n from './i18n';
|
43
|
+
|
44
|
+
const b = cn('ydb-nodes');
|
45
|
+
|
46
|
+
interface IAdditionalNodesInfo extends Record<string, unknown> {
|
47
|
+
getNodeRef?: Function;
|
48
|
+
}
|
49
|
+
|
50
|
+
interface NodesProps {
|
51
|
+
tenantPath?: string;
|
52
|
+
className?: string;
|
53
|
+
additionalNodesInfo?: IAdditionalNodesInfo;
|
54
|
+
}
|
55
|
+
|
56
|
+
export const Nodes = ({tenantPath, className, additionalNodesInfo = {}}: NodesProps) => {
|
57
|
+
const dispatch = useDispatch();
|
58
|
+
|
59
|
+
const isClusterNodes = !tenantPath;
|
60
|
+
|
61
|
+
// Since Nodes component is used in several places,
|
62
|
+
// we need to reset filters, searchValue and loading state
|
63
|
+
// in nodes reducer when path changes
|
64
|
+
useEffect(() => {
|
65
|
+
dispatch(resetNodesState());
|
66
|
+
}, [dispatch, tenantPath]);
|
67
|
+
|
68
|
+
const {wasLoaded, loading, error, nodesUptimeFilter, searchValue, totalNodes} =
|
69
|
+
useTypedSelector((state) => state.nodes);
|
70
|
+
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
|
71
|
+
const {autorefresh} = useTypedSelector((state) => state.schema);
|
72
|
+
|
73
|
+
const nodes = useTypedSelector(getFilteredPreparedNodesList);
|
74
|
+
|
75
|
+
const useNodesEndpoint = useTypedSelector((state) =>
|
76
|
+
getSettingValue(state, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY),
|
77
|
+
);
|
78
|
+
|
79
|
+
const fetchNodes = useCallback(() => {
|
80
|
+
if (tenantPath && !JSON.parse(useNodesEndpoint)) {
|
81
|
+
dispatch(getComputeNodes(tenantPath));
|
82
|
+
} else {
|
83
|
+
dispatch(getNodes({tenant: tenantPath}));
|
84
|
+
}
|
85
|
+
}, [dispatch, tenantPath, useNodesEndpoint]);
|
86
|
+
|
87
|
+
useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
|
88
|
+
|
89
|
+
useEffect(() => {
|
90
|
+
if (isClusterNodes) {
|
91
|
+
dispatch(
|
92
|
+
setHeader([
|
93
|
+
{
|
94
|
+
text: CLUSTER_PAGES.nodes.title,
|
95
|
+
link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.nodes.id}),
|
96
|
+
},
|
97
|
+
]),
|
98
|
+
);
|
99
|
+
}
|
100
|
+
}, [dispatch, isClusterNodes]);
|
101
|
+
|
102
|
+
const handleSearchQueryChange = (value: string) => {
|
103
|
+
dispatch(setSearchValue(value));
|
104
|
+
};
|
105
|
+
|
106
|
+
const handleProblemFilterChange = (value: string) => {
|
107
|
+
dispatch(changeFilter(value));
|
108
|
+
};
|
109
|
+
|
110
|
+
const handleUptimeFilterChange = (value: string) => {
|
111
|
+
dispatch(setNodesUptimeFilter(value as NodesUptimeFilterValues));
|
112
|
+
};
|
113
|
+
|
114
|
+
const renderControls = () => {
|
115
|
+
return (
|
116
|
+
<div className={b('controls')}>
|
117
|
+
<Search
|
118
|
+
onChange={handleSearchQueryChange}
|
119
|
+
placeholder="Host name"
|
120
|
+
className={b('search')}
|
121
|
+
value={searchValue}
|
122
|
+
/>
|
123
|
+
<ProblemFilter value={problemFilter} onChange={handleProblemFilterChange} />
|
124
|
+
<UptimeFilter value={nodesUptimeFilter} onChange={handleUptimeFilterChange} />
|
125
|
+
<EntitiesCount
|
126
|
+
total={totalNodes}
|
127
|
+
current={nodes?.length || 0}
|
128
|
+
label={'Nodes'}
|
129
|
+
loading={loading && !wasLoaded}
|
130
|
+
/>
|
131
|
+
</div>
|
132
|
+
);
|
133
|
+
};
|
134
|
+
|
135
|
+
const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
|
136
|
+
dispatch(showTooltip(...args));
|
137
|
+
};
|
138
|
+
|
139
|
+
const onHideTooltip = () => {
|
140
|
+
dispatch(hideTooltip());
|
141
|
+
};
|
142
|
+
|
143
|
+
const renderTable = () => {
|
144
|
+
const columns = getNodesColumns({
|
145
|
+
showTooltip: onShowTooltip,
|
146
|
+
hideTooltip: onHideTooltip,
|
147
|
+
getNodeRef: additionalNodesInfo.getNodeRef,
|
148
|
+
});
|
149
|
+
|
150
|
+
if (nodes && nodes.length === 0) {
|
151
|
+
if (problemFilter !== ALL || nodesUptimeFilter !== NodesUptimeFilterValues.All) {
|
152
|
+
return <Illustration name="thumbsUp" width="200" />;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
return (
|
157
|
+
<div className={b('table-wrapper')}>
|
158
|
+
<div className={b('table-content')}>
|
159
|
+
<DataTable
|
160
|
+
theme="yandex-cloud"
|
161
|
+
data={nodes || []}
|
162
|
+
columns={columns}
|
163
|
+
settings={DEFAULT_TABLE_SETTINGS}
|
164
|
+
initialSortOrder={{
|
165
|
+
columnId: 'NodeId',
|
166
|
+
order: DataTable.ASCENDING,
|
167
|
+
}}
|
168
|
+
emptyDataMessage={i18n('empty.default')}
|
169
|
+
/>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
);
|
173
|
+
};
|
174
|
+
|
175
|
+
const renderContent = () => {
|
176
|
+
return (
|
177
|
+
<div className={b(null, className)}>
|
178
|
+
{renderControls()}
|
179
|
+
{renderTable()}
|
180
|
+
</div>
|
181
|
+
);
|
182
|
+
};
|
183
|
+
|
184
|
+
if (loading && !wasLoaded) {
|
185
|
+
return <Loader size={isClusterNodes ? 'l' : 'm'} />;
|
186
|
+
}
|
187
|
+
|
188
|
+
if (error) {
|
189
|
+
if (error.status === 403) {
|
190
|
+
return <AccessDenied />;
|
191
|
+
}
|
192
|
+
return <div>{error.statusText}</div>;
|
193
|
+
}
|
194
|
+
|
195
|
+
return renderContent();
|
196
|
+
};
|