ydb-embedded-ui 4.3.0 → 4.4.1
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 +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;
|