@oneuptime/common 14.0.8 → 14.0.9

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 (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -0,0 +1,1253 @@
1
+ /**
2
+ * @timezone UTC
3
+ */
4
+ import "@testing-library/jest-dom";
5
+ import {
6
+ afterEach,
7
+ beforeEach,
8
+ describe,
9
+ expect,
10
+ jest,
11
+ test,
12
+ } from "@jest/globals";
13
+ import {
14
+ cleanup,
15
+ fireEvent,
16
+ render,
17
+ screen,
18
+ within,
19
+ } from "@testing-library/react";
20
+ import * as React from "react";
21
+ import getJestMockFunction, { MockFunction } from "../../MockType";
22
+
23
+ /*
24
+ * Issue #4105 review, the Proxmox rate charts (ProxmoxRateChart) on Insights
25
+ * and the node and guest Metrics tabs.
26
+ *
27
+ * - A drag or a double-click on a rate chart reloads that very chart. It
28
+ * used to drop to a 192px skeleton (the chart is 300px) for the length
29
+ * of the load, so the block shrank by 108px under the pointer and the
30
+ * cards below jumped, then jumped back. Now only the first load is a
31
+ * skeleton, at the chart's height; a reload keeps the last chart on
32
+ * screen, over the window it was fetched for, dimmed and marked
33
+ * "Refreshing"; a failed reload keeps it with the error above.
34
+ * - Every zoom is a Custom window, which a card's Refresh re-resolves to
35
+ * the same instants, so Refresh never reached the rate charts and a
36
+ * failed one could not be retried while zoomed. They now reload on the
37
+ * card's Refresh count, whether the card renders them as children (the
38
+ * Insights Network card) or as extra charts (the Storage card, the
39
+ * Metrics tabs).
40
+ *
41
+ * The network is mocked; the pages, EmbeddedMetricCard, ResourceMetricsTab
42
+ * and ProxmoxRateChart are the production code. The line chart, the card's
43
+ * MetricView and its picker are the zoom page harness's stand-ins.
44
+ */
45
+
46
+ const CLUSTER_ID: string = "0193c0de-7777-4aaa-8bbb-000000000007";
47
+ const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
48
+ const RATE_ERROR: string = "The metrics service is unavailable.";
49
+
50
+ function at(time: string): Date {
51
+ return new Date(`2026-09-28T${time}:00.000Z`);
52
+ }
53
+
54
+ const getItemMock: MockFunction = getJestMockFunction();
55
+ const getListMock: MockFunction = getJestMockFunction();
56
+ const aggregateMock: MockFunction = getJestMockFunction();
57
+ let mockLastParam: string = "node%2Fpve1";
58
+
59
+ jest.mock("react-i18next", () => {
60
+ return {
61
+ useTranslation: () => {
62
+ return {
63
+ t: (key: string, opts?: { defaultValue?: string }): string => {
64
+ return opts?.defaultValue ?? key;
65
+ },
66
+ };
67
+ },
68
+ };
69
+ });
70
+
71
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
72
+ return {
73
+ __esModule: true,
74
+ default: {
75
+ getItem: (...args: Array<unknown>): unknown => {
76
+ return getItemMock(...args);
77
+ },
78
+ getList: (...args: Array<unknown>): unknown => {
79
+ return getListMock(...args);
80
+ },
81
+ },
82
+ };
83
+ });
84
+
85
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
86
+ return {
87
+ __esModule: true,
88
+ default: {
89
+ aggregate: (...args: Array<unknown>): unknown => {
90
+ return aggregateMock(...args);
91
+ },
92
+ },
93
+ };
94
+ });
95
+
96
+ jest.mock("../../../UI/Utils/API/API", () => {
97
+ return {
98
+ __esModule: true,
99
+ default: {
100
+ getFriendlyMessage: (error: unknown): string => {
101
+ return String((error as Error)?.message || error);
102
+ },
103
+ },
104
+ };
105
+ });
106
+
107
+ jest.mock("../../../UI/Utils/Navigation", () => {
108
+ return {
109
+ __esModule: true,
110
+ default: {
111
+ getLastParamAsObjectID: (): unknown => {
112
+ const { default: ObjectIDType } = jest.requireActual(
113
+ "../../../Types/ObjectID",
114
+ ) as { default: new (id: string) => unknown };
115
+ return new ObjectIDType("0193c0de-7777-4aaa-8bbb-000000000007");
116
+ },
117
+ getLastParamAsString: (): string => {
118
+ return mockLastParam;
119
+ },
120
+ navigate: (): void => {},
121
+ isOnThisPage: (): boolean => {
122
+ return false;
123
+ },
124
+ },
125
+ };
126
+ });
127
+
128
+ jest.mock("../../../UI/Utils/Project", () => {
129
+ return {
130
+ __esModule: true,
131
+ default: {
132
+ getCurrentProjectId: (): unknown => {
133
+ const { default: ObjectIDType } = jest.requireActual(
134
+ "../../../Types/ObjectID",
135
+ ) as { default: new (id: string) => unknown };
136
+ return new ObjectIDType("10000000-0000-4000-8000-000000000001");
137
+ },
138
+ },
139
+ };
140
+ });
141
+
142
+ jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
143
+ const harness: { StandInLineChart: unknown } = jest.requireActual(
144
+ "./TimeRangeZoomPageHarness",
145
+ ) as { StandInLineChart: unknown };
146
+ return { __esModule: true, default: harness.StandInLineChart };
147
+ });
148
+
149
+ jest.mock(
150
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/MetricView",
151
+ () => {
152
+ const harness: { StandInMetricView: unknown } = jest.requireActual(
153
+ "./TimeRangeZoomPageHarness",
154
+ ) as { StandInMetricView: unknown };
155
+ return { __esModule: true, default: harness.StandInMetricView };
156
+ },
157
+ );
158
+
159
+ jest.mock("../../../UI/Components/Date/RangeStartAndEndDateView", () => {
160
+ const harness: { StandInRangeStartAndEndDateView: unknown } =
161
+ jest.requireActual("./TimeRangeZoomPageHarness") as {
162
+ StandInRangeStartAndEndDateView: unknown;
163
+ };
164
+ return {
165
+ __esModule: true,
166
+ default: harness.StandInRangeStartAndEndDateView,
167
+ };
168
+ });
169
+
170
+ jest.mock(
171
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/UseEventTimeReferenceLines",
172
+ () => {
173
+ return {
174
+ __esModule: true,
175
+ default: () => {
176
+ return { lines: [], markerCount: 0 };
177
+ },
178
+ };
179
+ },
180
+ );
181
+
182
+ jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
183
+ return {
184
+ __esModule: true,
185
+ default: () => {
186
+ return <div data-testid="card-model-detail" />;
187
+ },
188
+ };
189
+ });
190
+
191
+ jest.mock(
192
+ "../../../../App/FeatureSet/Dashboard/src/Components/ResourceActivity/ResourceActivityCards",
193
+ () => {
194
+ return {
195
+ __esModule: true,
196
+ default: () => {
197
+ return null;
198
+ },
199
+ };
200
+ },
201
+ );
202
+
203
+ import ProxmoxClusterInsights from "../../../../App/FeatureSet/Dashboard/src/Pages/Proxmox/View/Insights";
204
+ import ProxmoxClusterNodeDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Proxmox/View/NodeDetail";
205
+ import ProxmoxClusterGuestDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Proxmox/View/GuestDetail";
206
+ import ProxmoxRateChart from "../../../../App/FeatureSet/Dashboard/src/Components/Proxmox/ProxmoxRateChart";
207
+ import EmbeddedMetricCard from "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/EmbeddedMetricCard";
208
+ import {
209
+ CHART_LOADING_SKELETON_TEST_ID,
210
+ CHART_REFETCHING_TEST_ID,
211
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/ChartRefetchFrame";
212
+ import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
213
+ import ProxmoxResource from "../../../Models/DatabaseModels/ProxmoxResource";
214
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
215
+ import TimeRange from "../../../Types/Time/TimeRange";
216
+ import {
217
+ cardPickers,
218
+ chartWindows,
219
+ doubleClick,
220
+ dragAcross,
221
+ expectRevealedOnHoverOf,
222
+ flush,
223
+ metricViews,
224
+ resetZoomButtons,
225
+ windowOf,
226
+ zoomCharts,
227
+ zoomHints,
228
+ } from "./TimeRangeZoomPageHarness";
229
+ import {
230
+ TIME_RANGE_ZOOM_HINT_RESET_TEXT,
231
+ TIME_RANGE_ZOOM_HINT_TEXT,
232
+ } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint";
233
+
234
+ const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
235
+
236
+ interface AggregateCall {
237
+ aggregateBy: {
238
+ query: { name: string; attributes: Record<string, unknown> };
239
+ startTimestamp: Date;
240
+ endTimestamp: Date;
241
+ };
242
+ }
243
+
244
+ type Point = {
245
+ timestamp: Date;
246
+ value: number;
247
+ attributes: Record<string, string>;
248
+ };
249
+
250
+ const DISK_METRICS: Array<string> = [
251
+ "pve_disk_read_bytes",
252
+ "pve_disk_write_bytes",
253
+ ];
254
+ const NETWORK_METRICS: Array<string> = [
255
+ "pve_network_receive_bytes",
256
+ "pve_network_transmit_bytes",
257
+ ];
258
+ const RATE_METRICS: Array<string> = [...DISK_METRICS, ...NETWORK_METRICS];
259
+
260
+ // From when the counters exist.
261
+ let countersFrom: Date = at("11:00");
262
+
263
+ /*
264
+ * A sample a minute from 11:00 to 11:59 inside the asked-for window: every
265
+ * counter rising 60 KB a minute for node pve1 and guest 100.
266
+ */
267
+ function points(call: AggregateCall): Array<Point> {
268
+ const name: string = call.aggregateBy.query.name;
269
+ const start: number = call.aggregateBy.startTimestamp.getTime();
270
+ const end: number = call.aggregateBy.endTimestamp.getTime();
271
+ const result: Array<Point> = [];
272
+
273
+ if (!RATE_METRICS.includes(name)) {
274
+ return result;
275
+ }
276
+
277
+ for (let minute: number = 0; minute < 60; minute++) {
278
+ const time: Date = new Date(Date.UTC(2026, 8, 28, 11, minute));
279
+ if (
280
+ time.getTime() < start ||
281
+ time.getTime() > end ||
282
+ time.getTime() < countersFrom.getTime()
283
+ ) {
284
+ continue;
285
+ }
286
+ for (const id of ["node/pve1", "qemu/100"]) {
287
+ result.push({
288
+ timestamp: time,
289
+ value: minute * 60_000,
290
+ attributes: { id: id },
291
+ });
292
+ }
293
+ }
294
+
295
+ return result;
296
+ }
297
+
298
+ interface HeldRateLoad {
299
+ call: AggregateCall;
300
+ release: () => void;
301
+ }
302
+
303
+ let holdRates: boolean = false;
304
+ let heldRates: Array<HeldRateLoad> = [];
305
+ // Rate loads over a window ending at one of these fail.
306
+ let failingRateEnds: Array<number> = [];
307
+
308
+ function isRateCall(call: AggregateCall): boolean {
309
+ return RATE_METRICS.includes(call.aggregateBy.query.name);
310
+ }
311
+
312
+ function answer(call: AggregateCall): Promise<unknown> {
313
+ if (
314
+ isRateCall(call) &&
315
+ failingRateEnds.includes(call.aggregateBy.endTimestamp.getTime())
316
+ ) {
317
+ return Promise.reject(new Error(RATE_ERROR));
318
+ }
319
+ return Promise.resolve({ data: points(call) });
320
+ }
321
+
322
+ function aggregateCalls(): Array<AggregateCall> {
323
+ return aggregateMock.mock.calls.map((args: Array<unknown>): AggregateCall => {
324
+ return args[0] as AggregateCall;
325
+ });
326
+ }
327
+
328
+ // How many loads of these metrics asked for exactly [start, end].
329
+ function callsOver(names: Array<string>, start: Date, end: Date): number {
330
+ return aggregateCalls().filter((call: AggregateCall): boolean => {
331
+ return (
332
+ names.includes(call.aggregateBy.query.name) &&
333
+ call.aggregateBy.startTimestamp.getTime() === start.getTime() &&
334
+ call.aggregateBy.endTimestamp.getTime() === end.getTime()
335
+ );
336
+ }).length;
337
+ }
338
+
339
+ function rateCallCount(): number {
340
+ return aggregateCalls().filter(isRateCall).length;
341
+ }
342
+
343
+ async function releaseRatesEndingAt(end: Date): Promise<void> {
344
+ const releasing: Array<HeldRateLoad> = heldRates.filter(
345
+ (held: HeldRateLoad): boolean => {
346
+ return held.call.aggregateBy.endTimestamp.getTime() === end.getTime();
347
+ },
348
+ );
349
+ heldRates = heldRates.filter((held: HeldRateLoad): boolean => {
350
+ return !releasing.includes(held);
351
+ });
352
+ expect(releasing.length).toBeGreaterThan(0);
353
+ for (const held of releasing) {
354
+ held.release();
355
+ }
356
+ await flush();
357
+ }
358
+
359
+ async function releaseAllRates(): Promise<void> {
360
+ const releasing: Array<HeldRateLoad> = heldRates;
361
+ heldRates = [];
362
+ for (const held of releasing) {
363
+ held.release();
364
+ }
365
+ await flush();
366
+ }
367
+
368
+ function skeletons(): Array<HTMLElement> {
369
+ return screen.queryAllByTestId(CHART_LOADING_SKELETON_TEST_ID);
370
+ }
371
+
372
+ function refreshingMarkers(): Array<HTMLElement> {
373
+ return screen.queryAllByTestId(CHART_REFETCHING_TEST_ID);
374
+ }
375
+
376
+ function alertsAround(chart: HTMLElement): Array<HTMLElement> {
377
+ // The frame around the chart: its error sits above the chart, inside it.
378
+ const frame: HTMLElement = chart.closest("[aria-busy]")!
379
+ .parentElement as HTMLElement;
380
+ return within(frame).queryAllByRole("alert");
381
+ }
382
+
383
+ function same<T>(value: T, count: number): Array<T> {
384
+ return Array.from({ length: count }, (): T => {
385
+ return value;
386
+ });
387
+ }
388
+
389
+ function expectSameCharts(charts: Array<HTMLElement>): void {
390
+ const now: Array<HTMLElement> = zoomCharts();
391
+ expect(now).toHaveLength(charts.length);
392
+ charts.forEach((chart: HTMLElement, index: number) => {
393
+ expect(now[index]).toBe(chart);
394
+ });
395
+ }
396
+
397
+ function pressCardRefresh(index: number = 0): void {
398
+ fireEvent.click(screen.getAllByRole("button", { name: "Refresh" })[index]!);
399
+ }
400
+
401
+ const HOUR: [string, string] = windowOf(at("11:00"), NOW);
402
+ const ZOOM: [string, string] = windowOf(at("11:20"), at("11:30"));
403
+
404
+ beforeEach(() => {
405
+ jest.useFakeTimers();
406
+ jest.setSystemTime(NOW);
407
+ window.localStorage.clear();
408
+ getItemMock.mockReset();
409
+ getListMock.mockReset();
410
+ aggregateMock.mockReset();
411
+ holdRates = false;
412
+ heldRates = [];
413
+ failingRateEnds = [];
414
+ countersFrom = at("11:00");
415
+ mockLastParam = "node%2Fpve1";
416
+
417
+ getItemMock.mockResolvedValue({
418
+ _id: CLUSTER_ID,
419
+ name: "pve-prod",
420
+ otelCollectorStatus: "connected",
421
+ lastSeenAt: NOW,
422
+ nodeCount: 1,
423
+ onlineNodeCount: 1,
424
+ guestCount: 1,
425
+ storageCount: 0,
426
+ guestsWithoutBackupCount: 0,
427
+ });
428
+
429
+ getListMock.mockImplementation((request: unknown) => {
430
+ const { modelType, query } = request as {
431
+ modelType: unknown;
432
+ query: Record<string, unknown>;
433
+ };
434
+ const rows: Array<Record<string, unknown>> = [
435
+ {
436
+ kind: "Node",
437
+ externalId: "node/pve1",
438
+ name: "pve1",
439
+ isUp: true,
440
+ latestCpuPercent: 10,
441
+ metricsUpdatedAt: NOW,
442
+ lastSeenAt: NOW,
443
+ },
444
+ {
445
+ kind: "Guest",
446
+ externalId: "qemu/100",
447
+ name: "web",
448
+ vmid: 100,
449
+ guestType: "qemu",
450
+ parentNodeName: "pve1",
451
+ isUp: true,
452
+ isBackedUp: true,
453
+ metricsUpdatedAt: NOW,
454
+ lastSeenAt: NOW,
455
+ },
456
+ ].filter((row: Record<string, unknown>): boolean => {
457
+ return (
458
+ modelType === ProxmoxResource &&
459
+ (!query["kind"] || row["kind"] === query["kind"]) &&
460
+ (!query["externalId"] || row["externalId"] === query["externalId"])
461
+ );
462
+ });
463
+
464
+ return Promise.resolve({ data: rows, count: rows.length });
465
+ });
466
+
467
+ aggregateMock.mockImplementation((request: unknown) => {
468
+ const call: AggregateCall = request as AggregateCall;
469
+ if (!holdRates || !isRateCall(call)) {
470
+ return answer(call);
471
+ }
472
+ return new Promise(
473
+ (resolve: (value: unknown) => void, reject: (error: Error) => void) => {
474
+ heldRates.push({
475
+ call: call,
476
+ release: (): void => {
477
+ answer(call).then(resolve, reject);
478
+ },
479
+ });
480
+ },
481
+ );
482
+ });
483
+ });
484
+
485
+ afterEach(() => {
486
+ cleanup();
487
+ jest.useRealTimers();
488
+ });
489
+
490
+ // ------------------------------------------------------------------ Insights
491
+
492
+ // The five cards' Refresh buttons, in page order.
493
+ const STORAGE_CARD: number = 2;
494
+ const NETWORK_CARD: number = 4;
495
+
496
+ async function renderInsights(): Promise<void> {
497
+ render(<ProxmoxClusterInsights {...PAGE_PROPS} />);
498
+ await flush();
499
+ }
500
+
501
+ // [Disk Throughput (Storage card), Network (Network card)]
502
+ async function renderLoadedInsights(): Promise<Array<HTMLElement>> {
503
+ await renderInsights();
504
+ expect(metricViews()).toHaveLength(4);
505
+ const charts: Array<HTMLElement> = zoomCharts();
506
+ expect(charts).toHaveLength(2);
507
+ expect(charts[0]).toHaveAttribute("data-series", "Read,Write");
508
+ expect(charts[1]).toHaveAttribute("data-series", "Receive,Transmit");
509
+ expect(chartWindows()).toEqual(same(HOUR, 2));
510
+ return charts;
511
+ }
512
+
513
+ describe("Proxmox Insights: the rate charts stay on screen while they reload", () => {
514
+ test("before the first load, each skeleton holds the chart's 300px", async () => {
515
+ holdRates = true;
516
+ await renderInsights();
517
+
518
+ expect(zoomCharts()).toHaveLength(0);
519
+ expect(skeletons()).toHaveLength(2);
520
+ for (const skeleton of skeletons()) {
521
+ expect(skeleton).toHaveStyle({ height: "300px" });
522
+ expect(skeleton.className).not.toMatch(/\bh-48\b/);
523
+ }
524
+
525
+ await releaseAllRates();
526
+
527
+ expect(skeletons()).toHaveLength(0);
528
+ expect(zoomCharts()).toHaveLength(2);
529
+ expect(refreshingMarkers()).toHaveLength(0);
530
+ });
531
+
532
+ test("a drag on Disk Throughput keeps both charts, over the hour they were fetched for, until the zoom lands", async () => {
533
+ const charts: Array<HTMLElement> = await renderLoadedInsights();
534
+ holdRates = true;
535
+
536
+ await dragAcross(charts[0]!, at("11:20"), at("11:30"));
537
+
538
+ // Every metric card moves to the zoom...
539
+ expect(chartWindows(metricViews())).toEqual(same(ZOOM, 4));
540
+ expect(callsOver(RATE_METRICS, at("11:20"), at("11:30"))).toBe(4);
541
+ // ...and both rate charts stay, still on THEIR hour, marked Refreshing.
542
+ expectSameCharts(charts);
543
+ expect(chartWindows()).toEqual(same(HOUR, 2));
544
+ expect(refreshingMarkers()).toHaveLength(2);
545
+ expect(skeletons()).toHaveLength(0);
546
+
547
+ await releaseRatesEndingAt(at("11:30"));
548
+
549
+ expectSameCharts(charts);
550
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
551
+ expect(refreshingMarkers()).toHaveLength(0);
552
+ });
553
+
554
+ test("a double-click reset keeps them on screen too, then draws the hour again", async () => {
555
+ await renderLoadedInsights();
556
+ await dragAcross(zoomCharts()[1]!, at("11:20"), at("11:30"));
557
+ const charts: Array<HTMLElement> = zoomCharts();
558
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
559
+ holdRates = true;
560
+
561
+ await doubleClick(charts[0]!);
562
+
563
+ expect(chartWindows(metricViews())).toEqual(same(HOUR, 4));
564
+ expectSameCharts(charts);
565
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
566
+ expect(refreshingMarkers()).toHaveLength(2);
567
+
568
+ await releaseRatesEndingAt(NOW);
569
+
570
+ expectSameCharts(charts);
571
+ expect(chartWindows()).toEqual(same(HOUR, 2));
572
+ expect(resetZoomButtons()).toHaveLength(0);
573
+ });
574
+
575
+ test("a reload that fails keeps the last chart with the error above it; the chart still takes the double-click back", async () => {
576
+ await renderLoadedInsights();
577
+ failingRateEnds = [at("11:30").getTime()];
578
+
579
+ await dragAcross(metricViews()[0]!, at("11:20"), at("11:30"));
580
+
581
+ const charts: Array<HTMLElement> = zoomCharts();
582
+ expect(charts).toHaveLength(2);
583
+ expect(chartWindows()).toEqual(same(HOUR, 2));
584
+ for (const chart of charts) {
585
+ const alerts: Array<HTMLElement> = alertsAround(chart);
586
+ expect(alerts).toHaveLength(1);
587
+ expect(alerts[0]).toHaveTextContent(
588
+ `Couldn't refresh — showing previously loaded data. ${RATE_ERROR}`,
589
+ );
590
+ }
591
+
592
+ await doubleClick(charts[1]!);
593
+
594
+ expect(chartWindows(metricViews())).toEqual(same(HOUR, 4));
595
+ expect(chartWindows()).toEqual(same(HOUR, 2));
596
+ expect(screen.queryAllByRole("alert")).toHaveLength(0);
597
+ });
598
+
599
+ test("a slow load for a window the reader has left cannot repaint the charts", async () => {
600
+ await renderLoadedInsights();
601
+ holdRates = true;
602
+
603
+ await dragAcross(zoomCharts()[0]!, at("11:20"), at("11:30"));
604
+ await doubleClick(zoomCharts()[1]!);
605
+ await releaseRatesEndingAt(NOW);
606
+ expect(chartWindows()).toEqual(same(HOUR, 2));
607
+
608
+ await releaseRatesEndingAt(at("11:30"));
609
+
610
+ expect(chartWindows()).toEqual(same(HOUR, 2));
611
+ expect(refreshingMarkers()).toHaveLength(0);
612
+ });
613
+
614
+ test("a zoom into a quiet stretch: the empty state keeps the chart's height, selects no text, and reloads in place", async () => {
615
+ countersFrom = at("11:40");
616
+ await renderLoadedInsights();
617
+
618
+ await dragAcross(metricViews()[1]!, at("11:10"), at("11:30"));
619
+
620
+ expect(zoomCharts()).toHaveLength(0);
621
+ const empties: Array<HTMLElement> = screen.getAllByText(
622
+ "No data reported for the selected time range.",
623
+ );
624
+ expect(empties).toHaveLength(2);
625
+ for (const empty of empties) {
626
+ expect(empty).toHaveStyle({ height: "300px" });
627
+ expect(empty).toHaveClass("select-none");
628
+ expect(empty.className).not.toMatch(/\bh-48\b/);
629
+ }
630
+
631
+ holdRates = true;
632
+ fireEvent.doubleClick(empties[0]!);
633
+ await flush();
634
+
635
+ // The way back reloads the hour; the empty states stay until it lands.
636
+ expect(
637
+ screen.getAllByText("No data reported for the selected time range."),
638
+ ).toEqual(empties);
639
+ expect(refreshingMarkers()).toHaveLength(2);
640
+ expect(skeletons()).toHaveLength(0);
641
+
642
+ await releaseRatesEndingAt(NOW);
643
+
644
+ expect(zoomCharts()).toHaveLength(2);
645
+ expect(chartWindows()).toEqual(same(HOUR, 2));
646
+ });
647
+ });
648
+
649
+ describe("Proxmox Insights: a card's Refresh reaches its rate chart while zoomed", () => {
650
+ test("the Storage card's Refresh retries its failed Disk Throughput chart (an extra chart)", async () => {
651
+ await renderLoadedInsights();
652
+ failingRateEnds = [at("11:30").getTime()];
653
+ await dragAcross(zoomCharts()[0]!, at("11:20"), at("11:30"));
654
+ expect(callsOver(DISK_METRICS, at("11:20"), at("11:30"))).toBe(2);
655
+ expect(alertsAround(zoomCharts()[0]!)).toHaveLength(1);
656
+
657
+ failingRateEnds = [];
658
+ pressCardRefresh(STORAGE_CARD);
659
+ await flush();
660
+
661
+ expect(callsOver(DISK_METRICS, at("11:20"), at("11:30"))).toBe(4);
662
+ expect(alertsAround(zoomCharts()[0]!)).toHaveLength(0);
663
+ expect(chartWindows()[0]).toEqual(ZOOM);
664
+ // Refresh re-sends the pinned window: still zoomed.
665
+ expect(cardPickers()[STORAGE_CARD]).toHaveTextContent(TimeRange.CUSTOM);
666
+ });
667
+
668
+ test("the Network card's Refresh retries its failed Network chart (the card's child)", async () => {
669
+ await renderLoadedInsights();
670
+ failingRateEnds = [at("11:30").getTime()];
671
+ await dragAcross(zoomCharts()[1]!, at("11:20"), at("11:30"));
672
+ expect(callsOver(NETWORK_METRICS, at("11:20"), at("11:30"))).toBe(2);
673
+ expect(alertsAround(zoomCharts()[1]!)).toHaveLength(1);
674
+
675
+ failingRateEnds = [];
676
+ pressCardRefresh(NETWORK_CARD);
677
+ await flush();
678
+
679
+ expect(callsOver(NETWORK_METRICS, at("11:20"), at("11:30"))).toBe(4);
680
+ expect(alertsAround(zoomCharts()[1]!)).toHaveLength(0);
681
+ expect(chartWindows()[1]).toEqual(ZOOM);
682
+ expect(resetZoomButtons()).toHaveLength(5);
683
+ });
684
+
685
+ test("not zoomed, a card's Refresh moves the page's hour and each rate metric loads it once", async () => {
686
+ await renderLoadedInsights();
687
+ const later: Date = new Date(NOW.getTime() + 60_000);
688
+ jest.setSystemTime(later);
689
+ const before: number = rateCallCount();
690
+
691
+ pressCardRefresh(NETWORK_CARD);
692
+ await flush();
693
+
694
+ /*
695
+ * The page re-resolves its one window for every card: both rate charts
696
+ * load it, each metric once (the Refresh count lands with the window).
697
+ */
698
+ expect(rateCallCount() - before).toBe(4);
699
+ expect(
700
+ callsOver(RATE_METRICS, new Date(later.getTime() - 60 * 60_000), later),
701
+ ).toBe(4);
702
+ });
703
+ });
704
+
705
+ // ------------------------------------------------- node and guest Metrics tab
706
+
707
+ async function renderNodeMetrics(): Promise<void> {
708
+ mockLastParam = "node%2Fpve1";
709
+ render(<ProxmoxClusterNodeDetail {...PAGE_PROPS} />);
710
+ await flush();
711
+ fireEvent.click(screen.getByRole("tab", { name: "Metrics" }));
712
+ await flush();
713
+ }
714
+
715
+ describe("Proxmox node detail, Metrics tab: the node's rate charts stay on screen while they reload", () => {
716
+ test("before the first load, each skeleton holds the chart's 300px", async () => {
717
+ holdRates = true;
718
+ await renderNodeMetrics();
719
+
720
+ expect(skeletons()).toHaveLength(2);
721
+ for (const skeleton of skeletons()) {
722
+ expect(skeleton).toHaveStyle({ height: "300px" });
723
+ }
724
+ await releaseAllRates();
725
+ expect(zoomCharts()).toHaveLength(2);
726
+ });
727
+
728
+ test("a drag keeps both charts on their hour until the zoom lands, still for this node", async () => {
729
+ await renderNodeMetrics();
730
+ const charts: Array<HTMLElement> = zoomCharts();
731
+ expect(charts).toHaveLength(2);
732
+ holdRates = true;
733
+
734
+ await dragAcross(charts[1]!, at("11:20"), at("11:30"));
735
+
736
+ expectSameCharts(charts);
737
+ expect(chartWindows()).toEqual(same(HOUR, 2));
738
+ expect(refreshingMarkers()).toHaveLength(2);
739
+ expect(heldRates).toHaveLength(4);
740
+ for (const held of heldRates) {
741
+ expect(held.call.aggregateBy.query.attributes["id"]).toBe("node/pve1");
742
+ }
743
+
744
+ await releaseRatesEndingAt(at("11:30"));
745
+
746
+ expectSameCharts(charts);
747
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
748
+ });
749
+
750
+ test("zoomed, the tab's Refresh retries both failed rate charts", async () => {
751
+ await renderNodeMetrics();
752
+ failingRateEnds = [at("11:30").getTime()];
753
+ await dragAcross(metricViews()[0]!, at("11:20"), at("11:30"));
754
+ expect(screen.getAllByRole("alert")).toHaveLength(2);
755
+ expect(callsOver(RATE_METRICS, at("11:20"), at("11:30"))).toBe(4);
756
+
757
+ failingRateEnds = [];
758
+ pressCardRefresh();
759
+ await flush();
760
+
761
+ expect(callsOver(RATE_METRICS, at("11:20"), at("11:30"))).toBe(8);
762
+ expect(screen.queryAllByRole("alert")).toHaveLength(0);
763
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
764
+ expect(cardPickers()[0]).toHaveTextContent(TimeRange.CUSTOM);
765
+ expect(resetZoomButtons()).toHaveLength(1);
766
+ });
767
+ });
768
+
769
+ describe("Proxmox guest detail, Metrics tab", () => {
770
+ test("a drag keeps the guest's rate charts on screen, and the tab's Refresh retries a failed load", async () => {
771
+ mockLastParam = "qemu%2F100";
772
+ render(<ProxmoxClusterGuestDetail {...PAGE_PROPS} />);
773
+ await flush();
774
+ fireEvent.click(screen.getByRole("tab", { name: "Metrics" }));
775
+ await flush();
776
+ const charts: Array<HTMLElement> = zoomCharts();
777
+ expect(charts).toHaveLength(2);
778
+
779
+ holdRates = true;
780
+ failingRateEnds = [at("11:30").getTime()];
781
+ await dragAcross(charts[0]!, at("11:20"), at("11:30"));
782
+
783
+ expectSameCharts(charts);
784
+ expect(chartWindows()).toEqual(same(HOUR, 2));
785
+ expect(refreshingMarkers()).toHaveLength(2);
786
+ for (const held of heldRates) {
787
+ expect(held.call.aggregateBy.query.attributes["id"]).toBe("qemu/100");
788
+ }
789
+
790
+ await releaseRatesEndingAt(at("11:30"));
791
+ expectSameCharts(charts);
792
+ expect(screen.getAllByRole("alert")).toHaveLength(2);
793
+
794
+ holdRates = false;
795
+ failingRateEnds = [];
796
+ pressCardRefresh();
797
+ await flush();
798
+
799
+ expect(screen.queryAllByRole("alert")).toHaveLength(0);
800
+ expect(chartWindows()).toEqual(same(ZOOM, 2));
801
+ });
802
+ });
803
+
804
+ /*
805
+ * The rate charts name the drag the way the Ceph ones do (issue #4105
806
+ * review): a hint revealed while the pointer is over that chart, in a named
807
+ * group holding exactly its heading and the chart. The Insights Network
808
+ * card holds nothing but its rate chart, so without it nothing in the card
809
+ * said the chart could be dragged at all.
810
+ */
811
+ function expectOneHintPerRateChart(titles: Array<string | null>): void {
812
+ const hints: Array<HTMLElement> = zoomHints();
813
+ expect(hints).toHaveLength(titles.length);
814
+ hints.forEach((hint: HTMLElement, index: number) => {
815
+ const group: HTMLElement = expectRevealedOnHoverOf(hint);
816
+ const title: string | null = titles[index]!;
817
+ if (title) {
818
+ expect(group).toHaveTextContent(title);
819
+ }
820
+ const charts: NodeListOf<Element> = group.querySelectorAll(
821
+ '[data-testid="zoom-chart"]',
822
+ );
823
+ expect(charts).toHaveLength(1);
824
+ expect(charts[0]).toBe(zoomCharts()[index]);
825
+ });
826
+ }
827
+
828
+ describe("Proxmox rate charts name the drag and the way back", () => {
829
+ test("Insights: a hint over the Storage card's Disk Throughput and over the Network card's chart", async () => {
830
+ await renderLoadedInsights();
831
+
832
+ expectOneHintPerRateChart(["Disk Throughput", null]);
833
+ for (const hint of zoomHints()) {
834
+ expect(hint).toHaveTextContent(TIME_RANGE_ZOOM_HINT_TEXT);
835
+ }
836
+ });
837
+
838
+ test("Insights: zoomed, both hints name the double-click back; reset, the drag again", async () => {
839
+ const charts: Array<HTMLElement> = await renderLoadedInsights();
840
+
841
+ await dragAcross(charts[1]!, at("11:20"), at("11:40"));
842
+ expect(zoomHints()).toHaveLength(2);
843
+ for (const hint of zoomHints()) {
844
+ expect(hint).toHaveTextContent(TIME_RANGE_ZOOM_HINT_RESET_TEXT);
845
+ }
846
+
847
+ await doubleClick(zoomCharts()[0]!);
848
+ for (const hint of zoomHints()) {
849
+ expect(hint).toHaveTextContent(TIME_RANGE_ZOOM_HINT_TEXT);
850
+ }
851
+ });
852
+
853
+ test("node Metrics tab: a hint in each rate chart's heading", async () => {
854
+ await renderNodeMetrics();
855
+
856
+ expectOneHintPerRateChart(["Network Throughput", "Disk Throughput"]);
857
+ for (const hint of zoomHints()) {
858
+ // The heading's own weight stays off the hint.
859
+ expect(hint).toHaveClass("ml-auto", "font-normal");
860
+ }
861
+ });
862
+
863
+ test("guest Metrics tab: a hint in each rate chart's heading", async () => {
864
+ mockLastParam = "qemu%2F100";
865
+ render(<ProxmoxClusterGuestDetail {...PAGE_PROPS} />);
866
+ await flush();
867
+ fireEvent.click(screen.getByRole("tab", { name: "Metrics" }));
868
+ await flush();
869
+
870
+ expectOneHintPerRateChart(["Network Throughput", "Disk Throughput"]);
871
+ });
872
+ });
873
+
874
+ // ------------------------------------ ProxmoxRateChart in a card of its own
875
+
876
+ describe("ProxmoxRateChart in a card on a Custom window", () => {
877
+ function renderCard(): void {
878
+ render(
879
+ <EmbeddedMetricCard
880
+ title="Network"
881
+ defaultTimeRange={{
882
+ range: TimeRange.CUSTOM,
883
+ startAndEndDate: new InBetween<Date>(at("11:20"), at("11:30")),
884
+ }}
885
+ >
886
+ <ProxmoxRateChart
887
+ clusterName="pve-prod"
888
+ series={[
889
+ { metricName: "pve_network_receive_bytes", label: "Receive" },
890
+ { metricName: "pve_network_transmit_bytes", label: "Transmit" },
891
+ ]}
892
+ startDate={at("11:20")}
893
+ endDate={at("11:30")}
894
+ heightInPx={240}
895
+ />
896
+ </EmbeddedMetricCard>,
897
+ );
898
+ }
899
+
900
+ test("a failed first load is retried by the card's Refresh: a skeleton at the given height while it runs, then the chart", async () => {
901
+ failingRateEnds = [at("11:30").getTime()];
902
+ renderCard();
903
+ await flush();
904
+
905
+ expect(screen.getByText(RATE_ERROR)).toBeInTheDocument();
906
+ expect(zoomCharts()).toHaveLength(0);
907
+ expect(callsOver(NETWORK_METRICS, at("11:20"), at("11:30"))).toBe(2);
908
+
909
+ failingRateEnds = [];
910
+ holdRates = true;
911
+ pressCardRefresh();
912
+ await flush();
913
+
914
+ expect(callsOver(NETWORK_METRICS, at("11:20"), at("11:30"))).toBe(4);
915
+ expect(skeletons()).toHaveLength(1);
916
+ expect(skeletons()[0]).toHaveStyle({ height: "240px" });
917
+
918
+ await releaseAllRates();
919
+
920
+ expect(zoomCharts()).toHaveLength(1);
921
+ expect(chartWindows()).toEqual([ZOOM]);
922
+ });
923
+
924
+ test("outside any card, nothing but its window reloads it", async () => {
925
+ render(
926
+ <ProxmoxRateChart
927
+ clusterName="pve-prod"
928
+ series={[{ metricName: "pve_network_receive_bytes", label: "Receive" }]}
929
+ startDate={at("11:20")}
930
+ endDate={at("11:30")}
931
+ />,
932
+ );
933
+ await flush();
934
+
935
+ expect(zoomCharts()).toHaveLength(1);
936
+ expect(
937
+ callsOver(["pve_network_receive_bytes"], at("11:20"), at("11:30")),
938
+ ).toBe(1);
939
+ });
940
+
941
+ test("a retry never moves the page: the error, the retry's skeleton and the error again all hold the chart's height", async () => {
942
+ failingRateEnds = [at("11:30").getTime()];
943
+ renderCard();
944
+ await flush();
945
+ expect(errorBoxes()).toHaveLength(1);
946
+ expect(errorBoxes()[0]).toHaveStyle({ minHeight: "240px" });
947
+
948
+ holdRates = true;
949
+ pressCardRefresh();
950
+ await flush();
951
+
952
+ expect(errorBoxes()).toHaveLength(0);
953
+ expect(skeletons()[0]).toHaveStyle({ height: "240px" });
954
+
955
+ await releaseAllRates();
956
+
957
+ expect(errorBoxes()).toHaveLength(1);
958
+ expect(errorBoxes()[0]).toHaveStyle({ minHeight: "240px" });
959
+ });
960
+ });
961
+
962
+ // ----------------------------- a rate chart with nothing drawn, failed
963
+
964
+ // The box around each rate chart's error when it has nothing to draw.
965
+ function errorBoxes(): Array<HTMLElement> {
966
+ return screen.queryAllByText(RATE_ERROR).map((error: HTMLElement) => {
967
+ return error.parentElement as HTMLElement;
968
+ });
969
+ }
970
+
971
+ describe("A Proxmox rate chart that failed with nothing drawn keeps its place and takes the way back", () => {
972
+ test("on Insights, the error holds the chart's 300px and selects no text; unzoomed, a double-click does nothing", async () => {
973
+ failingRateEnds = [NOW.getTime()];
974
+ await renderInsights();
975
+
976
+ expect(zoomCharts()).toHaveLength(0);
977
+ const boxes: Array<HTMLElement> = errorBoxes();
978
+ expect(boxes).toHaveLength(2);
979
+ for (const box of boxes) {
980
+ expect(box).toHaveStyle({ minHeight: "300px" });
981
+ expect(box).toHaveClass("select-none");
982
+ }
983
+ const calls: number = rateCallCount();
984
+
985
+ fireEvent.doubleClick(screen.getAllByText(RATE_ERROR)[0]!);
986
+ await flush();
987
+
988
+ // Not zoomed, so there is nothing to go back to.
989
+ expect(rateCallCount()).toBe(calls);
990
+ expect(cardPickers()[0]).toHaveTextContent(TimeRange.PAST_ONE_HOUR);
991
+ expect(errorBoxes()).toHaveLength(2);
992
+ });
993
+
994
+ test("zoomed from another card, a double-click on the error resets every card and the charts draw the hour", async () => {
995
+ failingRateEnds = [NOW.getTime(), at("11:30").getTime()];
996
+ await renderInsights();
997
+ await dragAcross(metricViews()[0]!, at("11:20"), at("11:30"));
998
+
999
+ expect(chartWindows(metricViews())).toEqual(same(ZOOM, 4));
1000
+ expect(callsOver(RATE_METRICS, at("11:20"), at("11:30"))).toBe(4);
1001
+ expect(errorBoxes()).toHaveLength(2);
1002
+
1003
+ failingRateEnds = [];
1004
+ fireEvent.doubleClick(screen.getAllByText(RATE_ERROR)[1]!);
1005
+ await flush();
1006
+
1007
+ expect(cardPickers()[0]).toHaveTextContent(TimeRange.PAST_ONE_HOUR);
1008
+ expect(chartWindows(metricViews())).toEqual(same(HOUR, 4));
1009
+ expect(resetZoomButtons()).toHaveLength(0);
1010
+ expect(errorBoxes()).toHaveLength(0);
1011
+ expect(zoomCharts()).toHaveLength(2);
1012
+ expect(chartWindows()).toEqual(same(HOUR, 2));
1013
+ });
1014
+ });
1015
+
1016
+ // ----------------------- ProxmoxRateChart rerendered for another resource
1017
+
1018
+ /*
1019
+ * Runs its callback in the layout phase of every commit that rerenders it:
1020
+ * after that commit's DOM changes and before any effect can follow them up,
1021
+ * so it sees what the reader could see for a frame.
1022
+ */
1023
+ function CommitProbe(props: { onCommit: () => void }): null {
1024
+ React.useLayoutEffect((): void => {
1025
+ props.onCommit();
1026
+ });
1027
+ return null;
1028
+ }
1029
+
1030
+ describe("ProxmoxRateChart keeps its last chart, and its error, only for the resource they came from", () => {
1031
+ interface ResourceChartOptions {
1032
+ id: string;
1033
+ start: Date;
1034
+ end: Date;
1035
+ clusterName?: string | undefined;
1036
+ }
1037
+
1038
+ // The page's text after each commit of the tree around the chart.
1039
+ let commits: Array<string> = [];
1040
+
1041
+ // A node's or guest's Network chart as its Metrics tab draws it.
1042
+ function resourceChart(options: ResourceChartOptions): React.ReactElement {
1043
+ return (
1044
+ <>
1045
+ <ProxmoxRateChart
1046
+ clusterName={options.clusterName ?? "pve-prod"}
1047
+ series={[
1048
+ { metricName: "pve_network_receive_bytes", label: "Receive" },
1049
+ { metricName: "pve_network_transmit_bytes", label: "Transmit" },
1050
+ ]}
1051
+ extraAttributes={{ id: options.id }}
1052
+ startDate={options.start}
1053
+ endDate={options.end}
1054
+ />
1055
+ <CommitProbe
1056
+ onCommit={(): void => {
1057
+ commits.push(document.body.textContent || "");
1058
+ }}
1059
+ />
1060
+ </>
1061
+ );
1062
+ }
1063
+
1064
+ function heldAttribute(name: string): Array<unknown> {
1065
+ return heldRates.map((held: HeldRateLoad): unknown => {
1066
+ return held.call.aggregateBy.query.attributes[name];
1067
+ });
1068
+ }
1069
+
1070
+ async function releaseRatesFor(id: string): Promise<void> {
1071
+ const releasing: Array<HeldRateLoad> = heldRates.filter(
1072
+ (held: HeldRateLoad): boolean => {
1073
+ return held.call.aggregateBy.query.attributes["id"] === id;
1074
+ },
1075
+ );
1076
+ heldRates = heldRates.filter((held: HeldRateLoad): boolean => {
1077
+ return !releasing.includes(held);
1078
+ });
1079
+ expect(releasing.length).toBeGreaterThan(0);
1080
+ for (const held of releasing) {
1081
+ held.release();
1082
+ }
1083
+ await flush();
1084
+ }
1085
+
1086
+ beforeEach(() => {
1087
+ commits = [];
1088
+ });
1089
+
1090
+ test("a new window for the same guest keeps the chart, on its last window, until the load lands", async () => {
1091
+ const { rerender } = render(
1092
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1093
+ );
1094
+ await flush();
1095
+ const charts: Array<HTMLElement> = zoomCharts();
1096
+ expect(charts).toHaveLength(1);
1097
+ holdRates = true;
1098
+
1099
+ rerender(
1100
+ resourceChart({ id: "qemu/100", start: at("11:20"), end: at("11:30") }),
1101
+ );
1102
+ await flush();
1103
+
1104
+ expectSameCharts(charts);
1105
+ expect(chartWindows()).toEqual([HOUR]);
1106
+ expect(refreshingMarkers()).toHaveLength(1);
1107
+ expect(skeletons()).toHaveLength(0);
1108
+
1109
+ await releaseAllRates();
1110
+
1111
+ expectSameCharts(charts);
1112
+ expect(chartWindows()).toEqual([ZOOM]);
1113
+ });
1114
+
1115
+ test("another guest starts over from a skeleton at the chart's height, never drawing the last one's rates", async () => {
1116
+ const { rerender } = render(
1117
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1118
+ );
1119
+ await flush();
1120
+ expect(zoomCharts()).toHaveLength(1);
1121
+ holdRates = true;
1122
+ commits = [];
1123
+
1124
+ rerender(resourceChart({ id: "qemu/101", start: at("11:00"), end: NOW }));
1125
+
1126
+ // Already the switch's own commit draws no chart under guest 101.
1127
+ expect(commits.length).toBeGreaterThan(0);
1128
+ for (const text of commits) {
1129
+ expect(text).not.toContain("Drag across the chart");
1130
+ }
1131
+ expect(zoomCharts()).toHaveLength(0);
1132
+ expect(skeletons()).toHaveLength(1);
1133
+ expect(skeletons()[0]).toHaveStyle({ height: "300px" });
1134
+ expect(refreshingMarkers()).toHaveLength(0);
1135
+ expect(heldAttribute("id")).toEqual(["qemu/101", "qemu/101"]);
1136
+
1137
+ await releaseAllRates();
1138
+
1139
+ expect(skeletons()).toHaveLength(0);
1140
+ expect(zoomCharts()).toHaveLength(1);
1141
+ });
1142
+
1143
+ test("another cluster starts over from the skeleton too", async () => {
1144
+ const { rerender } = render(
1145
+ resourceChart({ id: "node/pve1", start: at("11:00"), end: NOW }),
1146
+ );
1147
+ await flush();
1148
+ holdRates = true;
1149
+
1150
+ rerender(
1151
+ resourceChart({
1152
+ id: "node/pve1",
1153
+ start: at("11:00"),
1154
+ end: NOW,
1155
+ clusterName: "pve-dr",
1156
+ }),
1157
+ );
1158
+ await flush();
1159
+
1160
+ expect(zoomCharts()).toHaveLength(0);
1161
+ expect(skeletons()).toHaveLength(1);
1162
+ expect(heldAttribute("resource.proxmox.cluster.name")).toEqual([
1163
+ "pve-dr",
1164
+ "pve-dr",
1165
+ ]);
1166
+
1167
+ await releaseAllRates();
1168
+
1169
+ expect(zoomCharts()).toHaveLength(1);
1170
+ });
1171
+
1172
+ test("a slow load for the last guest cannot paint over the next one", async () => {
1173
+ holdRates = true;
1174
+ const { rerender } = render(
1175
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1176
+ );
1177
+ await flush();
1178
+ rerender(resourceChart({ id: "qemu/101", start: at("11:00"), end: NOW }));
1179
+ await flush();
1180
+
1181
+ await releaseRatesFor("qemu/100");
1182
+
1183
+ expect(zoomCharts()).toHaveLength(0);
1184
+ expect(skeletons()).toHaveLength(1);
1185
+
1186
+ await releaseRatesFor("qemu/101");
1187
+
1188
+ expect(zoomCharts()).toHaveLength(1);
1189
+ });
1190
+
1191
+ test("a failed first load for one guest is never shown under another, not even for a frame", async () => {
1192
+ failingRateEnds = [NOW.getTime()];
1193
+ const { rerender } = render(
1194
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1195
+ );
1196
+ await flush();
1197
+ expect(screen.getByText(RATE_ERROR)).toBeInTheDocument();
1198
+ holdRates = true;
1199
+ commits = [];
1200
+
1201
+ rerender(resourceChart({ id: "qemu/101", start: at("11:00"), end: NOW }));
1202
+
1203
+ expect(commits.length).toBeGreaterThan(0);
1204
+ for (const text of commits) {
1205
+ expect(text).not.toContain(RATE_ERROR);
1206
+ }
1207
+ expect(skeletons()).toHaveLength(1);
1208
+ expect(screen.queryByText(RATE_ERROR)).toBeNull();
1209
+ });
1210
+
1211
+ test("a failed reload's error stays with its guest: another guest's first frame is only its skeleton", async () => {
1212
+ const { rerender } = render(
1213
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1214
+ );
1215
+ await flush();
1216
+ failingRateEnds = [at("11:30").getTime()];
1217
+ rerender(
1218
+ resourceChart({ id: "qemu/100", start: at("11:20"), end: at("11:30") }),
1219
+ );
1220
+ await flush();
1221
+ expect(zoomCharts()).toHaveLength(1);
1222
+ expect(screen.getByRole("alert")).toHaveTextContent(RATE_ERROR);
1223
+ holdRates = true;
1224
+ commits = [];
1225
+
1226
+ rerender(
1227
+ resourceChart({ id: "qemu/101", start: at("11:20"), end: at("11:30") }),
1228
+ );
1229
+
1230
+ expect(commits.length).toBeGreaterThan(0);
1231
+ for (const text of commits) {
1232
+ expect(text).not.toContain(RATE_ERROR);
1233
+ }
1234
+ expect(zoomCharts()).toHaveLength(0);
1235
+ expect(skeletons()).toHaveLength(1);
1236
+ expect(screen.queryByRole("alert")).toBeNull();
1237
+ });
1238
+
1239
+ test("a guest's own failure shows once its load for that guest fails", async () => {
1240
+ const { rerender } = render(
1241
+ resourceChart({ id: "qemu/100", start: at("11:00"), end: NOW }),
1242
+ );
1243
+ await flush();
1244
+ failingRateEnds = [NOW.getTime()];
1245
+
1246
+ rerender(resourceChart({ id: "qemu/101", start: at("11:00"), end: NOW }));
1247
+ await flush();
1248
+
1249
+ expect(zoomCharts()).toHaveLength(0);
1250
+ expect(skeletons()).toHaveLength(0);
1251
+ expect(screen.getByText(RATE_ERROR)).toBeInTheDocument();
1252
+ });
1253
+ });