ydb-embedded-ui 4.10.0 → 4.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/components/InfoViewer/formatters/schema.ts +3 -1
  3. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.scss +32 -0
  4. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.tsx +43 -0
  5. package/dist/containers/AsideNavigation/AsideNavigation.tsx +2 -2
  6. package/dist/containers/Cluster/Cluster.scss +4 -5
  7. package/dist/containers/Cluster/Cluster.tsx +3 -22
  8. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +4 -0
  9. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -0
  10. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  11. package/dist/containers/Cluster/utils.tsx +0 -11
  12. package/dist/containers/Header/Header.scss +1 -5
  13. package/dist/containers/Nodes/Nodes.scss +1 -24
  14. package/dist/containers/Nodes/Nodes.tsx +28 -38
  15. package/dist/containers/Storage/Storage.scss +1 -14
  16. package/dist/containers/Storage/Storage.tsx +15 -18
  17. package/dist/containers/Storage/StorageTypeFilter/StorageTypeFilter.tsx +3 -1
  18. package/dist/containers/Storage/{StorageVisibleEntityFilter/StorageVisibleEntityFilter.tsx → StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter.tsx} +4 -2
  19. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +6 -2
  20. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +3 -12
  21. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +2 -7
  22. package/dist/containers/Tenant/Query/i18n/en.json +1 -1
  23. package/dist/containers/Tenant/Query/i18n/ru.json +1 -1
  24. package/dist/containers/Tenants/Tenants.scss +1 -13
  25. package/dist/containers/Tenants/Tenants.tsx +17 -24
  26. package/dist/store/reducers/nodes/nodes.ts +4 -112
  27. package/dist/store/reducers/nodes/selectors.ts +74 -0
  28. package/dist/store/reducers/nodes/utils.ts +46 -0
  29. package/dist/store/reducers/storage/selectors.ts +1 -1
  30. package/dist/types/api/compute.ts +27 -2
  31. package/dist/types/api/nodes.ts +12 -1
  32. package/dist/types/api/schema/cdcStream.ts +32 -0
  33. package/dist/types/api/schema/columnEntity.ts +138 -0
  34. package/dist/types/api/schema/externalDataSource.ts +24 -0
  35. package/dist/types/api/schema/externalTable.ts +14 -0
  36. package/dist/types/api/schema/index.ts +10 -0
  37. package/dist/types/api/schema/persQueueGroup.ts +191 -0
  38. package/dist/types/api/schema/schema.ts +299 -0
  39. package/dist/types/api/schema/shared.ts +42 -0
  40. package/dist/types/api/schema/table.ts +616 -0
  41. package/dist/types/api/schema/tableIndex.ts +33 -0
  42. package/package.json +1 -1
  43. package/dist/assets/icons/versions.svg +0 -3
  44. package/dist/types/api/schema.ts +0 -1326
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.10.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.10.0...v4.10.1) (2023-07-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * apply design fixes ([#475](https://github.com/ydb-platform/ydb-embedded-ui/issues/475)) ([5e7c9ca](https://github.com/ydb-platform/ydb-embedded-ui/commit/5e7c9caa9f54094a3eb6448d92d43242d3e738dd))
9
+ * **AsideNavigation:** replace query icon ([#466](https://github.com/ydb-platform/ydb-embedded-ui/issues/466)) ([4495eb2](https://github.com/ydb-platform/ydb-embedded-ui/commit/4495eb2634e48feda677c03591b92393ad28981e))
10
+ * **ClusterInfo:** add Databases field ([#474](https://github.com/ydb-platform/ydb-embedded-ui/issues/474)) ([28a9936](https://github.com/ydb-platform/ydb-embedded-ui/commit/28a99364bf5e916381a54a59d4d3f979b35f6eff))
11
+ * **Cluster:** make global scroll ([#470](https://github.com/ydb-platform/ydb-embedded-ui/issues/470)) ([30f8bc5](https://github.com/ydb-platform/ydb-embedded-ui/commit/30f8bc5ce52fceda076d278b1464d413e899ae21))
12
+ * **Cluster:** remove tabs icons and numbers ([#473](https://github.com/ydb-platform/ydb-embedded-ui/issues/473)) ([d2e43d4](https://github.com/ydb-platform/ydb-embedded-ui/commit/d2e43d41759b085f34b7f29f52f3aba60cd0588f))
13
+ * **Query:** rename New Query tab to Query ([#467](https://github.com/ydb-platform/ydb-embedded-ui/issues/467)) ([c3f5585](https://github.com/ydb-platform/ydb-embedded-ui/commit/c3f5585562a204ef0831d0c45766b17c3dc72f82))
14
+ * **TableIndexInfo:** format DataSize ([#468](https://github.com/ydb-platform/ydb-embedded-ui/issues/468)) ([a189b8c](https://github.com/ydb-platform/ydb-embedded-ui/commit/a189b8cf9610f6b1b7b5f4c01896eda5f8347ebf))
15
+
3
16
  ## [4.10.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.9.0...v4.10.0) (2023-07-07)
4
17
 
5
18
 
@@ -1,4 +1,5 @@
1
- import {TIndexDescription} from '../../../types/api/schema';
1
+ import type {TIndexDescription} from '../../../types/api/schema';
2
+ import {toFormattedSize} from '../../FormattedBytes/utils';
2
3
 
3
4
  import {createInfoFormatter} from '../utils';
4
5
 
@@ -8,6 +9,7 @@ export const formatTableIndexItem = createInfoFormatter<TIndexDescription>({
8
9
  State: (value) => value?.substring(11), // trims EIndexState prefix
9
10
  KeyColumnNames: (value) => value?.join(', '),
10
11
  DataColumnNames: (value) => value?.join(', '),
12
+ DataSize: toFormattedSize,
11
13
  },
12
14
  labels: {
13
15
  KeyColumnNames: 'Columns',
@@ -0,0 +1,32 @@
1
+ @import '../../styles/mixins.scss';
2
+
3
+ .ydb-table-with-controls-layout {
4
+ display: inline-block;
5
+
6
+ box-sizing: border-box;
7
+ min-width: 100%;
8
+
9
+ &__controls-wrapper {
10
+ z-index: 3;
11
+
12
+ box-sizing: border-box;
13
+ width: 100%;
14
+
15
+ @include sticky-top();
16
+ }
17
+
18
+ &__controls {
19
+ z-index: 3;
20
+
21
+ width: max-content;
22
+ height: 62px;
23
+
24
+ @include controls();
25
+ @include sticky-top();
26
+ }
27
+
28
+ .data-table__sticky_moving {
29
+ // Place table head right after controls
30
+ top: 62px !important;
31
+ }
32
+ }
@@ -0,0 +1,43 @@
1
+ import type {ReactNode} from 'react';
2
+ import block from 'bem-cn-lite';
3
+
4
+ import {TableSkeleton} from '../TableSkeleton/TableSkeleton';
5
+
6
+ import './TableWithControlsLayout.scss';
7
+
8
+ const b = block('ydb-table-with-controls-layout');
9
+
10
+ interface TableWithControlsLayoutItemProps {
11
+ children: ReactNode;
12
+ className?: string;
13
+ }
14
+
15
+ interface TableProps extends TableWithControlsLayoutItemProps {
16
+ loading?: boolean;
17
+ }
18
+
19
+ export const TableWithControlsLayout = ({
20
+ children,
21
+ className,
22
+ }: TableWithControlsLayoutItemProps) => {
23
+ return <div className={b(null, className)}>{children}</div>;
24
+ };
25
+
26
+ TableWithControlsLayout.Controls = function TableControls({
27
+ children,
28
+ className,
29
+ }: TableWithControlsLayoutItemProps) {
30
+ return (
31
+ <div className={b('controls-wrapper')}>
32
+ <div className={b('controls', className)}>{children}</div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ TableWithControlsLayout.Table = function Table({children, loading, className}: TableProps) {
38
+ if (loading) {
39
+ return <TableSkeleton className={b('loader')} />;
40
+ }
41
+
42
+ return <div className={b('table', className)}>{children}</div>;
43
+ };
@@ -7,7 +7,7 @@ import cn from 'bem-cn-lite';
7
7
  import {Icon, Button} from '@gravity-ui/uikit';
8
8
  import {AsideHeader, MenuItem as AsideHeaderMenuItem, FooterItem} from '@gravity-ui/navigation';
9
9
 
10
- import squareChartBarIcon from '@gravity-ui/icons/svgs/square-chart-bar.svg';
10
+ import terminalIcon from '@gravity-ui/icons/svgs/terminal.svg';
11
11
  import pulseIcon from '@gravity-ui/icons/svgs/pulse.svg';
12
12
 
13
13
  import signOutIcon from '../../assets/icons/signOut.svg';
@@ -141,7 +141,7 @@ export const useGetLeftNavigationItems = () => {
141
141
  {
142
142
  id: TENANT_PAGES_IDS.query,
143
143
  title: 'Query',
144
- icon: squareChartBarIcon,
144
+ icon: terminalIcon,
145
145
  iconSize: 20,
146
146
  location: getTenantPath({
147
147
  ...queryParams,
@@ -5,13 +5,12 @@
5
5
  flex-grow: 1;
6
6
 
7
7
  height: 100%;
8
- padding: 20px 20px 0px;
8
+ padding: 0 20px;
9
9
 
10
10
  @include flex-container();
11
11
 
12
- &__content {
13
- overflow: auto;
14
-
15
- height: 100%;
12
+ &__tabs {
13
+ position: sticky;
14
+ left: 0;
16
15
  }
17
16
  }
@@ -118,20 +118,6 @@ function Cluster({
118
118
  }
119
119
  };
120
120
 
121
- const getTabEntityCount = (tabId: ClusterTab) => {
122
- switch (tabId) {
123
- case clusterTabsIds.tenants: {
124
- return cluster?.Tenants ? Number(cluster.Tenants) : undefined;
125
- }
126
- case clusterTabsIds.nodes: {
127
- return cluster?.NodesTotal ? Number(cluster.NodesTotal) : undefined;
128
- }
129
- default: {
130
- return undefined;
131
- }
132
- }
133
- };
134
-
135
121
  return (
136
122
  <div className={b()}>
137
123
  <ClusterInfo
@@ -142,17 +128,12 @@ function Cluster({
142
128
  additionalClusterProps={additionalClusterProps}
143
129
  />
144
130
 
145
- <div>
131
+ <div className={b('tabs')}>
146
132
  <Tabs
147
133
  size="l"
148
134
  allowNotSelected={true}
149
135
  activeTab={activeTab as string}
150
- items={clusterTabs.map((item) => {
151
- return {
152
- ...item,
153
- counter: getTabEntityCount(item.id),
154
- };
155
- })}
136
+ items={clusterTabs}
156
137
  wrapTo={({id}, node) => {
157
138
  const path = getClusterPath(id as ClusterTab, queryParams);
158
139
  return (
@@ -164,7 +145,7 @@ function Cluster({
164
145
  />
165
146
  </div>
166
147
 
167
- <div className={b('content')}>{renderTab()}</div>
148
+ <div>{renderTab()}</div>
168
149
  </div>
169
150
  );
170
151
  }
@@ -1,7 +1,11 @@
1
1
  @import '../../../styles/mixins';
2
2
 
3
3
  .cluster-info {
4
+ position: sticky;
5
+ left: 0;
6
+
4
7
  width: 100%;
8
+ padding-top: 20px;
5
9
 
6
10
  &__header {
7
11
  display: flex;
@@ -57,6 +57,13 @@ const getInfo = (
57
57
  });
58
58
  }
59
59
 
60
+ if (cluster.Tenants) {
61
+ info.push({
62
+ label: 'Databases',
63
+ value: cluster.Tenants,
64
+ });
65
+ }
66
+
60
67
  info.push(
61
68
  {
62
69
  label: 'Nodes',
@@ -18,7 +18,7 @@ interface ClusterInfoSkeletonProps {
18
18
  rows?: number;
19
19
  }
20
20
 
21
- export const ClusterInfoSkeleton = ({rows = 6, className}: ClusterInfoSkeletonProps) => (
21
+ export const ClusterInfoSkeleton = ({rows = 7, className}: ClusterInfoSkeletonProps) => (
22
22
  <div className={b(null, className)}>
23
23
  {[...new Array(rows)].map((_, index) => (
24
24
  <div className={b('row')} key={`skeleton-row-${index}`}>
@@ -1,10 +1,3 @@
1
- import {Icon} from '@gravity-ui/uikit';
2
- import cubes3Icon from '@gravity-ui/icons/svgs/cubes-3.svg';
3
- import databasesIcon from '@gravity-ui/icons/svgs/databases.svg';
4
- import hardDriveIcon from '@gravity-ui/icons/svgs/hard-drive.svg';
5
-
6
- import versionsIcon from '../../assets/icons/versions.svg';
7
-
8
1
  import type {ValueOf} from '../../types/common';
9
2
  import routes, {createHref} from '../../routes';
10
3
 
@@ -20,22 +13,18 @@ export type ClusterTab = ValueOf<typeof clusterTabsIds>;
20
13
  const tenants = {
21
14
  id: clusterTabsIds.tenants,
22
15
  title: 'Databases',
23
- icon: <Icon data={databasesIcon} />,
24
16
  };
25
17
  const nodes = {
26
18
  id: clusterTabsIds.nodes,
27
19
  title: 'Nodes',
28
- icon: <Icon data={cubes3Icon} />,
29
20
  };
30
21
  const storage = {
31
22
  id: clusterTabsIds.storage,
32
23
  title: 'Storage',
33
- icon: <Icon data={hardDriveIcon} />,
34
24
  };
35
25
  const versions = {
36
26
  id: clusterTabsIds.versions,
37
27
  title: 'Versions',
38
- icon: <Icon data={versionsIcon} />,
39
28
  };
40
29
 
41
30
  export const clusterTabs = [tenants, nodes, storage, versions];
@@ -6,14 +6,10 @@
6
6
  justify-content: space-between;
7
7
  align-items: center;
8
8
 
9
- padding: 0 20px 0 18px;
10
-
11
- font-weight: 600;
9
+ padding: 0 20px 0 12px;
12
10
 
13
11
  border-bottom: 1px solid var(--yc-color-line-generic);
14
12
 
15
- @include body2-typography;
16
-
17
13
  &__breadcrumb {
18
14
  display: flex;
19
15
  align-items: center;
@@ -1,21 +1,10 @@
1
1
  @import '../../styles/mixins.scss';
2
2
 
3
3
  .ydb-nodes {
4
- overflow: auto;
5
- flex-grow: 1;
6
-
7
- height: 100%;
8
-
9
- @include flex-container();
10
-
11
4
  &__search {
12
5
  @include search();
13
6
  }
14
7
 
15
- &__controls {
16
- @include controls();
17
- }
18
-
19
8
  &__show-all-wrapper {
20
9
  position: sticky;
21
10
  left: 0;
@@ -23,19 +12,7 @@
23
12
  margin-bottom: 15px;
24
13
  }
25
14
 
26
- &__table-wrapper {
27
- overflow: auto;
28
- @include flex-container();
29
- }
30
-
31
- &__table-content {
32
- overflow: auto;
33
-
34
- height: 100%;
35
-
36
- @include freeze-nth-column(1);
37
- @include freeze-nth-column(2, 80px);
38
-
15
+ &__table {
39
16
  @include table-styles;
40
17
  @include table-sticky-styles;
41
18
  }
@@ -9,11 +9,12 @@ import type {ProblemFilterValue} from '../../store/reducers/settings/types';
9
9
 
10
10
  import {AccessDenied} from '../../components/Errors/403';
11
11
  import {Illustration} from '../../components/Illustration';
12
- import {Loader} from '../../components/Loader';
13
12
  import {Search} from '../../components/Search';
14
13
  import {ProblemFilter} from '../../components/ProblemFilter';
15
14
  import {UptimeFilter} from '../../components/UptimeFIlter';
16
15
  import {EntitiesCount} from '../../components/EntitiesCount';
16
+ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
17
+ import {ResponseError} from '../../components/Errors/ResponseError';
17
18
 
18
19
  import {DEFAULT_TABLE_SETTINGS, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY} from '../../utils/constants';
19
20
  import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
@@ -21,12 +22,12 @@ import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '.
21
22
 
22
23
  import {
23
24
  getNodes,
24
- getFilteredPreparedNodesList,
25
25
  setNodesUptimeFilter,
26
26
  setSearchValue,
27
27
  resetNodesState,
28
28
  getComputeNodes,
29
29
  } from '../../store/reducers/nodes/nodes';
30
+ import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
30
31
  import {changeFilter, ProblemFilterValues} from '../../store/reducers/settings/settings';
31
32
 
32
33
  import {isDatabaseEntityType} from '../Tenant/utils/schema';
@@ -42,11 +43,10 @@ const b = cn('ydb-nodes');
42
43
  interface NodesProps {
43
44
  path?: string;
44
45
  type?: EPathType;
45
- className?: string;
46
46
  additionalNodesInfo?: AdditionalNodesInfo;
47
47
  }
48
48
 
49
- export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesProps) => {
49
+ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
50
50
  const dispatch = useDispatch();
51
51
 
52
52
  const isClusterNodes = !path;
@@ -63,7 +63,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
63
63
  const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
64
64
  const {autorefresh} = useTypedSelector((state) => state.schema);
65
65
 
66
- const nodes = useTypedSelector(getFilteredPreparedNodesList);
66
+ const nodes = useTypedSelector(selectFilteredNodes);
67
67
 
68
68
  const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
69
69
 
@@ -93,7 +93,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
93
93
 
94
94
  const renderControls = () => {
95
95
  return (
96
- <div className={b('controls')}>
96
+ <>
97
97
  <Search
98
98
  onChange={handleSearchQueryChange}
99
99
  placeholder="Host name"
@@ -108,7 +108,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
108
108
  label={'Nodes'}
109
109
  loading={loading && !wasLoaded}
110
110
  />
111
- </div>
111
+ </>
112
112
  );
113
113
  };
114
114
 
@@ -127,44 +127,34 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
127
127
  }
128
128
 
129
129
  return (
130
- <div className={b('table-wrapper')}>
131
- <div className={b('table-content')}>
132
- <DataTable
133
- theme="yandex-cloud"
134
- data={nodes || []}
135
- columns={columns}
136
- settings={DEFAULT_TABLE_SETTINGS}
137
- initialSortOrder={{
138
- columnId: 'NodeId',
139
- order: DataTable.ASCENDING,
140
- }}
141
- emptyDataMessage={i18n('empty.default')}
142
- rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
143
- />
144
- </div>
145
- </div>
130
+ <DataTable
131
+ theme="yandex-cloud"
132
+ data={nodes || []}
133
+ columns={columns}
134
+ settings={DEFAULT_TABLE_SETTINGS}
135
+ initialSortOrder={{
136
+ columnId: 'NodeId',
137
+ order: DataTable.ASCENDING,
138
+ }}
139
+ emptyDataMessage={i18n('empty.default')}
140
+ rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
141
+ />
146
142
  );
147
143
  };
148
144
 
149
- const renderContent = () => {
150
- return (
151
- <div className={b(null, className)}>
152
- {renderControls()}
153
- {renderTable()}
154
- </div>
155
- );
156
- };
157
-
158
- if (loading && !wasLoaded) {
159
- return <Loader size={isClusterNodes ? 'l' : 'm'} />;
160
- }
161
-
162
145
  if (error) {
163
146
  if (error.status === 403) {
164
147
  return <AccessDenied />;
165
148
  }
166
- return <div>{error.statusText}</div>;
149
+ return <ResponseError error={error} />;
167
150
  }
168
151
 
169
- return renderContent();
152
+ return (
153
+ <TableWithControlsLayout>
154
+ <TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
155
+ <TableWithControlsLayout.Table loading={loading && !wasLoaded} className={b('table')}>
156
+ {renderTable()}
157
+ </TableWithControlsLayout.Table>
158
+ </TableWithControlsLayout>
159
+ );
170
160
  };
@@ -1,24 +1,11 @@
1
1
  @import '../../styles/mixins.scss';
2
2
 
3
3
  .global-storage {
4
- height: 100%;
5
- max-height: 100%;
6
- @include flex-container;
7
-
8
- &__controls {
9
- @include controls();
10
- }
11
4
  &__search {
12
5
  @include search();
13
6
  }
14
7
 
15
- &__table-wrapper {
16
- overflow: auto;
17
- flex-grow: 1;
18
-
19
- font-size: var(--yc-text-body-2-font-size);
20
- line-height: var(--yc-text-body-2-line-height);
21
-
8
+ &__table {
22
9
  .yc-tooltip {
23
10
  // stylelint-disable-next-line declaration-no-important
24
11
  height: var(--yc-text-body-2-line-height) !important;
@@ -5,10 +5,11 @@ import cn from 'bem-cn-lite';
5
5
  import DataTable, {Settings} from '@gravity-ui/react-data-table';
6
6
 
7
7
  import {Search} from '../../components/Search';
8
- import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton';
9
8
  import {UptimeFilter} from '../../components/UptimeFIlter';
10
9
  import {AccessDenied} from '../../components/Errors/403';
11
10
  import {EntitiesCount} from '../../components/EntitiesCount';
11
+ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
12
+ import {ResponseError} from '../../components/Errors/ResponseError';
12
13
 
13
14
  import type {StorageType, VisibleEntities} from '../../store/reducers/storage/types';
14
15
  import {
@@ -37,7 +38,7 @@ import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
37
38
  import {StorageGroups} from './StorageGroups/StorageGroups';
38
39
  import {StorageNodes} from './StorageNodes/StorageNodes';
39
40
  import {StorageTypeFilter} from './StorageTypeFilter/StorageTypeFilter';
40
- import {StorageVisibleEntityFilter} from './StorageVisibleEntityFilter/StorageVisibleEntityFilter';
41
+ import {StorageVisibleEntitiesFilter} from './StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter';
41
42
  import {UsageFilter} from './UsageFilter';
42
43
 
43
44
  import './Storage.scss';
@@ -137,13 +138,9 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
137
138
  handleUptimeFilterChange(NodesUptimeFilterValues.All);
138
139
  };
139
140
 
140
- const renderLoader = () => {
141
- return <TableSkeleton className={b('loader')} />;
142
- };
143
-
144
141
  const renderDataTable = () => {
145
142
  return (
146
- <div className={b('table-wrapper')}>
143
+ <>
147
144
  {storageType === STORAGE_TYPES.groups && (
148
145
  <StorageGroups
149
146
  visibleEntities={visibleEntities}
@@ -163,7 +160,7 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
163
160
  additionalNodesInfo={additionalNodesInfo}
164
161
  />
165
162
  )}
166
- </div>
163
+ </>
167
164
  );
168
165
  };
169
166
 
@@ -185,7 +182,7 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
185
182
 
186
183
  const renderControls = () => {
187
184
  return (
188
- <div className={b('controls')}>
185
+ <>
189
186
  <div className={b('search')}>
190
187
  <Search
191
188
  placeholder={
@@ -199,7 +196,7 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
199
196
  </div>
200
197
 
201
198
  <StorageTypeFilter value={storageType} onChange={handleStorageTypeChange} />
202
- <StorageVisibleEntityFilter
199
+ <StorageVisibleEntitiesFilter
203
200
  value={visibleEntities}
204
201
  onChange={handleGroupVisibilityChange}
205
202
  />
@@ -217,24 +214,24 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
217
214
  />
218
215
  )}
219
216
  {renderEntitiesCount()}
220
- </div>
217
+ </>
221
218
  );
222
219
  };
223
220
 
224
- const showLoader = loading && !wasLoaded;
225
-
226
221
  if (error) {
227
222
  if (error.status === 403) {
228
223
  return <AccessDenied />;
229
224
  }
230
225
 
231
- return <div className={b()}>{error.statusText}</div>;
226
+ return <ResponseError error={error} />;
232
227
  }
233
228
 
234
229
  return (
235
- <div className={b()}>
236
- {renderControls()}
237
- {showLoader ? renderLoader() : renderDataTable()}
238
- </div>
230
+ <TableWithControlsLayout>
231
+ <TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
232
+ <TableWithControlsLayout.Table loading={loading && !wasLoaded} className={b('table')}>
233
+ {renderDataTable()}
234
+ </TableWithControlsLayout.Table>
235
+ </TableWithControlsLayout>
239
236
  );
240
237
  };
@@ -13,9 +13,11 @@ interface StorageTypeFilterProps {
13
13
  onChange: (value: string) => void;
14
14
  }
15
15
 
16
+ const storageTypeFilterQa = 'storage-type-filter';
17
+
16
18
  export const StorageTypeFilter = ({value, onChange}: StorageTypeFilterProps) => {
17
19
  return (
18
- <RadioButton value={value} onUpdate={onChange}>
20
+ <RadioButton value={value} onUpdate={onChange} qa={storageTypeFilterQa}>
19
21
  <RadioButton.Option value={STORAGE_TYPES.groups}>
20
22
  {StorageTypesTitles[STORAGE_TYPES.groups]}
21
23
  </RadioButton.Option>
@@ -14,9 +14,11 @@ interface StorageProblemFilterProps {
14
14
  onChange: (value: string) => void;
15
15
  }
16
16
 
17
- export const StorageVisibleEntityFilter = ({value, onChange}: StorageProblemFilterProps) => {
17
+ const storageVisibleEntitiesFilterQa = 'storage-visible-entities-filter';
18
+
19
+ export const StorageVisibleEntitiesFilter = ({value, onChange}: StorageProblemFilterProps) => {
18
20
  return (
19
- <RadioButton value={value} onUpdate={onChange}>
21
+ <RadioButton value={value} onUpdate={onChange} qa={storageVisibleEntitiesFilterQa}>
20
22
  <RadioButton.Option value={VISIBLE_ENTITIES.missing}>
21
23
  {VisibleEntitiesTitles[VISIBLE_ENTITIES.missing]}
22
24
  </RadioButton.Option>
@@ -39,11 +39,15 @@
39
39
  width: 100%;
40
40
  padding: 0 20px;
41
41
 
42
- & .global-storage,
43
- & .ydb-nodes {
42
+ .ydb-table-with-controls-layout {
44
43
  &__controls {
44
+ height: 46px;
45
45
  padding-top: 0;
46
46
  }
47
+
48
+ .data-table__sticky_moving {
49
+ top: 46px !important;
50
+ }
47
51
  }
48
52
  }
49
53
  }
@@ -26,7 +26,7 @@
26
26
 
27
27
  &__action-button {
28
28
  position: absolute;
29
- top: 8px; // centered relative to the heading
29
+ top: 19px; // centered relative to the heading
30
30
  right: 5px; // centered relative to the collapsed panel
31
31
 
32
32
  background-color: var(--yc-color-base-background);
@@ -51,20 +51,11 @@
51
51
  flex: 1 1 auto;
52
52
 
53
53
  height: 100%;
54
- padding: 0 12px 12px;
54
+ padding: 0 12px 12px 16px;
55
55
  }
56
56
 
57
57
  &__tree-header {
58
- display: flex;
59
- flex: 0 0 auto;
60
- justify-content: space-between;
61
- align-items: center;
62
-
63
- padding: 12px 12px 8px;
64
- }
65
-
66
- &__tree-title {
67
- font-weight: 600;
58
+ padding: 23px 12px 17px 20px;
68
59
  }
69
60
 
70
61
  &__sticky-top {