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.
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
+ }