ydb-embedded-ui 22.4.0 → 22.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 (266) hide show
  1. package/dist/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.d.ts +3 -2
  2. package/dist/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.js +19 -4
  3. package/dist/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.js.map +1 -1
  4. package/dist/components/CopyableValue/CopyableValue.d.ts +10 -0
  5. package/dist/components/CopyableValue/CopyableValue.js +10 -0
  6. package/dist/components/CopyableValue/CopyableValue.js.map +1 -0
  7. package/dist/components/CopyableValue/CopyableValue.scss +16 -0
  8. package/dist/components/CopyableValue/i18n/en.json +3 -0
  9. package/dist/components/CopyableValue/i18n/index.d.ts +2 -0
  10. package/dist/components/CopyableValue/i18n/index.js +6 -0
  11. package/dist/components/CopyableValue/i18n/index.js.map +1 -0
  12. package/dist/components/CriticalActionDialog/CriticalActionDialog.d.ts +2 -1
  13. package/dist/components/CriticalActionDialog/CriticalActionDialog.js +2 -1
  14. package/dist/components/CriticalActionDialog/CriticalActionDialog.js.map +1 -1
  15. package/dist/components/DiskCapacityInfo/DiskCapacityInfo.d.ts +1 -21
  16. package/dist/components/DiskCapacityInfo/DiskCapacityInfo.js +40 -126
  17. package/dist/components/DiskCapacityInfo/DiskCapacityInfo.js.map +1 -1
  18. package/dist/components/DiskCapacityInfo/i18n/en.json +0 -1
  19. package/dist/components/DiskCapacityInfo/i18n/index.d.ts +1 -1
  20. package/dist/components/EntityStatus/EntityStatus.d.ts +18 -6
  21. package/dist/components/EntityStatus/EntityStatus.js +5 -1
  22. package/dist/components/EntityStatus/EntityStatus.js.map +1 -1
  23. package/dist/components/EntityStatus/EntityStatus.scss +5 -0
  24. package/dist/components/EvictVDiskButton/EvictVDiskButton.d.ts +5 -2
  25. package/dist/components/EvictVDiskButton/EvictVDiskButton.js +3 -2
  26. package/dist/components/EvictVDiskButton/EvictVDiskButton.js.map +1 -1
  27. package/dist/components/EvictVDiskButton/i18n/en.json +2 -2
  28. package/dist/components/FullNodeViewer/FullNodeViewer.js +4 -7
  29. package/dist/components/FullNodeViewer/FullNodeViewer.js.map +1 -1
  30. package/dist/components/FullNodeViewer/i18n/index.d.ts +1 -1
  31. package/dist/components/HoverPopup/HoverPopup.d.ts +1 -0
  32. package/dist/components/HoverPopup/HoverPopup.js +87 -16
  33. package/dist/components/HoverPopup/HoverPopup.js.map +1 -1
  34. package/dist/components/MemoryViewer/MemoryViewer.d.ts +3 -1
  35. package/dist/components/MemoryViewer/MemoryViewer.js +25 -16
  36. package/dist/components/MemoryViewer/MemoryViewer.js.map +1 -1
  37. package/dist/components/MemoryViewer/i18n/en.json +4 -3
  38. package/dist/components/MemoryViewer/i18n/index.d.ts +1 -1
  39. package/dist/components/MemoryViewer/utils.d.ts +4 -2
  40. package/dist/components/MemoryViewer/utils.js +11 -11
  41. package/dist/components/MemoryViewer/utils.js.map +1 -1
  42. package/dist/components/PDiskInfo/PDiskInfo.d.ts +4 -9
  43. package/dist/components/PDiskInfo/PDiskInfo.js +32 -144
  44. package/dist/components/PDiskInfo/PDiskInfo.js.map +1 -1
  45. package/dist/components/PDiskInfo/PDiskInfo.scss +15 -0
  46. package/dist/components/PDiskInfo/PDiskLogSize.js +1 -1
  47. package/dist/components/PDiskInfo/PDiskLogSize.js.map +1 -1
  48. package/dist/components/PDiskInfo/i18n/en.json +6 -3
  49. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  50. package/dist/components/PDiskInfo/statuses.js +12 -3
  51. package/dist/components/PDiskInfo/statuses.js.map +1 -1
  52. package/dist/components/Search/Search.js +3 -1
  53. package/dist/components/Search/Search.js.map +1 -1
  54. package/dist/components/StorageGroupInfo/StorageGroupInfo.js +4 -9
  55. package/dist/components/StorageGroupInfo/StorageGroupInfo.js.map +1 -1
  56. package/dist/components/StorageGroupInfo/i18n/index.d.ts +1 -1
  57. package/dist/components/TenantNameWrapper/i18n/index.d.ts +1 -1
  58. package/dist/components/VDisk/VDisk.js +2 -2
  59. package/dist/components/VDisk/VDisk.js.map +1 -1
  60. package/dist/components/VDiskInfo/VDiskInfo.d.ts +3 -7
  61. package/dist/components/VDiskInfo/VDiskInfo.js +57 -235
  62. package/dist/components/VDiskInfo/VDiskInfo.js.map +1 -1
  63. package/dist/components/VDiskInfo/VDiskInfo.scss +19 -3
  64. package/dist/components/VDiskInfo/getVDiskDetails.d.ts +7 -1
  65. package/dist/components/VDiskInfo/getVDiskDetails.js +14 -1
  66. package/dist/components/VDiskInfo/getVDiskDetails.js.map +1 -1
  67. package/dist/components/VDiskInfo/i18n/en.json +4 -13
  68. package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
  69. package/dist/components/VDiskPopup/VDiskPopup.d.ts +2 -1
  70. package/dist/components/VDiskPopup/VDiskPopup.js +10 -26
  71. package/dist/components/VDiskPopup/VDiskPopup.js.map +1 -1
  72. package/dist/components/VDiskPopup/i18n/en.json +0 -2
  73. package/dist/components/VDiskPopup/i18n/index.d.ts +1 -1
  74. package/dist/components/VDiskStatus/VDiskReplicationStatus.js +1 -1
  75. package/dist/components/VDiskStatus/VDiskReplicationStatus.js.map +1 -1
  76. package/dist/components/VDiskStatus/VDiskStatus.js +2 -2
  77. package/dist/components/VDiskStatus/VDiskStatus.js.map +1 -1
  78. package/dist/components/VDiskStatus/VDiskStatus.scss +4 -0
  79. package/dist/components/VDiskStatus/statuses.js +2 -2
  80. package/dist/components/VDiskStatus/statuses.js.map +1 -1
  81. package/dist/components/nodesColumns/columns.d.ts +1 -0
  82. package/dist/components/nodesColumns/columns.js +13 -6
  83. package/dist/components/nodesColumns/columns.js.map +1 -1
  84. package/dist/containers/Clusters/Clusters.js +35 -6
  85. package/dist/containers/Clusters/Clusters.js.map +1 -1
  86. package/dist/containers/Clusters/Clusters.scss +24 -6
  87. package/dist/containers/Clusters/columns.js +9 -4
  88. package/dist/containers/Clusters/columns.js.map +1 -1
  89. package/dist/containers/Clusters/i18n/en.json +6 -0
  90. package/dist/containers/Clusters/i18n/index.d.ts +1 -1
  91. package/dist/containers/Header/Header.js +8 -0
  92. package/dist/containers/Header/Header.js.map +1 -1
  93. package/dist/containers/Header/HeaderLeftControls.js +3 -1
  94. package/dist/containers/Header/HeaderLeftControls.js.map +1 -1
  95. package/dist/containers/Header/PDiskLeftControls.d.ts +1 -0
  96. package/dist/containers/Header/PDiskLeftControls.js +24 -0
  97. package/dist/containers/Header/PDiskLeftControls.js.map +1 -0
  98. package/dist/containers/Header/VDiskLeftControls.d.ts +1 -0
  99. package/dist/containers/Header/VDiskLeftControls.js +23 -0
  100. package/dist/containers/Header/VDiskLeftControls.js.map +1 -0
  101. package/dist/containers/Header/breadcrumbs.js +5 -2
  102. package/dist/containers/Header/breadcrumbs.js.map +1 -1
  103. package/dist/containers/Header/i18n/en.json +4 -1
  104. package/dist/containers/Header/i18n/index.d.ts +1 -1
  105. package/dist/containers/Nodes/NodesControls/NodesControls.js +1 -1
  106. package/dist/containers/Nodes/NodesControls/NodesControls.js.map +1 -1
  107. package/dist/containers/Nodes/PaginatedNodes/GroupedNodesComponent.js +5 -3
  108. package/dist/containers/Nodes/PaginatedNodes/GroupedNodesComponent.js.map +1 -1
  109. package/dist/containers/Nodes/PaginatedNodes/NodesComponent.js +4 -2
  110. package/dist/containers/Nodes/PaginatedNodes/NodesComponent.js.map +1 -1
  111. package/dist/containers/Nodes/useDebouncedSearch.d.ts +2 -0
  112. package/dist/containers/Nodes/useDebouncedSearch.js +11 -0
  113. package/dist/containers/Nodes/useDebouncedSearch.js.map +1 -0
  114. package/dist/containers/PDiskPage/PDiskPage.js +30 -31
  115. package/dist/containers/PDiskPage/PDiskPage.js.map +1 -1
  116. package/dist/containers/PDiskPage/PDiskPage.scss +22 -11
  117. package/dist/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.js +3 -3
  118. package/dist/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.js.map +1 -1
  119. package/dist/containers/PDiskPage/PDiskStorageDetails.d.ts +6 -0
  120. package/dist/containers/PDiskPage/PDiskStorageDetails.js +28 -0
  121. package/dist/containers/PDiskPage/PDiskStorageDetails.js.map +1 -0
  122. package/dist/containers/PDiskPage/PDiskStorageDetails.scss +51 -0
  123. package/dist/containers/PDiskPage/i18n/en.json +2 -0
  124. package/dist/containers/PDiskPage/i18n/index.d.ts +1 -1
  125. package/dist/containers/Storage/PaginatedStorageGroupsTable/columns/i18n/index.d.ts +1 -1
  126. package/dist/containers/Storage/StorageExpertModePanel/constants.d.ts +1 -1
  127. package/dist/containers/Storage/StorageExpertModePanel/i18n/index.d.ts +1 -1
  128. package/dist/containers/Storage/useStorageQueryParams.d.ts +4 -4
  129. package/dist/containers/StorageGroupPage/StorageGroupPage.js +7 -5
  130. package/dist/containers/StorageGroupPage/StorageGroupPage.js.map +1 -1
  131. package/dist/containers/StorageGroupPage/StorageGroupPage.scss +10 -0
  132. package/dist/containers/StorageGroupPage/i18n/en.json +0 -1
  133. package/dist/containers/StorageGroupPage/i18n/index.d.ts +1 -1
  134. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/ManagePartitioningDialog/i18n/index.d.ts +1 -1
  135. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +5 -12
  136. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js.map +1 -1
  137. package/dist/containers/Tenant/Diagnostics/TopQueries/columns/i18n/index.d.ts +1 -1
  138. package/dist/containers/Tenant/Healthcheck/Healthcheck.scss +6 -1
  139. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js +2 -2
  140. package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js.map +1 -1
  141. package/dist/containers/Tenant/TenantDrawerHealthcheck.js +10 -2
  142. package/dist/containers/Tenant/TenantDrawerHealthcheck.js.map +1 -1
  143. package/dist/containers/VDiskPage/VDiskPage.js +33 -33
  144. package/dist/containers/VDiskPage/VDiskPage.js.map +1 -1
  145. package/dist/containers/VDiskPage/VDiskPage.scss +21 -8
  146. package/dist/containers/VDiskPage/VDiskStorageDetails.js +27 -29
  147. package/dist/containers/VDiskPage/VDiskStorageDetails.js.map +1 -1
  148. package/dist/containers/VDiskPage/VDiskStorageDetails.scss +24 -93
  149. package/dist/containers/VDiskPage/i18n/en.json +3 -12
  150. package/dist/containers/VDiskPage/i18n/index.d.ts +1 -1
  151. package/dist/store/reducers/cluster/parseFields.js +1 -0
  152. package/dist/store/reducers/cluster/parseFields.js.map +1 -1
  153. package/dist/store/reducers/clusters/clusters.js +1 -0
  154. package/dist/store/reducers/clusters/clusters.js.map +1 -1
  155. package/dist/store/reducers/clusters/selectors.d.ts +1 -0
  156. package/dist/store/reducers/clusters/selectors.js +4 -1
  157. package/dist/store/reducers/clusters/selectors.js.map +1 -1
  158. package/dist/store/reducers/clusters/types.d.ts +2 -0
  159. package/dist/store/reducers/storage/prepareGroupsDisks.d.ts +1 -1
  160. package/dist/store/reducers/storage/prepareGroupsDisks.js +2 -1
  161. package/dist/store/reducers/storage/prepareGroupsDisks.js.map +1 -1
  162. package/dist/store/reducers/storage/utils.js +10 -3
  163. package/dist/store/reducers/storage/utils.js.map +1 -1
  164. package/dist/store/reducers/vdisk/types.d.ts +1 -1
  165. package/dist/store/reducers/vdisk/utils.js +5 -5
  166. package/dist/store/reducers/vdisk/utils.js.map +1 -1
  167. package/dist/types/api/meta.d.ts +1 -0
  168. package/dist/types/api/nodesList.d.ts +2 -0
  169. package/dist/utils/hooks/useDatabaseHealthcheckAvailable.d.ts +2 -0
  170. package/dist/utils/hooks/useDatabaseHealthcheckAvailable.js +6 -0
  171. package/dist/utils/hooks/useDatabaseHealthcheckAvailable.js.map +1 -0
  172. package/dist/utils/memory.d.ts +5 -0
  173. package/dist/utils/memory.js +9 -0
  174. package/dist/utils/memory.js.map +1 -0
  175. package/dist/utils/nodes.js +3 -3
  176. package/dist/utils/nodes.js.map +1 -1
  177. package/dist/utils/storageMetrics.d.ts +1 -0
  178. package/dist/utils/storageMetrics.js +8 -0
  179. package/dist/utils/storageMetrics.js.map +1 -1
  180. package/package.json +1 -1
  181. package/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx +22 -3
  182. package/src/components/CopyableValue/CopyableValue.scss +16 -0
  183. package/src/components/CopyableValue/CopyableValue.tsx +40 -0
  184. package/src/components/CopyableValue/i18n/en.json +3 -0
  185. package/src/components/CopyableValue/i18n/index.ts +8 -0
  186. package/src/components/CriticalActionDialog/CriticalActionDialog.tsx +3 -0
  187. package/src/components/DiskCapacityInfo/DiskCapacityInfo.tsx +40 -185
  188. package/src/components/DiskCapacityInfo/i18n/en.json +0 -1
  189. package/src/components/EntityStatus/EntityStatus.scss +5 -0
  190. package/src/components/EntityStatus/EntityStatus.tsx +38 -5
  191. package/src/components/EvictVDiskButton/EvictVDiskButton.tsx +10 -2
  192. package/src/components/EvictVDiskButton/i18n/en.json +2 -2
  193. package/src/components/FullNodeViewer/FullNodeViewer.tsx +4 -9
  194. package/src/components/HoverPopup/HoverPopup.tsx +95 -19
  195. package/src/components/MemoryViewer/MemoryViewer.tsx +30 -17
  196. package/src/components/MemoryViewer/i18n/en.json +4 -3
  197. package/src/components/MemoryViewer/utils.ts +19 -12
  198. package/src/components/PDiskInfo/PDiskInfo.scss +15 -0
  199. package/src/components/PDiskInfo/PDiskInfo.tsx +51 -243
  200. package/src/components/PDiskInfo/PDiskLogSize.tsx +12 -8
  201. package/src/components/PDiskInfo/i18n/en.json +6 -3
  202. package/src/components/PDiskInfo/statuses.ts +16 -3
  203. package/src/components/Search/Search.tsx +5 -2
  204. package/src/components/StorageGroupInfo/StorageGroupInfo.tsx +8 -13
  205. package/src/components/VDisk/VDisk.tsx +3 -2
  206. package/src/components/VDiskInfo/VDiskInfo.scss +19 -3
  207. package/src/components/VDiskInfo/VDiskInfo.tsx +119 -341
  208. package/src/components/VDiskInfo/getVDiskDetails.tsx +24 -2
  209. package/src/components/VDiskInfo/i18n/en.json +4 -13
  210. package/src/components/VDiskPopup/VDiskPopup.tsx +16 -24
  211. package/src/components/VDiskPopup/i18n/en.json +0 -2
  212. package/src/components/VDiskStatus/VDiskReplicationStatus.tsx +1 -1
  213. package/src/components/VDiskStatus/VDiskStatus.scss +4 -0
  214. package/src/components/VDiskStatus/VDiskStatus.tsx +13 -2
  215. package/src/components/VDiskStatus/statuses.ts +2 -2
  216. package/src/components/nodesColumns/columns.tsx +26 -12
  217. package/src/containers/Clusters/Clusters.scss +24 -6
  218. package/src/containers/Clusters/Clusters.tsx +74 -5
  219. package/src/containers/Clusters/columns.tsx +36 -6
  220. package/src/containers/Clusters/i18n/en.json +6 -0
  221. package/src/containers/Header/Header.tsx +8 -0
  222. package/src/containers/Header/HeaderLeftControls.tsx +3 -1
  223. package/src/containers/Header/PDiskLeftControls.tsx +41 -0
  224. package/src/containers/Header/VDiskLeftControls.tsx +40 -0
  225. package/src/containers/Header/breadcrumbs.tsx +4 -2
  226. package/src/containers/Header/i18n/en.json +4 -1
  227. package/src/containers/Nodes/NodesControls/NodesControls.tsx +1 -0
  228. package/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx +5 -3
  229. package/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx +4 -2
  230. package/src/containers/Nodes/useDebouncedSearch.ts +13 -0
  231. package/src/containers/PDiskPage/PDiskPage.scss +22 -11
  232. package/src/containers/PDiskPage/PDiskPage.tsx +88 -56
  233. package/src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx +3 -9
  234. package/src/containers/PDiskPage/PDiskStorageDetails.scss +51 -0
  235. package/src/containers/PDiskPage/PDiskStorageDetails.tsx +64 -0
  236. package/src/containers/PDiskPage/i18n/en.json +2 -0
  237. package/src/containers/StorageGroupPage/StorageGroupPage.scss +10 -0
  238. package/src/containers/StorageGroupPage/StorageGroupPage.tsx +37 -28
  239. package/src/containers/StorageGroupPage/i18n/en.json +0 -1
  240. package/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +5 -32
  241. package/src/containers/Tenant/Healthcheck/Healthcheck.scss +6 -1
  242. package/src/containers/Tenant/Healthcheck/components/HealthcheckIssue.tsx +18 -8
  243. package/src/containers/Tenant/TenantDrawerHealthcheck.tsx +14 -1
  244. package/src/containers/VDiskPage/VDiskPage.scss +21 -8
  245. package/src/containers/VDiskPage/VDiskPage.tsx +103 -64
  246. package/src/containers/VDiskPage/VDiskStorageDetails.scss +24 -93
  247. package/src/containers/VDiskPage/VDiskStorageDetails.tsx +50 -158
  248. package/src/containers/VDiskPage/i18n/en.json +3 -14
  249. package/src/store/reducers/cluster/parseFields.ts +1 -0
  250. package/src/store/reducers/clusters/clusters.ts +1 -0
  251. package/src/store/reducers/clusters/selectors.ts +4 -0
  252. package/src/store/reducers/clusters/types.ts +3 -0
  253. package/src/store/reducers/storage/prepareGroupsDisks.ts +2 -0
  254. package/src/store/reducers/storage/utils.ts +13 -3
  255. package/src/store/reducers/vdisk/types.ts +2 -1
  256. package/src/store/reducers/vdisk/utils.ts +4 -5
  257. package/src/types/api/meta.ts +1 -0
  258. package/src/types/api/nodesList.ts +3 -0
  259. package/src/utils/hooks/useDatabaseHealthcheckAvailable.ts +9 -0
  260. package/src/utils/memory.ts +16 -0
  261. package/src/utils/nodes.ts +3 -2
  262. package/src/utils/storageMetrics.ts +11 -0
  263. package/dist/components/VDiskInfo/getVDiskCapacityColumns.d.ts +0 -11
  264. package/dist/components/VDiskInfo/getVDiskCapacityColumns.js +0 -52
  265. package/dist/components/VDiskInfo/getVDiskCapacityColumns.js.map +0 -1
  266. package/src/components/VDiskInfo/getVDiskCapacityColumns.tsx +0 -83
@@ -1,10 +1,5 @@
1
- import React from 'react';
2
-
3
- import {Label} from '@gravity-ui/uikit';
4
-
5
1
  import type {PreparedStorageGroup} from '../../store/reducers/storage/types';
6
- import {isCapacityAlert} from '../../types/api/enums';
7
- import {getCapacityAlertTheme, normalizeCapacityAlert} from '../../utils/capacityAlerts';
2
+ import {normalizeCapacityAlert} from '../../utils/capacityAlerts';
8
3
  import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
9
4
  import type {DiskDetailItem} from '../../utils/disks/diskInfo/getDiskLocationItems';
10
5
  import type {PreparedPDisk, PreparedVDisk} from '../../utils/disks/types';
@@ -15,11 +10,8 @@ import {
15
10
  formatStorageMetricPair,
16
11
  } from '../../utils/storageMetrics';
17
12
  import {DiskCapacityAlertLabel} from '../DiskStatus/DiskStatus';
18
- import type {InfoViewerItem} from '../InfoViewer';
19
13
  import {pDiskInfoKeyset} from '../PDiskInfo/i18n';
20
- import {TitleWithHelpMark} from '../TitleWithHelpmark/TitleWithHelpmark';
21
14
  import {vDiskInfoKeyset} from '../VDiskInfo/i18n';
22
- import type {YDBDefinitionListItem} from '../YDBDefinitionList/YDBDefinitionList';
23
15
  import {
24
16
  CAPACITY_CONFIGURATION_HELP_TEXT,
25
17
  CAPACITY_METRICS_COLUMN_TITLES,
@@ -29,139 +21,6 @@ import {formatCapacityUnitCount} from '../capacityMetricsColumns/formatters';
29
21
 
30
22
  import i18n from './i18n';
31
23
 
32
- export interface DiskCapacityInfoItem {
33
- id: string;
34
- title: string;
35
- value: React.ReactNode;
36
- note?: string;
37
- }
38
-
39
- function getCapacityAlertValue(value: unknown): React.ReactNode {
40
- return normalizeCapacityAlert(value) ?? EMPTY_DATA_PLACEHOLDER;
41
- }
42
-
43
- function getAlertAwareUsageValue(value: string, capacityAlertValue: unknown): React.ReactNode {
44
- if (value === EMPTY_DATA_PLACEHOLDER) {
45
- return value;
46
- }
47
-
48
- const capacityAlert = normalizeCapacityAlert(capacityAlertValue);
49
- const theme = isCapacityAlert(capacityAlert) ? getCapacityAlertTheme(capacityAlert) : 'normal';
50
-
51
- return <Label theme={theme}>{value}</Label>;
52
- }
53
-
54
- export function getVDiskCapacityInfoItems(
55
- data: PreparedVDisk | undefined,
56
- {withRawUsage}: {withRawUsage: boolean},
57
- ): DiskCapacityInfoItem[] {
58
- const sizeData = data?.WhiteboardSize ?? data?.DisplaySize ?? data;
59
- const items: DiskCapacityInfoItem[] = [
60
- {
61
- id: 'size',
62
- title: i18n('field_size'),
63
- value: formatStorageMetricPair(sizeData?.AllocatedSize, sizeData?.SizeLimit),
64
- },
65
- {
66
- id: 'vdisk-slot-usage',
67
- title: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskSlotUsage,
68
- value: getAlertAwareUsageValue(
69
- formatMetricPercent(data?.VDiskSlotUsage),
70
- data?.CapacityAlert,
71
- ),
72
- note: CAPACITY_METRICS_HELP_TEXT.MaxVDiskSlotUsage,
73
- },
74
- ];
75
-
76
- if (withRawUsage) {
77
- items.push({
78
- id: 'vdisk-raw-usage',
79
- title: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskRawUsage,
80
- value: formatMetricPercent(data?.VDiskRawUsage),
81
- note: CAPACITY_METRICS_HELP_TEXT.MaxVDiskRawUsage,
82
- });
83
- }
84
-
85
- items.push(
86
- {
87
- id: 'group-size-in-units',
88
- title: i18n('field_group-size-in-units'),
89
- value: formatCapacityUnitCount(data?.GroupSizeInUnits),
90
- note: CAPACITY_CONFIGURATION_HELP_TEXT.GroupSizeInUnits,
91
- },
92
- {
93
- id: 'capacity-alert',
94
- title: CAPACITY_METRICS_COLUMN_TITLES.CapacityAlert,
95
- value: getCapacityAlertValue(data?.CapacityAlert),
96
- note: CAPACITY_METRICS_HELP_TEXT.CapacityAlert,
97
- },
98
- );
99
-
100
- return items;
101
- }
102
-
103
- export function getPDiskCapacityInfoItems(
104
- data: PreparedPDisk | undefined,
105
- {
106
- withUsage,
107
- withCapacityAlert,
108
- fixedDecimalPlaces,
109
- useWhiteboardSize = true,
110
- }: {
111
- withUsage: boolean;
112
- withCapacityAlert: boolean;
113
- fixedDecimalPlaces?: number;
114
- useWhiteboardSize?: boolean;
115
- },
116
- ): DiskCapacityInfoItem[] {
117
- const sizeData = useWhiteboardSize ? (data?.WhiteboardSize ?? data) : data;
118
- const items: DiskCapacityInfoItem[] = [
119
- {
120
- id: 'space',
121
- title: i18n('field_space'),
122
- value: formatStorageMetricPair(
123
- sizeData?.AllocatedSize,
124
- sizeData?.TotalSize,
125
- fixedDecimalPlaces,
126
- ),
127
- },
128
- ];
129
-
130
- if (withUsage) {
131
- items.push({
132
- id: 'pdisk-usage',
133
- title: CAPACITY_METRICS_COLUMN_TITLES.MaxPDiskUsage,
134
- value: formatMetricPercent(data?.PDiskUsage, fixedDecimalPlaces),
135
- note: CAPACITY_METRICS_HELP_TEXT.MaxPDiskUsage,
136
- });
137
- }
138
-
139
- items.push(
140
- {
141
- id: 'slots',
142
- title: i18n('field_slots'),
143
- value: formatMetricCountPair(data?.NumActiveSlots, data?.ExpectedSlotCount),
144
- },
145
- {
146
- id: 'slot-size-in-units',
147
- title: i18n('field_slot-size-in-units'),
148
- value: formatCapacityUnitCount(data?.SlotSizeInUnits),
149
- note: CAPACITY_CONFIGURATION_HELP_TEXT.SlotSizeInUnits,
150
- },
151
- );
152
-
153
- if (withCapacityAlert) {
154
- items.push({
155
- id: 'capacity-alert',
156
- title: CAPACITY_METRICS_COLUMN_TITLES.CapacityAlert,
157
- value: getCapacityAlertValue(data?.PDiskCapacityAlert),
158
- note: CAPACITY_METRICS_HELP_TEXT.CapacityAlert,
159
- });
160
- }
161
-
162
- return items;
163
- }
164
-
165
24
  export function getVDiskCapacityItems(
166
25
  data: PreparedVDisk,
167
26
  {capacityMetricsEnabled}: {capacityMetricsEnabled: boolean},
@@ -206,42 +65,49 @@ export function getPDiskCapacityItems(
206
65
  data: PreparedPDisk,
207
66
  {useWhiteboardSize = true}: {useWhiteboardSize?: boolean} = {},
208
67
  ): DiskDetailItem[] {
209
- const fieldOrder = ['slot-size-in-units', 'space', 'capacity-alert', 'pdisk-usage', 'slots'];
210
- const capacityItems = getPDiskCapacityInfoItems(data, {
211
- withUsage: true,
212
- withCapacityAlert: true,
213
- fixedDecimalPlaces: 2,
214
- useWhiteboardSize,
215
- });
216
- const items: DiskDetailItem[] = [];
217
- for (const id of fieldOrder) {
218
- const field = capacityItems.find((item) => item.id === id);
219
- if (field) {
220
- items.push({
221
- id,
222
- name: field.title,
223
- content:
224
- id === 'capacity-alert' ? (
225
- <DiskCapacityAlertLabel value={data.PDiskCapacityAlert} />
226
- ) : (
227
- field.value
228
- ),
229
- note: id === 'slots' ? pDiskInfoKeyset('context_slots') : field.note,
230
- });
231
- }
232
- }
233
- return items;
68
+ const size = useWhiteboardSize ? (data.WhiteboardSize ?? data) : data;
69
+ return [
70
+ {
71
+ id: 'slot-size-in-units',
72
+ name: i18n('field_slot-size-in-units'),
73
+ content: formatCapacityUnitCount(data.SlotSizeInUnits),
74
+ note: CAPACITY_CONFIGURATION_HELP_TEXT.SlotSizeInUnits,
75
+ },
76
+ {
77
+ id: 'space',
78
+ name: i18n('field_space'),
79
+ content: formatStorageMetricPair(size.AllocatedSize, size.TotalSize, 2),
80
+ },
81
+ {
82
+ id: 'capacity-alert',
83
+ name: CAPACITY_METRICS_COLUMN_TITLES.CapacityAlert,
84
+ content: <DiskCapacityAlertLabel value={data.PDiskCapacityAlert} />,
85
+ note: CAPACITY_METRICS_HELP_TEXT.CapacityAlert,
86
+ },
87
+ {
88
+ id: 'pdisk-usage',
89
+ name: CAPACITY_METRICS_COLUMN_TITLES.MaxPDiskUsage,
90
+ content: formatMetricPercent(data.PDiskUsage, 2),
91
+ note: CAPACITY_METRICS_HELP_TEXT.MaxPDiskUsage,
92
+ },
93
+ {
94
+ id: 'slots',
95
+ name: i18n('field_slots'),
96
+ content: formatMetricCountPair(data.NumActiveSlots, data.ExpectedSlotCount),
97
+ note: pDiskInfoKeyset('context_slots'),
98
+ },
99
+ ];
234
100
  }
235
101
 
236
102
  export function getStorageGroupCapacityInfoItems(
237
103
  data: PreparedStorageGroup | undefined,
238
- ): DiskCapacityInfoItem[] {
104
+ ): DiskDetailItem[] {
239
105
  const capacityAlert = normalizeCapacityAlert(data?.CapacityAlert);
240
106
  return [
241
107
  {
242
108
  id: 'capacity-alert',
243
- title: CAPACITY_METRICS_COLUMN_TITLES.CapacityAlert,
244
- value: capacityAlert ? (
109
+ name: CAPACITY_METRICS_COLUMN_TITLES.CapacityAlert,
110
+ content: capacityAlert ? (
245
111
  <DiskCapacityAlertLabel value={capacityAlert} />
246
112
  ) : (
247
113
  EMPTY_DATA_PLACEHOLDER
@@ -250,26 +116,15 @@ export function getStorageGroupCapacityInfoItems(
250
116
  },
251
117
  {
252
118
  id: 'vdisk-slot-usage',
253
- title: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskSlotUsage,
254
- value: formatNormalizedMetricPercent(data?.MaxVDiskSlotUsage),
119
+ name: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskSlotUsage,
120
+ content: formatNormalizedMetricPercent(data?.MaxVDiskSlotUsage),
255
121
  note: CAPACITY_METRICS_HELP_TEXT.MaxVDiskSlotUsage,
256
122
  },
257
123
  {
258
124
  id: 'vdisk-raw-usage',
259
- title: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskRawUsage,
260
- value: formatNormalizedMetricPercent(data?.MaxVDiskRawUsage),
125
+ name: CAPACITY_METRICS_COLUMN_TITLES.MaxVDiskRawUsage,
126
+ content: formatNormalizedMetricPercent(data?.MaxVDiskRawUsage),
261
127
  note: CAPACITY_METRICS_HELP_TEXT.MaxVDiskRawUsage,
262
128
  },
263
129
  ];
264
130
  }
265
-
266
- export function toDefinitionListItems(items: DiskCapacityInfoItem[]): YDBDefinitionListItem[] {
267
- return items.map(({title, value, note}) => ({name: title, content: value, note}));
268
- }
269
-
270
- export function toInfoViewerItems(items: DiskCapacityInfoItem[]): InfoViewerItem[] {
271
- return items.map(({title, value, note}) => ({
272
- label: note ? <TitleWithHelpMark header={title} note={note} /> : title,
273
- value,
274
- }));
275
- }
@@ -1,5 +1,4 @@
1
1
  {
2
- "field_size": "Size",
3
2
  "field_space": "Space",
4
3
  "field_slots": "Slots",
5
4
  "field_group-size-in-units": "Group Size In Units",
@@ -1,6 +1,11 @@
1
1
  @use '../../styles/mixins.scss';
2
2
 
3
3
  .ydb-entity-status {
4
+ &__compact {
5
+ display: inline-flex;
6
+ flex-shrink: 0;
7
+ }
8
+
4
9
  &__note {
5
10
  color: inherit;
6
11
  }
@@ -44,31 +44,64 @@ const EFlagToStatusName: Record<EFlag, string> = {
44
44
  },
45
45
  };
46
46
 
47
- interface EntityStatusLabelProps {
47
+ interface EntityStatusLabelBaseProps {
48
48
  status: EFlag;
49
+ className?: string;
50
+ size?: LabelProps['size'];
51
+ iconSize?: number;
52
+ qa?: string;
53
+ }
54
+
55
+ interface DefaultEntityStatusLabelProps {
56
+ view?: 'default';
49
57
  note?: React.ReactNode;
50
58
  children?: React.ReactNode;
51
- className?: string;
52
59
  endContent?: React.ReactNode;
53
60
  withStatusName?: boolean;
54
- size?: LabelProps['size'];
55
- iconSize?: number;
56
61
  onClick?: (event: React.MouseEvent<HTMLElement>) => void;
57
- qa?: string;
58
62
  }
59
63
 
64
+ interface CompactEntityStatusLabelProps {
65
+ view: 'compact';
66
+ note?: never;
67
+ children?: never;
68
+ endContent?: never;
69
+ withStatusName?: never;
70
+ onClick?: never;
71
+ }
72
+
73
+ type EntityStatusLabelProps = EntityStatusLabelBaseProps &
74
+ (DefaultEntityStatusLabelProps | CompactEntityStatusLabelProps);
75
+
60
76
  function EntityStatusLabel({
61
77
  children,
62
78
  className,
63
79
  endContent,
64
80
  status,
65
81
  withStatusName = true,
82
+ view = 'default',
66
83
  note,
67
84
  size = 'm',
68
85
  iconSize = 14,
69
86
  onClick,
70
87
  qa,
71
88
  }: EntityStatusLabelProps) {
89
+ if (view === 'compact') {
90
+ const statusName = EFlagToStatusName[status];
91
+ return (
92
+ <ActionTooltip title={statusName}>
93
+ <span className={b('compact')} role="img" aria-label={statusName}>
94
+ <Label
95
+ theme={status === EFlag.Blue ? 'info' : EFlagToLabelTheme[status]}
96
+ icon={<StatusIcon size={iconSize} status={status} />}
97
+ size={size}
98
+ className={b(null, className)}
99
+ qa={qa}
100
+ />
101
+ </span>
102
+ </ActionTooltip>
103
+ );
104
+ }
72
105
  const theme = EFlagToLabelTheme[status];
73
106
  const isClickable = Boolean(onClick);
74
107
  return (
@@ -2,6 +2,7 @@ import React from 'react';
2
2
 
3
3
  import {ArrowsOppositeToDots} from '@gravity-ui/icons';
4
4
  import {Icon} from '@gravity-ui/uikit';
5
+ import type {ButtonProps} from '@gravity-ui/uikit';
5
6
  import {isNil} from 'lodash';
6
7
 
7
8
  import {
@@ -10,6 +11,7 @@ import {
10
11
  } from '../../store/reducers/capabilities/hooks';
11
12
  import type {ModifyDiskResponse} from '../../types/api/modifyDisk';
12
13
  import type {TVDiskID} from '../../types/api/vdisk';
14
+ import {BRAND_BUTTON_CLASS} from '../../utils/constants';
13
15
  import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges';
14
16
  import {ButtonWithConfirmDialog} from '../ButtonWithConfirmDialog/ButtonWithConfirmDialog';
15
17
 
@@ -19,7 +21,9 @@ interface EvictVDiskButtonProps {
19
21
  vDiskId: Required<TVDiskID>;
20
22
  donorMode?: boolean;
21
23
  fullWidth?: boolean;
22
- onSuccess?: () => void;
24
+ view?: ButtonProps['view'];
25
+ onSuccess?: (isDialogOpen: boolean) => void;
26
+ onDialogOpen?: () => VoidFunction;
23
27
  }
24
28
 
25
29
  export function isAllVdiskParamsDefined(vDiskId?: TVDiskID): vDiskId is Required<TVDiskID> {
@@ -36,7 +40,9 @@ export const EvictVDiskButton = ({
36
40
  vDiskId,
37
41
  donorMode,
38
42
  fullWidth,
43
+ view = 'normal',
39
44
  onSuccess,
45
+ onDialogOpen,
40
46
  }: EvictVDiskButtonProps) => {
41
47
  const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges();
42
48
  const newDiskApiAvailable = useDiskPagesAvailable();
@@ -79,8 +85,10 @@ export const EvictVDiskButton = ({
79
85
  <ButtonWithConfirmDialog
80
86
  onConfirmAction={handleEvictVDisk}
81
87
  onConfirmActionSuccess={onSuccess}
88
+ onDialogOpen={onDialogOpen}
82
89
  buttonDisabled={!isUserAllowedToMakeChanges || donorMode}
83
- buttonView="normal"
90
+ buttonView={view}
91
+ buttonClassName={view === 'action' ? BRAND_BUTTON_CLASS : undefined}
84
92
  buttonWidth={fullWidth ? 'max' : undefined}
85
93
  dialogHeader={evictVDiskButtonKeyset('title_evict-dialog')}
86
94
  dialogText={evictVDiskButtonKeyset('confirm_evict')}
@@ -3,8 +3,8 @@
3
3
  "action_evict-short": "Evict",
4
4
  "action_force-evict": "Evict anyway",
5
5
  "title_evict-dialog": "Evict VDisk?",
6
- "confirm_evict": "The VDisk will be evicted from the PDisk. This VDisk will be replaced by a new one in the group. Data will be replicated to the new disk. The old VDisk will serve as a donor until replication is complete, and then it will be removed.",
7
- "alert_evict": "This operation cannot be cancelled.",
6
+ "confirm_evict": "The VDisk will be evicted from the PDisk.",
7
+ "alert_evict": "This VDisk will be replaced by a new one in the group. Data will be replicated to the new disk. The old VDisk will serve as\u00a0a\u00a0donor until replication is complete, and then it will be\u00a0removed.",
8
8
  "alert_not-allowed": "You don't have enough rights to evict VDisk",
9
9
  "alert_donor-mode-impossible": "Evicting a donor VDisk is not possible"
10
10
  }
@@ -5,7 +5,7 @@ import {cn} from '../../utils/cn';
5
5
  import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
6
6
  import {formatStorageValues} from '../../utils/dataFormatters/dataFormatters';
7
7
  import {useNodeDeveloperUIHref} from '../../utils/hooks/useNodeDeveloperUIHref';
8
- import {isNumeric} from '../../utils/utils';
8
+ import {getNodeMemory} from '../../utils/memory';
9
9
  import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
10
10
  import {PoolUsage} from '../PoolUsage/PoolUsage';
11
11
  import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
@@ -90,18 +90,13 @@ export const FullNodeViewer = ({node, className}: FullNodeViewerProps) => {
90
90
  return <div className="error">{i18n('no-data')}</div>;
91
91
  }
92
92
 
93
- const memoryUsed = isNumeric(node.MemoryUsed) ? Number(node.MemoryUsed) : undefined;
94
- const memoryLimit = isNumeric(node.MemoryLimit) ? Number(node.MemoryLimit) : undefined;
95
- const hasMemoryUsed =
96
- memoryUsed !== undefined && Number.isFinite(memoryUsed) && memoryUsed >= 0;
97
- const hasMemoryLimit =
98
- memoryLimit !== undefined && Number.isFinite(memoryLimit) && memoryLimit > 0;
93
+ const {memoryUsed, memoryLimit} = getNodeMemory(node);
99
94
 
100
95
  const renderMemory = () => {
101
- if (!hasMemoryUsed) {
96
+ if (memoryUsed === undefined) {
102
97
  return EMPTY_DATA_PLACEHOLDER;
103
98
  }
104
- if (!hasMemoryLimit) {
99
+ if (memoryLimit === undefined) {
105
100
  return formatMemoryValues(memoryUsed)[0];
106
101
  }
107
102
 
@@ -44,7 +44,10 @@ function useVisibleAnchor(
44
44
 
45
45
  type HoverPopupProps = {
46
46
  children: React.ReactNode;
47
- renderPopupContent: (controls: {onClose: VoidFunction}) => React.ReactNode;
47
+ renderPopupContent: (controls: {
48
+ onClose: VoidFunction;
49
+ holdOpen: () => VoidFunction;
50
+ }) => React.ReactNode;
48
51
  showPopup?: boolean;
49
52
  anchorRef?: React.RefObject<HTMLElement>;
50
53
  onShowPopup?: VoidFunction;
@@ -72,8 +75,11 @@ export const HoverPopup = ({
72
75
  const [isPopupVisible, setIsPopupVisible] = React.useState(false);
73
76
  const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false);
74
77
  const [isFocused, setIsFocused] = React.useState(false);
78
+ const [hoverClosing, setHoverClosing] = React.useState(false);
79
+ const dialogHoldRef = React.useRef<object | null>(null);
75
80
 
76
81
  const anchor = React.useRef<HTMLSpanElement>(null);
82
+ const popupContent = React.useRef<HTMLDivElement>(null);
77
83
  const {activate, zIndex} = usePopupPriority();
78
84
  const bringToFront = useEventHandler(() => activate(anchorRef?.current || anchor.current));
79
85
 
@@ -102,32 +108,36 @@ export const HoverPopup = ({
102
108
  [delayOpen, reportOpen],
103
109
  );
104
110
 
105
- const hidePopup = React.useCallback(() => {
111
+ const hidePopup = useEventHandler(() => {
112
+ if (dialogHoldRef.current || isFocused) {
113
+ return;
114
+ }
115
+ setHoverClosing(open && visibleAnchorRef.current === anchorElement);
106
116
  setIsPopupVisible(false);
107
- }, []);
117
+ reportOpen(false);
118
+ });
108
119
 
109
120
  const debouncedHandleHidePopup = React.useMemo(
110
- () =>
111
- debounce(() => {
112
- hidePopup();
113
- reportOpen(false);
114
- }, delayClose),
115
- [delayClose, reportOpen, hidePopup],
121
+ () => debounce(hidePopup, delayClose),
122
+ [delayClose, hidePopup],
116
123
  );
117
124
 
118
125
  React.useEffect(() => {
119
126
  return () => {
120
127
  debouncedHandleShowPopup.cancel();
121
128
  debouncedHandleHidePopup.cancel();
129
+ dialogHoldRef.current = null;
122
130
  };
123
131
  }, [debouncedHandleShowPopup, debouncedHandleHidePopup]);
124
132
 
125
133
  const closePopup = React.useCallback(() => {
126
134
  debouncedHandleShowPopup.cancel();
127
135
  debouncedHandleHidePopup.cancel();
136
+ dialogHoldRef.current = null;
128
137
  setIsPopupVisible(false);
129
138
  setIsPopupContentHovered(false);
130
139
  setIsFocused(false);
140
+ setHoverClosing(false);
131
141
  reportOpen(false, true);
132
142
  onClosePopup?.();
133
143
  }, [debouncedHandleHidePopup, debouncedHandleShowPopup, onClosePopup, reportOpen]);
@@ -136,6 +146,7 @@ export const HoverPopup = ({
136
146
  debouncedHandleShowPopup.cancel();
137
147
  setIsPopupContentHovered(false);
138
148
  setIsFocused(false);
149
+ setHoverClosing(false);
139
150
  debouncedHandleHidePopup();
140
151
  });
141
152
 
@@ -146,11 +157,59 @@ export const HoverPopup = ({
146
157
  // Release only this popup's hover source; a hovered peer can keep the pair open.
147
158
  const [isAnchorVisible, visibleAnchorRef] = useVisibleAnchor(
148
159
  anchorElement,
149
- open,
160
+ open || hoverClosing,
150
161
  hideClippedPopup,
151
162
  );
152
163
  const container = getPopupScrollContainer(anchorElement);
153
164
 
165
+ React.useEffect(() => {
166
+ if (!hoverClosing || !anchorElement) {
167
+ return undefined;
168
+ }
169
+ // Escape may close this popup through its paired sibling's handler.
170
+ const preventHoverReturn = (event: KeyboardEvent) => {
171
+ if (event.key === 'Escape') {
172
+ setHoverClosing(false);
173
+ }
174
+ };
175
+ const doc = anchorElement.ownerDocument;
176
+ doc.addEventListener('keydown', preventHoverReturn, true);
177
+ return () => doc.removeEventListener('keydown', preventHoverReturn, true);
178
+ }, [anchorElement, hoverClosing]);
179
+
180
+ const resumeHover = useEventHandler(() => {
181
+ const contentHovered = Boolean(popupContent.current?.matches(':hover'));
182
+ const hovered =
183
+ visibleAnchorRef.current === anchorElement &&
184
+ (anchorElement?.matches(':hover') || contentHovered);
185
+ debouncedHandleShowPopup.cancel();
186
+ debouncedHandleHidePopup.cancel();
187
+ setIsFocused(false);
188
+ setIsPopupContentHovered(Boolean(hovered && contentHovered));
189
+ if (hovered) {
190
+ setIsPopupVisible(true);
191
+ reportOpen(true);
192
+ } else {
193
+ debouncedHandleHidePopup();
194
+ }
195
+ });
196
+
197
+ const holdOpen = () => {
198
+ const interaction = {};
199
+ dialogHoldRef.current = interaction;
200
+ debouncedHandleShowPopup.cancel();
201
+ debouncedHandleHidePopup.cancel();
202
+ setIsFocused(false);
203
+ setIsPopupVisible(true);
204
+ reportOpen(true);
205
+ return () => {
206
+ if (dialogHoldRef.current === interaction) {
207
+ dialogHoldRef.current = null;
208
+ resumeHover();
209
+ }
210
+ };
211
+ };
212
+
154
213
  const onMouseEnter = (event: React.MouseEvent<HTMLSpanElement>) => {
155
214
  if (event.buttons !== 0) {
156
215
  return;
@@ -167,18 +226,27 @@ export const HoverPopup = ({
167
226
 
168
227
  const onPopupMouseEnter = React.useCallback(
169
228
  (event: React.MouseEvent<HTMLDivElement>) => {
170
- // An exiting popup can still receive events during its close animation.
171
- if (!open || visibleAnchorRef.current !== anchorElement) {
229
+ if ((!open && !hoverClosing) || visibleAnchorRef.current !== anchorElement) {
172
230
  return;
173
231
  }
174
232
  if (event.buttons === 0) {
175
233
  bringToFront();
176
234
  }
177
235
  debouncedHandleHidePopup.cancel();
236
+ setHoverClosing(false);
237
+ setIsPopupVisible(true);
178
238
  setIsPopupContentHovered(true);
179
239
  reportOpen(true);
180
240
  },
181
- [bringToFront, reportOpen, debouncedHandleHidePopup, open, visibleAnchorRef, anchorElement],
241
+ [
242
+ bringToFront,
243
+ reportOpen,
244
+ debouncedHandleHidePopup,
245
+ open,
246
+ hoverClosing,
247
+ visibleAnchorRef,
248
+ anchorElement,
249
+ ],
182
250
  );
183
251
 
184
252
  const onPopupMouseLeave = React.useCallback(() => {
@@ -187,7 +255,7 @@ export const HoverPopup = ({
187
255
  }, [debouncedHandleHidePopup]);
188
256
 
189
257
  const onPopupContextMenu = React.useCallback(() => {
190
- if (!open || visibleAnchorRef.current !== anchorElement) {
258
+ if (dialogHoldRef.current || !open || visibleAnchorRef.current !== anchorElement) {
191
259
  return;
192
260
  }
193
261
  bringToFront();
@@ -195,12 +263,16 @@ export const HoverPopup = ({
195
263
  reportOpen(true);
196
264
  }, [bringToFront, reportOpen, open, visibleAnchorRef, anchorElement]);
197
265
 
198
- const onPopupBlur = React.useCallback(() => {
199
- setIsFocused(false);
200
- }, []);
266
+ const onPopupBlur = useEventHandler(() => {
267
+ if (isFocused && !dialogHoldRef.current) {
268
+ resumeHover();
269
+ }
270
+ });
201
271
 
202
272
  const onPopupEscapeKeyDown = React.useCallback(() => {
203
- closePopup();
273
+ if (!dialogHoldRef.current) {
274
+ closePopup();
275
+ }
204
276
  }, [closePopup]);
205
277
 
206
278
  return (
@@ -218,6 +290,8 @@ export const HoverPopup = ({
218
290
  onOpenChange={(_open, _event, reason) => {
219
291
  if (reason === 'escape-key') {
220
292
  onPopupEscapeKeyDown();
293
+ } else if (reason === 'outside-press') {
294
+ onPopupBlur();
221
295
  }
222
296
  }}
223
297
  placement={placement}
@@ -226,11 +300,13 @@ export const HoverPopup = ({
226
300
  returnFocus={false}
227
301
  hasArrow
228
302
  open={open && isAnchorVisible}
303
+ onTransitionOutComplete={() => setHoverClosing(false)}
229
304
  // bigger offset for easier switching to neighbour nodes
230
305
  // matches the default offset for popup with arrow out of a sense of beauty
231
306
  offset={offset || {mainAxis: 12, crossAxis: 0}}
232
307
  >
233
308
  <div
309
+ ref={popupContent}
234
310
  className={contentClassName}
235
311
  onContextMenu={onPopupContextMenu}
236
312
  onMouseEnter={onPopupMouseEnter}
@@ -238,7 +314,7 @@ export const HoverPopup = ({
238
314
  onBlur={onPopupBlur}
239
315
  >
240
316
  <div className={YDB_POPOVER_CLASS_NAME}>
241
- {renderPopupContent({onClose: closePopup})}
317
+ {renderPopupContent({onClose: closePopup, holdOpen})}
242
318
  </div>
243
319
  </div>
244
320
  </Popup>