ydb-embedded-ui 4.10.0 → 4.10.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 (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 {