ydb-embedded-ui 4.3.0 → 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +17 -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 -99
- 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
@@ -4,7 +4,7 @@
|
|
4
4
|
"headers.unread": "End offset - Last read offset",
|
5
5
|
"headers.uncommited": "End offset - Committed offset",
|
6
6
|
"controls.consumerSelector": "Consumer:",
|
7
|
-
"controls.consumerSelector.
|
7
|
+
"controls.consumerSelector.emptyOption": "No consumer",
|
8
8
|
"controls.partitionSearch": "Partition ID",
|
9
9
|
"controls.generalSearch": "Host, Host ID, Reader, Read Session ID",
|
10
10
|
"table.emptyDataMessage": "No partitions match the current search",
|
@@ -4,7 +4,7 @@
|
|
4
4
|
"headers.unread": "End offset - Last read offset",
|
5
5
|
"headers.uncommited": "End offset - Committed offset",
|
6
6
|
"controls.consumerSelector": "Читатель:",
|
7
|
-
"controls.consumerSelector.
|
7
|
+
"controls.consumerSelector.emptyOption": "Нет читателя",
|
8
8
|
"controls.partitionSearch": "Partition ID",
|
9
9
|
"controls.generalSearch": "Host, Host ID, Reader, Read Session ID",
|
10
10
|
"table.emptyDataMessage": "По заданному поиску нет партиций",
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import type {ReactNode} from 'react';
|
2
|
+
|
3
|
+
import {RadioButton, Switch} from '@gravity-ui/uikit';
|
4
|
+
import {Settings} from '@gravity-ui/navigation';
|
5
|
+
|
6
|
+
import {LabelWithPopover} from '../../components/LabelWithPopover/LabelWithPopover';
|
7
|
+
|
8
|
+
import {useSetting} from '../../utils/hooks';
|
9
|
+
|
10
|
+
import {b} from './UserSettings';
|
11
|
+
|
12
|
+
export type SettingsElementType = 'switch' | 'radio';
|
13
|
+
|
14
|
+
export interface SettingProps {
|
15
|
+
type?: SettingsElementType;
|
16
|
+
title: string;
|
17
|
+
settingKey: string;
|
18
|
+
helpPopoverContent?: ReactNode;
|
19
|
+
values?: {value: string; content: string}[];
|
20
|
+
defaultValue?: unknown;
|
21
|
+
}
|
22
|
+
|
23
|
+
export const Setting = ({
|
24
|
+
type = 'switch',
|
25
|
+
settingKey,
|
26
|
+
title,
|
27
|
+
helpPopoverContent,
|
28
|
+
values,
|
29
|
+
defaultValue,
|
30
|
+
}: SettingProps) => {
|
31
|
+
const [settingValue, setValue] = useSetting(settingKey, defaultValue);
|
32
|
+
|
33
|
+
const renderTitleComponent = (value: ReactNode) => {
|
34
|
+
if (helpPopoverContent) {
|
35
|
+
return (
|
36
|
+
<LabelWithPopover
|
37
|
+
className={b('item-with-popup')}
|
38
|
+
contentClassName={b('popup')}
|
39
|
+
text={value}
|
40
|
+
popoverContent={helpPopoverContent}
|
41
|
+
/>
|
42
|
+
);
|
43
|
+
}
|
44
|
+
|
45
|
+
return value;
|
46
|
+
};
|
47
|
+
|
48
|
+
const getSettingsElement = (elementType: SettingsElementType) => {
|
49
|
+
switch (elementType) {
|
50
|
+
case 'switch': {
|
51
|
+
return <Switch checked={Boolean(settingValue)} onUpdate={setValue} />;
|
52
|
+
}
|
53
|
+
|
54
|
+
case 'radio': {
|
55
|
+
if (!values) {
|
56
|
+
return null;
|
57
|
+
}
|
58
|
+
|
59
|
+
return (
|
60
|
+
<RadioButton value={String(settingValue)} onUpdate={setValue}>
|
61
|
+
{values.map(({value, content}) => {
|
62
|
+
return (
|
63
|
+
<RadioButton.Option value={value} key={value}>
|
64
|
+
{content}
|
65
|
+
</RadioButton.Option>
|
66
|
+
);
|
67
|
+
})}
|
68
|
+
</RadioButton>
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
default:
|
73
|
+
return null;
|
74
|
+
}
|
75
|
+
};
|
76
|
+
|
77
|
+
return (
|
78
|
+
<Settings.Item title={title} renderTitleComponent={renderTitleComponent}>
|
79
|
+
{getSettingsElement(type)}
|
80
|
+
</Settings.Item>
|
81
|
+
);
|
82
|
+
};
|
@@ -1,15 +1,10 @@
|
|
1
|
-
import {ReactNode} from 'react';
|
2
|
-
import {connect} from 'react-redux';
|
3
1
|
import cn from 'bem-cn-lite';
|
4
2
|
|
5
|
-
import {RadioButton, Switch} from '@gravity-ui/uikit';
|
6
3
|
import {Settings} from '@gravity-ui/navigation';
|
7
4
|
|
8
5
|
import favoriteFilledIcon from '../../assets/icons/star.svg';
|
9
6
|
import flaskIcon from '../../assets/icons/flask.svg';
|
10
7
|
|
11
|
-
import {LabelWithPopover} from '../../components/LabelWithPopover/LabelWithPopover';
|
12
|
-
|
13
8
|
import {
|
14
9
|
ENABLE_QUERY_MODES_FOR_EXPLAIN,
|
15
10
|
INVERTED_DISKS_KEY,
|
@@ -17,11 +12,11 @@ import {
|
|
17
12
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
18
13
|
} from '../../utils/constants';
|
19
14
|
|
20
|
-
import {
|
15
|
+
import {Setting, SettingProps} from './Setting';
|
21
16
|
|
22
17
|
import './UserSettings.scss';
|
23
18
|
|
24
|
-
const b = cn('ydb-user-settings');
|
19
|
+
export const b = cn('ydb-user-settings');
|
25
20
|
|
26
21
|
enum Theme {
|
27
22
|
light = 'light',
|
@@ -29,112 +24,79 @@ enum Theme {
|
|
29
24
|
system = 'system',
|
30
25
|
}
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
<LabelWithPopover
|
52
|
-
className={b('item-with-popup')}
|
53
|
-
contentClassName={b('popup')}
|
54
|
-
text={title}
|
55
|
-
popoverContent={
|
56
|
-
'Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on older versions'
|
57
|
-
}
|
58
|
-
/>
|
59
|
-
);
|
60
|
-
};
|
27
|
+
const themeValues = [
|
28
|
+
{
|
29
|
+
value: Theme.system,
|
30
|
+
content: 'System',
|
31
|
+
},
|
32
|
+
{
|
33
|
+
value: Theme.light,
|
34
|
+
content: 'Light',
|
35
|
+
},
|
36
|
+
{
|
37
|
+
value: Theme.dark,
|
38
|
+
content: 'Dark',
|
39
|
+
},
|
40
|
+
];
|
41
|
+
|
42
|
+
export enum SettingsSection {
|
43
|
+
general = 'general',
|
44
|
+
experiments = 'experiments',
|
45
|
+
}
|
61
46
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
className={b('item-with-popup')}
|
66
|
-
contentClassName={b('popup')}
|
67
|
-
text={title}
|
68
|
-
popoverContent={
|
69
|
-
'Enable script | scan query mode selector for both run and explain. May not work on some versions'
|
70
|
-
}
|
71
|
-
/>
|
72
|
-
);
|
73
|
-
};
|
47
|
+
interface UserSettingsProps {
|
48
|
+
settings?: Partial<Record<SettingsSection, SettingProps[]>>;
|
49
|
+
}
|
74
50
|
|
51
|
+
export const UserSettings = ({settings}: UserSettingsProps) => {
|
75
52
|
return (
|
76
53
|
<Settings>
|
77
54
|
<Settings.Page
|
78
|
-
id=
|
55
|
+
id={SettingsSection.general}
|
79
56
|
title="General"
|
80
57
|
icon={{data: favoriteFilledIcon, height: 14, width: 14}}
|
81
58
|
>
|
82
59
|
<Settings.Section title="General">
|
83
|
-
<
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
60
|
+
<Setting
|
61
|
+
settingKey={THEME_KEY}
|
62
|
+
title="Interface theme"
|
63
|
+
type="radio"
|
64
|
+
values={themeValues}
|
65
|
+
/>
|
66
|
+
{settings?.[SettingsSection.general]?.map((setting) => (
|
67
|
+
<Setting key={setting.settingKey} {...setting} />
|
68
|
+
))}
|
90
69
|
</Settings.Section>
|
91
70
|
</Settings.Page>
|
92
|
-
<Settings.Page
|
71
|
+
<Settings.Page
|
72
|
+
id={SettingsSection.experiments}
|
73
|
+
title="Experiments"
|
74
|
+
icon={{data: flaskIcon}}
|
75
|
+
>
|
93
76
|
<Settings.Section title="Experiments">
|
94
|
-
<
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
<
|
114
|
-
|
115
|
-
onUpdate={_onExplainQueryModesChangeHandler}
|
116
|
-
/>
|
117
|
-
</Settings.Item>
|
77
|
+
<Setting
|
78
|
+
settingKey={INVERTED_DISKS_KEY}
|
79
|
+
title={'Inverted disks space indicators'}
|
80
|
+
/>
|
81
|
+
<Setting
|
82
|
+
settingKey={USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY}
|
83
|
+
title={'Break the Nodes tab in Diagnostics'}
|
84
|
+
helpPopoverContent={
|
85
|
+
'Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on older versions'
|
86
|
+
}
|
87
|
+
/>
|
88
|
+
<Setting
|
89
|
+
settingKey={ENABLE_QUERY_MODES_FOR_EXPLAIN}
|
90
|
+
title={'Enable query modes for explain'}
|
91
|
+
helpPopoverContent={
|
92
|
+
'Enable script | scan query mode selector for both run and explain. May not work on some versions'
|
93
|
+
}
|
94
|
+
/>
|
95
|
+
{settings?.[SettingsSection.experiments]?.map((setting) => (
|
96
|
+
<Setting key={setting.settingKey} {...setting} />
|
97
|
+
))}
|
118
98
|
</Settings.Section>
|
119
99
|
</Settings.Page>
|
120
100
|
</Settings>
|
121
101
|
);
|
122
|
-
}
|
123
|
-
|
124
|
-
const mapStateToProps = (state: any) => {
|
125
|
-
const {theme, invertedDisks, useNodesEndpointInDiagnostics, enableQueryModesForExplain} =
|
126
|
-
state.settings.userSettings;
|
127
|
-
|
128
|
-
return {
|
129
|
-
theme,
|
130
|
-
invertedDisks: JSON.parse(invertedDisks),
|
131
|
-
useNodesEndpointInDiagnostics: JSON.parse(useNodesEndpointInDiagnostics),
|
132
|
-
enableQueryModesForExplain: JSON.parse(enableQueryModesForExplain),
|
133
|
-
};
|
134
102
|
};
|
135
|
-
|
136
|
-
const mapDispatchToProps = {
|
137
|
-
setSettingValue,
|
138
|
-
};
|
139
|
-
|
140
|
-
export default connect(mapStateToProps, mapDispatchToProps)(UserSettings);
|
@@ -0,0 +1,59 @@
|
|
1
|
+
@import '../../../styles/mixins.scss';
|
2
|
+
|
3
|
+
.ydb-versions-grouped-node-tree {
|
4
|
+
$item-width: 100%;
|
5
|
+
$margin-size: 24px;
|
6
|
+
|
7
|
+
&_first-level {
|
8
|
+
margin-top: 10px;
|
9
|
+
margin-bottom: 10px;
|
10
|
+
|
11
|
+
border: 1px solid var(--yc-color-line-generic);
|
12
|
+
border-radius: 10px;
|
13
|
+
}
|
14
|
+
|
15
|
+
&__dt-wrapper {
|
16
|
+
position: relative;
|
17
|
+
z-index: 0;
|
18
|
+
|
19
|
+
overflow-x: auto;
|
20
|
+
|
21
|
+
margin-right: $margin-size;
|
22
|
+
margin-left: $margin-size;
|
23
|
+
|
24
|
+
@include freeze-nth-column(1);
|
25
|
+
@include freeze-nth-column(2, 80px);
|
26
|
+
|
27
|
+
@include table-styles;
|
28
|
+
}
|
29
|
+
|
30
|
+
.ydb-tree-view {
|
31
|
+
font-size: var(--yc-text-body-2-font-size);
|
32
|
+
line-height: var(--yc-text-body-2-line-height);
|
33
|
+
|
34
|
+
// Apply margin ignoring first element of the tree
|
35
|
+
.ydb-tree-view {
|
36
|
+
margin-left: $margin-size;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
& .tree-view_item {
|
41
|
+
height: 40px;
|
42
|
+
margin: 0;
|
43
|
+
|
44
|
+
// By default tree is rendered with padding calculated based on level
|
45
|
+
// We replace padding with margin for correct hover
|
46
|
+
padding: 0 10px !important;
|
47
|
+
|
48
|
+
border: 0;
|
49
|
+
border-radius: 10px;
|
50
|
+
}
|
51
|
+
|
52
|
+
& .tree-view_children .tree-view_item {
|
53
|
+
width: $item-width;
|
54
|
+
}
|
55
|
+
|
56
|
+
& .yc-progress__stack {
|
57
|
+
cursor: pointer;
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import {useState, useEffect} from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import {TreeView} from 'ydb-ui-components';
|
5
|
+
|
6
|
+
import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
|
7
|
+
import type {VersionValue} from '../../../types/versions';
|
8
|
+
|
9
|
+
import type {GroupedNodesItem} from '../types';
|
10
|
+
import {NodesTreeTitle} from '../NodesTreeTitle/NodesTreeTitle';
|
11
|
+
import {NodesTable} from '../NodesTable/NodesTable';
|
12
|
+
|
13
|
+
import './GroupedNodesTree.scss';
|
14
|
+
|
15
|
+
export const b = cn('ydb-versions-grouped-node-tree');
|
16
|
+
|
17
|
+
interface GroupedNodesTreeProps {
|
18
|
+
title?: string;
|
19
|
+
nodes?: PreparedClusterNode[];
|
20
|
+
items?: GroupedNodesItem[];
|
21
|
+
expanded?: boolean;
|
22
|
+
versionColor?: string;
|
23
|
+
versionsValues?: VersionValue[];
|
24
|
+
level?: number;
|
25
|
+
}
|
26
|
+
|
27
|
+
export const GroupedNodesTree = ({
|
28
|
+
title,
|
29
|
+
nodes,
|
30
|
+
items,
|
31
|
+
expanded = false,
|
32
|
+
versionColor,
|
33
|
+
versionsValues,
|
34
|
+
level = 0,
|
35
|
+
}: GroupedNodesTreeProps) => {
|
36
|
+
const [isOpened, toggleBlock] = useState(false);
|
37
|
+
|
38
|
+
useEffect(() => {
|
39
|
+
toggleBlock(expanded);
|
40
|
+
}, [expanded]);
|
41
|
+
|
42
|
+
const groupTitle = (
|
43
|
+
<NodesTreeTitle
|
44
|
+
title={title}
|
45
|
+
nodes={nodes}
|
46
|
+
items={items}
|
47
|
+
versionColor={versionColor}
|
48
|
+
versionsValues={versionsValues}
|
49
|
+
/>
|
50
|
+
);
|
51
|
+
|
52
|
+
const toggleCollapsed = () => {
|
53
|
+
toggleBlock((value) => !value);
|
54
|
+
};
|
55
|
+
|
56
|
+
if (items) {
|
57
|
+
return (
|
58
|
+
<div className={b({'first-level': level === 0})}>
|
59
|
+
<TreeView
|
60
|
+
key={title}
|
61
|
+
name={groupTitle}
|
62
|
+
collapsed={!isOpened}
|
63
|
+
hasArrow={true}
|
64
|
+
onClick={toggleCollapsed}
|
65
|
+
onArrowClick={toggleCollapsed}
|
66
|
+
>
|
67
|
+
{items.map((item, index) => (
|
68
|
+
<GroupedNodesTree
|
69
|
+
key={index}
|
70
|
+
title={item.title}
|
71
|
+
nodes={item.nodes}
|
72
|
+
expanded={expanded}
|
73
|
+
versionColor={item.versionColor}
|
74
|
+
level={level + 1}
|
75
|
+
/>
|
76
|
+
))}
|
77
|
+
</TreeView>
|
78
|
+
</div>
|
79
|
+
);
|
80
|
+
}
|
81
|
+
|
82
|
+
return (
|
83
|
+
<div className={b({'first-level': level === 0})}>
|
84
|
+
<TreeView
|
85
|
+
key={title}
|
86
|
+
name={groupTitle}
|
87
|
+
collapsed={!isOpened}
|
88
|
+
hasArrow
|
89
|
+
onClick={toggleCollapsed}
|
90
|
+
onArrowClick={toggleCollapsed}
|
91
|
+
>
|
92
|
+
<div className={b('dt-wrapper')}>
|
93
|
+
<NodesTable nodes={nodes || []} />
|
94
|
+
</div>
|
95
|
+
</TreeView>
|
96
|
+
</div>
|
97
|
+
);
|
98
|
+
};
|
@@ -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
|
+
}
|