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.
Files changed (76) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/containers/App/Content.js +2 -8
  3. package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
  4. package/dist/containers/Cluster/Cluster.scss +4 -0
  5. package/dist/containers/Cluster/Cluster.tsx +14 -8
  6. package/dist/{components → containers}/ClusterInfo/ClusterInfo.scss +39 -0
  7. package/dist/containers/ClusterInfo/ClusterInfo.tsx +207 -0
  8. package/dist/containers/ClusterInfo/utils.ts +13 -0
  9. package/dist/containers/Header/Header.tsx +9 -16
  10. package/dist/containers/Nodes/Nodes.tsx +4 -6
  11. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +2 -3
  12. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +4 -4
  13. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +4 -0
  14. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +2 -2
  15. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +20 -26
  16. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +1 -1
  17. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +1 -1
  18. package/dist/containers/UserSettings/Setting.tsx +82 -0
  19. package/dist/containers/UserSettings/UserSettings.tsx +61 -99
  20. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
  21. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
  22. package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
  23. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
  24. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
  25. package/dist/containers/Versions/Versions.scss +32 -0
  26. package/dist/containers/Versions/Versions.tsx +121 -0
  27. package/dist/containers/Versions/groupNodes.ts +124 -0
  28. package/dist/containers/Versions/types.ts +16 -0
  29. package/dist/routes.ts +0 -6
  30. package/dist/services/api.ts +3 -0
  31. package/dist/store/reducers/cluster/cluster.ts +4 -0
  32. package/dist/store/reducers/cluster/types.ts +3 -2
  33. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
  34. package/dist/store/reducers/clusterNodes/types.ts +22 -0
  35. package/dist/store/reducers/index.ts +2 -8
  36. package/dist/store/reducers/partitions/partitions.ts +2 -2
  37. package/dist/store/reducers/partitions/types.ts +1 -1
  38. package/dist/types/additionalProps.ts +5 -0
  39. package/dist/types/versions.ts +9 -0
  40. package/dist/utils/constants.ts +0 -11
  41. package/dist/utils/hooks/useSetting.ts +5 -3
  42. package/dist/utils/versions/getVersionsColors.ts +98 -0
  43. package/dist/utils/versions/index.ts +3 -0
  44. package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
  45. package/dist/utils/versions/parseVersion.ts +23 -0
  46. package/package.json +1 -1
  47. package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
  48. package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
  49. package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
  50. package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
  51. package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
  52. package/dist/components/GroupViewer/GroupViewer.js +0 -100
  53. package/dist/components/GroupViewer/GroupViewer.scss +0 -45
  54. package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
  55. package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
  56. package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
  57. package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
  58. package/dist/components/VerticalBars/VerticalBars.scss +0 -15
  59. package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
  60. package/dist/components/VerticalBars/index.ts +0 -1
  61. package/dist/containers/Group/Group.js +0 -97
  62. package/dist/containers/Group/Group.scss +0 -6
  63. package/dist/containers/Header/Host/Host.js +0 -66
  64. package/dist/containers/Header/Host/Host.scss +0 -50
  65. package/dist/containers/Pdisk/Pdisk.js +0 -156
  66. package/dist/containers/Pdisk/Pdisk.scss +0 -42
  67. package/dist/containers/Pool/Pool.js +0 -170
  68. package/dist/containers/Pool/Pool.scss +0 -35
  69. package/dist/containers/Vdisk/Vdisk.js +0 -158
  70. package/dist/containers/Vdisk/Vdisk.scss +0 -42
  71. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
  72. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
  73. package/dist/store/reducers/group.js +0 -49
  74. package/dist/store/reducers/pdisk.js +0 -51
  75. package/dist/store/reducers/pool.js +0 -42
  76. 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.placeholder": "Consumer",
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.placeholder": "Читатель",
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 {setSettingValue} from '../../store/reducers/settings';
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
- function UserSettings(props: any) {
33
- const _onThemeChangeHandler = (value: string) => {
34
- props.setSettingValue(THEME_KEY, value);
35
- };
36
-
37
- const _onInvertedDisksChangeHandler = (value: boolean) => {
38
- props.setSettingValue(INVERTED_DISKS_KEY, String(value));
39
- };
40
-
41
- const _onNodesEndpointChangeHandler = (value: boolean) => {
42
- props.setSettingValue(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY, String(value));
43
- };
44
-
45
- const _onExplainQueryModesChangeHandler = (value: boolean) => {
46
- props.setSettingValue(ENABLE_QUERY_MODES_FOR_EXPLAIN, String(value));
47
- };
48
-
49
- const renderBreakNodesSettingsItem = (title: ReactNode) => {
50
- return (
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
- const renderEnableExplainQueryModesItem = (title: ReactNode) => {
63
- return (
64
- <LabelWithPopover
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="general"
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
- <Settings.Item title="Interface theme">
84
- <RadioButton value={props.theme} onUpdate={_onThemeChangeHandler}>
85
- <RadioButton.Option value={Theme.system}>System</RadioButton.Option>
86
- <RadioButton.Option value={Theme.light}>Light</RadioButton.Option>
87
- <RadioButton.Option value={Theme.dark}>Dark</RadioButton.Option>
88
- </RadioButton>
89
- </Settings.Item>
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 id="experiments" title="Experiments" icon={{data: flaskIcon}}>
71
+ <Settings.Page
72
+ id={SettingsSection.experiments}
73
+ title="Experiments"
74
+ icon={{data: flaskIcon}}
75
+ >
93
76
  <Settings.Section title="Experiments">
94
- <Settings.Item title="Inverted disks space indicators">
95
- <Switch
96
- checked={props.invertedDisks}
97
- onUpdate={_onInvertedDisksChangeHandler}
98
- />
99
- </Settings.Item>
100
- <Settings.Item
101
- title="Break the Nodes tab in Diagnostics"
102
- renderTitleComponent={renderBreakNodesSettingsItem}
103
- >
104
- <Switch
105
- checked={props.useNodesEndpointInDiagnostics}
106
- onUpdate={_onNodesEndpointChangeHandler}
107
- />
108
- </Settings.Item>
109
- <Settings.Item
110
- title="Enable query modes for explain"
111
- renderTitleComponent={renderEnableExplainQueryModesItem}
112
- >
113
- <Switch
114
- checked={props.enableQueryModesForExplain}
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
+ }