ydb-embedded-ui 20.3.0 → 20.5.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 (167) 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/InfoViewer/formatters/pqGroup.d.ts +5 -1
  8. package/dist/components/InfoViewer/formatters/pqGroup.js +24 -2
  9. package/dist/components/InfoViewer/formatters/pqGroup.js.map +1 -1
  10. package/dist/components/InfoViewer/i18n/en.json +8 -1
  11. package/dist/components/InfoViewer/i18n/index.d.ts +1 -1
  12. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  13. package/dist/components/TenantNameWrapper/utils.d.ts +1 -1
  14. package/dist/components/TenantNameWrapper/utils.js.map +1 -1
  15. package/dist/containers/App/Content.js +9 -2
  16. package/dist/containers/App/Content.js.map +1 -1
  17. package/dist/containers/App/clusterRouteGuard.d.ts +5 -0
  18. package/dist/containers/App/clusterRouteGuard.js +13 -0
  19. package/dist/containers/App/clusterRouteGuard.js.map +1 -0
  20. package/dist/containers/Cluster/Cluster.js +1 -1
  21. package/dist/containers/Cluster/Cluster.js.map +1 -1
  22. package/dist/containers/Cluster/ClusterDrawerHealthcheck.d.ts +8 -0
  23. package/dist/containers/Cluster/ClusterDrawerHealthcheck.js +71 -9
  24. package/dist/containers/Cluster/ClusterDrawerHealthcheck.js.map +1 -1
  25. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.d.ts +2 -1
  26. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js +2 -2
  27. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js.map +1 -1
  28. package/dist/containers/Cluster/ClusterOverview/ClusterOverview.d.ts +1 -0
  29. package/dist/containers/Cluster/ClusterOverview/ClusterOverview.js.map +1 -1
  30. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.d.ts +2 -0
  31. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js +54 -103
  32. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js.map +1 -1
  33. package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
  34. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.d.ts +13 -0
  35. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js +94 -0
  36. package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js.map +1 -0
  37. package/dist/containers/Cluster/i18n/en.json +8 -5
  38. package/dist/containers/Cluster/i18n/index.d.ts +1 -1
  39. package/dist/containers/Clusters/constants.d.ts +1 -1
  40. package/dist/containers/Header/DatabaseRightControls.d.ts +4 -2
  41. package/dist/containers/Header/DatabaseRightControls.js +2 -2
  42. package/dist/containers/Header/DatabaseRightControls.js.map +1 -1
  43. package/dist/containers/Header/Header.js +2 -2
  44. package/dist/containers/Header/Header.js.map +1 -1
  45. package/dist/containers/Header/HeaderActionsMenu.d.ts +4 -2
  46. package/dist/containers/Header/HeaderActionsMenu.js +19 -2
  47. package/dist/containers/Header/HeaderActionsMenu.js.map +1 -1
  48. package/dist/containers/Header/HeaderRightControls.d.ts +4 -2
  49. package/dist/containers/Header/HeaderRightControls.js +2 -2
  50. package/dist/containers/Header/HeaderRightControls.js.map +1 -1
  51. package/dist/containers/Header/hooks/useClusterData.d.ts +1 -0
  52. package/dist/containers/Header/hooks/useHeaderData.d.ts +1 -0
  53. package/dist/containers/Header/hooks/useHeaderData.js +1 -0
  54. package/dist/containers/Header/hooks/useHeaderData.js.map +1 -1
  55. package/dist/containers/Header/hooks/useSharedDatabasePath.d.ts +10 -0
  56. package/dist/containers/Header/hooks/useSharedDatabasePath.js +102 -0
  57. package/dist/containers/Header/hooks/useSharedDatabasePath.js.map +1 -0
  58. package/dist/containers/Header/i18n/en.json +1 -0
  59. package/dist/containers/Header/i18n/index.d.ts +1 -1
  60. package/dist/containers/Operations/constants.d.ts +1 -1
  61. package/dist/containers/Storage/PaginatedStorageGroupsTable/columns/i18n/index.d.ts +1 -1
  62. package/dist/containers/Tablets/Tablets.js +3 -3
  63. package/dist/containers/Tablets/Tablets.js.map +1 -1
  64. package/dist/containers/Tablets/TabletsTable.js +17 -27
  65. package/dist/containers/Tablets/TabletsTable.js.map +1 -1
  66. package/dist/containers/Tablets/i18n/en.json +3 -0
  67. package/dist/containers/Tablets/i18n/index.d.ts +1 -1
  68. package/dist/containers/Tablets/useTabletQueryParams.d.ts +8 -0
  69. package/dist/containers/Tablets/useTabletQueryParams.js +40 -0
  70. package/dist/containers/Tablets/useTabletQueryParams.js.map +1 -0
  71. package/dist/containers/Tablets/utils.d.ts +11 -0
  72. package/dist/containers/Tablets/utils.js +23 -0
  73. package/dist/containers/Tablets/utils.js.map +1 -0
  74. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.d.ts +1 -1
  75. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js +17 -3
  76. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js.map +1 -1
  77. package/dist/containers/Tenant/Healthcheck/Healthcheck.d.ts +6 -1
  78. package/dist/containers/Tenant/Healthcheck/Healthcheck.js +5 -5
  79. package/dist/containers/Tenant/Healthcheck/Healthcheck.js.map +1 -1
  80. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.d.ts +2 -1
  81. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js +2 -2
  82. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js.map +1 -1
  83. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.d.ts +5 -1
  84. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js +5 -3
  85. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js.map +1 -1
  86. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.d.ts +1 -1
  87. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js +11 -2
  88. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js.map +1 -1
  89. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js +2 -0
  90. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js.map +1 -1
  91. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js +4 -1
  92. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js.map +1 -1
  93. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.d.ts +5 -1
  94. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js +2 -2
  95. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js.map +1 -1
  96. package/dist/containers/Tenant/Healthcheck/i18n/en.json +2 -1
  97. package/dist/containers/Tenant/Healthcheck/i18n/index.d.ts +1 -1
  98. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
  99. package/dist/containers/VDiskPage/i18n/index.d.ts +1 -1
  100. package/dist/routes.d.ts +4 -1
  101. package/dist/routes.js +3 -2
  102. package/dist/routes.js.map +1 -1
  103. package/dist/services/api/viewer.d.ts +3 -1
  104. package/dist/services/api/viewer.js +3 -2
  105. package/dist/services/api/viewer.js.map +1 -1
  106. package/dist/store/reducers/cluster/cluster.d.ts +2 -1
  107. package/dist/store/reducers/cluster/cluster.js +1 -0
  108. package/dist/store/reducers/cluster/cluster.js.map +1 -1
  109. package/dist/store/reducers/healthcheckInfo/types.d.ts +1 -0
  110. package/dist/store/reducers/healthcheckInfo/utils.d.ts +1 -0
  111. package/dist/store/reducers/healthcheckInfo/utils.js +43 -9
  112. package/dist/store/reducers/healthcheckInfo/utils.js.map +1 -1
  113. package/dist/store/reducers/tenants/tenants.d.ts +10 -1
  114. package/dist/store/reducers/tenants/tenants.js +33 -0
  115. package/dist/store/reducers/tenants/tenants.js.map +1 -1
  116. package/dist/store/reducers/tenants/types.d.ts +4 -0
  117. package/dist/types/api/healthcheck.d.ts +7 -0
  118. package/dist/types/api/schema/persQueueGroup.d.ts +16 -0
  119. package/dist/types/api/schema/persQueueGroup.js +7 -0
  120. package/dist/types/api/schema/persQueueGroup.js.map +1 -1
  121. package/package.json +2 -2
  122. package/src/components/Drawer/Drawer.tsx +6 -0
  123. package/src/components/EntityStatus/EntityStatus.tsx +9 -1
  124. package/src/components/InfoViewer/formatters/pqGroup.ts +30 -2
  125. package/src/components/InfoViewer/i18n/en.json +8 -1
  126. package/src/components/TenantNameWrapper/utils.ts +1 -1
  127. package/src/containers/App/Content.tsx +9 -1
  128. package/src/containers/App/clusterRouteGuard.ts +24 -0
  129. package/src/containers/Cluster/Cluster.tsx +1 -0
  130. package/src/containers/Cluster/ClusterDrawerHealthcheck.tsx +112 -13
  131. package/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -1
  132. package/src/containers/Cluster/ClusterOverview/ClusterOverview.tsx +1 -0
  133. package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
  134. package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx +156 -200
  135. package/src/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.ts +142 -0
  136. package/src/containers/Cluster/i18n/en.json +8 -5
  137. package/src/containers/Header/DatabaseRightControls.tsx +7 -1
  138. package/src/containers/Header/Header.tsx +3 -0
  139. package/src/containers/Header/HeaderActionsMenu.tsx +24 -2
  140. package/src/containers/Header/HeaderRightControls.tsx +7 -1
  141. package/src/containers/Header/hooks/useHeaderData.ts +1 -0
  142. package/src/containers/Header/hooks/useSharedDatabasePath.ts +195 -0
  143. package/src/containers/Header/i18n/en.json +1 -0
  144. package/src/containers/Tablets/Tablets.tsx +3 -3
  145. package/src/containers/Tablets/TabletsTable.tsx +37 -31
  146. package/src/containers/Tablets/i18n/en.json +3 -0
  147. package/src/containers/Tablets/useTabletQueryParams.ts +54 -0
  148. package/src/containers/Tablets/utils.ts +43 -0
  149. package/src/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +23 -2
  150. package/src/containers/Tenant/Healthcheck/Healthcheck.tsx +37 -4
  151. package/src/containers/Tenant/Healthcheck/components/HealthcheckDrawer.tsx +3 -0
  152. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssue.tsx +17 -4
  153. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.tsx +13 -3
  154. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.tsx +1 -0
  155. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.tsx +3 -1
  156. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssues.tsx +15 -2
  157. package/src/containers/Tenant/Healthcheck/i18n/en.json +2 -1
  158. package/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
  159. package/src/routes.ts +8 -2
  160. package/src/services/api/viewer.ts +10 -2
  161. package/src/store/reducers/cluster/cluster.ts +2 -1
  162. package/src/store/reducers/healthcheckInfo/types.ts +1 -0
  163. package/src/store/reducers/healthcheckInfo/utils.ts +50 -10
  164. package/src/store/reducers/tenants/tenants.ts +55 -1
  165. package/src/store/reducers/tenants/types.ts +5 -0
  166. package/src/types/api/healthcheck.ts +8 -0
  167. package/src/types/api/schema/persQueueGroup.ts +19 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "20.3.0",
3
+ "version": "20.5.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -50,7 +50,7 @@
50
50
  "numeral": "^2.0.6",
51
51
  "path-to-regexp": "^3.3.0",
52
52
  "qrcode": "^1.5.4",
53
- "qs": "^6.15.2",
53
+ "qs": "^6.16.0",
54
54
  "react-error-boundary": "^4.1.2",
55
55
  "react-helmet-async": "^2.0.5",
56
56
  "react-hook-form": "^7.55.0",
@@ -30,6 +30,7 @@ type DrawerEvent = MouseEvent & {
30
30
  interface DrawerPaneContentWrapperProps {
31
31
  isVisible: boolean;
32
32
  onClose: () => void;
33
+ onTransitionInComplete?: () => void;
33
34
  children: React.ReactNode;
34
35
  drawerId?: string;
35
36
  storageKey?: string;
@@ -44,6 +45,7 @@ interface DrawerPaneContentWrapperProps {
44
45
  const DrawerPaneContentWrapper = ({
45
46
  isVisible,
46
47
  onClose,
48
+ onTransitionInComplete,
47
49
  children,
48
50
  drawerId = 'drawer',
49
51
  storageKey = DRAWER_WIDTH_KEY,
@@ -172,6 +174,7 @@ const DrawerPaneContentWrapper = ({
172
174
  qa={drawerId}
173
175
  open={isVisible}
174
176
  onOpenChange={handleOpenChange}
177
+ onTransitionInComplete={onTransitionInComplete}
175
178
  placement={direction}
176
179
  hideVeil={hideVeil}
177
180
  className={b('container', className)}
@@ -203,6 +206,7 @@ interface DrawerPaneProps {
203
206
  renderDrawerContent: () => React.ReactNode;
204
207
  isDrawerVisible: boolean;
205
208
  onCloseDrawer: () => void;
209
+ onTransitionInComplete?: () => void;
206
210
  drawerId?: string;
207
211
  storageKey?: string;
208
212
  defaultWidth?: number;
@@ -221,6 +225,7 @@ export const DrawerWrapper = ({
221
225
  renderDrawerContent,
222
226
  isDrawerVisible,
223
227
  onCloseDrawer,
228
+ onTransitionInComplete,
224
229
  drawerId,
225
230
  storageKey,
226
231
  defaultWidth,
@@ -289,6 +294,7 @@ export const DrawerWrapper = ({
289
294
  hideVeil={hideVeil}
290
295
  isVisible={isDrawerVisible}
291
296
  onClose={onCloseDrawer}
297
+ onTransitionInComplete={onTransitionInComplete}
292
298
  drawerId={drawerId}
293
299
  storageKey={storageKey}
294
300
  defaultWidth={defaultWidth}
@@ -48,20 +48,26 @@ interface EntityStatusLabelProps {
48
48
  status: EFlag;
49
49
  note?: React.ReactNode;
50
50
  children?: React.ReactNode;
51
+ className?: string;
52
+ endContent?: React.ReactNode;
51
53
  withStatusName?: boolean;
52
54
  size?: LabelProps['size'];
53
55
  iconSize?: number;
54
56
  onClick?: (event: React.MouseEvent<HTMLElement>) => void;
57
+ qa?: string;
55
58
  }
56
59
 
57
60
  function EntityStatusLabel({
58
61
  children,
62
+ className,
63
+ endContent,
59
64
  status,
60
65
  withStatusName = true,
61
66
  note,
62
67
  size = 'm',
63
68
  iconSize = 14,
64
69
  onClick,
70
+ qa,
65
71
  }: EntityStatusLabelProps) {
66
72
  const theme = EFlagToLabelTheme[status];
67
73
  const isClickable = Boolean(onClick);
@@ -71,13 +77,15 @@ function EntityStatusLabel({
71
77
  theme={theme}
72
78
  icon={<StatusIcon size={iconSize} status={status} />}
73
79
  size={size}
74
- className={b({critical: status === EFlag.Red, clickable: isClickable})}
80
+ className={b({critical: status === EFlag.Red, clickable: isClickable}, className)}
75
81
  onClick={onClick}
76
82
  interactive={isClickable}
83
+ qa={qa}
77
84
  >
78
85
  <Flex gap="2" wrap="nowrap">
79
86
  {children}
80
87
  {withStatusName ? EFlagToStatusName[status] : null}
88
+ {endContent ? <Flex alignSelf="center">{endContent}</Flex> : null}
81
89
  {note && <HelpMark className={b('note')}>{note}</HelpMark>}
82
90
  </Flex>
83
91
  </Label>
@@ -1,11 +1,13 @@
1
1
  import type {
2
2
  TPQPartitionConfig,
3
+ TPQPartitionStrategy,
3
4
  TPQTabletConfig,
4
5
  TPersQueueGroupDescription,
5
6
  } from '../../../types/api/schema';
6
- import {EMeteringMode} from '../../../types/api/schema';
7
- import {HOUR_IN_SECONDS} from '../../../utils/constants';
7
+ import {EMeteringMode, EPQPartitionStrategyType} from '../../../types/api/schema';
8
+ import {EMPTY_DATA_PLACEHOLDER, HOUR_IN_SECONDS} from '../../../utils/constants';
8
9
  import {formatBps, formatBytes, formatNumber} from '../../../utils/dataFormatters/dataFormatters';
10
+ import i18n from '../i18n';
9
11
  import {createInfoFormatter} from '../utils';
10
12
 
11
13
  const EMeteringModeToNames: Record<EMeteringMode, string> = {
@@ -13,6 +15,13 @@ const EMeteringModeToNames: Record<EMeteringMode, string> = {
13
15
  [EMeteringMode.METERING_MODE_RESERVED_CAPACITY]: 'reserved-capacity',
14
16
  };
15
17
 
18
+ const EPQPartitionStrategyTypeToNames: Record<EPQPartitionStrategyType, string> = {
19
+ [EPQPartitionStrategyType.DISABLED]: i18n('value_autopartitioning-disabled'),
20
+ [EPQPartitionStrategyType.CAN_SPLIT]: i18n('value_autopartitioning-up'),
21
+ [EPQPartitionStrategyType.CAN_SPLIT_AND_MERGE]: i18n('value_autopartitioning-up-and-down'),
22
+ [EPQPartitionStrategyType.PAUSED]: i18n('value_autopartitioning-paused'),
23
+ };
24
+
16
25
  export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
17
26
  values: {
18
27
  Partitions: (value) => formatNumber(value?.length || 0),
@@ -48,3 +57,22 @@ export const formatPQPartitionConfig = createInfoFormatter<TPQPartitionConfig>({
48
57
  WriteSpeedInBytesPerSecond: 'Partitions write speed',
49
58
  },
50
59
  });
60
+
61
+ const formatPartitionCount = (value?: number) =>
62
+ value === undefined ? EMPTY_DATA_PLACEHOLDER : formatNumber(value);
63
+
64
+ export const formatPQPartitionStrategy = createInfoFormatter<TPQPartitionStrategy>({
65
+ values: {
66
+ // Fall back to the raw enum value so a strategy introduced by a newer YDB
67
+ // version still renders the Autopartitioning row instead of being dropped.
68
+ PartitionStrategyType: (value) =>
69
+ value && (EPQPartitionStrategyTypeToNames[value] ?? value),
70
+ MinPartitionCount: formatPartitionCount,
71
+ MaxPartitionCount: formatPartitionCount,
72
+ },
73
+ labels: {
74
+ PartitionStrategyType: i18n('field_autopartitioning'),
75
+ MinPartitionCount: i18n('field_min-partitions-count'),
76
+ MaxPartitionCount: i18n('field_max-partitions-count'),
77
+ },
78
+ });
@@ -1,5 +1,12 @@
1
1
  {
2
2
  "common.created": "Created",
3
3
  "common.type": "Type",
4
- "no-data": "No data"
4
+ "no-data": "No data",
5
+ "field_autopartitioning": "Autopartitioning",
6
+ "field_min-partitions-count": "Min partitions count",
7
+ "field_max-partitions-count": "Max partitions count",
8
+ "value_autopartitioning-disabled": "Disabled",
9
+ "value_autopartitioning-up": "Up",
10
+ "value_autopartitioning-up-and-down": "Up and down",
11
+ "value_autopartitioning-paused": "Paused"
5
12
  }
@@ -2,7 +2,7 @@ import type {PreparedTenant} from '../../store/reducers/tenants/types';
2
2
  import type {AdditionalTenantsProps} from '../../types/additionalProps';
3
3
 
4
4
  export function getTenantBackend(
5
- tenant: PreparedTenant,
5
+ tenant: Pick<PreparedTenant, 'NodeIds'> & Partial<Pick<PreparedTenant, 'sharedNodeIds'>>,
6
6
  additionalTenantsProps?: AdditionalTenantsProps,
7
7
  ) {
8
8
  if (typeof additionalTenantsProps?.prepareTenantBackend !== 'function') {
@@ -2,7 +2,7 @@ import React from 'react';
2
2
 
3
3
  import {connect} from 'react-redux';
4
4
  import type {RedirectProps} from 'react-router-dom';
5
- import {Redirect, Route, Switch} from 'react-router-dom';
5
+ import {Redirect, Route, Switch, useLocation} from 'react-router-dom';
6
6
 
7
7
  import {Unauthenticated} from '../../components/Errors/401';
8
8
  import {AccessDenied} from '../../components/Errors/403';
@@ -41,6 +41,7 @@ import {
41
41
  TenantSlot,
42
42
  VDiskPageSlot,
43
43
  } from './appSlots';
44
+ import {shouldRedirectClusterRouteToRoot} from './clusterRouteGuard';
44
45
  import {getLegacyClusterTenantsRedirect, getLegacyTenantRedirect} from './legacyRedirects';
45
46
 
46
47
  import './App.scss';
@@ -148,6 +149,12 @@ export function Content(props: ContentProps) {
148
149
  const {singleClusterMode} = props;
149
150
 
150
151
  const slots = useSlots(props);
152
+ const currentLocation = useLocation();
153
+ const shouldRedirectToRoot = shouldRedirectClusterRouteToRoot({
154
+ singleClusterMode,
155
+ pathname: currentLocation.pathname,
156
+ search: currentLocation.search,
157
+ });
151
158
 
152
159
  const additionalRoutes = slots.get(RoutesSlot);
153
160
 
@@ -158,6 +165,7 @@ export function Content(props: ContentProps) {
158
165
  return (
159
166
  <Switch>
160
167
  {additionalRoutes?.rendered}
168
+ {shouldRedirectToRoot ? <Redirect to="/" /> : null}
161
169
  <Route>
162
170
  <Header />
163
171
  <Switch>
@@ -0,0 +1,24 @@
1
+ import {matchPath} from 'react-router-dom';
2
+
3
+ import routes from '../../routes';
4
+
5
+ export function shouldRedirectClusterRouteToRoot({
6
+ singleClusterMode,
7
+ pathname,
8
+ search,
9
+ }: {
10
+ singleClusterMode: boolean;
11
+ pathname: string;
12
+ search: string;
13
+ }) {
14
+ const clusterRouteMatch = matchPath(pathname, {
15
+ path: routes.cluster,
16
+ exact: false,
17
+ });
18
+
19
+ if (singleClusterMode || !clusterRouteMatch) {
20
+ return false;
21
+ }
22
+
23
+ return !new URLSearchParams(search).get('clusterName');
24
+ }
@@ -170,6 +170,7 @@ export function Cluster({additionalClusterProps, additionalTenantsProps}: Cluste
170
170
  <div className={b('dashboard')}>
171
171
  <ClusterOverview
172
172
  cluster={cluster ?? {}}
173
+ clusterName={clusterName ?? undefined}
173
174
  groupStats={groupsStats}
174
175
  loading={infoLoading}
175
176
  error={clusterError || cluster?.error}
@@ -19,10 +19,15 @@ interface ClusterDrawerHealthcheckProps {
19
19
  }
20
20
 
21
21
  export function useClusterHealthcheckQueryParams() {
22
- const [{showHealthcheck, issuesFilter, view}, setQueryParams] = useQueryParams({
22
+ const [
23
+ {showHealthcheck, issuesFilter, view, healthcheckIssue, healthcheckLeaf},
24
+ setQueryParams,
25
+ ] = useQueryParams({
23
26
  showHealthcheck: BooleanParam,
24
27
  issuesFilter: StringParam,
25
28
  view: StringParam,
29
+ healthcheckIssue: StringParam,
30
+ healthcheckLeaf: StringParam,
26
31
  });
27
32
 
28
33
  const handleShowHealthcheckChange = React.useCallback(
@@ -46,6 +51,35 @@ export function useClusterHealthcheckQueryParams() {
46
51
  [setQueryParams],
47
52
  );
48
53
 
54
+ const handleOpenHealthcheckIssue = React.useCallback(
55
+ (target: {issueId: string; leafIssueId: string; view: string}) => {
56
+ setQueryParams(
57
+ {
58
+ showHealthcheck: true,
59
+ issuesFilter: undefined,
60
+ view: target.view,
61
+ healthcheckIssue: target.issueId,
62
+ healthcheckLeaf: target.leafIssueId,
63
+ },
64
+ 'replaceIn',
65
+ );
66
+ },
67
+ [setQueryParams],
68
+ );
69
+
70
+ const handleCloseHealthcheck = React.useCallback(() => {
71
+ setQueryParams(
72
+ {
73
+ showHealthcheck: undefined,
74
+ issuesFilter: undefined,
75
+ view: undefined,
76
+ healthcheckIssue: undefined,
77
+ healthcheckLeaf: undefined,
78
+ },
79
+ 'replaceIn',
80
+ );
81
+ }, [setQueryParams]);
82
+
49
83
  return {
50
84
  showHealthcheck,
51
85
  handleShowHealthcheckChange,
@@ -53,6 +87,10 @@ export function useClusterHealthcheckQueryParams() {
53
87
  handleIssuesFilterChange,
54
88
  view,
55
89
  handleHealthcheckViewChange,
90
+ healthcheckIssue,
91
+ healthcheckLeaf,
92
+ handleOpenHealthcheckIssue,
93
+ handleCloseHealthcheck,
56
94
  };
57
95
  }
58
96
 
@@ -61,12 +99,8 @@ export function ClusterDrawerHealthcheck({
61
99
  database,
62
100
  clusterName,
63
101
  }: ClusterDrawerHealthcheckProps) {
64
- const {
65
- handleShowHealthcheckChange,
66
- showHealthcheck,
67
- handleIssuesFilterChange,
68
- handleHealthcheckViewChange,
69
- } = useClusterHealthcheckQueryParams();
102
+ const {showHealthcheck, healthcheckIssue, healthcheckLeaf, handleCloseHealthcheck} =
103
+ useClusterHealthcheckQueryParams();
70
104
 
71
105
  const healthcheckStatus = useTypedSelector((state) =>
72
106
  selectCheckStatus(state, database, clusterName),
@@ -75,21 +109,86 @@ export function ClusterDrawerHealthcheck({
75
109
  const healthcheckData = useTypedSelector((state) =>
76
110
  selectAllHealthcheckInfo(state, database, clusterName),
77
111
  );
112
+ const targetLeafIssueRef = React.useRef<HTMLDivElement | null>(null);
113
+ const drawerTransitionCompleteRef = React.useRef(false);
114
+ const scrolledTargetRef = React.useRef<{
115
+ issueId?: string;
116
+ leafIssueId?: string;
117
+ }>({});
118
+
119
+ const scrollToTargetIssue = React.useCallback(() => {
120
+ if (!drawerTransitionCompleteRef.current || !healthcheckIssue || !healthcheckLeaf) {
121
+ return;
122
+ }
123
+
124
+ if (
125
+ scrolledTargetRef.current.issueId === healthcheckIssue &&
126
+ scrolledTargetRef.current.leafIssueId === healthcheckLeaf
127
+ ) {
128
+ return;
129
+ }
130
+
131
+ const targetIssue = targetLeafIssueRef.current;
132
+ const scrollContainer = targetIssue?.closest<HTMLElement>('.ydb-fullscreen__content');
133
+ if (!targetIssue || !scrollContainer) {
134
+ return;
135
+ }
136
+
137
+ const controls = scrollContainer.querySelector<HTMLElement>('.ydb-healthcheck__controls');
138
+ const targetTop =
139
+ scrollContainer.scrollTop +
140
+ targetIssue.getBoundingClientRect().top -
141
+ scrollContainer.getBoundingClientRect().top -
142
+ (controls?.getBoundingClientRect().height ?? 0);
143
+
144
+ scrolledTargetRef.current = {
145
+ issueId: healthcheckIssue,
146
+ leafIssueId: healthcheckLeaf,
147
+ };
148
+ scrollContainer.scrollTo({top: Math.max(0, targetTop), behavior: 'smooth'});
149
+ }, [healthcheckIssue, healthcheckLeaf]);
150
+
151
+ const handleTargetLeafIssueRef = React.useCallback(
152
+ (element: HTMLDivElement | null) => {
153
+ targetLeafIssueRef.current = element;
154
+ if (element) {
155
+ window.requestAnimationFrame(scrollToTargetIssue);
156
+ }
157
+ },
158
+ [scrollToTargetIssue],
159
+ );
160
+
161
+ const handleTransitionInComplete = React.useCallback(() => {
162
+ drawerTransitionCompleteRef.current = true;
163
+ scrollToTargetIssue();
164
+ }, [scrollToTargetIssue]);
78
165
 
79
166
  const handleCloseDrawer = React.useCallback(() => {
80
- handleShowHealthcheckChange(false);
81
- handleIssuesFilterChange(undefined);
82
- handleHealthcheckViewChange(undefined);
83
- }, [handleShowHealthcheckChange, handleIssuesFilterChange, handleHealthcheckViewChange]);
167
+ drawerTransitionCompleteRef.current = false;
168
+ scrolledTargetRef.current = {};
169
+ handleCloseHealthcheck();
170
+ }, [handleCloseHealthcheck]);
84
171
 
85
172
  const renderDrawerContent = React.useCallback(() => {
86
- return <Healthcheck database={database} clusterName={clusterName} scope="cluster" />;
87
- }, [clusterName, database]);
173
+ return (
174
+ <Healthcheck
175
+ database={database}
176
+ clusterName={clusterName}
177
+ scope="cluster"
178
+ targetIssueId={healthcheckIssue ?? undefined}
179
+ targetLeafIssueId={healthcheckLeaf ?? undefined}
180
+ targetLeafIssueRef={handleTargetLeafIssueRef}
181
+ />
182
+ );
183
+ }, [clusterName, database, handleTargetLeafIssueRef, healthcheckIssue, healthcheckLeaf]);
88
184
 
89
185
  return (
90
186
  <HealthcheckDrawer
91
187
  isDrawerVisible={Boolean(showHealthcheck) && Boolean(database)}
92
188
  onCloseDrawer={handleCloseDrawer}
189
+ onTransitionInComplete={
190
+ healthcheckIssue && healthcheckLeaf ? handleTransitionInComplete : undefined
191
+ }
93
192
  renderDrawerContent={renderDrawerContent}
94
193
  drawerId="cluster-healthcheck-details"
95
194
  storageKey="cluster-healthcheck-details-drawer-width"
@@ -21,6 +21,7 @@ import './ClusterInfo.scss';
21
21
 
22
22
  interface ClusterInfoProps {
23
23
  cluster?: TClusterInfo;
24
+ clusterName?: string;
24
25
  loading?: boolean;
25
26
  error?: IResponseError | string;
26
27
  additionalClusterProps?: AdditionalClusterProps;
@@ -30,6 +31,7 @@ interface ClusterInfoProps {
30
31
 
31
32
  export const ClusterInfo = ({
32
33
  cluster,
34
+ clusterName,
33
35
  loading,
34
36
  error,
35
37
  additionalClusterProps = {},
@@ -131,7 +133,11 @@ export const ClusterInfo = ({
131
133
  {formatNumber(bridgePiles.length)}
132
134
  </Text>
133
135
  </Text>
134
- <BridgeInfoTable piles={bridgePiles} />
136
+ <BridgeInfoTable
137
+ piles={bridgePiles}
138
+ database={cluster?.Domain}
139
+ clusterName={clusterName}
140
+ />
135
141
  </InfoSection>
136
142
  );
137
143
  };
@@ -32,6 +32,7 @@ import './ClusterOverview.scss';
32
32
 
33
33
  interface ClusterOverviewProps {
34
34
  cluster: TClusterInfo;
35
+ clusterName?: string;
35
36
  groupStats?: ClusterGroupsStats;
36
37
  loading?: boolean;
37
38
  error?: IResponseError | string;
@@ -1,50 +1,29 @@
1
- @use '../../../../styles/mixins.scss';
2
-
3
- @mixin bridge-group-status-background {
4
- &_theme_positive {
5
- background-color: var(--g-color-text-positive);
6
- }
7
-
8
- &_theme_warning {
9
- background-color: var(--g-color-text-warning);
10
- }
11
-
12
- &_theme_danger {
13
- background-color: var(--g-color-text-danger);
14
- }
15
-
16
- &_theme_dark-complementary {
17
- background-color: var(--g-color-text-dark-complementary);
18
- }
19
-
20
- &_theme_secondary {
21
- background-color: var(--g-color-text-secondary);
22
- }
23
- }
24
-
25
1
  .ydb-bridge-info-table {
2
+ gap: 10px;
3
+
26
4
  height: 100%;
27
5
 
28
6
  &__pile {
7
+ box-sizing: border-box;
29
8
  width: 321px;
30
- padding: var(--g-spacing-3) var(--g-spacing-4);
9
+ min-height: 98px;
10
+ padding: var(--g-spacing-3);
31
11
 
32
12
  border-radius: var(--g-border-radius-s);
33
13
  background-color: var(--g-color-base-generic);
34
-
35
- @include mixins.body-2-typography();
36
14
  }
37
15
 
38
- &__pile-title {
16
+ &__pile-name {
39
17
  min-width: 0;
40
18
  }
41
19
 
42
- &__state-label {
20
+ &__current-pile {
43
21
  flex-shrink: 0;
22
+ }
44
23
 
45
- &_with-help .g-label__text {
46
- margin-inline: var(--g-spacing-1) var(--g-spacing-1);
47
- }
24
+ &__state-label,
25
+ &__healthcheck-status {
26
+ flex-shrink: 0;
48
27
  }
49
28
 
50
29
  &__state-help {
@@ -55,69 +34,25 @@
55
34
  white-space: pre-line;
56
35
  }
57
36
 
58
- &__statuses {
59
- width: 100%;
37
+ &__healthcheck-skeleton {
38
+ width: 72px;
39
+ height: 20px;
60
40
  }
61
- }
62
-
63
- .ydb-bridge-info-table-progress {
64
- display: flex;
65
- overflow: hidden;
66
- align-items: center;
67
- gap: var(--g-spacing-half);
68
41
 
69
- height: 10px;
42
+ &__healthcheck-accessible-prefix {
43
+ position: absolute;
70
44
 
71
- border-radius: var(--g-border-radius-xs);
72
-
73
- &_empty {
74
- background-color: var(--g-color-sfx-shadow);
75
- }
76
-
77
- &__item {
78
- min-width: 8px;
79
- height: 4px;
80
-
81
- border-radius: var(--g-border-radius-xs);
82
-
83
- @include bridge-group-status-background();
84
- }
85
- }
86
-
87
- .ydb-bridge-info-table-legend {
88
- display: flex;
89
- flex-wrap: wrap;
90
- gap: var(--g-spacing-2);
91
-
92
- min-width: 0;
93
- margin: 0;
94
- padding: 0;
95
-
96
- list-style: none;
97
-
98
- &__item {
99
- display: inline-flex;
100
- align-items: center;
101
- gap: var(--g-spacing-1);
102
-
103
- min-width: 0;
104
- }
105
-
106
- &__dot {
107
- flex: 0 0 6px;
108
-
109
- width: 6px;
110
- height: 6px;
45
+ overflow: hidden;
46
+ clip: rect(0 0 0 0);
111
47
 
112
- border-radius: 50%;
48
+ width: 1px;
49
+ height: 1px;
113
50
 
114
- @include bridge-group-status-background();
51
+ white-space: nowrap;
52
+ clip-path: inset(50%);
115
53
  }
116
54
 
117
- &__label {
118
- overflow: hidden;
119
-
120
- white-space: nowrap;
121
- text-overflow: ellipsis;
55
+ &__nodes {
56
+ color: var(--g-color-text-secondary);
122
57
  }
123
58
  }