ydb-embedded-ui 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +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
|
+
}
|