ydb-embedded-ui 20.3.0 → 20.4.0

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 (145) hide show
  1. package/dist/components/Drawer/Drawer.d.ts +2 -1
  2. package/dist/components/Drawer/Drawer.js +4 -4
  3. package/dist/components/Drawer/Drawer.js.map +1 -1
  4. package/dist/components/EntityStatus/EntityStatus.d.ts +4 -1
  5. package/dist/components/EntityStatus/EntityStatus.js +2 -2
  6. package/dist/components/EntityStatus/EntityStatus.js.map +1 -1
  7. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  8. package/dist/components/TenantNameWrapper/utils.d.ts +1 -1
  9. package/dist/components/TenantNameWrapper/utils.js.map +1 -1
  10. package/dist/containers/Cluster/Cluster.js +1 -1
  11. package/dist/containers/Cluster/Cluster.js.map +1 -1
  12. package/dist/containers/Cluster/ClusterDrawerHealthcheck.d.ts +8 -0
  13. package/dist/containers/Cluster/ClusterDrawerHealthcheck.js +71 -9
  14. package/dist/containers/Cluster/ClusterDrawerHealthcheck.js.map +1 -1
  15. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.d.ts +2 -1
  16. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js +2 -2
  17. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js.map +1 -1
  18. package/dist/containers/Cluster/ClusterOverview/ClusterOverview.d.ts +1 -0
  19. package/dist/containers/Cluster/ClusterOverview/ClusterOverview.js.map +1 -1
  20. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.d.ts +2 -0
  21. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js +54 -103
  22. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js.map +1 -1
  23. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
  24. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.d.ts +13 -0
  25. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js +94 -0
  26. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js.map +1 -0
  27. package/dist/containers/Cluster/i18n/en.json +8 -5
  28. package/dist/containers/Cluster/i18n/index.d.ts +1 -1
  29. package/dist/containers/Clusters/constants.d.ts +1 -1
  30. package/dist/containers/Header/DatabaseRightControls.d.ts +4 -2
  31. package/dist/containers/Header/DatabaseRightControls.js +2 -2
  32. package/dist/containers/Header/DatabaseRightControls.js.map +1 -1
  33. package/dist/containers/Header/Header.js +2 -2
  34. package/dist/containers/Header/Header.js.map +1 -1
  35. package/dist/containers/Header/HeaderActionsMenu.d.ts +4 -2
  36. package/dist/containers/Header/HeaderActionsMenu.js +19 -2
  37. package/dist/containers/Header/HeaderActionsMenu.js.map +1 -1
  38. package/dist/containers/Header/HeaderRightControls.d.ts +4 -2
  39. package/dist/containers/Header/HeaderRightControls.js +2 -2
  40. package/dist/containers/Header/HeaderRightControls.js.map +1 -1
  41. package/dist/containers/Header/hooks/useClusterData.d.ts +1 -0
  42. package/dist/containers/Header/hooks/useHeaderData.d.ts +1 -0
  43. package/dist/containers/Header/hooks/useHeaderData.js +1 -0
  44. package/dist/containers/Header/hooks/useHeaderData.js.map +1 -1
  45. package/dist/containers/Header/hooks/useSharedDatabasePath.d.ts +10 -0
  46. package/dist/containers/Header/hooks/useSharedDatabasePath.js +102 -0
  47. package/dist/containers/Header/hooks/useSharedDatabasePath.js.map +1 -0
  48. package/dist/containers/Header/i18n/en.json +1 -0
  49. package/dist/containers/Header/i18n/index.d.ts +1 -1
  50. package/dist/containers/Operations/constants.d.ts +1 -1
  51. package/dist/containers/Storage/PaginatedStorageGroupsTable/columns/i18n/index.d.ts +1 -1
  52. package/dist/containers/Tablets/Tablets.js +3 -3
  53. package/dist/containers/Tablets/Tablets.js.map +1 -1
  54. package/dist/containers/Tablets/TabletsTable.js +17 -27
  55. package/dist/containers/Tablets/TabletsTable.js.map +1 -1
  56. package/dist/containers/Tablets/i18n/en.json +3 -0
  57. package/dist/containers/Tablets/i18n/index.d.ts +1 -1
  58. package/dist/containers/Tablets/useTabletQueryParams.d.ts +8 -0
  59. package/dist/containers/Tablets/useTabletQueryParams.js +40 -0
  60. package/dist/containers/Tablets/useTabletQueryParams.js.map +1 -0
  61. package/dist/containers/Tablets/utils.d.ts +11 -0
  62. package/dist/containers/Tablets/utils.js +23 -0
  63. package/dist/containers/Tablets/utils.js.map +1 -0
  64. package/dist/containers/Tenant/Healthcheck/Healthcheck.d.ts +6 -1
  65. package/dist/containers/Tenant/Healthcheck/Healthcheck.js +5 -5
  66. package/dist/containers/Tenant/Healthcheck/Healthcheck.js.map +1 -1
  67. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.d.ts +2 -1
  68. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js +2 -2
  69. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js.map +1 -1
  70. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.d.ts +5 -1
  71. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js +5 -3
  72. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js.map +1 -1
  73. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.d.ts +1 -1
  74. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js +11 -2
  75. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js.map +1 -1
  76. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js +2 -0
  77. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js.map +1 -1
  78. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js +4 -1
  79. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js.map +1 -1
  80. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.d.ts +5 -1
  81. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js +2 -2
  82. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js.map +1 -1
  83. package/dist/containers/Tenant/Healthcheck/i18n/en.json +2 -1
  84. package/dist/containers/Tenant/Healthcheck/i18n/index.d.ts +1 -1
  85. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
  86. package/dist/containers/VDiskPage/i18n/index.d.ts +1 -1
  87. package/dist/routes.d.ts +4 -1
  88. package/dist/routes.js +3 -2
  89. package/dist/routes.js.map +1 -1
  90. package/dist/services/api/viewer.d.ts +3 -1
  91. package/dist/services/api/viewer.js +3 -2
  92. package/dist/services/api/viewer.js.map +1 -1
  93. package/dist/store/reducers/cluster/cluster.d.ts +2 -1
  94. package/dist/store/reducers/cluster/cluster.js +1 -0
  95. package/dist/store/reducers/cluster/cluster.js.map +1 -1
  96. package/dist/store/reducers/healthcheckInfo/types.d.ts +1 -0
  97. package/dist/store/reducers/healthcheckInfo/utils.d.ts +1 -0
  98. package/dist/store/reducers/healthcheckInfo/utils.js +43 -9
  99. package/dist/store/reducers/healthcheckInfo/utils.js.map +1 -1
  100. package/dist/store/reducers/tenants/tenants.d.ts +10 -1
  101. package/dist/store/reducers/tenants/tenants.js +33 -0
  102. package/dist/store/reducers/tenants/tenants.js.map +1 -1
  103. package/dist/store/reducers/tenants/types.d.ts +4 -0
  104. package/dist/types/api/healthcheck.d.ts +7 -0
  105. package/package.json +1 -1
  106. package/src/components/Drawer/Drawer.tsx +6 -0
  107. package/src/components/EntityStatus/EntityStatus.tsx +9 -1
  108. package/src/components/TenantNameWrapper/utils.ts +1 -1
  109. package/src/containers/Cluster/Cluster.tsx +1 -0
  110. package/src/containers/Cluster/ClusterDrawerHealthcheck.tsx +112 -13
  111. package/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -1
  112. package/src/containers/Cluster/ClusterOverview/ClusterOverview.tsx +1 -0
  113. package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
  114. package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx +156 -200
  115. package/src/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.ts +142 -0
  116. package/src/containers/Cluster/i18n/en.json +8 -5
  117. package/src/containers/Header/DatabaseRightControls.tsx +7 -1
  118. package/src/containers/Header/Header.tsx +3 -0
  119. package/src/containers/Header/HeaderActionsMenu.tsx +24 -2
  120. package/src/containers/Header/HeaderRightControls.tsx +7 -1
  121. package/src/containers/Header/hooks/useHeaderData.ts +1 -0
  122. package/src/containers/Header/hooks/useSharedDatabasePath.ts +195 -0
  123. package/src/containers/Header/i18n/en.json +1 -0
  124. package/src/containers/Tablets/Tablets.tsx +3 -3
  125. package/src/containers/Tablets/TabletsTable.tsx +37 -31
  126. package/src/containers/Tablets/i18n/en.json +3 -0
  127. package/src/containers/Tablets/useTabletQueryParams.ts +54 -0
  128. package/src/containers/Tablets/utils.ts +43 -0
  129. package/src/containers/Tenant/Healthcheck/Healthcheck.tsx +37 -4
  130. package/src/containers/Tenant/Healthcheck/components/HealthcheckDrawer.tsx +3 -0
  131. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssue.tsx +17 -4
  132. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.tsx +13 -3
  133. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.tsx +1 -0
  134. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.tsx +3 -1
  135. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssues.tsx +15 -2
  136. package/src/containers/Tenant/Healthcheck/i18n/en.json +2 -1
  137. package/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
  138. package/src/routes.ts +8 -2
  139. package/src/services/api/viewer.ts +10 -2
  140. package/src/store/reducers/cluster/cluster.ts +2 -1
  141. package/src/store/reducers/healthcheckInfo/types.ts +1 -0
  142. package/src/store/reducers/healthcheckInfo/utils.ts +50 -10
  143. package/src/store/reducers/tenants/tenants.ts +55 -1
  144. package/src/store/reducers/tenants/types.ts +5 -0
  145. package/src/types/api/healthcheck.ts +8 -0
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import {Pencil, PlugConnection, TrashBin} from '@gravity-ui/icons';
3
+ import {DatabaseArrowRight, Pencil, PlugConnection, TrashBin} from '@gravity-ui/icons';
4
4
  import {useHistory} from 'react-router-dom';
5
5
 
6
6
  import {getConnectToDBDialog} from '../../components/ConnectToDB/ConnectToDBDialog';
@@ -9,11 +9,12 @@ import {DropdownMenu} from '../../components/DropdownMenu';
9
9
  import {getClusterPath} from '../../routes';
10
10
  import {useEmMetaAvailable} from '../../store/reducers/capabilities/hooks';
11
11
  import type {PreparedTenant} from '../../store/reducers/tenants/types';
12
- import type {ClusterLinkWithTitle} from '../../types/additionalProps';
12
+ import type {AdditionalTenantsProps, ClusterLinkWithTitle} from '../../types/additionalProps';
13
13
  import {uiFactory} from '../../uiFactory/uiFactory';
14
14
  import {clusterTabsIds} from '../Cluster/utils';
15
15
 
16
16
  import {b} from './constants';
17
+ import {useSharedDatabasePath} from './hooks/useSharedDatabasePath';
17
18
  import {headerKeyset} from './i18n';
18
19
 
19
20
  export interface HeaderActionsMenuProps {
@@ -22,6 +23,8 @@ export interface HeaderActionsMenuProps {
22
23
  databaseData?: PreparedTenant;
23
24
  isDatabaseDataLoading: boolean;
24
25
  isV2NavigationEnabled: boolean;
26
+ isViewerUser?: boolean;
27
+ prepareTenantBackend?: AdditionalTenantsProps['prepareTenantBackend'];
25
28
  databaseLinks: ClusterLinkWithTitle[];
26
29
  }
27
30
 
@@ -31,11 +34,19 @@ export function DBHeaderActionsMenu({
31
34
  databaseData,
32
35
  isDatabaseDataLoading,
33
36
  isV2NavigationEnabled,
37
+ isViewerUser,
38
+ prepareTenantBackend,
34
39
  databaseLinks,
35
40
  }: HeaderActionsMenuProps) {
36
41
  const history = useHistory();
37
42
 
38
43
  const emMetaAvailable = useEmMetaAvailable();
44
+ const sharedDatabasePath = useSharedDatabasePath({
45
+ clusterName,
46
+ databaseData,
47
+ isViewerUser,
48
+ prepareTenantBackend,
49
+ });
39
50
 
40
51
  const isEditDBAvailable = emMetaAvailable && uiFactory.onEditDB !== undefined;
41
52
  const isDeleteDBAvailable = emMetaAvailable && uiFactory.onDeleteDB !== undefined;
@@ -70,6 +81,16 @@ export function DBHeaderActionsMenu({
70
81
  ]);
71
82
  }
72
83
 
84
+ if (sharedDatabasePath) {
85
+ menuItems.push([
86
+ {
87
+ title: headerKeyset('action_go-to-shared-db'),
88
+ iconStart: DatabaseArrowRight,
89
+ href: sharedDatabasePath,
90
+ },
91
+ ]);
92
+ }
93
+
73
94
  const {onEditDB, onDeleteDB} = uiFactory;
74
95
 
75
96
  const isEnoughData = clusterName && databaseData;
@@ -115,6 +136,7 @@ export function DBHeaderActionsMenu({
115
136
  history,
116
137
  isEditDBAvailable,
117
138
  isDeleteDBAvailable,
139
+ sharedDatabasePath,
118
140
  ]);
119
141
 
120
142
  if (!menuItems.length) {
@@ -1,7 +1,7 @@
1
1
  import {Flex} from '@gravity-ui/uikit';
2
2
 
3
3
  import type {PreparedTenant} from '../../store/reducers/tenants/types';
4
- import type {ClusterLinkWithTitle} from '../../types/additionalProps';
4
+ import type {AdditionalTenantsProps, ClusterLinkWithTitle} from '../../types/additionalProps';
5
5
  import {useHasDeveloperUi} from '../../utils/developerUI/developerUI';
6
6
 
7
7
  import {ClusterRightControls} from './ClusterRightControls';
@@ -20,6 +20,8 @@ interface HeaderRightControlsProps {
20
20
  isDatabaseDataLoading: boolean;
21
21
  isAddClusterAvailable: boolean;
22
22
  isV2NavigationEnabled: boolean;
23
+ isViewerUser?: boolean;
24
+ prepareTenantBackend?: AdditionalTenantsProps['prepareTenantBackend'];
23
25
  clusterLinks: ClusterLinkWithTitle[];
24
26
  databaseLinks: ClusterLinkWithTitle[];
25
27
  handleEditCluster?: () => Promise<boolean>;
@@ -37,6 +39,8 @@ export function HeaderRightControls({
37
39
  isDatabaseDataLoading,
38
40
  isAddClusterAvailable,
39
41
  isV2NavigationEnabled,
42
+ isViewerUser,
43
+ prepareTenantBackend,
40
44
  clusterLinks,
41
45
  databaseLinks,
42
46
  handleEditCluster,
@@ -58,6 +62,8 @@ export function HeaderRightControls({
58
62
  databaseData={databaseData}
59
63
  isDatabaseDataLoading={isDatabaseDataLoading}
60
64
  isV2NavigationEnabled={isV2NavigationEnabled}
65
+ isViewerUser={isViewerUser}
66
+ prepareTenantBackend={prepareTenantBackend}
61
67
  showDeveloperUI={showDeveloperUI}
62
68
  databaseLinks={databaseLinks}
63
69
  />
@@ -82,5 +82,6 @@ export function useHeaderData({
82
82
  isDatabaseDataLoading,
83
83
  clusterLinks,
84
84
  databaseLinks,
85
+ prepareTenantBackend: additionalTenantProps.prepareTenantBackend,
85
86
  };
86
87
  }
@@ -0,0 +1,195 @@
1
+ import React from 'react';
2
+
3
+ import {skipToken} from '@reduxjs/toolkit/query';
4
+
5
+ import {getTenantBackend} from '../../../components/TenantNameWrapper/utils';
6
+ import {getTenantPath} from '../../../routes';
7
+ import {backend as activeBackend, environment} from '../../../store';
8
+ import {useClusterBaseInfo} from '../../../store/reducers/cluster/cluster';
9
+ import {tenantsApi} from '../../../store/reducers/tenants/tenants';
10
+ import type {PreparedTenant, SharedDatabaseTarget} from '../../../store/reducers/tenants/types';
11
+ import type {AdditionalTenantsProps} from '../../../types/additionalProps';
12
+ import {uiFactory} from '../../../uiFactory/uiFactory';
13
+ import {useDatabasesV2} from '../../../utils/hooks/useDatabasesV2';
14
+ import {useIsUserAllowedToMakeChanges} from '../../../utils/hooks/useIsUserAllowedToMakeChanges';
15
+
16
+ interface UseSharedDatabasePathParams {
17
+ clusterName?: string;
18
+ databaseData?: PreparedTenant;
19
+ isViewerUser?: boolean;
20
+ prepareTenantBackend?: AdditionalTenantsProps['prepareTenantBackend'];
21
+ }
22
+
23
+ interface SharedDatabaseNameResolutionParams {
24
+ databaseData?: PreparedTenant;
25
+ isClusterBaseInfoResolved: boolean;
26
+ isViewerUser?: boolean;
27
+ useDatabaseId?: boolean;
28
+ }
29
+
30
+ function canResolveSharedDatabaseName({
31
+ databaseData,
32
+ isClusterBaseInfoResolved,
33
+ isViewerUser,
34
+ useDatabaseId,
35
+ }: SharedDatabaseNameResolutionParams) {
36
+ return Boolean(
37
+ isViewerUser &&
38
+ isClusterBaseInfoResolved &&
39
+ databaseData?.Type === 'Serverless' &&
40
+ databaseData.ResourceId &&
41
+ !useDatabaseId &&
42
+ !databaseData.sharedTenantName,
43
+ );
44
+ }
45
+
46
+ function getSharedDatabaseBackend(
47
+ sharedDatabase: SharedDatabaseTarget | undefined,
48
+ prepareTenantBackend: AdditionalTenantsProps['prepareTenantBackend'],
49
+ ) {
50
+ if (!sharedDatabase) {
51
+ return activeBackend;
52
+ }
53
+
54
+ return (
55
+ getTenantBackend({NodeIds: sharedDatabase.nodeIds}, {prepareTenantBackend}) ?? activeBackend
56
+ );
57
+ }
58
+
59
+ function shouldResolveSharedDatabaseNodeIds(
60
+ balancer: string | undefined,
61
+ settings: {use_meta_proxy?: boolean} | undefined,
62
+ prepareTenantBackend: AdditionalTenantsProps['prepareTenantBackend'],
63
+ ) {
64
+ const useMetaProxy = Boolean(
65
+ window.api.meta && uiFactory.useMetaProxy && settings?.use_meta_proxy !== false,
66
+ );
67
+
68
+ return Boolean(
69
+ balancer && !useMetaProxy && prepareTenantBackend && !prepareTenantBackend(undefined),
70
+ );
71
+ }
72
+
73
+ function getResolvedSharedDatabasePath({
74
+ backend,
75
+ clusterName,
76
+ databaseData,
77
+ isClusterBaseInfoResolved,
78
+ isViewerUser,
79
+ sharedDatabase,
80
+ }: {
81
+ backend?: string;
82
+ clusterName?: string;
83
+ databaseData?: PreparedTenant;
84
+ isClusterBaseInfoResolved: boolean;
85
+ isViewerUser?: boolean;
86
+ sharedDatabase?: string;
87
+ }) {
88
+ if (
89
+ !isViewerUser ||
90
+ !isClusterBaseInfoResolved ||
91
+ databaseData?.Type !== 'Serverless' ||
92
+ !sharedDatabase
93
+ ) {
94
+ return undefined;
95
+ }
96
+
97
+ return getTenantPath(
98
+ {clusterName, database: sharedDatabase, backend},
99
+ {withBasename: true, environment},
100
+ );
101
+ }
102
+
103
+ export function useSharedDatabasePath({
104
+ clusterName,
105
+ databaseData,
106
+ isViewerUser,
107
+ prepareTenantBackend,
108
+ }: UseSharedDatabasePathParams) {
109
+ const isMetaDatabasesAvailable = useDatabasesV2();
110
+ const {
111
+ balancer,
112
+ settings,
113
+ isError: isClusterBaseInfoError,
114
+ isResolved: isClusterBaseInfoResolved,
115
+ } = useClusterBaseInfo();
116
+ const isMonitoringAllowed = useIsUserAllowedToMakeChanges();
117
+ const isClusterBaseInfoAvailable = isClusterBaseInfoResolved && !isClusterBaseInfoError;
118
+
119
+ const useDatabaseId = uiFactory.useDatabaseId && settings?.use_meta_proxy !== false;
120
+ const needSharedDatabaseNodeIds = shouldResolveSharedDatabaseNodeIds(
121
+ balancer,
122
+ settings,
123
+ prepareTenantBackend,
124
+ );
125
+ const shouldResolveSharedDatabaseName = canResolveSharedDatabaseName({
126
+ databaseData,
127
+ isClusterBaseInfoResolved: isClusterBaseInfoAvailable,
128
+ isViewerUser,
129
+ useDatabaseId,
130
+ });
131
+
132
+ const shouldResolveSharedDatabaseFromViewer =
133
+ shouldResolveSharedDatabaseName && !window.api.meta;
134
+
135
+ const {currentData: sharedDatabaseFromViewer} = tenantsApi.useGetSharedDatabaseQuery(
136
+ shouldResolveSharedDatabaseFromViewer && databaseData?.ResourceId
137
+ ? {
138
+ clusterName,
139
+ database: databaseData.Name,
140
+ backend: activeBackend,
141
+ environmentName: environment,
142
+ isMonitoringAllowed: isMonitoringAllowed === true,
143
+ needNodeIds: needSharedDatabaseNodeIds,
144
+ resourceId: databaseData.ResourceId,
145
+ }
146
+ : skipToken,
147
+ );
148
+
149
+ const shouldResolveSharedDatabaseFromMetaList =
150
+ shouldResolveSharedDatabaseName && Boolean(window.api.meta);
151
+
152
+ const {currentData: databases} = tenantsApi.useGetTenantsInfoQuery(
153
+ shouldResolveSharedDatabaseFromMetaList
154
+ ? {clusterName, environmentName: environment, isMetaDatabasesAvailable}
155
+ : skipToken,
156
+ );
157
+
158
+ const sharedDatabaseTarget = React.useMemo<SharedDatabaseTarget | undefined>(() => {
159
+ if (databaseData?.sharedTenantName) {
160
+ return {
161
+ name: databaseData.sharedTenantName,
162
+ nodeIds: databaseData.sharedNodeIds,
163
+ };
164
+ }
165
+
166
+ if (sharedDatabaseFromViewer) {
167
+ return sharedDatabaseFromViewer;
168
+ }
169
+
170
+ const sharedDatabase = databases?.find(({Id}) => Id === databaseData?.ResourceId);
171
+ const sharedDatabaseName = sharedDatabase?.Name?.trim();
172
+
173
+ return sharedDatabaseName
174
+ ? {name: sharedDatabaseName, nodeIds: sharedDatabase?.NodeIds}
175
+ : undefined;
176
+ }, [
177
+ databaseData?.ResourceId,
178
+ databaseData?.sharedNodeIds,
179
+ databaseData?.sharedTenantName,
180
+ databases,
181
+ sharedDatabaseFromViewer,
182
+ ]);
183
+
184
+ const sharedDatabase = useDatabaseId ? databaseData?.ResourceId : sharedDatabaseTarget?.name;
185
+ const backend = getSharedDatabaseBackend(sharedDatabaseTarget, prepareTenantBackend);
186
+
187
+ return getResolvedSharedDatabasePath({
188
+ backend,
189
+ clusterName,
190
+ databaseData,
191
+ isClusterBaseInfoResolved: isClusterBaseInfoAvailable,
192
+ isViewerUser,
193
+ sharedDatabase,
194
+ });
195
+ }
@@ -15,6 +15,7 @@
15
15
  "action_edit-cluster": "Edit Cluster",
16
16
  "action_delete-cluster": "Delete Cluster",
17
17
  "action_connect-to-db": "Connect to database",
18
+ "action_go-to-shared-db": "Go to shared database",
18
19
  "action_manage": "Manage",
19
20
 
20
21
  "title_developer-ui": "Developer UI",
@@ -2,7 +2,6 @@ import React from 'react';
2
2
 
3
3
  import {skipToken} from '@reduxjs/toolkit/query';
4
4
  import {isNil} from 'lodash';
5
- import {StringParam, useQueryParams} from 'use-query-params';
6
5
 
7
6
  import {useClusterWithProxy} from '../../store/reducers/cluster/cluster';
8
7
  import {selectTabletsWithFqdn, tabletsApi} from '../../store/reducers/tablets';
@@ -12,6 +11,7 @@ import {valueIsDefined} from '../../utils';
12
11
  import {useAutoRefreshInterval, useTypedSelector} from '../../utils/hooks';
13
12
 
14
13
  import {TabletsTable} from './TabletsTable';
14
+ import {useTabletQueryParams} from './useTabletQueryParams';
15
15
 
16
16
  const TABLET_ID_SEARCH_DEBOUNCE = 1000;
17
17
  const TABLET_ID_PATTERN = /^\d+$/;
@@ -60,8 +60,8 @@ export function Tablets({
60
60
  // search remains client-side over the already loaded tablets list.
61
61
  const isDatabasePage = !isNil(path) && !isNil(databaseFullPath) && path === databaseFullPath;
62
62
 
63
- const [{tabletsSearch}] = useQueryParams({tabletsSearch: StringParam});
64
- const currentSearch = tabletsSearch ?? '';
63
+ const {tabletsSearch} = useTabletQueryParams();
64
+ const currentSearch = tabletsSearch;
65
65
 
66
66
  // Debounce the search value before sending it to the backend, since the
67
67
  // request can be heavy.
@@ -2,9 +2,8 @@ import React from 'react';
2
2
 
3
3
  import {ArrowRotateLeft} from '@gravity-ui/icons';
4
4
  import type {Column as DataTableColumn, SortOrder} from '@gravity-ui/react-data-table';
5
- import {Icon, Text} from '@gravity-ui/uikit';
5
+ import {Icon, Select, Text} from '@gravity-ui/uikit';
6
6
  import {isNil} from 'lodash';
7
- import {StringParam, useQueryParams} from 'use-query-params';
8
7
 
9
8
  import {ButtonWithConfirmDialog} from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog';
10
9
  import {EntitiesCount} from '../../components/EntitiesCount';
@@ -24,6 +23,8 @@ import {DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER} from '../../utils/consta
24
23
  import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges';
25
24
 
26
25
  import i18n from './i18n';
26
+ import {useTabletQueryParams} from './useTabletQueryParams';
27
+ import {filterTablets, getAvailableTabletTypes} from './utils';
27
28
 
28
29
  function isFollowerTablet(state: TTabletStateInfo) {
29
30
  return state.Leader === false;
@@ -228,55 +229,58 @@ export function TabletsTable({
228
229
  nodeId,
229
230
  backendSearchActive,
230
231
  }: TabletsTableProps) {
231
- const [{tabletsSearch}, setQueryParams] = useQueryParams({
232
- tabletsSearch: StringParam,
233
- });
232
+ const {tabletsSearch, tabletTypes, handleTabletsSearchChange, handleTabletTypesChange} =
233
+ useTabletQueryParams();
234
234
 
235
235
  // Track sort state for scroll dependencies
236
236
  const [sortParams, setSortParams] = React.useState<SortOrder | SortOrder[] | undefined>();
237
237
 
238
238
  const {filteredTablets, showEndOfRange} = React.useMemo(() => {
239
- let showEnd = false;
240
- if (backendSearchActive) {
241
- // Backend already filtered the list; just scan once for showEndOfRange.
242
- for (const tablet of tablets) {
243
- if (tablet.EndOfRangeKeyPrefix) {
244
- showEnd = true;
245
- break;
246
- }
247
- }
248
- return {filteredTablets: tablets, showEndOfRange: showEnd};
249
- }
250
-
251
- const preparedTabletsSearch = tabletsSearch?.trim() ?? '';
252
-
253
239
  return {
254
- filteredTablets: tablets.filter((tablet) => {
255
- showEnd = showEnd || Boolean(tablet.EndOfRangeKeyPrefix);
256
- return String(tablet.TabletId).includes(preparedTabletsSearch);
240
+ filteredTablets: filterTablets(tablets, {
241
+ tabletIdSearch: backendSearchActive ? undefined : tabletsSearch,
242
+ tabletTypes,
257
243
  }),
258
- showEndOfRange: showEnd,
244
+ showEndOfRange: tablets.some((tablet) => Boolean(tablet.EndOfRangeKeyPrefix)),
259
245
  };
260
- }, [tablets, tabletsSearch, backendSearchActive]);
246
+ }, [tablets, tabletsSearch, tabletTypes, backendSearchActive]);
247
+
248
+ const tabletTypeOptions = React.useMemo(() => {
249
+ return getAvailableTabletTypes(tablets, tabletTypes).map((tabletType) => ({
250
+ value: tabletType,
251
+ content: tabletType,
252
+ }));
253
+ }, [tablets, tabletTypes]);
261
254
 
262
255
  const columns = React.useMemo(
263
256
  () => getColumns({nodeId, showEndOfRange}),
264
257
  [nodeId, showEndOfRange],
265
258
  );
266
259
 
267
- const handleSearchQueryChange = (value: string) => {
268
- setQueryParams({tabletsSearch: value || undefined}, 'replaceIn');
269
- };
260
+ const filtersActive = Boolean(tabletsSearch.trim()) || tabletTypes.length > 0;
270
261
 
271
262
  return (
272
263
  <TableWithControlsLayout fullHeight>
273
264
  <TableWithControlsLayout.Controls>
274
265
  <Search
275
266
  placeholder={i18n('controls.search-placeholder')}
276
- onChange={handleSearchQueryChange}
277
- value={tabletsSearch ?? ''}
267
+ onChange={handleTabletsSearchChange}
268
+ value={tabletsSearch}
278
269
  width={238}
279
270
  />
271
+ <Select
272
+ multiple
273
+ filterable
274
+ hasClear
275
+ qa="tablets-type-filter"
276
+ aria-label={i18n('controls.type-filter-label')}
277
+ label={i18n('controls.type-filter-label')}
278
+ placeholder={i18n('controls.type-filter-placeholder')}
279
+ value={tabletTypes}
280
+ options={tabletTypeOptions}
281
+ onUpdate={handleTabletTypesChange}
282
+ width={220}
283
+ />
280
284
  <EntitiesCount
281
285
  label={i18n('controls.entities-count-label')}
282
286
  loading={loading}
@@ -287,14 +291,16 @@ export function TabletsTable({
287
291
  {error ? <ResponseError error={error} /> : null}
288
292
  <TableWithControlsLayout.Table
289
293
  scrollContainerRef={scrollContainerRef}
290
- scrollDependencies={[tabletsSearch, sortParams]}
294
+ scrollDependencies={[tabletsSearch, tabletTypes, sortParams]}
291
295
  loading={loading}
292
296
  >
293
297
  <ResizeableDataTable
294
298
  columns={columns}
295
299
  data={filteredTablets}
296
300
  settings={DEFAULT_TABLE_SETTINGS}
297
- emptyDataMessage={i18n('noTabletsData')}
301
+ emptyDataMessage={
302
+ filtersActive ? i18n('noTabletsMatchFilters') : i18n('noTabletsData')
303
+ }
298
304
  onSortChange={setSortParams}
299
305
  />
300
306
  </TableWithControlsLayout.Table>
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "noTabletsData": "No tablets data",
3
+ "noTabletsMatchFilters": "No tablets match the selected filters",
3
4
  "Type": "Type",
4
5
  "Tablet": "Tablet",
5
6
  "State": "State",
@@ -13,5 +14,7 @@
13
14
  "controls.kill-not-allowed": "You don't have enough rights to restart tablet",
14
15
  "controls.kill-impossible-follower": "It's impossible to restart a follower",
15
16
  "controls.search-placeholder": "Tablet ID",
17
+ "controls.type-filter-label": "Type",
18
+ "controls.type-filter-placeholder": "All types",
16
19
  "controls.entities-count-label": "Tablets"
17
20
  }
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+
3
+ import type {QueryParamConfig} from 'use-query-params';
4
+ import {StringParam, useQueryParams} from 'use-query-params';
5
+ import {z} from 'zod';
6
+
7
+ const tabletTypesSchema = z
8
+ .array(
9
+ z
10
+ .string()
11
+ .trim()
12
+ .min(1)
13
+ .max(128)
14
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/),
15
+ )
16
+ .max(100)
17
+ .catch([]);
18
+
19
+ export const TabletTypesParam: QueryParamConfig<string[] | undefined, string[]> = {
20
+ encode: (value) => (value?.length ? value.join(',') : undefined),
21
+ decode: (value) => {
22
+ if (typeof value !== 'string') {
23
+ return [];
24
+ }
25
+
26
+ return Array.from(new Set(tabletTypesSchema.parse(value.split(','))));
27
+ },
28
+ };
29
+
30
+ export function useTabletQueryParams() {
31
+ const [{tabletsSearch, tabletTypes}, setQueryParams] = useQueryParams({
32
+ tabletsSearch: StringParam,
33
+ tabletTypes: TabletTypesParam,
34
+ });
35
+ const handleTabletsSearchChange = React.useCallback(
36
+ (value: string) => {
37
+ setQueryParams({tabletsSearch: value || undefined}, 'replaceIn');
38
+ },
39
+ [setQueryParams],
40
+ );
41
+ const handleTabletTypesChange = React.useCallback(
42
+ (value: string[]) => {
43
+ setQueryParams({tabletTypes: value.length ? value : undefined}, 'replaceIn');
44
+ },
45
+ [setQueryParams],
46
+ );
47
+
48
+ return {
49
+ tabletsSearch: tabletsSearch ?? '',
50
+ tabletTypes: tabletTypes ?? [],
51
+ handleTabletsSearchChange,
52
+ handleTabletTypesChange,
53
+ };
54
+ }
@@ -0,0 +1,43 @@
1
+ interface FilterTabletsOptions {
2
+ tabletIdSearch?: string | null;
3
+ tabletTypes: string[];
4
+ }
5
+
6
+ interface FilterableTablet {
7
+ TabletId?: string;
8
+ Type?: string;
9
+ }
10
+
11
+ export function filterTablets<T extends FilterableTablet>(
12
+ tablets: T[],
13
+ options: FilterTabletsOptions,
14
+ ) {
15
+ const tabletIdSearch = options.tabletIdSearch?.trim() ?? '';
16
+ const tabletTypes = new Set(options.tabletTypes);
17
+
18
+ if (!tabletIdSearch && tabletTypes.size === 0) {
19
+ return tablets;
20
+ }
21
+
22
+ return tablets.filter((tablet) => {
23
+ const matchesTabletId = String(tablet.TabletId).includes(tabletIdSearch);
24
+ const matchesTabletType =
25
+ tabletTypes.size === 0 || (tablet.Type !== undefined && tabletTypes.has(tablet.Type));
26
+
27
+ return matchesTabletId && matchesTabletType;
28
+ });
29
+ }
30
+
31
+ export function getAvailableTabletTypes<T extends FilterableTablet>(
32
+ tablets: T[],
33
+ selectedTabletTypes: string[],
34
+ ) {
35
+ const availableTabletTypes = new Set(selectedTabletTypes);
36
+ tablets.forEach((tablet) => {
37
+ if (tablet.Type) {
38
+ availableTabletTypes.add(tablet.Type);
39
+ }
40
+ });
41
+
42
+ return Array.from(availableTabletTypes).sort();
43
+ }
@@ -34,7 +34,7 @@ import cryCatIcon from '../../../assets/icons/cry-cat.svg';
34
34
 
35
35
  import './Healthcheck.scss';
36
36
 
37
- type HealthcheckDetailsProps =
37
+ type HealthcheckDetailsProps = (
38
38
  | {
39
39
  database: string;
40
40
  clusterName?: string;
@@ -46,7 +46,12 @@ type HealthcheckDetailsProps =
46
46
  database?: undefined;
47
47
  databaseType?: undefined;
48
48
  scope?: 'cluster';
49
- };
49
+ }
50
+ ) & {
51
+ targetIssueId?: string;
52
+ targetLeafIssueId?: string;
53
+ targetLeafIssueRef?: React.Ref<HTMLDivElement>;
54
+ };
50
55
 
51
56
  export function Healthcheck(props: HealthcheckDetailsProps) {
52
57
  if (props.database !== undefined) {
@@ -59,6 +64,9 @@ export function Healthcheck(props: HealthcheckDetailsProps) {
59
64
  clusterName={props.clusterName}
60
65
  databaseType={props.databaseType}
61
66
  scope={props.scope}
67
+ targetIssueId={props.targetIssueId}
68
+ targetLeafIssueId={props.targetLeafIssueId}
69
+ targetLeafIssueRef={props.targetLeafIssueRef}
62
70
  />
63
71
  </HealthcheckContext.Provider>
64
72
  );
@@ -76,16 +84,30 @@ function DatabaseHealthcheckInner({
76
84
  clusterName,
77
85
  databaseType,
78
86
  scope = 'database',
87
+ targetIssueId,
88
+ targetLeafIssueId,
89
+ targetLeafIssueRef,
79
90
  }: {
80
91
  database: string;
81
92
  clusterName?: string;
82
93
  databaseType?: ETenantType;
83
94
  scope?: 'cluster' | 'database';
95
+ targetIssueId?: string;
96
+ targetLeafIssueId?: string;
97
+ targetLeafIssueRef?: React.Ref<HTMLDivElement>;
84
98
  }) {
85
99
  const healthcheck = useHealthcheck(database, {clusterName, databaseType});
86
100
  const target = getDatabaseHealthcheckAssistantTarget({database, clusterName, scope});
87
101
 
88
- return <HealthcheckContent healthcheck={healthcheck} target={target} />;
102
+ return (
103
+ <HealthcheckContent
104
+ healthcheck={healthcheck}
105
+ target={target}
106
+ targetIssueId={targetIssueId}
107
+ targetLeafIssueId={targetLeafIssueId}
108
+ targetLeafIssueRef={targetLeafIssueRef}
109
+ />
110
+ );
89
111
  }
90
112
 
91
113
  function ClusterHealthcheckInner({clusterName}: {clusterName: string}) {
@@ -112,9 +134,15 @@ interface HealthcheckResult {
112
134
  function HealthcheckContent({
113
135
  healthcheck,
114
136
  target,
137
+ targetIssueId,
138
+ targetLeafIssueId,
139
+ targetLeafIssueRef,
115
140
  }: {
116
141
  healthcheck: HealthcheckResult;
117
142
  target: HealthcheckAssistantTarget;
143
+ targetIssueId?: string;
144
+ targetLeafIssueId?: string;
145
+ targetLeafIssueRef?: React.Ref<HTMLDivElement>;
118
146
  }) {
119
147
  const SuccessImage = getIllustration('SuccessOperation');
120
148
  const renderAssistantAction = uiFactory.healthcheck.renderAssistantAction;
@@ -214,7 +242,12 @@ function HealthcheckContent({
214
242
  <Flex direction="column" grow={1}>
215
243
  {renderControls()}
216
244
  <Flex direction="column" gap={4} grow={1} className={b('issues')}>
217
- <Issues issues={leavesIssues} />
245
+ <Issues
246
+ issues={leavesIssues}
247
+ targetIssueId={targetIssueId}
248
+ targetLeafIssueId={targetLeafIssueId}
249
+ targetLeafIssueRef={targetLeafIssueRef}
250
+ />
218
251
  </Flex>
219
252
  </Flex>
220
253
  );