ydb-embedded-ui 4.3.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/containers/App/Content.js +2 -8
  3. package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
  4. package/dist/containers/Cluster/Cluster.scss +4 -0
  5. package/dist/containers/Cluster/Cluster.tsx +14 -8
  6. package/dist/{components → containers}/ClusterInfo/ClusterInfo.scss +39 -0
  7. package/dist/containers/ClusterInfo/ClusterInfo.tsx +207 -0
  8. package/dist/containers/ClusterInfo/utils.ts +13 -0
  9. package/dist/containers/Header/Header.tsx +9 -16
  10. package/dist/containers/Nodes/Nodes.tsx +4 -6
  11. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +2 -3
  12. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +4 -4
  13. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +4 -0
  14. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +2 -2
  15. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +20 -26
  16. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +1 -1
  17. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +1 -1
  18. package/dist/containers/UserSettings/Setting.tsx +82 -0
  19. package/dist/containers/UserSettings/UserSettings.tsx +61 -102
  20. package/dist/containers/UserSettings/i18n/en.json +20 -0
  21. package/dist/containers/UserSettings/i18n/index.ts +11 -0
  22. package/dist/containers/UserSettings/i18n/ru.json +20 -0
  23. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
  24. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
  25. package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
  26. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
  27. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
  28. package/dist/containers/Versions/Versions.scss +32 -0
  29. package/dist/containers/Versions/Versions.tsx +121 -0
  30. package/dist/containers/Versions/groupNodes.ts +124 -0
  31. package/dist/containers/Versions/types.ts +16 -0
  32. package/dist/routes.ts +0 -6
  33. package/dist/services/api.ts +3 -0
  34. package/dist/store/reducers/cluster/cluster.ts +4 -0
  35. package/dist/store/reducers/cluster/types.ts +3 -2
  36. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
  37. package/dist/store/reducers/clusterNodes/types.ts +22 -0
  38. package/dist/store/reducers/index.ts +2 -8
  39. package/dist/store/reducers/partitions/partitions.ts +2 -2
  40. package/dist/store/reducers/partitions/types.ts +1 -1
  41. package/dist/types/additionalProps.ts +5 -0
  42. package/dist/types/versions.ts +9 -0
  43. package/dist/utils/constants.ts +0 -11
  44. package/dist/utils/hooks/useSetting.ts +5 -3
  45. package/dist/utils/versions/getVersionsColors.ts +98 -0
  46. package/dist/utils/versions/index.ts +3 -0
  47. package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
  48. package/dist/utils/versions/parseVersion.ts +23 -0
  49. package/package.json +1 -1
  50. package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
  51. package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
  52. package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
  53. package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
  54. package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
  55. package/dist/components/GroupViewer/GroupViewer.js +0 -100
  56. package/dist/components/GroupViewer/GroupViewer.scss +0 -45
  57. package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
  58. package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
  59. package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
  60. package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
  61. package/dist/components/VerticalBars/VerticalBars.scss +0 -15
  62. package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
  63. package/dist/components/VerticalBars/index.ts +0 -1
  64. package/dist/containers/Group/Group.js +0 -97
  65. package/dist/containers/Group/Group.scss +0 -6
  66. package/dist/containers/Header/Host/Host.js +0 -66
  67. package/dist/containers/Header/Host/Host.scss +0 -50
  68. package/dist/containers/Pdisk/Pdisk.js +0 -156
  69. package/dist/containers/Pdisk/Pdisk.scss +0 -42
  70. package/dist/containers/Pool/Pool.js +0 -170
  71. package/dist/containers/Pool/Pool.scss +0 -35
  72. package/dist/containers/Vdisk/Vdisk.js +0 -158
  73. package/dist/containers/Vdisk/Vdisk.scss +0 -42
  74. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
  75. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
  76. package/dist/store/reducers/group.js +0 -49
  77. package/dist/store/reducers/pdisk.js +0 -51
  78. package/dist/store/reducers/pool.js +0 -42
  79. package/dist/store/reducers/vdisk.js +0 -49
@@ -0,0 +1,150 @@
1
+ import {useDispatch} from 'react-redux';
2
+
3
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
4
+
5
+ import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
6
+ import type {ShowTooltipFunction} from '../../../types/store/tooltip';
7
+
8
+ import {hideTooltip, showTooltip} from '../../../store/reducers/tooltip';
9
+ import {isUnavailableNode} from '../../../utils/nodes';
10
+ import {formatBytes} from '../../../utils';
11
+ import {getDefaultNodePath} from '../../Node/NodePages';
12
+
13
+ import ProgressViewer from '../../../components/ProgressViewer/ProgressViewer';
14
+ import PoolsGraph from '../../../components/PoolsGraph/PoolsGraph';
15
+ import EntityStatus from '../../../components/EntityStatus/EntityStatus';
16
+
17
+ const getColumns = ({
18
+ onShowTooltip,
19
+ onHideTooltip,
20
+ }: {
21
+ onShowTooltip: (...args: Parameters<ShowTooltipFunction>) => void;
22
+ onHideTooltip: VoidFunction;
23
+ }): Column<PreparedClusterNode>[] => [
24
+ {
25
+ name: 'NodeId',
26
+ header: '#',
27
+ width: '80px',
28
+ align: DataTable.LEFT,
29
+ render: ({row}) => row.NodeId,
30
+ },
31
+ {
32
+ name: 'Host',
33
+ render: ({row}) => {
34
+ const port =
35
+ row.Endpoints && row.Endpoints.find((item) => item.Name === 'http-mon')?.Address;
36
+ const host = row.Host && `${row.Host}${port || ''}`;
37
+ const title = host || 'unknown';
38
+
39
+ const nodePath =
40
+ !isUnavailableNode(row) && row.NodeId ? getDefaultNodePath(row.NodeId) : undefined;
41
+
42
+ return (
43
+ <EntityStatus name={title} path={nodePath} hasClipboardButton showStatus={false} />
44
+ );
45
+ },
46
+ width: '400px',
47
+ align: DataTable.LEFT,
48
+ },
49
+ {
50
+ name: 'Endpoints',
51
+ sortable: false,
52
+ render: ({row}) =>
53
+ row.Endpoints
54
+ ? row.Endpoints.map(({Name, Address}) => `${Name} ${Address}`).join(', ')
55
+ : '-',
56
+ width: '300px',
57
+ align: DataTable.LEFT,
58
+ },
59
+ {
60
+ name: 'uptime',
61
+ header: 'Uptime',
62
+ sortAccessor: ({StartTime}) => StartTime && -StartTime,
63
+ width: '120px',
64
+ align: DataTable.LEFT,
65
+ render: ({row}) => row.uptime,
66
+ },
67
+ {
68
+ name: 'MemoryUsed',
69
+ header: 'Memory used',
70
+ sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
71
+ defaultOrder: DataTable.DESCENDING,
72
+ render: ({row}) => (row.MemoryUsed ? formatBytes(row.MemoryUsed) : '—'),
73
+ width: '120px',
74
+ align: DataTable.RIGHT,
75
+ },
76
+ {
77
+ name: 'MemoryLimit',
78
+ header: 'Memory limit',
79
+ sortAccessor: ({MemoryLimit = 0}) => Number(MemoryLimit),
80
+ defaultOrder: DataTable.DESCENDING,
81
+ render: ({row}) => (row.MemoryLimit ? formatBytes(row.MemoryLimit) : '—'),
82
+ width: '120px',
83
+ align: DataTable.RIGHT,
84
+ },
85
+ {
86
+ name: 'PoolStats',
87
+ header: 'Pools',
88
+ sortAccessor: ({PoolStats = []}) =>
89
+ PoolStats.reduce((acc, item) => acc + (item.Usage || 0), 0),
90
+ defaultOrder: DataTable.DESCENDING,
91
+ width: '120px',
92
+ render: ({row}) =>
93
+ row.PoolStats ? (
94
+ <PoolsGraph
95
+ onMouseEnter={onShowTooltip}
96
+ onMouseLeave={onHideTooltip}
97
+ pools={row.PoolStats}
98
+ />
99
+ ) : (
100
+ '—'
101
+ ),
102
+ align: DataTable.LEFT,
103
+ },
104
+ {
105
+ name: 'LoadAverage',
106
+ header: 'Load average',
107
+ sortAccessor: ({LoadAverage = []}) =>
108
+ LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
109
+ defaultOrder: DataTable.DESCENDING,
110
+ width: '200px',
111
+ render: ({row}) =>
112
+ row.LoadAverage && row.LoadAverage.length > 0 ? (
113
+ <ProgressViewer
114
+ value={row.LoadAverage[0]}
115
+ percents={true}
116
+ colorizeProgress={true}
117
+ />
118
+ ) : (
119
+ '—'
120
+ ),
121
+ align: DataTable.LEFT,
122
+ },
123
+ ];
124
+
125
+ interface NodesTableProps {
126
+ nodes: PreparedClusterNode[];
127
+ }
128
+
129
+ export const NodesTable = ({nodes}: NodesTableProps) => {
130
+ const dispatch = useDispatch();
131
+
132
+ const onShowTooltip = (...args: Parameters<ShowTooltipFunction>) => {
133
+ dispatch(showTooltip(...args));
134
+ };
135
+
136
+ const onHideTooltip = () => {
137
+ dispatch(hideTooltip());
138
+ };
139
+
140
+ return (
141
+ <DataTable
142
+ theme="yandex-cloud"
143
+ data={nodes}
144
+ columns={getColumns({onShowTooltip, onHideTooltip})}
145
+ settings={{
146
+ displayIndices: false,
147
+ }}
148
+ />
149
+ );
150
+ };
@@ -0,0 +1,55 @@
1
+ .ydb-versions-nodes-tree-title {
2
+ &__overview {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+
7
+ width: 100%;
8
+ }
9
+
10
+ &__overview-info {
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ margin-left: 25px;
15
+
16
+ & > *:not(:first-child) {
17
+ margin-left: 30px;
18
+ }
19
+ }
20
+ &__overview-container {
21
+ display: flex;
22
+ align-items: center;
23
+ }
24
+ &__info-label {
25
+ font-weight: 200;
26
+
27
+ color: var(--yc-color-text-complementary);
28
+
29
+ &_margin_left {
30
+ margin-left: 5px;
31
+ }
32
+ &_margin_right {
33
+ margin-right: 5px;
34
+ }
35
+ }
36
+
37
+ &__version-color {
38
+ width: 16px;
39
+ height: 16px;
40
+ margin-right: 10px;
41
+
42
+ border-radius: 100%;
43
+ }
44
+
45
+ &__version-progress {
46
+ display: flex;
47
+ align-items: center;
48
+
49
+ width: 250px;
50
+
51
+ & .yc-progress {
52
+ width: 200px;
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,62 @@
1
+ import block from 'bem-cn-lite';
2
+
3
+ import {Progress} from '@gravity-ui/uikit';
4
+
5
+ import type {VersionValue} from '../../../types/versions';
6
+ import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
7
+ import type {GroupedNodesItem} from '../types';
8
+
9
+ import './NodesTreeTitle.scss';
10
+
11
+ const b = block('ydb-versions-nodes-tree-title');
12
+
13
+ interface NodesTreeTitleProps {
14
+ title?: string;
15
+ nodes?: PreparedClusterNode[];
16
+ items?: GroupedNodesItem[];
17
+ versionColor?: string;
18
+ versionsValues?: VersionValue[];
19
+ }
20
+
21
+ export const NodesTreeTitle = ({
22
+ title,
23
+ nodes,
24
+ items,
25
+ versionColor,
26
+ versionsValues,
27
+ }: NodesTreeTitleProps) => {
28
+ let nodesAmount;
29
+ if (items) {
30
+ nodesAmount = items.reduce((acc, curr) => {
31
+ if (!curr.nodes) {
32
+ return acc;
33
+ }
34
+ return acc + curr.nodes.length;
35
+ }, 0);
36
+ } else {
37
+ nodesAmount = nodes ? nodes.length : 0;
38
+ }
39
+
40
+ return (
41
+ <div className={b('overview')}>
42
+ <div className={b('overview-container')}>
43
+ {versionColor ? (
44
+ <div className={b('version-color')} style={{background: versionColor}} />
45
+ ) : null}
46
+ <span className={b('overview-title')}>{title}</span>
47
+ </div>
48
+ <div className={b('overview-info')}>
49
+ <div>
50
+ <span className={b('info-value')}>{nodesAmount}</span>
51
+ <span className={b('info-label', {margin: 'left'})}>Nodes</span>
52
+ </div>
53
+ {versionsValues ? (
54
+ <div className={b('version-progress')}>
55
+ <span className={b('info-label', {margin: 'right'})}>Versions</span>
56
+ <Progress view="thin" value={100} stack={versionsValues} />
57
+ </div>
58
+ ) : null}
59
+ </div>
60
+ </div>
61
+ );
62
+ };
@@ -0,0 +1,32 @@
1
+ @import '../../styles/mixins.scss';
2
+
3
+ .ydb-versions {
4
+ $_: &;
5
+
6
+ &__content {
7
+ padding: 20px;
8
+ }
9
+
10
+ &__controls {
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ padding: 0 0 20px;
15
+
16
+ #{$_} {
17
+ &__label {
18
+ margin-right: 10px;
19
+
20
+ font-weight: 500;
21
+ }
22
+
23
+ &__checkbox {
24
+ margin: 0;
25
+ }
26
+ }
27
+
28
+ & > * {
29
+ margin-right: 25px;
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,121 @@
1
+ import {useState} from 'react';
2
+ import block from 'bem-cn-lite';
3
+
4
+ import {Checkbox, RadioButton} from '@gravity-ui/uikit';
5
+
6
+ import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/types';
7
+ import type {VersionToColorMap} from '../../types/versions';
8
+ import {getGroupedStorageNodes, getGroupedTenantNodes, getOtherNodes} from './groupNodes';
9
+ import {GroupedNodesTree} from './GroupedNodesTree/GroupedNodesTree';
10
+ import {GroupByValue} from './types';
11
+
12
+ import './Versions.scss';
13
+
14
+ const b = block('ydb-versions');
15
+
16
+ interface VersionsProps {
17
+ nodes?: PreparedClusterNode[];
18
+ versionToColor?: VersionToColorMap;
19
+ }
20
+
21
+ export const Versions = ({nodes = [], versionToColor}: VersionsProps) => {
22
+ const [groupByValue, setGroupByValue] = useState<GroupByValue>(GroupByValue.VERSION);
23
+ const [expanded, setExpanded] = useState(false);
24
+
25
+ const handleGroupByValueChange = (value: string) => {
26
+ setGroupByValue(value as GroupByValue);
27
+ };
28
+
29
+ const renderGroupControl = () => {
30
+ return (
31
+ <div className={b('group')}>
32
+ <span className={b('label')}>Group by:</span>
33
+ <RadioButton value={groupByValue} onUpdate={handleGroupByValueChange}>
34
+ <RadioButton.Option value={GroupByValue.TENANT}>
35
+ {GroupByValue.TENANT}
36
+ </RadioButton.Option>
37
+ <RadioButton.Option value={GroupByValue.VERSION}>
38
+ {GroupByValue.VERSION}
39
+ </RadioButton.Option>
40
+ </RadioButton>
41
+ </div>
42
+ );
43
+ };
44
+ const renderControls = () => {
45
+ return (
46
+ <div className={b('controls')}>
47
+ {renderGroupControl()}
48
+ <Checkbox
49
+ className={b('checkbox')}
50
+ onChange={() => setExpanded((value) => !value)}
51
+ checked={expanded}
52
+ >
53
+ All expanded
54
+ </Checkbox>
55
+ </div>
56
+ );
57
+ };
58
+ const renderGroupedNodes = () => {
59
+ const tenantNodes = getGroupedTenantNodes(nodes, versionToColor, groupByValue);
60
+ const storageNodes = getGroupedStorageNodes(nodes, versionToColor);
61
+ const otherNodes = getOtherNodes(nodes, versionToColor);
62
+ const storageNodesContent = storageNodes?.length ? (
63
+ <>
64
+ <h3>Storage nodes</h3>
65
+ {storageNodes.map(({title, nodes: itemNodes, items, versionColor}, index) => (
66
+ <GroupedNodesTree
67
+ key={`storage-nodes-${index}`}
68
+ title={title}
69
+ nodes={itemNodes}
70
+ items={items}
71
+ versionColor={versionColor}
72
+ />
73
+ ))}
74
+ </>
75
+ ) : null;
76
+ const tenantNodesContent = tenantNodes?.length ? (
77
+ <>
78
+ <h3>Database nodes</h3>
79
+ {renderControls()}
80
+ {tenantNodes.map(
81
+ ({title, nodes: itemNodes, items, versionColor, versionsValues}, index) => (
82
+ <GroupedNodesTree
83
+ key={`tenant-nodes-${index}`}
84
+ title={title}
85
+ nodes={itemNodes}
86
+ items={items}
87
+ expanded={expanded}
88
+ versionColor={versionColor}
89
+ versionsValues={versionsValues}
90
+ />
91
+ ),
92
+ )}
93
+ </>
94
+ ) : null;
95
+ const otherNodesContent = otherNodes?.length ? (
96
+ <>
97
+ <h3>Other nodes</h3>
98
+ {otherNodes.map(
99
+ ({title, nodes: itemNodes, items, versionColor, versionsValues}, index) => (
100
+ <GroupedNodesTree
101
+ key={`other-nodes-${index}`}
102
+ title={title}
103
+ nodes={itemNodes}
104
+ items={items}
105
+ versionColor={versionColor}
106
+ versionsValues={versionsValues}
107
+ />
108
+ ),
109
+ )}
110
+ </>
111
+ ) : null;
112
+ return (
113
+ <div className={b('versions')}>
114
+ {storageNodesContent}
115
+ {tenantNodesContent}
116
+ {otherNodesContent}
117
+ </div>
118
+ );
119
+ };
120
+ return <div className={b('content')}>{renderGroupedNodes()}</div>;
121
+ };
@@ -0,0 +1,124 @@
1
+ import {groupBy} from 'lodash';
2
+
3
+ import type {VersionToColorMap} from '../../types/versions';
4
+ import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/types';
5
+ import {getMinorVersion, parseNodesToVersionsValues} from '../../utils/versions';
6
+
7
+ import {GroupByValue, GroupedNodesItem} from './types';
8
+
9
+ const sortByTitle = (a: GroupedNodesItem, b: GroupedNodesItem) =>
10
+ a.title?.localeCompare(b.title || '') || -1;
11
+
12
+ export const getGroupedTenantNodes = (
13
+ nodes: PreparedClusterNode[] | undefined,
14
+ versionToColor: VersionToColorMap | undefined,
15
+ groupByValue: GroupByValue,
16
+ ): GroupedNodesItem[] | undefined => {
17
+ if (!nodes || !nodes.length) {
18
+ return undefined;
19
+ }
20
+
21
+ if (groupByValue === GroupByValue.VERSION) {
22
+ const dividedByVersion = groupBy(nodes, 'Version');
23
+
24
+ return Object.keys(dividedByVersion)
25
+ .map<GroupedNodesItem | null>((version) => {
26
+ const filteredNodes = dividedByVersion[version].filter(({Tenants}) =>
27
+ Boolean(Tenants),
28
+ );
29
+ const dividedByTenant = groupBy(filteredNodes, 'Tenants');
30
+
31
+ const items = Object.keys(dividedByTenant)
32
+ .map((tenant) => {
33
+ return {
34
+ title: tenant,
35
+ nodes: dividedByTenant[tenant],
36
+ };
37
+ })
38
+ .sort(sortByTitle);
39
+
40
+ if (!items.length) {
41
+ return null;
42
+ }
43
+
44
+ return {
45
+ title: version,
46
+ items: items,
47
+ versionColor: versionToColor?.get(getMinorVersion(version)),
48
+ };
49
+ })
50
+ .filter((item): item is GroupedNodesItem => Boolean(item));
51
+ } else {
52
+ const filteredNodes = nodes.filter(({Tenants}) => Boolean(Tenants));
53
+ const dividedByTenant = groupBy(filteredNodes, 'Tenants');
54
+
55
+ return Object.keys(dividedByTenant)
56
+ .map<GroupedNodesItem | null>((tenant) => {
57
+ const versionsValues = parseNodesToVersionsValues(
58
+ dividedByTenant[tenant],
59
+ versionToColor,
60
+ );
61
+
62
+ const dividedByVersion = groupBy(dividedByTenant[tenant], 'Version');
63
+ const preparedItems = Object.keys(dividedByVersion).map((version) => {
64
+ return {
65
+ title: version,
66
+ nodes: dividedByVersion[version],
67
+ versionColor: versionToColor?.get(getMinorVersion(version)),
68
+ };
69
+ });
70
+
71
+ if (!preparedItems.length) {
72
+ return null;
73
+ }
74
+
75
+ return {
76
+ title: tenant,
77
+ items: preparedItems,
78
+ versionsValues,
79
+ };
80
+ })
81
+ .filter((item): item is GroupedNodesItem => Boolean(item))
82
+ .sort(sortByTitle);
83
+ }
84
+ };
85
+
86
+ export const getGroupedStorageNodes = (
87
+ nodes: PreparedClusterNode[] | undefined,
88
+ versionToColor: VersionToColorMap | undefined,
89
+ ): GroupedNodesItem[] | undefined => {
90
+ if (!nodes || !nodes.length) {
91
+ return undefined;
92
+ }
93
+
94
+ const storageNodes = nodes.filter(({Roles}) => Roles?.includes('Storage'));
95
+ const storageNodesDividedByVersion = groupBy(storageNodes, 'Version');
96
+
97
+ return Object.keys(storageNodesDividedByVersion).map((version) => {
98
+ return {
99
+ title: version,
100
+ nodes: storageNodesDividedByVersion[version],
101
+ versionColor: versionToColor?.get(getMinorVersion(version)),
102
+ };
103
+ });
104
+ };
105
+
106
+ export const getOtherNodes = (
107
+ nodes: PreparedClusterNode[] | undefined,
108
+ versionToColor: VersionToColorMap | undefined,
109
+ ): GroupedNodesItem[] | undefined => {
110
+ if (!nodes || !nodes.length) {
111
+ return undefined;
112
+ }
113
+
114
+ const otherNodes = nodes.filter(({Roles}) => !Roles);
115
+ const otherNodesDividedByVersion = groupBy(otherNodes, 'Version');
116
+
117
+ return Object.keys(otherNodesDividedByVersion).map((version) => {
118
+ return {
119
+ title: version,
120
+ nodes: otherNodesDividedByVersion[version],
121
+ versionColor: versionToColor?.get(getMinorVersion(version)),
122
+ };
123
+ });
124
+ };
@@ -0,0 +1,16 @@
1
+ import type {VersionValue} from '../../types/versions';
2
+ import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/types';
3
+
4
+ export interface GroupedNodesItem {
5
+ title?: string;
6
+ nodes?: PreparedClusterNode[];
7
+ items?: GroupedNodesItem[];
8
+ versionColor?: string;
9
+ versionsValues?: VersionValue[];
10
+ }
11
+
12
+ export enum GroupByValue {
13
+ VERSION = 'Version',
14
+ TENANT = 'Database',
15
+ STORAGE = 'Storage',
16
+ }
package/dist/routes.ts CHANGED
@@ -8,14 +8,8 @@ const routes = {
8
8
  cluster: '/cluster/:activeTab?',
9
9
  tenant: '/tenant',
10
10
  node: '/node/:id/:activeTab?',
11
- pdisk: '/pdisk/:id',
12
- group: '/group/:id',
13
- vdisk: '/vdisk',
14
- network: '/network',
15
- pool: '/pool/:poolName',
16
11
  tablet: '/tablet/:id',
17
12
  tabletsFilters: '/tabletsFilters',
18
- clusterPage: '/clusters/:name',
19
13
  auth: '/auth',
20
14
  };
21
15
 
@@ -50,6 +50,9 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
50
50
  tablets: true,
51
51
  });
52
52
  }
53
+ getClusterNodes() {
54
+ return this.get<TEvSystemStateResponse>(this.getPath('/viewer/json/sysinfo'), {});
55
+ }
53
56
  getNodeInfo(id?: string) {
54
57
  return this.get<TEvSystemStateResponse>(this.getPath('/viewer/json/sysinfo?enums=true'), {
55
58
  node_id: id,
@@ -26,6 +26,10 @@ const cluster: Reducer<ClusterState, ClusterAction> = (state = initialState, act
26
26
  };
27
27
  }
28
28
  case FETCH_CLUSTER.FAILURE: {
29
+ if (action.error?.isCancelled) {
30
+ return state;
31
+ }
32
+
29
33
  return {
30
34
  ...state,
31
35
  error: action.error,
@@ -2,12 +2,13 @@ import {FETCH_CLUSTER} from './cluster';
2
2
 
3
3
  import type {TClusterInfo} from '../../../types/api/cluster';
4
4
  import type {ApiRequestAction} from '../../utils';
5
+ import type {IResponseError} from '../../../types/api/error';
5
6
 
6
7
  export interface ClusterState {
7
8
  loading: boolean;
8
9
  wasLoaded: boolean;
9
10
  data?: TClusterInfo;
10
- error?: unknown;
11
+ error?: IResponseError;
11
12
  }
12
13
 
13
- export type ClusterAction = ApiRequestAction<typeof FETCH_CLUSTER, TClusterInfo, unknown>;
14
+ export type ClusterAction = ApiRequestAction<typeof FETCH_CLUSTER, TClusterInfo, IResponseError>;
@@ -0,0 +1,64 @@
1
+ import {Reducer} from 'redux';
2
+
3
+ import {createRequestActionTypes, createApiRequest} from '../../utils';
4
+
5
+ import type {ClusterNodesAction, ClusterNodesState, PreparedClusterNode} from './types';
6
+
7
+ import '../../../services/api';
8
+ import {calcUptime} from '../../../utils';
9
+
10
+ export const FETCH_CLUSTER_NODES = createRequestActionTypes('cluster', 'FETCH_CLUSTER_NODES');
11
+
12
+ const initialState = {loading: false, wasLoaded: false};
13
+
14
+ const clusterNodes: Reducer<ClusterNodesState, ClusterNodesAction> = (
15
+ state = initialState,
16
+ action,
17
+ ) => {
18
+ switch (action.type) {
19
+ case FETCH_CLUSTER_NODES.REQUEST: {
20
+ return {
21
+ ...state,
22
+ loading: true,
23
+ };
24
+ }
25
+ case FETCH_CLUSTER_NODES.SUCCESS: {
26
+ const {data = []} = action;
27
+
28
+ return {
29
+ ...state,
30
+ nodes: data,
31
+ loading: false,
32
+ wasLoaded: true,
33
+ error: undefined,
34
+ };
35
+ }
36
+ case FETCH_CLUSTER_NODES.FAILURE: {
37
+ return {
38
+ ...state,
39
+ error: action.error,
40
+ loading: false,
41
+ };
42
+ }
43
+ default:
44
+ return state;
45
+ }
46
+ };
47
+
48
+ export function getClusterNodes() {
49
+ return createApiRequest({
50
+ request: window.api.getClusterNodes(),
51
+ actions: FETCH_CLUSTER_NODES,
52
+ dataHandler: (data): PreparedClusterNode[] => {
53
+ const {SystemStateInfo: nodes = []} = data;
54
+ return nodes.map((node) => {
55
+ return {
56
+ ...node,
57
+ uptime: calcUptime(node.StartTime),
58
+ };
59
+ });
60
+ },
61
+ });
62
+ }
63
+
64
+ export default clusterNodes;