ydb-embedded-ui 4.3.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/CHANGELOG.md +24 -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 -102
  20. package/dist/containers/UserSettings/i18n/en.json +20 -0
  21. package/dist/containers/UserSettings/i18n/index.ts +11 -0
  22. package/dist/containers/UserSettings/i18n/ru.json +20 -0
  23. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
  24. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
  25. package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
  26. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
  27. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
  28. package/dist/containers/Versions/Versions.scss +32 -0
  29. package/dist/containers/Versions/Versions.tsx +121 -0
  30. package/dist/containers/Versions/groupNodes.ts +124 -0
  31. package/dist/containers/Versions/types.ts +16 -0
  32. package/dist/routes.ts +0 -6
  33. package/dist/services/api.ts +3 -0
  34. package/dist/store/reducers/cluster/cluster.ts +4 -0
  35. package/dist/store/reducers/cluster/types.ts +3 -2
  36. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
  37. package/dist/store/reducers/clusterNodes/types.ts +22 -0
  38. package/dist/store/reducers/index.ts +2 -8
  39. package/dist/store/reducers/partitions/partitions.ts +2 -2
  40. package/dist/store/reducers/partitions/types.ts +1 -1
  41. package/dist/types/additionalProps.ts +5 -0
  42. package/dist/types/versions.ts +9 -0
  43. package/dist/utils/constants.ts +0 -11
  44. package/dist/utils/hooks/useSetting.ts +5 -3
  45. package/dist/utils/versions/getVersionsColors.ts +98 -0
  46. package/dist/utils/versions/index.ts +3 -0
  47. package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
  48. package/dist/utils/versions/parseVersion.ts +23 -0
  49. package/package.json +1 -1
  50. package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
  51. package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
  52. package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
  53. package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
  54. package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
  55. package/dist/components/GroupViewer/GroupViewer.js +0 -100
  56. package/dist/components/GroupViewer/GroupViewer.scss +0 -45
  57. package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
  58. package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
  59. package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
  60. package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
  61. package/dist/components/VerticalBars/VerticalBars.scss +0 -15
  62. package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
  63. package/dist/components/VerticalBars/index.ts +0 -1
  64. package/dist/containers/Group/Group.js +0 -97
  65. package/dist/containers/Group/Group.scss +0 -6
  66. package/dist/containers/Header/Host/Host.js +0 -66
  67. package/dist/containers/Header/Host/Host.scss +0 -50
  68. package/dist/containers/Pdisk/Pdisk.js +0 -156
  69. package/dist/containers/Pdisk/Pdisk.scss +0 -42
  70. package/dist/containers/Pool/Pool.js +0 -170
  71. package/dist/containers/Pool/Pool.scss +0 -35
  72. package/dist/containers/Vdisk/Vdisk.js +0 -158
  73. package/dist/containers/Vdisk/Vdisk.scss +0 -42
  74. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
  75. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
  76. package/dist/store/reducers/group.js +0 -49
  77. package/dist/store/reducers/pdisk.js +0 -51
  78. package/dist/store/reducers/pool.js +0 -42
  79. package/dist/store/reducers/vdisk.js +0 -49
@@ -2,7 +2,7 @@ import {useEffect, useMemo, useState} from 'react';
2
2
  import {escapeRegExp} from 'lodash/fp';
3
3
 
4
4
  import {TableColumnSetupItem} from '@gravity-ui/uikit/build/esm/components/Table/hoc/withTableSettings/withTableSettings';
5
- import {Select, TableColumnSetup} from '@gravity-ui/uikit';
5
+ import {Select, SelectOption, TableColumnSetup} from '@gravity-ui/uikit';
6
6
 
7
7
  import type {ValueOf} from '../../../../../types/common';
8
8
 
@@ -15,8 +15,8 @@ import {b} from '../Partitions';
15
15
 
16
16
  interface PartitionsControlsProps {
17
17
  consumers: string[] | undefined;
18
- selectedConsumer: string | undefined;
19
- onSelectedConsumerChange: (consumer: string | undefined) => void;
18
+ selectedConsumer: string;
19
+ onSelectedConsumerChange: (consumer: string) => void;
20
20
  selectDisabled: boolean;
21
21
  partitions: PreparedPartitionDataWithHosts[] | undefined;
22
22
  onSearchChange: (filteredPartitions: PreparedPartitionDataWithHosts[]) => void;
@@ -39,9 +39,6 @@ export const PartitionsControls = ({
39
39
  const [generalSearchValue, setGeneralSearchValue] = useState('');
40
40
  const [partitionIdSearchValue, setPartitionIdSearchValue] = useState('');
41
41
 
42
- // Manual select control to enforce single-select behaviour for multiple select type
43
- const [consumerSelectOpen, setConsumerSelectOpen] = useState(false);
44
-
45
42
  useEffect(() => {
46
43
  if (!partitions) {
47
44
  return;
@@ -83,16 +80,17 @@ export const PartitionsControls = ({
83
80
  onSearchChange(filteredPartitions);
84
81
  }, [partitionIdSearchValue, generalSearchValue, partitions, onSearchChange]);
85
82
 
86
- const consumersToSelect = useMemo(
87
- () =>
88
- consumers
83
+ const consumersToSelect = useMemo(() => {
84
+ const options =
85
+ consumers && consumers.length
89
86
  ? consumers.map((consumer) => ({
90
87
  value: consumer,
91
88
  content: consumer,
92
89
  }))
93
- : undefined,
94
- [consumers],
95
- );
90
+ : [];
91
+
92
+ return [{value: '', content: i18n('controls.consumerSelector.emptyOption')}, ...options];
93
+ }, [consumers]);
96
94
 
97
95
  const columnsToSelect = useMemo(() => {
98
96
  return initialColumnsIds.map((id) => {
@@ -106,10 +104,7 @@ export const PartitionsControls = ({
106
104
  }, [initialColumnsIds, hiddenColumns]);
107
105
 
108
106
  const handleConsumerSelectChange = (value: string[]) => {
109
- // As we have selector with multiple options, the first value corresponds to previous value
110
- // The second value is currently chosen consumer
111
- onSelectedConsumerChange(value[1]);
112
- setConsumerSelectOpen(false);
107
+ onSelectedConsumerChange(value[0]);
113
108
  };
114
109
 
115
110
  const handlePartitionIdSearchChange = (value: string) => {
@@ -137,25 +132,24 @@ export const PartitionsControls = ({
137
132
  onHiddenColumnsChange(result);
138
133
  };
139
134
 
135
+ const renderOption = (option: SelectOption) => {
136
+ return (
137
+ <div className={b('select-option', {empty: option.value === ''})}>{option.content}</div>
138
+ );
139
+ };
140
+
140
141
  return (
141
142
  <div className={b('controls')}>
142
143
  <Select
143
144
  className={b('consumer-select')}
144
- placeholder={i18n('controls.consumerSelector.placeholder')}
145
145
  label={i18n('controls.consumerSelector')}
146
146
  options={consumersToSelect}
147
- value={[selectedConsumer || '']}
147
+ value={[selectedConsumer]}
148
148
  onUpdate={handleConsumerSelectChange}
149
149
  filterable={consumers && consumers.length > 5}
150
150
  disabled={selectDisabled || !consumers || !consumers.length}
151
- open={consumerSelectOpen}
152
- onOpenChange={setConsumerSelectOpen}
153
- // Although only one value could be selected
154
- // Multiple type Select is used
155
- // The reason - we need Select to be able to work with no value
156
- // And it is easier to make multiple Select close on value change
157
- // Than to enable single select to work with empty values
158
- multiple
151
+ renderOption={renderOption}
152
+ renderSelectedOption={renderOption}
159
153
  />
160
154
  <Search
161
155
  onChange={handlePartitionIdSearchChange}
@@ -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,12 @@ 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';
16
+ import i18n from './i18n';
21
17
 
22
18
  import './UserSettings.scss';
23
19
 
24
- const b = cn('ydb-user-settings');
20
+ export const b = cn('ydb-user-settings');
25
21
 
26
22
  enum Theme {
27
23
  light = 'light',
@@ -29,112 +25,75 @@ enum Theme {
29
25
  system = 'system',
30
26
  }
31
27
 
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
- };
28
+ const themeValues = [
29
+ {
30
+ value: Theme.system,
31
+ content: i18n('settings.theme.option-system'),
32
+ },
33
+ {
34
+ value: Theme.light,
35
+ content: i18n('settings.theme.option-light'),
36
+ },
37
+ {
38
+ value: Theme.dark,
39
+ content: i18n('settings.theme.option-dark'),
40
+ },
41
+ ];
42
+
43
+ export enum SettingsSection {
44
+ general = 'general',
45
+ experiments = 'experiments',
46
+ }
61
47
 
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
- };
48
+ interface UserSettingsProps {
49
+ settings?: Partial<Record<SettingsSection, SettingProps[]>>;
50
+ }
74
51
 
52
+ export const UserSettings = ({settings}: UserSettingsProps) => {
75
53
  return (
76
54
  <Settings>
77
55
  <Settings.Page
78
- id="general"
79
- title="General"
56
+ id={SettingsSection.general}
57
+ title={i18n('page.general')}
80
58
  icon={{data: favoriteFilledIcon, height: 14, width: 14}}
81
59
  >
82
- <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
+ <Settings.Section title={i18n('section.general')}>
61
+ <Setting
62
+ settingKey={THEME_KEY}
63
+ title={i18n('settings.theme.title')}
64
+ type="radio"
65
+ values={themeValues}
66
+ />
67
+ {settings?.[SettingsSection.general]?.map((setting) => (
68
+ <Setting key={setting.settingKey} {...setting} />
69
+ ))}
90
70
  </Settings.Section>
91
71
  </Settings.Page>
92
- <Settings.Page id="experiments" title="Experiments" icon={{data: flaskIcon}}>
93
- <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>
72
+ <Settings.Page
73
+ id={SettingsSection.experiments}
74
+ title={i18n('page.experiments')}
75
+ icon={{data: flaskIcon}}
76
+ >
77
+ <Settings.Section title={i18n('section.experiments')}>
78
+ <Setting
79
+ settingKey={INVERTED_DISKS_KEY}
80
+ title={i18n('settings.invertedDisks.title')}
81
+ />
82
+ <Setting
83
+ settingKey={USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY}
84
+ title={i18n('settings.useNodesEndpoint.title')}
85
+ helpPopoverContent={i18n('settings.useNodesEndpoint.popover')}
86
+ />
87
+ <Setting
88
+ settingKey={ENABLE_QUERY_MODES_FOR_EXPLAIN}
89
+ title={i18n('settings.enableQueryModesForExplain.title')}
90
+ helpPopoverContent={i18n('settings.enableQueryModesForExplain.popover')}
91
+ />
92
+ {settings?.[SettingsSection.experiments]?.map((setting) => (
93
+ <Setting key={setting.settingKey} {...setting} />
94
+ ))}
118
95
  </Settings.Section>
119
96
  </Settings.Page>
120
97
  </Settings>
121
98
  );
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
99
  };
135
-
136
- const mapDispatchToProps = {
137
- setSettingValue,
138
- };
139
-
140
- export default connect(mapStateToProps, mapDispatchToProps)(UserSettings);
@@ -0,0 +1,20 @@
1
+ {
2
+ "page.general": "General",
3
+ "section.general": "General",
4
+
5
+ "page.experiments": "Experiments",
6
+ "section.experiments": "Experiments",
7
+
8
+ "settings.theme.title": "Interface theme",
9
+ "settings.theme.option-dark": "Dark",
10
+ "settings.theme.option-light": "Light",
11
+ "settings.theme.option-system": "System",
12
+
13
+ "settings.invertedDisks.title": "Inverted disks space indicators",
14
+
15
+ "settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
16
+ "settings.useNodesEndpoint.popover": "Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on versions before 23-1",
17
+
18
+ "settings.enableQueryModesForExplain.title": "Enable query modes for explain",
19
+ "settings.enableQueryModesForExplain.popover": "Enable script | scan query mode selector for both run and explain. May not work on versions before 23-2"
20
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-user-settings';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,20 @@
1
+ {
2
+ "page.general": "Общие",
3
+ "section.general": "Общие",
4
+
5
+ "page.experiments": "Эксперименты",
6
+ "section.experiments": "Эксперименты",
7
+
8
+ "settings.theme.title": "Тема",
9
+ "settings.theme.option-dark": "Тёмная",
10
+ "settings.theme.option-light": "Светлая",
11
+ "settings.theme.option-system": "Системная",
12
+
13
+ "settings.invertedDisks.title": "Инвертированные индикаторы места на дисках",
14
+
15
+ "settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
16
+ "settings.useNodesEndpoint.popover": "Использовать эндпоинт /viewer/json/nodes для вкладки Nodes в диагностике. Может возвращать некорректные данные на версиях до 23-1",
17
+
18
+ "settings.enableQueryModesForExplain.title": "Включить режимы выполнения запроса для explain",
19
+ "settings.enableQueryModesForExplain.popover": "Включить общий переключатель script | scan для run и explain. Может работать некорректно на версиях до 23-2"
20
+ }
@@ -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
+ };