ydb-embedded-ui 3.2.3 → 3.3.0
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/CHANGELOG.md +20 -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/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/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 -214
- 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,25 @@
|
|
|
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
|
+
|
|
3
23
|
## [3.2.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.2...v3.2.3) (2023-01-16)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -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
|
+
};
|