ydb-embedded-ui 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/components/InfoViewer/formatters/schema.ts +3 -1
  3. package/dist/components/QueryResultTable/Cell/Cell.tsx +8 -8
  4. package/dist/components/QueryResultTable/i18n/en.json +1 -1
  5. package/dist/components/QueryResultTable/i18n/ru.json +1 -1
  6. package/dist/components/ShortyString/ShortyString.tsx +3 -6
  7. package/dist/components/ShortyString/i18n/en.json +8 -8
  8. package/dist/components/ShortyString/i18n/ru.json +8 -8
  9. package/dist/components/SpeedMultiMeter/i18n/index.ts +0 -2
  10. package/dist/components/Stack/Stack.tsx +16 -16
  11. package/dist/components/TableSkeleton/TableSkeleton.tsx +3 -3
  12. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.scss +32 -0
  13. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.tsx +43 -0
  14. package/dist/containers/AsideNavigation/AsideNavigation.tsx +2 -2
  15. package/dist/containers/Cluster/Cluster.scss +4 -5
  16. package/dist/containers/Cluster/Cluster.tsx +3 -22
  17. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +4 -0
  18. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +14 -3
  19. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  20. package/dist/containers/Cluster/utils.tsx +0 -11
  21. package/dist/containers/Header/Header.scss +1 -5
  22. package/dist/containers/Header/Header.tsx +2 -2
  23. package/dist/containers/Header/breadcrumbs.ts +2 -1
  24. package/dist/containers/Heatmap/Heatmap.tsx +4 -3
  25. package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +2 -8
  26. package/dist/containers/Nodes/Nodes.scss +1 -24
  27. package/dist/containers/Nodes/Nodes.tsx +29 -39
  28. package/dist/containers/Storage/EmptyFilter/i18n/en.json +2 -2
  29. package/dist/containers/Storage/EmptyFilter/i18n/ru.json +2 -2
  30. package/dist/containers/Storage/Storage.scss +1 -14
  31. package/dist/containers/Storage/Storage.tsx +15 -18
  32. package/dist/containers/Storage/StorageGroups/i18n/en.json +5 -5
  33. package/dist/containers/Storage/StorageGroups/i18n/ru.json +5 -5
  34. package/dist/containers/Storage/StorageTypeFilter/StorageTypeFilter.tsx +3 -1
  35. package/dist/containers/Storage/{StorageVisibleEntityFilter/StorageVisibleEntityFilter.tsx → StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter.tsx} +4 -2
  36. package/dist/containers/Storage/UsageFilter/i18n/en.json +3 -8
  37. package/dist/containers/Storage/UsageFilter/i18n/ru.json +3 -8
  38. package/dist/containers/Tablet/Tablet.tsx +2 -2
  39. package/dist/containers/Tenant/Acl/Acl.scss +1 -9
  40. package/dist/containers/Tenant/Acl/Acl.tsx +137 -0
  41. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -2
  42. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +6 -2
  43. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -0
  44. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
  45. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +2 -0
  46. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +2 -3
  47. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +3 -18
  48. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +95 -88
  49. package/dist/containers/Tenant/Query/Issues/Issues.tsx +27 -23
  50. package/dist/containers/Tenant/Query/Issues/models.ts +0 -11
  51. package/dist/containers/Tenant/Query/Preview/Preview.tsx +3 -3
  52. package/dist/containers/Tenant/Query/i18n/en.json +1 -1
  53. package/dist/containers/Tenant/Query/i18n/ru.json +1 -1
  54. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +2 -2
  55. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +99 -0
  56. package/dist/containers/Tenant/Tenant.tsx +1 -5
  57. package/dist/containers/Tenant/TenantPages.tsx +9 -14
  58. package/dist/containers/Tenant/i18n/en.json +11 -0
  59. package/dist/containers/Tenant/i18n/index.ts +11 -0
  60. package/dist/containers/Tenant/i18n/ru.json +11 -0
  61. package/dist/containers/Tenant/utils/schema.ts +24 -0
  62. package/dist/containers/Tenant/utils/schemaActions.ts +28 -24
  63. package/dist/containers/Tenants/Tenants.scss +1 -13
  64. package/dist/containers/Tenants/Tenants.tsx +18 -28
  65. package/dist/services/api.ts +6 -7
  66. package/dist/store/index.js +1 -1
  67. package/dist/store/reducers/nodes/nodes.ts +12 -111
  68. package/dist/store/reducers/nodes/selectors.ts +74 -0
  69. package/dist/store/reducers/nodes/types.ts +22 -3
  70. package/dist/store/reducers/nodes/utils.ts +59 -0
  71. package/dist/store/reducers/preview.ts +6 -4
  72. package/dist/store/reducers/schemaAcl/schemaAcl.ts +17 -0
  73. package/dist/store/reducers/schemaAcl/types.ts +9 -7
  74. package/dist/store/reducers/storage/selectors.ts +1 -1
  75. package/dist/store/reducers/tenant/constants.ts +6 -0
  76. package/dist/store/reducers/tenant/tenant.ts +15 -0
  77. package/dist/store/reducers/tenant/types.ts +18 -3
  78. package/dist/store/state-url-mapping.js +3 -0
  79. package/dist/types/api/cluster.ts +1 -1
  80. package/dist/types/api/compute.ts +27 -2
  81. package/dist/types/api/error.ts +2 -2
  82. package/dist/types/api/netInfo.ts +3 -3
  83. package/dist/types/api/nodes.ts +13 -1
  84. package/dist/types/api/query.ts +1 -1
  85. package/dist/types/api/schema/cdcStream.ts +32 -0
  86. package/dist/types/api/schema/columnEntity.ts +138 -0
  87. package/dist/types/api/schema/externalDataSource.ts +24 -0
  88. package/dist/types/api/schema/externalTable.ts +14 -0
  89. package/dist/types/api/schema/index.ts +10 -0
  90. package/dist/types/api/schema/persQueueGroup.ts +191 -0
  91. package/dist/types/api/schema/schema.ts +302 -0
  92. package/dist/types/api/schema/shared.ts +42 -0
  93. package/dist/types/api/schema/table.ts +616 -0
  94. package/dist/types/api/schema/tableIndex.ts +33 -0
  95. package/dist/types/api/storage.ts +1 -1
  96. package/dist/types/assets.d.ts +1 -2
  97. package/dist/types/store/executeQuery.ts +2 -3
  98. package/dist/types/store/executeTopQueries.ts +8 -5
  99. package/dist/types/store/explainQuery.ts +4 -4
  100. package/dist/types/store/query.ts +4 -3
  101. package/dist/types/store/shardsWorkload.ts +8 -5
  102. package/dist/utils/constants.ts +4 -1
  103. package/dist/utils/error.ts +2 -3
  104. package/dist/utils/query.ts +3 -9
  105. package/dist/utils/tests/providers.tsx +6 -9
  106. package/package.json +6 -2
  107. package/dist/assets/icons/versions.svg +0 -3
  108. package/dist/containers/Tenant/Acl/Acl.js +0 -153
  109. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +0 -94
  110. package/dist/types/api/schema.ts +0 -1326
@@ -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
 
@@ -71,7 +71,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
71
71
  // For not DB entities we always use /compute endpoint instead of /nodes
72
72
  // since /nodes can return data only for tenants
73
73
  if (path && (!useNodesEndpoint || !isDatabaseEntityType(type))) {
74
- dispatch(getComputeNodes(path));
74
+ dispatch(getComputeNodes({path}));
75
75
  } else {
76
76
  dispatch(getNodes({tenant: path}));
77
77
  }
@@ -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,4 +1,4 @@
1
1
  {
2
- "default_message": "Everything is fine!",
3
- "default_button_label": "Show All"
2
+ "default_message": "Everything is fine!",
3
+ "default_button_label": "Show All"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
- "default_message": "Всё в порядке!",
3
- "default_button_label": "Показать все"
2
+ "default_message": "Всё в порядке!",
3
+ "default_button_label": "Показать все"
4
4
  }
@@ -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
  };
@@ -1,7 +1,7 @@
1
1
  {
2
- "empty.default": "No such groups",
3
- "empty.out_of_space": "No groups with out of space errors",
4
- "empty.degraded": "No degraded groups",
5
- "show_all": "Show all groups",
6
- "encrypted": "Encrypted group"
2
+ "empty.default": "No such groups",
3
+ "empty.out_of_space": "No groups with out of space errors",
4
+ "empty.degraded": "No degraded groups",
5
+ "show_all": "Show all groups",
6
+ "encrypted": "Encrypted group"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "empty.default": "Нет групп",
3
- "empty.out_of_space": "Нет групп, в которых кончается место",
4
- "empty.degraded": "Нет деградировавших групп",
5
- "show_all": "Показать все группы",
6
- "encrypted": "Зашифрованная группа"
2
+ "empty.default": "Нет групп",
3
+ "empty.out_of_space": "Нет групп, в которых кончается место",
4
+ "empty.degraded": "Нет деградировавших групп",
5
+ "show_all": "Показать все группы",
6
+ "encrypted": "Зашифрованная группа"
7
7
  }
@@ -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>
@@ -1,10 +1,5 @@
1
1
  {
2
- "label": "Usage:",
3
- "default_value": "Any",
4
- "groups_count": [
5
- "{{count}} group",
6
- "{{count}} groups",
7
- "{{count}} groups",
8
- "No groups"
9
- ]
2
+ "label": "Usage:",
3
+ "default_value": "Any",
4
+ "groups_count": ["{{count}} group", "{{count}} groups", "{{count}} groups", "No groups"]
10
5
  }
@@ -1,10 +1,5 @@
1
1
  {
2
- "label": "Использование:",
3
- "default_value": "Любое",
4
- "groups_count": [
5
- "{{count}} группа",
6
- "{{count}} группы",
7
- "{{count}} групп",
8
- "Нет групп"
9
- ]
2
+ "label": "Использование:",
3
+ "default_value": "Любое",
4
+ "groups_count": ["{{count}} группа", "{{count}} группы", "{{count}} групп", "Нет групп"]
10
5
  }
@@ -9,7 +9,7 @@ import {getTablet, getTabletDescribe, clearTabletData} from '../../store/reducer
9
9
  import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
10
10
 
11
11
  import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
12
- import {DEVELOPER_UI} from '../../utils/constants';
12
+ import {DEVELOPER_UI_TITLE} from '../../utils/constants';
13
13
  import '../../services/api';
14
14
  import {parseQuery} from '../../routes';
15
15
 
@@ -113,7 +113,7 @@ export const Tablet = () => {
113
113
 
114
114
  const externalLinks = [
115
115
  {
116
- name: `${DEVELOPER_UI} - tablet`,
116
+ name: `${DEVELOPER_UI_TITLE} - tablet`,
117
117
  path: `/tablets?TabletID=${TabletId}`,
118
118
  },
119
119
  ];
@@ -1,6 +1,6 @@
1
1
  @import '../../../styles/mixins.scss';
2
2
 
3
- .kv-acl {
3
+ .ydb-acl {
4
4
  display: flex;
5
5
  overflow: auto;
6
6
  flex-grow: 1;
@@ -16,14 +16,6 @@
16
16
  padding: 0 12px 16px;
17
17
  }
18
18
 
19
- &__loader-container {
20
- display: flex;
21
- justify-content: center;
22
- align-items: center;
23
-
24
- height: 100%;
25
- }
26
-
27
19
  &__owner-container {
28
20
  position: sticky;
29
21
  z-index: 2;
@@ -0,0 +1,137 @@
1
+ import {useDispatch} from 'react-redux';
2
+ import {useEffect} from 'react';
3
+ import cn from 'bem-cn-lite';
4
+
5
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
6
+
7
+ import type {TACE} from '../../../types/api/acl';
8
+ import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
9
+ import {useTypedSelector} from '../../../utils/hooks';
10
+ import {getSchemaAcl, setAclWasNotLoaded} from '../../../store/reducers/schemaAcl/schemaAcl';
11
+
12
+ import {ResponseError} from '../../../components/Errors/ResponseError';
13
+ import {Loader} from '../../../components/Loader';
14
+
15
+ import './Acl.scss';
16
+ import i18n from '../i18n';
17
+
18
+ const b = cn('ydb-acl');
19
+
20
+ const TABLE_SETTINGS = {
21
+ ...DEFAULT_TABLE_SETTINGS,
22
+ dynamicRender: false,
23
+ stickyTop: 36,
24
+ };
25
+
26
+ const prepareLogin = (value: string | undefined) => {
27
+ if (value && value.endsWith('@staff') && !value.startsWith('svc_')) {
28
+ const login = value.split('@')[0];
29
+ return login;
30
+ }
31
+
32
+ return value;
33
+ };
34
+
35
+ const columns: Column<TACE>[] = [
36
+ {
37
+ name: 'AccessType',
38
+ header: 'Access Type',
39
+ sortable: false,
40
+ render: ({row}) => row.AccessType,
41
+ },
42
+ {
43
+ name: 'AccessRights',
44
+ header: 'Access Rights',
45
+ render: ({row}) => {
46
+ return row.AccessRights?.map((item, index) => {
47
+ return <div key={index}>{item}</div>;
48
+ });
49
+ },
50
+ sortable: false,
51
+ },
52
+ {
53
+ name: 'Subject',
54
+ sortable: false,
55
+ render: ({row}) => {
56
+ return prepareLogin(row.Subject);
57
+ },
58
+ width: 140,
59
+ },
60
+ {
61
+ name: 'InheritanceType',
62
+ header: 'Inheritance Type',
63
+ render: ({row}) => {
64
+ return row.InheritanceType?.map((item, index) => {
65
+ return <div key={index}>{item}</div>;
66
+ });
67
+ },
68
+ sortable: false,
69
+ },
70
+ ];
71
+
72
+ export const Acl = () => {
73
+ const dispatch = useDispatch();
74
+
75
+ const {currentSchemaPath} = useTypedSelector((state) => state.schema);
76
+ const {loading, error, acl, owner, wasLoaded} = useTypedSelector((state) => state.schemaAcl);
77
+
78
+ useEffect(() => {
79
+ if (currentSchemaPath) {
80
+ dispatch(getSchemaAcl({path: currentSchemaPath}));
81
+ }
82
+
83
+ return () => {
84
+ // Ensures correct acl on path change
85
+ dispatch(setAclWasNotLoaded());
86
+ };
87
+ }, [currentSchemaPath, dispatch]);
88
+
89
+ const renderTable = () => {
90
+ if (!acl || !acl.length) {
91
+ return null;
92
+ }
93
+
94
+ return (
95
+ <DataTable
96
+ theme="yandex-cloud"
97
+ columns={columns}
98
+ data={acl}
99
+ settings={TABLE_SETTINGS}
100
+ />
101
+ );
102
+ };
103
+
104
+ const renderOwner = () => {
105
+ if (!owner) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ <div className={b('owner-container')}>
111
+ <span className={b('owner-label')}>{`${i18n('acl.owner')}: `}</span>
112
+ {prepareLogin(owner)}
113
+ </div>
114
+ );
115
+ };
116
+
117
+ if (loading && !wasLoaded) {
118
+ return <Loader />;
119
+ }
120
+
121
+ if (error) {
122
+ return <ResponseError error={error} className={b('message-container')} />;
123
+ }
124
+
125
+ if (!loading && !acl && !owner) {
126
+ return <div className={b('message-container')}>{i18n('acl.empty')}</div>;
127
+ }
128
+
129
+ return (
130
+ <div className={b()}>
131
+ <div className={b('result')}>
132
+ {renderOwner()}
133
+ {renderTable()}
134
+ </div>
135
+ </div>
136
+ );
137
+ };
@@ -6,8 +6,8 @@ import JSONTree from 'react-json-inspector';
6
6
  import 'react-json-inspector/json-inspector.css';
7
7
 
8
8
  import {Loader} from '../../../../components/Loader';
9
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
9
10
 
10
- import {prepareQueryError} from '../../../../utils/query';
11
11
  import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
12
12
  import {
13
13
  getDescribe,
@@ -86,7 +86,7 @@ const Describe = ({tenant, type}: IDescribeProps) => {
86
86
  }
87
87
 
88
88
  if (error) {
89
- return <div className={b('message-container', 'error')}>{prepareQueryError(error)}</div>;
89
+ return <ResponseError error={error} className={b('message-container')} />;
90
90
  }
91
91
 
92
92
  if (!loading && !preparedDescribeData) {
@@ -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
  }
@@ -83,6 +83,9 @@ export const DIR_PAGES = [overview, topShards, nodes, describe];
83
83
  export const CDC_STREAM_PAGES = [overview, consumers, partitions, nodes, describe];
84
84
  export const TOPIC_PAGES = [overview, consumers, partitions, nodes, describe];
85
85
 
86
+ export const EXTERNAL_DATA_SOURCE_PAGES = [overview, describe];
87
+ export const EXTERNAL_TABLE_PAGES = [overview, describe];
88
+
86
89
  // verbose mapping to guarantee correct tabs for new path types
87
90
  // TS will error when a new type is added but not mapped here
88
91
  const pathTypeToPages: Record<EPathType, Page[] | undefined> = {
@@ -101,6 +104,9 @@ const pathTypeToPages: Record<EPathType, Page[] | undefined> = {
101
104
  [EPathType.EPathTypeCdcStream]: CDC_STREAM_PAGES,
102
105
 
103
106
  [EPathType.EPathTypePersQueueGroup]: TOPIC_PAGES,
107
+
108
+ [EPathType.EPathTypeExternalDataSource]: EXTERNAL_DATA_SOURCE_PAGES,
109
+ [EPathType.EPathTypeExternalTable]: EXTERNAL_TABLE_PAGES,
104
110
  };
105
111
 
106
112
  export const getPagesByType = (type?: EPathType) => (type && pathTypeToPages[type]) || DIR_PAGES;
@@ -5,10 +5,10 @@ import {Loader} from '@gravity-ui/uikit';
5
5
  import DataTable from '@gravity-ui/react-data-table';
6
6
 
7
7
  import {Icon} from '../../../../components/Icon';
8
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
8
9
 
9
10
  import {AutoFetcher} from '../../../../utils/autofetcher';
10
11
  import {getHotKeys, setHotKeysOptions} from '../../../../store/reducers/hotKeys';
11
- import {prepareQueryError} from '../../../../utils/query';
12
12
 
13
13
  import {isColumnEntityType, isTableType} from '../../utils/schema';
14
14
 
@@ -86,7 +86,7 @@ function HotKeys({
86
86
  sortable: false,
87
87
  align: DataTable.RIGHT,
88
88
  },
89
- ...keyColumnsIds?.map((col, index) => ({
89
+ ...keyColumnsIds.map((col, index) => ({
90
90
  name: col,
91
91
  header: (
92
92
  <div className={b('primary-key-column')}>
@@ -107,7 +107,7 @@ function HotKeys({
107
107
 
108
108
  const renderContent = () => {
109
109
  if (error) {
110
- return prepareQueryError(error);
110
+ return <ResponseError error={error} />;
111
111
  }
112
112
  return data !== null ? (
113
113
  <div className={b('table-content')}>
@@ -124,6 +124,8 @@ function Overview({type, tenantName}: OverviewProps) {
124
124
  <ChangefeedInfo data={data} topic={additionalData?.[0]} />
125
125
  ),
126
126
  [EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={data} />,
127
+ [EPathType.EPathTypeExternalTable]: undefined,
128
+ [EPathType.EPathTypeExternalDataSource]: undefined,
127
129
  };
128
130
 
129
131
  return (
@@ -21,9 +21,8 @@ export const prepareTopicSchemaInfo = (data?: TEvDescribeSchemeResult): Array<In
21
21
 
22
22
  const {Partitions = [], PQTabletConfig = {PartitionConfig: {LifetimeSeconds: 0}}} = pqGroupData;
23
23
 
24
- const {Codecs, MeteringMode} = pqGroupData?.PQTabletConfig;
25
- const {WriteSpeedInBytesPerSecond, StorageLimitBytes} =
26
- pqGroupData?.PQTabletConfig?.PartitionConfig;
24
+ const {Codecs, MeteringMode} = PQTabletConfig;
25
+ const {WriteSpeedInBytesPerSecond, StorageLimitBytes} = PQTabletConfig.PartitionConfig;
27
26
 
28
27
  const pqGeneralInfo = formatObject<TPersQueueGroupDescription>(formatPQGroupItem, {
29
28
  Partitions,