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
@@ -15,6 +15,7 @@ interface HealthcheckDrawerProps {
15
15
  children: React.ReactNode;
16
16
  isDrawerVisible: boolean;
17
17
  onCloseDrawer: () => void;
18
+ onTransitionInComplete?: () => void;
18
19
  renderDrawerContent: () => React.ReactNode;
19
20
  drawerId: string;
20
21
  storageKey: string;
@@ -30,6 +31,7 @@ export function HealthcheckDrawer({
30
31
  children,
31
32
  isDrawerVisible,
32
33
  onCloseDrawer,
34
+ onTransitionInComplete,
33
35
  renderDrawerContent,
34
36
  drawerId,
35
37
  storageKey,
@@ -83,6 +85,7 @@ export function HealthcheckDrawer({
83
85
  <DrawerWrapper
84
86
  isDrawerVisible={isDrawerVisible}
85
87
  onCloseDrawer={onCloseDrawer}
88
+ onTransitionInComplete={onTransitionInComplete}
86
89
  renderDrawerContent={renderDrawerContent}
87
90
  drawerId={drawerId}
88
91
  storageKey={storageKey}
@@ -15,11 +15,19 @@ import {HealthcheckIssueTabs} from './HealthcheckIssueTabs';
15
15
  interface HealthcheckIssueProps {
16
16
  issue: IssuesTree;
17
17
  expanded?: boolean;
18
+ targeted?: boolean;
19
+ targetIssueId?: string;
20
+ targetRef?: React.Ref<HTMLDivElement>;
18
21
  }
19
22
 
20
- export function HealthcheckIssue({issue, expanded}: HealthcheckIssueProps) {
23
+ export function HealthcheckIssue({
24
+ issue,
25
+ expanded,
26
+ targeted,
27
+ targetIssueId,
28
+ targetRef,
29
+ }: HealthcheckIssueProps) {
21
30
  const {assistant} = useHealthcheckContext();
22
- const [selectedTab, setSelectedTab] = React.useState(issue.id);
23
31
  const parents = React.useMemo(() => {
24
32
  const parents = [];
25
33
  let current: IssuesTree | undefined = issue;
@@ -29,6 +37,11 @@ export function HealthcheckIssue({issue, expanded}: HealthcheckIssueProps) {
29
37
  }
30
38
  return parents.reverse();
31
39
  }, [issue]);
40
+ const [selectedTab, setSelectedTab] = React.useState(
41
+ targetIssueId && parents.some((parentIssue) => parentIssue.id === targetIssueId)
42
+ ? targetIssueId
43
+ : issue.id,
44
+ );
32
45
 
33
46
  const currentIssue = React.useMemo(() => {
34
47
  return parents.find((parent) => parent.id === selectedTab);
@@ -39,8 +52,8 @@ export function HealthcheckIssue({issue, expanded}: HealthcheckIssueProps) {
39
52
  );
40
53
 
41
54
  return (
42
- <Flex className={b('issue-wrapper')}>
43
- <Disclosure className={b('issue-content')} defaultExpanded={expanded}>
55
+ <Flex ref={targetRef} className={b('issue-wrapper')} qa={`healthcheck-issue-${issue.id}`}>
56
+ <Disclosure className={b('issue-content')} defaultExpanded={expanded || targeted}>
44
57
  <Disclosure.Summary>
45
58
  {(props) => {
46
59
  const {
@@ -13,7 +13,7 @@ import {NodeInfo} from './NodeInfo';
13
13
  import {PoolInfo} from './PoolInfo';
14
14
  import {IdList, LocationDetails, SectionWithTitle} from './utils';
15
15
 
16
- export type LocationFieldCompute = 'tablet' | 'schema' | 'node' | 'pool' | 'state_storage';
16
+ export type LocationFieldCompute = 'tablet' | 'schema' | 'node' | 'pool' | 'pile' | 'state_storage';
17
17
 
18
18
  type ComputeLocationType = Location['compute'];
19
19
 
@@ -25,6 +25,12 @@ const LocationFieldRenderer: Record<
25
25
  pool: (location: ComputeLocationType) => <PoolInfo location={location} key="pool" />,
26
26
  tablet: (location: ComputeLocationType) => <TabletInfo location={location} key="tablet" />,
27
27
  schema: (location: ComputeLocationType) => <SchemaInfo location={location} key="schema" />,
28
+ pile: (location: ComputeLocationType) => (
29
+ <LocationDetails
30
+ fields={[{value: location?.pile?.name, title: i18n('label_pile')}]}
31
+ key="pile"
32
+ />
33
+ ),
28
34
  state_storage: (location: ComputeLocationType) => (
29
35
  <StateStorageInfo location={location} key="state_storage" />
30
36
  ),
@@ -36,7 +42,7 @@ interface ComputeLocationProps {
36
42
  }
37
43
 
38
44
  export function ComputeLocation({location, hiddenFields = []}: ComputeLocationProps) {
39
- const {node, tablet, schema, pool, state_storage: stateStorage} = location ?? {};
45
+ const {node, tablet, schema, pool, pile, state_storage: stateStorage} = location ?? {};
40
46
 
41
47
  const fields = React.useMemo(() => {
42
48
  const fields: LocationFieldCompute[] = [];
@@ -52,11 +58,14 @@ export function ComputeLocation({location, hiddenFields = []}: ComputeLocationPr
52
58
  if (schema) {
53
59
  fields.push('schema');
54
60
  }
61
+ if (pile) {
62
+ fields.push('pile');
63
+ }
55
64
  if (stateStorage) {
56
65
  fields.push('state_storage');
57
66
  }
58
67
  return fields.filter((field) => !hiddenFields.includes(field));
59
- }, [node, pool, tablet, schema, stateStorage, hiddenFields]);
68
+ }, [node, pool, tablet, schema, pile, stateStorage, hiddenFields]);
60
69
 
61
70
  if (!location || isEmpty(location) || fields.length === 0) {
62
71
  return null;
@@ -142,6 +151,7 @@ function StateStorageInfo({location}: ComputeSectionProps) {
142
151
  : undefined,
143
152
  title: i18n('label_state-storage-ring'),
144
153
  },
154
+ {value: stateStorage.pile?.name, title: i18n('label_pile')},
145
155
  ]}
146
156
  />
147
157
  <NodeInfo node={stateStorage.node} />
@@ -33,6 +33,7 @@ export function NodeInfo({node, title}: NodeInfoProps) {
33
33
  {value: nodeLink, title: i18n('label_node-id')},
34
34
  {value: node.host, title: i18n('label_node-host'), copy: node.host},
35
35
  {value: node.port, title: i18n('label_node-port')},
36
+ {value: node.pile?.name, title: i18n('label_pile')},
36
37
  ]}
37
38
  />
38
39
  );
@@ -81,8 +81,9 @@ function GroupInfo({location}: StorageSectionProps) {
81
81
  const {group} = pool ?? {};
82
82
 
83
83
  const ids = group?.id;
84
+ const pileName = group?.pile?.name;
84
85
 
85
- if (!ids?.length) {
86
+ if (!ids?.length && !pileName) {
86
87
  return null;
87
88
  }
88
89
 
@@ -93,6 +94,7 @@ function GroupInfo({location}: StorageSectionProps) {
93
94
  value: ids?.length ? <IdList ids={ids} /> : undefined,
94
95
  title: i18n('label_group-id'),
95
96
  },
97
+ {value: pileName, title: i18n('label_pile')},
96
98
  ]}
97
99
  />
98
100
  );
@@ -14,9 +14,17 @@ import {HealthcheckIssue} from './HealthcheckIssue';
14
14
 
15
15
  interface IssuesProps {
16
16
  issues: IssuesTree[];
17
+ targetIssueId?: string;
18
+ targetLeafIssueId?: string;
19
+ targetLeafIssueRef?: React.Ref<HTMLDivElement>;
17
20
  }
18
21
 
19
- export function Issues({issues}: IssuesProps) {
22
+ export function Issues({
23
+ issues,
24
+ targetIssueId,
25
+ targetLeafIssueId,
26
+ targetLeafIssueRef,
27
+ }: IssuesProps) {
20
28
  const SuccessImage = getIllustration('SuccessOperation');
21
29
 
22
30
  const {view, issuesFilter} = useTenantQueryParams();
@@ -72,8 +80,13 @@ export function Issues({issues}: IssuesProps) {
72
80
  return filteredIssuesCurrentView.map((issue) => (
73
81
  <HealthcheckIssue
74
82
  issue={issue}
75
- key={issue.id + Boolean(issuesFilter)}
83
+ key={`${issue.id}:${Boolean(issuesFilter)}:${
84
+ issue.id === targetLeafIssueId ? targetIssueId : ''
85
+ }`}
76
86
  expanded={Boolean(issuesFilter)}
87
+ targeted={issue.id === targetLeafIssueId}
88
+ targetIssueId={issue.id === targetLeafIssueId ? targetIssueId : undefined}
89
+ targetRef={issue.id === targetLeafIssueId ? targetLeafIssueRef : undefined}
77
90
  />
78
91
  ));
79
92
  }
@@ -44,5 +44,6 @@
44
44
  "label_pdisk-path": "PDisk Path",
45
45
  "label_node_location": "Node Location",
46
46
  "label_peer_location": "Peer Node Location",
47
- "label_state-storage-ring": "Ring"
47
+ "label_state-storage-ring": "Ring",
48
+ "label_pile": "Pile"
48
49
  }
@@ -15,6 +15,8 @@
15
15
 
16
16
  @include mixins.flex-container();
17
17
 
18
+ @include mixins.freeze-nth-column(4, 0, 0, 'right');
19
+
18
20
  .data-table__th {
19
21
  height: 40px;
20
22
  }
package/src/routes.ts CHANGED
@@ -185,8 +185,14 @@ export const getClusterPath = (
185
185
  return createHref(routes.cluster, params, query, options, domain);
186
186
  };
187
187
 
188
- export const getTenantPath = (query: TenantQuery, options?: CreateHrefOptions) => {
189
- return createHref(routes.tenant, undefined, query, options);
188
+ export interface TenantPathOptions extends CreateHrefOptions {
189
+ environment?: string;
190
+ }
191
+
192
+ export const getTenantPath = (query: TenantQuery, options: TenantPathOptions = {}) => {
193
+ const {environment: routeEnvironment, ...createHrefOptions} = options;
194
+
195
+ return createHref(routes.tenant, {environment: routeEnvironment}, query, createHrefOptions);
190
196
  };
191
197
 
192
198
  export const homePageTabSchema = z.enum(['clusters', 'databases']);
@@ -83,12 +83,20 @@ export class ViewerAPI extends BaseYdbAPI {
83
83
  );
84
84
  }
85
85
 
86
- getTenants({clusterName}: {clusterName?: string}, {concurrentId, signal}: AxiosOptions = {}) {
86
+ getTenants(
87
+ {
88
+ clusterName,
89
+ metadataCache,
90
+ storage = true,
91
+ }: {clusterName?: string; metadataCache?: boolean; storage?: boolean},
92
+ {concurrentId, signal}: AxiosOptions = {},
93
+ ) {
87
94
  return this.get<TTenantInfo>(
88
95
  this.getPath('/viewer/json/tenantinfo'),
89
96
  {
90
97
  tablets: false,
91
- storage: true,
98
+ storage,
99
+ metadata_cache: metadataCache,
92
100
  cluster_name: clusterName,
93
101
  },
94
102
  {concurrentId, requestConfig: {signal}},
@@ -177,6 +177,7 @@ export function useClusterBaseInfo() {
177
177
  settings: parseSettingsField(data.settings),
178
178
  links: parseLinksField(data.links),
179
179
 
180
+ isError,
180
181
  isResolved,
181
182
  };
182
183
  }
@@ -201,7 +202,7 @@ export function useClusterProxySettingResolved() {
201
202
 
202
203
  type ClusterBaseInfo = ReturnType<typeof useClusterBaseInfo>;
203
204
 
204
- export type ClusterInfo = Omit<ClusterBaseInfo, 'isResolved'>;
205
+ export type ClusterInfo = Omit<ClusterBaseInfo, 'isError' | 'isResolved'>;
205
206
 
206
207
  const createClusterInfoSelector = createSelector(
207
208
  (clusterName?: string) => clusterName,
@@ -4,4 +4,5 @@ export interface IssuesTree extends IssueLog {
4
4
  reasonsItems?: IssuesTree[];
5
5
  parent?: IssuesTree;
6
6
  categoryForUI: string;
7
+ sourceOrderPath?: number[];
7
8
  }
@@ -23,6 +23,23 @@ export const selfCheckResultToHcStatus: Record<SelfCheckResult, StatusFlag> = {
23
23
  [SelfCheckResult.EMERGENCY]: StatusFlag.RED,
24
24
  };
25
25
 
26
+ export function getHealthcheckIssuePileNames(issue: Pick<IssueLog, 'location'>): string[] {
27
+ const rawNames = [
28
+ issue.location?.storage?.pool?.group?.pile?.name,
29
+ issue.location?.compute?.pile?.name,
30
+ issue.location?.compute?.state_storage?.pile?.name,
31
+ ];
32
+
33
+ const names = new Set<string>();
34
+ for (const rawName of rawNames) {
35
+ if (rawName?.trim()) {
36
+ names.add(rawName);
37
+ }
38
+ }
39
+
40
+ return Array.from(names);
41
+ }
42
+
26
43
  // Issue type prefixes that should be routed to the "storage" tab in the UI,
27
44
  // covering regular disk issues alongside ring/board/state-storage issues.
28
45
  // 'BOARD_' (with underscore) is intentionally narrow to match only BOARD_RING /
@@ -35,6 +52,7 @@ const STORAGE_TAB_PREFIXES = [
35
52
  'BOARD_',
36
53
  'STATE_STORAGE',
37
54
  ];
55
+ const STORAGE_TAB_EXACT_TYPES = new Set(['BOARD']);
38
56
 
39
57
  // Maps a state-storage summary issue type to the corresponding `_RING` type.
40
58
  // Backend reports the summary (BLUE) and the detailed RING/NODE chain as
@@ -50,7 +68,10 @@ export function isStorageRelatedType(type?: string): boolean {
50
68
  if (!type) {
51
69
  return false;
52
70
  }
53
- return STORAGE_TAB_PREFIXES.some((prefix) => type.startsWith(prefix));
71
+ return (
72
+ STORAGE_TAB_EXACT_TYPES.has(type) ||
73
+ STORAGE_TAB_PREFIXES.some((prefix) => type.startsWith(prefix))
74
+ );
54
75
  }
55
76
 
56
77
  export function isComputeRelatedType(type?: string): boolean {
@@ -112,9 +133,18 @@ export function linkStateStorageSummaries(issues: IssueLog[]): IssueLog[] {
112
133
 
113
134
  function getCategoryForUI(issueType?: string) {
114
135
  if (issueType) {
115
- for (const category of uiFactory.healthcheck.issueCategories) {
116
- if (uiFactory.healthcheck.isIssueTypeOfCategory(issueType, category)) {
117
- return category;
136
+ // Compatibility fallback until HealthCheck links every PILE_* summary to its
137
+ // *_RING children through reason. Once those links are guaranteed, the branch
138
+ // category will come from the direct child after the root and this can be removed.
139
+ const issueTypeVariants = issueType.startsWith('PILE_')
140
+ ? [issueType, issueType.slice('PILE_'.length)]
141
+ : [issueType];
142
+
143
+ for (const type of issueTypeVariants) {
144
+ for (const category of uiFactory.healthcheck.issueCategories) {
145
+ if (uiFactory.healthcheck.isIssueTypeOfCategory(type, category)) {
146
+ return category;
147
+ }
118
148
  }
119
149
  }
120
150
  }
@@ -125,7 +155,7 @@ function getCategoryForUI(issueType?: string) {
125
155
  function extendIssue(
126
156
  issue: IssueLog,
127
157
  rootTypeForUI?: string,
128
- fields?: {parent: IssuesTree},
158
+ fields?: {parent?: IssuesTree; sourceOrderPath?: number[]},
129
159
  ): IssuesTree {
130
160
  return {
131
161
  ...issue,
@@ -136,12 +166,14 @@ function extendIssue(
136
166
 
137
167
  export function getLeavesFromTree(issues: IssueLog[], root: IssueLog): IssuesTree[] {
138
168
  const result: IssuesTree[] = [];
169
+ const rootSourceIndex = issues.findIndex((issue) => issue.id === root.id);
170
+ const rootSourceOrderPath = [rootSourceIndex];
139
171
 
140
172
  if (!root.reason || root.reason.length === 0) {
141
- return [extendIssue(root)];
173
+ return [extendIssue(root, undefined, {sourceOrderPath: rootSourceOrderPath})];
142
174
  }
143
175
 
144
- for (const issueId of root.reason) {
176
+ for (const [reasonIndex, issueId] of root.reason.entries()) {
145
177
  const directChild: IssueLog | undefined = issues.find((issue) => issue.id === issueId);
146
178
  if (!directChild) {
147
179
  continue;
@@ -157,9 +189,12 @@ export function getLeavesFromTree(issues: IssueLog[], root: IssueLog): IssuesTre
157
189
  // standalone card (when it has no `reason` and is not referenced
158
190
  // by any other issue) or as a tab in some leaf's breadcrumb. The
159
191
  // leaf (issue without `reason`) is the rightmost tab.
160
- const rootNode = extendIssue(root, directChildCategory);
192
+ const rootNode = extendIssue(root, directChildCategory, {
193
+ sourceOrderPath: rootSourceOrderPath,
194
+ });
161
195
  const initialNode: IssuesTree = extendIssue(directChild, directChildCategory, {
162
196
  parent: rootNode,
197
+ sourceOrderPath: [...rootSourceOrderPath, reasonIndex],
163
198
  });
164
199
  const stack: IssuesTree[] = [initialNode];
165
200
 
@@ -174,12 +209,17 @@ export function getLeavesFromTree(issues: IssueLog[], root: IssueLog): IssuesTre
174
209
  continue;
175
210
  }
176
211
 
177
- for (const reason of currentNode.reason) {
212
+ for (const [childReasonIndex, reason] of currentNode.reason.entries()) {
178
213
  const child: IssueLog | undefined = issues.find((issue) => issue.id === reason);
179
214
  if (!child) {
180
215
  continue;
181
216
  }
182
- stack.push(extendIssue(child, directChildCategory, {parent: currentNode}));
217
+ stack.push(
218
+ extendIssue(child, directChildCategory, {
219
+ parent: currentNode,
220
+ sourceOrderPath: [...(currentNode.sourceOrderPath ?? []), childReasonIndex],
221
+ }),
222
+ );
183
223
  }
184
224
  }
185
225
  }
@@ -1,7 +1,7 @@
1
1
  import type {TTenantInfo} from '../../../types/api/tenant';
2
2
  import {api} from '../api';
3
3
 
4
- import type {PreparedTenant} from './types';
4
+ import type {PreparedTenant, SharedDatabaseTarget} from './types';
5
5
  import {prepareTenants} from './utils';
6
6
 
7
7
  export const tenantsApi = api.injectEndpoints({
@@ -49,6 +49,60 @@ export const tenantsApi = api.injectEndpoints({
49
49
  },
50
50
  providesTags: ['All'],
51
51
  }),
52
+ getSharedDatabase: build.query<
53
+ SharedDatabaseTarget | null,
54
+ {
55
+ backend?: string;
56
+ clusterName?: string;
57
+ database?: string;
58
+ environmentName?: string;
59
+ isMonitoringAllowed: boolean;
60
+ needNodeIds?: boolean;
61
+ resourceId: string;
62
+ }
63
+ >({
64
+ queryFn: async (
65
+ {clusterName, database, isMonitoringAllowed, needNodeIds, resourceId},
66
+ {signal},
67
+ ) => {
68
+ if (isMonitoringAllowed && database && !needNodeIds) {
69
+ try {
70
+ const data = await window.api.viewer.getTabletDescribe(
71
+ {PathId: resourceId},
72
+ database,
73
+ {signal},
74
+ );
75
+ const sharedDatabaseName = data?.Path?.trim();
76
+
77
+ if (sharedDatabaseName) {
78
+ return {data: {name: sharedDatabaseName}};
79
+ }
80
+ } catch (error) {
81
+ if (signal.aborted) {
82
+ return {error};
83
+ }
84
+ }
85
+ }
86
+
87
+ try {
88
+ const response = await window.api.viewer.getTenants(
89
+ {clusterName, metadataCache: false, storage: false},
90
+ {signal},
91
+ );
92
+ const sharedDatabase = response.TenantInfo?.find(({Id}) => Id === resourceId);
93
+ const sharedDatabaseName = sharedDatabase?.Name?.trim();
94
+
95
+ return {
96
+ data: sharedDatabaseName
97
+ ? {name: sharedDatabaseName, nodeIds: sharedDatabase?.NodeIds}
98
+ : null,
99
+ };
100
+ } catch (error) {
101
+ return {error};
102
+ }
103
+ },
104
+ providesTags: ['All'],
105
+ }),
52
106
  }),
53
107
  overrideExisting: 'throw',
54
108
  });
@@ -14,4 +14,9 @@ export interface PreparedTenant extends TTenant {
14
14
  groupsCount: number;
15
15
  }
16
16
 
17
+ export interface SharedDatabaseTarget {
18
+ name: string;
19
+ nodeIds?: number[];
20
+ }
21
+
17
22
  export type MetricStatus = ValueOf<typeof METRIC_STATUS>;
@@ -17,10 +17,15 @@ export enum StatusFlag {
17
17
  RED = 'RED',
18
18
  }
19
19
 
20
+ interface LocationBridgePile {
21
+ name?: string;
22
+ }
23
+
20
24
  interface LocationNode {
21
25
  id?: number;
22
26
  host?: string;
23
27
  port?: number;
28
+ pile?: LocationBridgePile;
24
29
  }
25
30
 
26
31
  interface LocationStoragePDisk {
@@ -36,6 +41,7 @@ interface LocationStorageVDisk {
36
41
  interface LocationStorageGroup {
37
42
  id?: string[];
38
43
  vdisk?: LocationStorageVDisk;
44
+ pile?: LocationBridgePile;
39
45
  }
40
46
 
41
47
  interface LocationStoragePool {
@@ -66,6 +72,7 @@ interface LocationComputeSchema {
66
72
  interface LocationComputeStateStorage {
67
73
  node?: LocationNode;
68
74
  ring?: number;
75
+ pile?: LocationBridgePile;
69
76
  }
70
77
 
71
78
  interface LocationCompute {
@@ -73,6 +80,7 @@ interface LocationCompute {
73
80
  pool?: LocationComputePool;
74
81
  tablet?: LocationComputeTablet;
75
82
  schema?: LocationComputeSchema;
83
+ pile?: LocationBridgePile;
76
84
  state_storage?: LocationComputeStateStorage;
77
85
  }
78
86