ydb-embedded-ui 4.3.0 → 4.4.1

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 (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
+ };