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.
- package/CHANGELOG.md +24 -0
- package/dist/containers/App/Content.js +2 -8
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
- package/dist/containers/Cluster/Cluster.scss +4 -0
- package/dist/containers/Cluster/Cluster.tsx +14 -8
- package/dist/{components → containers}/ClusterInfo/ClusterInfo.scss +39 -0
- package/dist/containers/ClusterInfo/ClusterInfo.tsx +207 -0
- package/dist/containers/ClusterInfo/utils.ts +13 -0
- package/dist/containers/Header/Header.tsx +9 -16
- package/dist/containers/Nodes/Nodes.tsx +4 -6
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +4 -4
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +4 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +20 -26
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +1 -1
- package/dist/containers/UserSettings/Setting.tsx +82 -0
- package/dist/containers/UserSettings/UserSettings.tsx +61 -102
- package/dist/containers/UserSettings/i18n/en.json +20 -0
- package/dist/containers/UserSettings/i18n/index.ts +11 -0
- package/dist/containers/UserSettings/i18n/ru.json +20 -0
- package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
- package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
- package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
- package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
- package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
- package/dist/containers/Versions/Versions.scss +32 -0
- package/dist/containers/Versions/Versions.tsx +121 -0
- package/dist/containers/Versions/groupNodes.ts +124 -0
- package/dist/containers/Versions/types.ts +16 -0
- package/dist/routes.ts +0 -6
- package/dist/services/api.ts +3 -0
- package/dist/store/reducers/cluster/cluster.ts +4 -0
- package/dist/store/reducers/cluster/types.ts +3 -2
- package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
- package/dist/store/reducers/clusterNodes/types.ts +22 -0
- package/dist/store/reducers/index.ts +2 -8
- package/dist/store/reducers/partitions/partitions.ts +2 -2
- package/dist/store/reducers/partitions/types.ts +1 -1
- package/dist/types/additionalProps.ts +5 -0
- package/dist/types/versions.ts +9 -0
- package/dist/utils/constants.ts +0 -11
- package/dist/utils/hooks/useSetting.ts +5 -3
- package/dist/utils/versions/getVersionsColors.ts +98 -0
- package/dist/utils/versions/index.ts +3 -0
- package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
- package/dist/utils/versions/parseVersion.ts +23 -0
- package/package.json +1 -1
- package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
- package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
- package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
- package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
- package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
- package/dist/components/GroupViewer/GroupViewer.js +0 -100
- package/dist/components/GroupViewer/GroupViewer.scss +0 -45
- package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
- package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
- package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
- package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
- package/dist/components/VerticalBars/VerticalBars.scss +0 -15
- package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
- package/dist/components/VerticalBars/index.ts +0 -1
- package/dist/containers/Group/Group.js +0 -97
- package/dist/containers/Group/Group.scss +0 -6
- package/dist/containers/Header/Host/Host.js +0 -66
- package/dist/containers/Header/Host/Host.scss +0 -50
- package/dist/containers/Pdisk/Pdisk.js +0 -156
- package/dist/containers/Pdisk/Pdisk.scss +0 -42
- package/dist/containers/Pool/Pool.js +0 -170
- package/dist/containers/Pool/Pool.scss +0 -35
- package/dist/containers/Vdisk/Vdisk.js +0 -158
- package/dist/containers/Vdisk/Vdisk.scss +0 -42
- package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
- package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
- package/dist/store/reducers/group.js +0 -49
- package/dist/store/reducers/pdisk.js +0 -51
- package/dist/store/reducers/pool.js +0 -42
- 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
|
|
package/dist/services/api.ts
CHANGED
@@ -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,
|
@@ -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?:
|
11
|
+
error?: IResponseError;
|
11
12
|
}
|
12
13
|
|
13
|
-
export type ClusterAction = ApiRequestAction<typeof FETCH_CLUSTER, TClusterInfo,
|
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;
|