@oneuptime/common 13.0.7 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (759) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  7. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  8. package/Scripts/generate-service-worker.js +65 -4
  9. package/Server/API/AIBillingAPI.ts +1 -0
  10. package/Server/API/AIInsightAPI.ts +1 -4
  11. package/Server/API/AIInvestigationAPI.ts +1 -4
  12. package/Server/API/AIReadinessAPI.ts +4 -5
  13. package/Server/API/AlertAPI.ts +1 -0
  14. package/Server/API/AutoRemediationAPI.ts +1 -4
  15. package/Server/API/BaseAPI.ts +34 -0
  16. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  17. package/Server/API/BillingAPI.ts +2 -0
  18. package/Server/API/BillingInvoiceAPI.ts +1 -0
  19. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  20. package/Server/API/CodeFixRunAPI.ts +1 -3
  21. package/Server/API/CommonAPI.ts +68 -4
  22. package/Server/API/DashboardAPI.ts +12 -12
  23. package/Server/API/GlobalConfigAPI.ts +243 -460
  24. package/Server/API/IncidentAPI.ts +2 -0
  25. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  26. package/Server/API/NotificationAPI.ts +1 -0
  27. package/Server/API/OnCallCalendarAPI.ts +4 -5
  28. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  29. package/Server/API/OnCallReadinessAPI.ts +4 -1
  30. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  31. package/Server/API/ProjectAPI.ts +1 -0
  32. package/Server/API/ProjectOIDC.ts +25 -16
  33. package/Server/API/ProjectSSO.ts +24 -16
  34. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  35. package/Server/API/StatusPageAPI.ts +261 -74
  36. package/Server/API/TeamMemberAPI.ts +13 -1
  37. package/Server/API/UserCallAPI.ts +2 -0
  38. package/Server/API/UserEmailAPI.ts +2 -0
  39. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  40. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  41. package/Server/API/UserSlackAPI.ts +1 -0
  42. package/Server/API/UserSmsAPI.ts +2 -0
  43. package/Server/API/UserTotpAuthAPI.ts +1 -0
  44. package/Server/API/UserWebAuthnAPI.ts +2 -0
  45. package/Server/API/UserWebhookAPI.ts +1 -0
  46. package/Server/API/UserWhatsAppAPI.ts +2 -0
  47. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  48. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  49. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  50. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  51. package/Server/EnvironmentConfig.ts +147 -2
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  54. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  55. package/Server/Middleware/UserAuthorization.ts +117 -21
  56. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  57. package/Server/Services/AuditLogService.ts +84 -1079
  58. package/Server/Services/DatabaseService.ts +245 -0
  59. package/Server/Services/DetectionRuleService.ts +5 -4
  60. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  61. package/Server/Services/GlobalConfigService.ts +80 -1
  62. package/Server/Services/MonitorProbeService.ts +116 -20
  63. package/Server/Services/MonitorService.ts +56 -4
  64. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  65. package/Server/Services/MonitorTemplateService.ts +26 -0
  66. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  67. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  69. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  70. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  71. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  72. package/Server/Services/ProjectService.ts +302 -0
  73. package/Server/Services/RumSessionPinService.ts +293 -22
  74. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  75. package/Server/Services/StatusPageService.ts +78 -1
  76. package/Server/Services/TeamMemberService.ts +11 -0
  77. package/Server/Services/TeamPermissionService.ts +120 -26
  78. package/Server/Services/TeamService.ts +10 -0
  79. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  80. package/Server/Services/UserService.ts +10 -17
  81. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  82. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  83. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  84. package/Server/Types/Database/Permissions/Index.ts +75 -11
  85. package/Server/Types/Database/Permissions/TablePermission.ts +34 -31
  86. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  87. package/Server/Types/Markdown.ts +41 -0
  88. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  89. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  90. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  91. package/Server/Utils/EditionEnforcement.ts +233 -0
  92. package/Server/Utils/FrontendEnvironment.ts +41 -2
  93. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  94. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  95. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  96. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  97. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  98. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  99. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  100. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  101. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  102. package/Server/Utils/Realtime.ts +163 -45
  103. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  104. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  105. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  106. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  107. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  108. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  109. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  110. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  111. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  112. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  113. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  114. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  115. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  116. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  117. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  118. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  119. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  120. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  121. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  122. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  123. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  124. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  125. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  126. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  127. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  128. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  129. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  130. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  131. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  132. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  133. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  134. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  135. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  136. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  137. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  138. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  139. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  140. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +133 -70
  141. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  142. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  143. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  144. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -58
  145. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  146. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  147. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  148. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  149. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  150. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  151. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  152. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  153. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  154. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  155. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  156. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  157. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  158. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  159. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  160. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  161. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  162. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  163. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  164. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  165. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  166. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  167. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  168. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  169. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  170. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  171. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  172. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  173. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  174. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  175. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  176. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  177. package/Tests/Server/API/GlobalConfigLicense.test.ts +766 -381
  178. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  179. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  180. package/Tests/Server/API/OnCallReadinessAPI.test.ts +196 -912
  181. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  182. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  183. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  184. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  185. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  186. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  187. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  188. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  189. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  190. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  191. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  192. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  193. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  194. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  195. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  196. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  197. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  198. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  199. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  200. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  201. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  202. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  203. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  204. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  205. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  206. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  207. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  208. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  209. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  210. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  211. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  212. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  213. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  214. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  215. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  216. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  217. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  218. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  219. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  220. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  221. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  222. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  223. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  224. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  225. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  226. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  227. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  228. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  229. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  230. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  231. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  232. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  233. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  234. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  235. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  236. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  237. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  238. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  239. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  240. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  241. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  242. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  243. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  244. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  245. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  246. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  247. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  248. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  249. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  250. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  251. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  252. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  253. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  254. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  255. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  256. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  257. package/Tests/Server/Types/Markdown.test.ts +181 -0
  258. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  259. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  260. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  261. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  262. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  263. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  264. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  265. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  266. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  267. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  268. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  269. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  270. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  271. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  272. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  273. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  274. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  275. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  276. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  277. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  278. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  279. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  280. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  281. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  282. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  283. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  284. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  285. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  286. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  287. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  288. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  289. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  290. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  291. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  292. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  293. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  294. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  295. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  296. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  297. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  298. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  299. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  300. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  301. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  302. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  303. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  304. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  305. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  306. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +165 -171
  307. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  308. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  309. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  310. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  311. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  312. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  313. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  314. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  315. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  316. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  317. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  318. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  319. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  320. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  321. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  322. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  323. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  324. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  325. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  326. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  327. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  328. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  329. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  330. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  331. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  332. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  333. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  334. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  335. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  336. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  337. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  338. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  339. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  340. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  341. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  342. package/Types/API/Hostname.ts +63 -16
  343. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  344. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  345. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  346. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  347. package/Types/Database/CanonicalModelInstance.ts +102 -0
  348. package/Types/Database/TableColumn.ts +20 -6
  349. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  350. package/Types/IP/IPv6.ts +6 -1
  351. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  352. package/Types/Monitor/MonitorStep.ts +6 -1
  353. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  354. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  355. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  356. package/Types/Realtime/EventName.ts +11 -0
  357. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  358. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  359. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  360. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  361. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  362. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  363. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  364. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  365. package/UI/Components/EditionLabel/EditionLabel.tsx +808 -1195
  366. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  367. package/UI/Components/EventItem/EventItem.tsx +12 -1
  368. package/UI/Components/Forms/BasicForm.tsx +151 -126
  369. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  370. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  371. package/UI/Components/Forms/FormSummary.tsx +7 -0
  372. package/UI/Components/Forms/Types/Field.ts +10 -0
  373. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  374. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  375. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  376. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  377. package/UI/Components/ModelList/ModelList.tsx +11 -1
  378. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  379. package/UI/Components/ModelTable/TableView.tsx +1 -1
  380. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  381. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  382. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  383. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  384. package/UI/Utils/API/API.ts +173 -12
  385. package/UI/Utils/API/RequestOptions.ts +11 -0
  386. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  387. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  388. package/UI/Utils/Navigation.ts +9 -0
  389. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  390. package/UI/Utils/Realtime.ts +176 -30
  391. package/UI/esbuild-enterprise.js +263 -0
  392. package/Utils/API.ts +14 -5
  393. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  394. package/Utils/HostAddressUtil.ts +94 -0
  395. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  396. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  397. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  398. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  399. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  400. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  401. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  402. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  404. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  406. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  408. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  410. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  412. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  414. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  415. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  416. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  417. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  418. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  419. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  420. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  421. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  422. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  423. package/build/dist/Server/API/AlertAPI.js +1 -1
  424. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  425. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  426. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  427. package/build/dist/Server/API/BaseAPI.js +33 -0
  428. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  429. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  430. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  431. package/build/dist/Server/API/BillingAPI.js +2 -2
  432. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  433. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  434. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  435. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  436. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  437. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  438. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  439. package/build/dist/Server/API/CommonAPI.js +50 -5
  440. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  441. package/build/dist/Server/API/DashboardAPI.js +12 -12
  442. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  443. package/build/dist/Server/API/GlobalConfigAPI.js +158 -342
  444. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  445. package/build/dist/Server/API/IncidentAPI.js +2 -2
  446. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  447. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  448. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  449. package/build/dist/Server/API/NotificationAPI.js +1 -1
  450. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  451. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  452. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  453. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  454. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  455. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  456. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  457. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  458. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  459. package/build/dist/Server/API/ProjectAPI.js +1 -1
  460. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  461. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  462. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  463. package/build/dist/Server/API/ProjectSSO.js +24 -16
  464. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  465. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  466. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  467. package/build/dist/Server/API/StatusPageAPI.js +218 -76
  468. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  469. package/build/dist/Server/API/TeamMemberAPI.js +11 -4
  470. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  471. package/build/dist/Server/API/UserCallAPI.js +2 -2
  472. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  473. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  474. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  475. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  476. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  477. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  478. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  479. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  480. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  481. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  482. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  483. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  484. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  485. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  486. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  487. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  488. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  489. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  490. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  491. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  492. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  493. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  494. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  495. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  496. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  497. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  498. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  499. package/build/dist/Server/EnvironmentConfig.js +84 -2
  500. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  501. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  502. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  503. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  504. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  505. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  506. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  507. package/build/dist/Server/Middleware/UserAuthorization.js +94 -15
  508. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  509. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  510. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  511. package/build/dist/Server/Services/AuditLogService.js +71 -740
  512. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  513. package/build/dist/Server/Services/DatabaseService.js +171 -0
  514. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  515. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  516. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  517. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  518. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  519. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  520. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  521. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  522. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  523. package/build/dist/Server/Services/MonitorService.js +45 -4
  524. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  525. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  526. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  527. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  528. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  529. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  530. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  531. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  532. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  533. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  534. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  535. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  536. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  537. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  538. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  539. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  540. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  541. package/build/dist/Server/Services/ProjectService.js +231 -0
  542. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  543. package/build/dist/Server/Services/RumSessionPinService.js +198 -16
  544. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  545. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  546. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  547. package/build/dist/Server/Services/StatusPageService.js +71 -0
  548. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  549. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  550. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  551. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  552. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  553. package/build/dist/Server/Services/TeamService.js +9 -0
  554. package/build/dist/Server/Services/TeamService.js.map +1 -1
  555. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  556. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  557. package/build/dist/Server/Services/UserService.js +10 -16
  558. package/build/dist/Server/Services/UserService.js.map +1 -1
  559. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  560. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  561. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  562. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  563. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  564. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  565. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  566. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  567. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +24 -16
  568. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  569. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  570. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  571. package/build/dist/Server/Types/Markdown.js +31 -0
  572. package/build/dist/Server/Types/Markdown.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  574. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  576. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  577. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  578. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  579. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  580. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  581. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  582. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  583. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  584. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  585. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  586. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  587. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  588. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  589. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  590. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  591. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  592. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  593. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  594. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  595. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  596. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  597. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  598. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  599. package/build/dist/Server/Utils/Realtime.js +126 -37
  600. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  601. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  602. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  603. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  604. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  605. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  606. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  607. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  608. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  609. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  610. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  611. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  612. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  613. package/build/dist/Types/API/Hostname.js +59 -11
  614. package/build/dist/Types/API/Hostname.js.map +1 -1
  615. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  616. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  617. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  618. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  619. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  620. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  621. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  622. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  623. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  624. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  625. package/build/dist/Types/Database/TableColumn.js +19 -6
  626. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  627. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  628. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  629. package/build/dist/Types/IP/IPv6.js +6 -1
  630. package/build/dist/Types/IP/IPv6.js.map +1 -1
  631. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  632. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  633. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  634. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  635. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  636. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  637. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  638. package/build/dist/Types/Realtime/EventName.js +10 -0
  639. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  640. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  641. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  642. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  643. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  644. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  645. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  646. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  647. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  648. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  649. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  650. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  651. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  652. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  653. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  654. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  655. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  656. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +425 -585
  657. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  658. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  659. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  660. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  661. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  662. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  663. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  664. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  665. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  666. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  667. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  668. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  669. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  670. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  671. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  672. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  673. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  674. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  675. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  676. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  677. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  678. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  679. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  680. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  681. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  682. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  683. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  684. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  685. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  686. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  687. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  688. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  689. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  690. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  691. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  692. package/build/dist/UI/Utils/API/API.js +143 -19
  693. package/build/dist/UI/Utils/API/API.js.map +1 -1
  694. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  695. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  696. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  697. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  698. package/build/dist/UI/Utils/Navigation.js +8 -0
  699. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  700. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  701. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  702. package/build/dist/UI/Utils/Realtime.js +120 -15
  703. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  704. package/build/dist/Utils/API.js +13 -5
  705. package/build/dist/Utils/API.js.map +1 -1
  706. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  707. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  708. package/build/dist/Utils/HostAddressUtil.js +80 -0
  709. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  710. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  711. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  712. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  713. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  714. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  715. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  716. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  717. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  718. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  719. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  720. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  721. package/jest.config.json +3 -1
  722. package/package.json +1 -1
  723. package/test-setup.sh +103 -34
  724. package/tsconfig.json +13 -0
  725. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  726. package/Server/API/TeamComplianceAPI.ts +0 -158
  727. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  728. package/Server/Services/TeamComplianceService.ts +0 -733
  729. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  730. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  731. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  732. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  733. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  734. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  735. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  736. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  737. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  738. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  739. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  740. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  741. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  742. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  743. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  744. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  745. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  746. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  747. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  748. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  749. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  750. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  751. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  752. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  753. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  754. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  755. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  756. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  757. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  758. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  759. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -6,7 +6,11 @@ import MetricMonitorResponse, {
6
6
  VMwareAffectedResource,
7
7
  CephAffectedResource,
8
8
  KubernetesAffectedResource,
9
+ DockerSwarmAffectedResource,
9
10
  } from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
11
+ import Markdown, {
12
+ MarkdownContentType,
13
+ } from "../../../../Server/Types/Markdown";
10
14
  import {
11
15
  getProxmoxAlertTemplateById,
12
16
  ProxmoxAlertTemplate,
@@ -36,12 +40,14 @@ import FilterCondition from "../../../../Types/Filter/FilterCondition";
36
40
  * the incident root-cause context. These tests drive the evaluator's
37
41
  * Proxmox/Ceph branches directly and lock in the render contract:
38
42
  *
39
- * - Proxmox table is Resource / Type / Node / Value; Ceph table is
40
- * Daemon / Pool / Host / Value,
43
+ * - the breakdown is a ranked list, not a table: one numbered item per
44
+ * resource titled "**Kind** `name` — **value**", with the rest of
45
+ * its identity (a Proxmox guest's node, a Ceph daemon's pool and
46
+ * host, a vSphere object's host and cluster) as nested bullets,
41
47
  * - zero-value rows are dropped (supplementary context — the
42
48
  * per-series criteria still alert on them), worst value first,
43
49
  * top 10 with an "... and N more" suffix,
44
- * - identity-less (cluster-wide) breakdowns render NO table and fall
50
+ * - identity-less (cluster-wide) breakdowns render NO list and fall
45
51
  * back to the metric summary,
46
52
  * - the cluster context lines surface the monitor step's
47
53
  * clusterIdentifier and resource filters (the pve.scope / pve.id /
@@ -68,6 +74,16 @@ type EvaluatorPrivate = {
68
74
  monitor: Monitor;
69
75
  criteriaInstance?: MonitorCriteriaInstance | undefined;
70
76
  }) => string | null;
77
+ buildKubernetesRootCauseContext: (input: {
78
+ dataToProcess: unknown;
79
+ monitorStep: MonitorStep;
80
+ monitor: Monitor;
81
+ }) => Promise<string | null>;
82
+ buildDockerSwarmRootCauseContext: (input: {
83
+ dataToProcess: unknown;
84
+ monitorStep: MonitorStep;
85
+ monitor: Monitor;
86
+ }) => string | null;
71
87
  buildKubernetesRootCauseAnalysis: (input: {
72
88
  breakdown: {
73
89
  clusterName: string;
@@ -142,12 +158,12 @@ function metricResponse(
142
158
  }
143
159
 
144
160
  describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
145
- test("renders the Resource/Type/Node/Value table: zero rows dropped, worst first", () => {
161
+ test("renders a ranked list of kind, name and node: zero rows dropped, worst first", () => {
146
162
  const affectedResources: Array<ProxmoxAffectedResource> = [
147
163
  /*
148
164
  * pve_cpu_usage_ratio is a [0, 1] ratio — the catalog says so and
149
165
  * pve-exporter reports it that way — so these are 42% and 97% of a
150
- * node's CPU, and the table is expected to say exactly that.
166
+ * node's CPU, and the list is expected to say exactly that.
151
167
  */
152
168
  {
153
169
  resourceId: "qemu/100",
@@ -165,7 +181,7 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
165
181
  nodeName: "pve2",
166
182
  metricValue: 0.97,
167
183
  },
168
- // Zero-value row — must be dropped from the table.
184
+ // Zero-value row — must be dropped from the list.
169
185
  {
170
186
  resourceId: "qemu/102",
171
187
  resourceName: "idle-vm",
@@ -195,7 +211,9 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
195
211
  expect(context).toContain("- Cluster: prod-cluster");
196
212
  expect(context).toContain("- Metric: CPU Usage (`pve_cpu_usage_ratio`)");
197
213
 
198
- expect(context).toContain("| Resource | Type | Node | Value |");
214
+ // A list, not a table: no GFM table row survives anywhere.
215
+ expect(context).not.toContain("| --- |");
216
+ expect(context).not.toMatch(/^\|/m);
199
217
  // Worst (0.97) sorts above 0.42; the zero row is gone entirely.
200
218
  const dbIndex: number = context!.indexOf("`db-vm` (`qemu/101`)");
201
219
  const webIndex: number = context!.indexOf("`web-vm` (`qemu/100`)");
@@ -205,20 +223,26 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
205
223
  expect(context).toContain("**Affected Resources** (2 total)");
206
224
 
207
225
  /*
208
- * The Value column is rendered in the unit the catalog declares for
209
- * the metric — a "ratio" on a `_ratio`-suffixed metric is a fraction,
210
- * so the reader gets "97.00%" rather than the bare "0.97" they would
211
- * otherwise have to recognise as a percentage themselves.
226
+ * The value is rendered in the unit the catalog declares for the
227
+ * metric — a "ratio" on a `_ratio`-suffixed metric is a fraction, so
228
+ * the reader gets "97.00%" rather than the bare "0.97" they would
229
+ * otherwise have to recognise as a percentage themselves. A qemu
230
+ * guest is titled "Virtual Machine", and the node it runs on sits
231
+ * under it.
212
232
  */
213
233
  expect(context).toContain(
214
- "| `db-vm` (`qemu/101`) | qemu | `pve2` | **97.00%** |",
215
- );
216
- expect(context).toContain(
217
- "| `web-vm` (`qemu/100`) | qemu | `pve1` | **42.00%** |",
234
+ [
235
+ "**Affected Resources** (2 total)",
236
+ "",
237
+ "1. **Virtual Machine** `db-vm` (`qemu/101`) — **97.00%**",
238
+ " - Node: `pve2`",
239
+ "2. **Virtual Machine** `web-vm` (`qemu/100`) — **42.00%**",
240
+ " - Node: `pve1`",
241
+ ].join("\n"),
218
242
  );
219
243
  });
220
244
 
221
- test("renders bytes in the Value column at human scale", () => {
245
+ test("renders byte values at human scale", () => {
222
246
  const context: string | null = Evaluator.buildProxmoxRootCauseContext({
223
247
  dataToProcess: metricResponse({
224
248
  proxmoxResourceBreakdown: {
@@ -247,12 +271,12 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
247
271
  * 3am should not have to divide by 2^30 in their head.
248
272
  */
249
273
  expect(context).toContain(
250
- "| `db-vm` (`qemu/101`) | qemu | `pve2` | **8.59 GB** |",
274
+ "1. **Virtual Machine** `db-vm` (`qemu/101`) — **8.59 GB**",
251
275
  );
252
276
  expect(context).not.toContain("**8589934592**");
253
277
  });
254
278
 
255
- test("caps the table at 10 rows, worst first, with an overflow suffix", () => {
279
+ test("caps the list at 10 items, worst first, with an overflow suffix", () => {
256
280
  const affectedResources: Array<ProxmoxAffectedResource> = [];
257
281
  for (let i: number = 1; i <= 12; i++) {
258
282
  affectedResources.push({
@@ -286,13 +310,18 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
286
310
  expect(context).toContain("**Affected Resources** (12 total)");
287
311
  expect(context).toContain("*... and 2 more affected resources*");
288
312
  // The worst rows survive the cap; the mildest two are cut.
289
- expect(context).toContain("**12**");
290
- expect(context).toContain("**3**");
291
- expect(context).not.toContain("| `qemu/102` | qemu | - | **2** |");
292
- expect(context).not.toContain("| `qemu/101` | qemu | - | **1** |");
313
+ expect(context).toContain("1. **Virtual Machine** `qemu/112` — **12**");
314
+ expect(context).toContain("10. **Virtual Machine** `qemu/103` — **3**");
315
+ expect(context).not.toContain("`qemu/102`");
316
+ expect(context).not.toContain("`qemu/101`");
317
+ expect(context).not.toContain("11. ");
318
+ // The summary is its own paragraph, not a continuation of item 10.
319
+ expect(context).toContain(
320
+ "10. **Virtual Machine** `qemu/103` — **3**\n\n*... and 2 more affected resources*",
321
+ );
293
322
  });
294
323
 
295
- test("identity-less (cluster-wide) breakdowns render no table and fall back to the metric summary", () => {
324
+ test("identity-less (cluster-wide) breakdowns render no list and fall back to the metric summary", () => {
296
325
  const context: string | null = Evaluator.buildProxmoxRootCauseContext({
297
326
  dataToProcess: metricResponse({
298
327
  proxmoxResourceBreakdown: {
@@ -311,7 +340,8 @@ describe("MonitorCriteriaEvaluator - Proxmox root cause breakdown", () => {
311
340
  monitor: new Monitor(),
312
341
  });
313
342
 
314
- expect(context).not.toContain("| Resource | Type | Node | Value |");
343
+ expect(context).not.toContain("**Affected Resources**");
344
+ expect(context).not.toContain("1. ");
315
345
  expect(context).toContain("**Metric Summary**");
316
346
  expect(context).toContain("- 2 metric data point(s) returned");
317
347
  });
@@ -346,12 +376,13 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
346
376
  /*
347
377
  * VMware twin of the Proxmox block. The differences are deliberate:
348
378
  * identity comes from the vcenter receiver's RESOURCE attributes
349
- * (stored `resource.vcenter.*`), the table is
350
- * Resource / Kind / Host / Cluster / Value, and utilization metrics
351
- * are already 0–100 percentages (catalog unit "%") — never [0, 1]
352
- * ratios — so the Value column must print them as-is and never ×100.
379
+ * (stored `resource.vcenter.*`), each item is titled by the vSphere
380
+ * object (VM, host, pool, ...) with its host and cluster beneath it,
381
+ * and utilization metrics are already 0–100 percentages (catalog unit
382
+ * "%") — never [0, 1] ratios — so the value must print as-is and never
383
+ * ×100.
353
384
  */
354
- test("renders the Resource/Kind/Host/Cluster/Value table: zero rows dropped, worst first, % as-is", () => {
385
+ test("renders a ranked list of object, host and cluster: zero rows dropped, worst first, % as-is", () => {
355
386
  const affectedResources: Array<VMwareAffectedResource> = [
356
387
  {
357
388
  datacenterName: "DC1",
@@ -365,7 +396,7 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
365
396
  hostName: "esx-02",
366
397
  metricValue: 97.25,
367
398
  },
368
- // Zero-value row — must be dropped from the table.
399
+ // Zero-value row — must be dropped from the list.
369
400
  {
370
401
  datacenterName: "DC1",
371
402
  clusterName: "prod-cluster",
@@ -395,7 +426,7 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
395
426
  "- Metric: Host CPU Utilization (`vcenter.host.cpu.utilization`)",
396
427
  );
397
428
 
398
- expect(context).toContain("| Resource | Kind | Host | Cluster | Value |");
429
+ expect(context).not.toContain("| --- |");
399
430
  // Worst (97.25) sorts above 42.5; the zero row is gone entirely.
400
431
  const worstIndex: number = context!.indexOf("`esx-02`");
401
432
  const mildIndex: number = context!.indexOf("`esx-01`");
@@ -407,15 +438,18 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
407
438
  /*
408
439
  * The catalog declares "%" for the receiver's utilization metrics,
409
440
  * whose values are already percentages: 97.25 must read "97.25%",
410
- * never "9725.00%". A host IS the resource, so its Host column is
411
- * "-" and the Cluster column carries the cluster it belongs to.
441
+ * never "9725.00%". A host IS the resource, so there is no "Host:"
442
+ * detail repeating it — only the cluster it belongs to.
412
443
  */
413
444
  expect(context).toContain(
414
- "| `esx-02` | Host | - | `prod-cluster` | **97.25%** |",
415
- );
416
- expect(context).toContain(
417
- "| `esx-01` | Host | - | `prod-cluster` | **42.50%** |",
445
+ [
446
+ "1. **Host** `esx-02` — **97.25%**",
447
+ " - Cluster: `prod-cluster`",
448
+ "2. **Host** `esx-01` — **42.50%**",
449
+ " - Cluster: `prod-cluster`",
450
+ ].join("\n"),
418
451
  );
452
+ expect(context).not.toContain("- Host: `esx-0");
419
453
  expect(context).not.toContain("9725");
420
454
  });
421
455
 
@@ -441,11 +475,11 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
441
475
  monitor: new Monitor(),
442
476
  });
443
477
 
444
- expect(context).toContain("| `esx-01` | Host | - | - | **0.42%** |");
478
+ expect(context).toContain("1. **Host** `esx-01` — **0.42%**");
445
479
  expect(context).not.toContain("42.00%");
446
480
  });
447
481
 
448
- test("renders VM rows with their parent host and cluster, and bytes at human scale", () => {
482
+ test("renders VMs with their parent host and cluster, and bytes at human scale", () => {
449
483
  const context: string | null = Evaluator.buildVMwareRootCauseContext({
450
484
  dataToProcess: metricResponse({
451
485
  vmwareResourceBreakdown: {
@@ -480,22 +514,25 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
480
514
 
481
515
  /*
482
516
  * A VM row carries a resource pool AND a host, but it is still a
483
- * VM: the Kind column must say so (VM check runs first), the Host
484
- * column is the ESXi host running it, and the on-call engineer
485
- * should not have to divide by 2^30 in their head.
517
+ * VM: the title must say so (VM check runs first), the Host detail
518
+ * is the ESXi host running it, and the on-call engineer should not
519
+ * have to divide by 2^30 in their head. A VM on a standalone ESXi
520
+ * host has no cluster, so it simply has no Cluster detail.
486
521
  */
487
522
  expect(context).toContain(
488
- "| `db-01` | Virtual Machine | `esx-02` | `prod-cluster` | **8.59 GB** |",
489
- );
490
- // A VM on a standalone ESXi host has no cluster.
491
- expect(context).toContain(
492
- "| `lab-01` | Virtual Machine | `esx-standalone` | - | **1.07 GB** |",
523
+ [
524
+ "1. **Virtual Machine** `db-01` — **8.59 GB**",
525
+ " - Host: `esx-02`",
526
+ " - Cluster: `prod-cluster`",
527
+ "2. **Virtual Machine** `lab-01` — **1.07 GB**",
528
+ " - Host: `esx-standalone`",
529
+ ].join("\n"),
493
530
  );
494
531
  expect(context).not.toContain("**8589934592**");
495
532
  expect(context).not.toContain("Resource Pool");
496
533
  });
497
534
 
498
- test("renders datastore, cluster, datacenter and resource pool rows by their own identity", () => {
535
+ test("renders datastores, clusters, datacenters and resource pools by their own identity", () => {
499
536
  const context: string | null = Evaluator.buildVMwareRootCauseContext({
500
537
  dataToProcess: metricResponse({
501
538
  vmwareResourceBreakdown: {
@@ -533,15 +570,18 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
533
570
  * `{hosts}` is an annotation-only unit, so counts render as bare
534
571
  * integers. Each kind resolves by the most specific identity
535
572
  * attribute it carries: a datacenter-only row is a Datacenter, a
536
- * cluster row shows no duplicate Cluster cell, a pool shows its
537
- * name with the inventory path (pool names are only unique within
538
- * a parent).
573
+ * cluster shows no duplicate Cluster detail, a pool shows its name
574
+ * with the inventory path (pool names are only unique within a
575
+ * parent).
539
576
  */
540
- expect(context).toContain("| `DC1` | Datacenter | - | - | **4** |");
541
- expect(context).toContain("| `prod-cluster` | Cluster | - | - | **3** |");
542
- expect(context).toContain("| `vsan-ds-01` | Datastore | - | - | **2** |");
543
577
  expect(context).toContain(
544
- "| `batch` (`/DC1/host/prod-cluster/Resources/batch`) | Resource Pool | - | `prod-cluster` | **1** |",
578
+ [
579
+ "1. **Datacenter** `DC1` — **4**",
580
+ "2. **Cluster** `prod-cluster` — **3**",
581
+ "3. **Datastore** `vsan-ds-01` — **2**",
582
+ "4. **Resource Pool** `batch` (`/DC1/host/prod-cluster/Resources/batch`) — **1**",
583
+ " - Cluster: `prod-cluster`",
584
+ ].join("\n"),
545
585
  );
546
586
  });
547
587
 
@@ -563,12 +603,10 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
563
603
  });
564
604
 
565
605
  // Unit from the catalog ("ms"), not a bare, unit-less 85.
566
- expect(context).toMatch(
567
- /\| `esx-01` \| Host \| - \| - \| \*\*85(\.00)? ms\*\* \|/,
568
- );
606
+ expect(context).toMatch(/1\. \*\*Host\*\* `esx-01` — \*\*85(\.00)? ms\*\*/);
569
607
  });
570
608
 
571
- test("caps the table at 10 rows, worst first, with an overflow suffix", () => {
609
+ test("caps the list at 10 items, worst first, with an overflow suffix", () => {
572
610
  const affectedResources: Array<VMwareAffectedResource> = [];
573
611
  for (let i: number = 1; i <= 12; i++) {
574
612
  affectedResources.push({
@@ -595,13 +633,13 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
595
633
  expect(context).toContain("**Affected Resources** (12 total)");
596
634
  expect(context).toContain("*... and 2 more affected resources*");
597
635
  // The worst rows survive the cap; the mildest two are cut.
598
- expect(context).toContain("`esx-12`");
599
- expect(context).toContain("`esx-03`");
636
+ expect(context).toContain("1. **Host** `esx-12` — **12**");
637
+ expect(context).toContain("10. **Host** `esx-03` — **3**");
600
638
  expect(context).not.toContain("`esx-02`");
601
639
  expect(context).not.toContain("`esx-01`");
602
640
  });
603
641
 
604
- test("identity-less breakdowns render no table and fall back to the metric summary", () => {
642
+ test("identity-less breakdowns render no list and fall back to the metric summary", () => {
605
643
  const context: string | null = Evaluator.buildVMwareRootCauseContext({
606
644
  dataToProcess: metricResponse({
607
645
  vmwareResourceBreakdown: {
@@ -620,9 +658,7 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
620
658
  monitor: new Monitor(),
621
659
  });
622
660
 
623
- expect(context).not.toContain(
624
- "| Resource | Kind | Host | Cluster | Value |",
625
- );
661
+ expect(context).not.toContain("**Affected Resources**");
626
662
  expect(context).toContain("**Metric Summary**");
627
663
  expect(context).toContain("- 2 metric data point(s) returned");
628
664
  });
@@ -678,7 +714,7 @@ describe("MonitorCriteriaEvaluator - VMware root cause breakdown", () => {
678
714
  });
679
715
 
680
716
  describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
681
- test("renders the Daemon/Pool/Host/Value table with pool name+id cells", () => {
717
+ test("renders a ranked list of daemons and pools with pool name+id", () => {
682
718
  const affectedResources: Array<CephAffectedResource> = [
683
719
  { daemon: "osd.3", hostname: "ceph-node-1", metricValue: 250 },
684
720
  { poolId: "2", poolName: "rbd", metricValue: 91 },
@@ -706,15 +742,20 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
706
742
  "- Metric: OSD Apply Latency (`ceph_osd_apply_latency_ms`)",
707
743
  );
708
744
 
709
- expect(context).toContain("| Daemon | Pool | Host | Value |");
710
745
  /*
711
746
  * ceph_osd_apply_latency_ms carries the catalog unit "ms", so the
712
- * Value column names it. 250 ms stays "250 ms" rather than scaling to
747
+ * value names it. 250 ms stays "250 ms" rather than scaling to
713
748
  * seconds — the ladder only moves a value when the magnitude asks
714
- * for it.
749
+ * for it. A row with no daemon is titled by its pool.
715
750
  */
716
- expect(context).toContain("| `osd.3` | - | `ceph-node-1` | **250 ms** |");
717
- expect(context).toContain("| - | `rbd` (`2`) | - | **91 ms** |");
751
+ expect(context).toContain(
752
+ [
753
+ "1. **Daemon** `osd.3` — **250 ms**",
754
+ " - Host: `ceph-node-1`",
755
+ "2. **Pool** `rbd` (`2`) — **91 ms**",
756
+ ].join("\n"),
757
+ );
758
+ expect(context).not.toContain("| --- |");
718
759
  expect(context).not.toContain("osd.5");
719
760
  expect(context).toContain("**Affected Resources** (2 total)");
720
761
 
@@ -726,14 +767,14 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
726
767
  });
727
768
 
728
769
  /*
729
- * BACKWARD COMPATIBILITY for the platform tables.
770
+ * BACKWARD COMPATIBILITY for the platform breakdowns.
730
771
  *
731
772
  * A metric whose catalog unit is "count" or absent has no dimension to
732
- * report, and its Value column must keep every digit — never a chart's
773
+ * report, and its value must keep every digit — never a chart's
733
774
  * abbreviated "1.2K", and never the noise "250 count". ceph_osd_up is
734
775
  * `unit: "count"`; ceph_health_status has no unit at all.
735
776
  */
736
- test("a count metric keeps exact bare digits in the Value column", () => {
777
+ test("a count metric keeps exact bare digits in the value", () => {
737
778
  const context: string | null = Evaluator.buildCephRootCauseContext({
738
779
  dataToProcess: metricResponse({
739
780
  cephResourceBreakdown: {
@@ -750,16 +791,18 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
750
791
  monitor: new Monitor(),
751
792
  });
752
793
 
753
- expect(context).toContain("| `osd.3` | - | `ceph-node-1` | **5000** |");
794
+ expect(context).toContain(
795
+ "1. **Daemon** `osd.3` — **5000**\n - Host: `ceph-node-1`",
796
+ );
754
797
  expect(context).not.toContain("5K");
755
798
  expect(context).not.toContain("count");
756
799
  });
757
800
 
758
801
  /*
759
- * ORDERING INVARIANT. The table sorts and filters on the RAW numeric
802
+ * ORDERING INVARIANT. The list sorts and filters on the RAW numeric
760
803
  * metricValue and only formats at render time. If a refactor ever sorted
761
804
  * the formatted strings instead, "1.07 GB" would sort below "900 KB" and
762
- * the worst-first table would silently invert — putting the healthiest
805
+ * the worst-first list would silently invert — putting the healthiest
763
806
  * daemon at the top of an incident.
764
807
  */
765
808
  test("rows still sort worst-first on the raw value, not the formatted string", () => {
@@ -780,8 +823,8 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
780
823
  monitor: new Monitor(),
781
824
  });
782
825
 
783
- expect(context).toContain("| `osd.bigger` | - | - | **1.07 GB** |");
784
- expect(context).toContain("| `osd.smaller` | - | - | **922 KB** |");
826
+ expect(context).toContain("1. **Daemon** `osd.bigger` — **1.07 GB**");
827
+ expect(context).toContain("2. **Daemon** `osd.smaller` — **922 KB**");
785
828
 
786
829
  // "1.07 GB" < "922 KB" as strings — the raw numbers must drive this.
787
830
  const biggerIndex: number = context!.indexOf("`osd.bigger`");
@@ -790,7 +833,7 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
790
833
  expect(smallerIndex).toBeGreaterThan(biggerIndex);
791
834
  });
792
835
 
793
- test("cluster-wide series (ceph_health_status) render no table", () => {
836
+ test("cluster-wide series (ceph_health_status) render no list", () => {
794
837
  const context: string | null = Evaluator.buildCephRootCauseContext({
795
838
  dataToProcess: metricResponse({
796
839
  cephResourceBreakdown: {
@@ -806,7 +849,7 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
806
849
  });
807
850
 
808
851
  expect(context).toContain("- Cluster: prod-cluster");
809
- expect(context).not.toContain("| Daemon | Pool | Host | Value |");
852
+ expect(context).not.toContain("**Affected Resources**");
810
853
  });
811
854
 
812
855
  test("surfaces the worker's resource-filter mapping (ceph_daemon / pool_id) in the cluster context", () => {
@@ -835,16 +878,16 @@ describe("MonitorCriteriaEvaluator - Ceph root cause breakdown", () => {
835
878
  * ceph_osd_up / ceph_osd_in / ceph_mon_quorum_status are 0/1 gauges and
836
879
  * the OSD Down / OSD Out / Quorum Degraded criteria fire on `< 1`, so the
837
880
  * ZERO rows are the down daemons — the filter dropped exactly the daemons
838
- * the incident was about and rendered a table of the HEALTHY ones, under
881
+ * the incident was about and rendered a list of the HEALTHY ones, under
839
882
  * an incident description that ends "Check the root cause for the affected
840
883
  * ceph_daemon label."
841
884
  *
842
885
  * The predicate now mirrors the criteria that matched, but ONLY inverts
843
886
  * for a criteria that fires when the metric FALLS. Every upward comparison
844
887
  * (`> 0` health checks, PG counts, latency, capacity ratios) and every
845
- * `= 0` RECOVERY criteria keeps the old `> 0`, worst-highest-first table
888
+ * `= 0` RECOVERY criteria keeps the old `> 0`, worst-highest-first list
846
889
  * byte for byte — the recovery case matters because deriving the predicate
847
- * from an `= 0` criteria would newly render an "Affected Resources" table
890
+ * from an `= 0` criteria would newly render an "Affected Resources" list
848
891
  * of every resource sitting at 0 on the all-clear.
849
892
  */
850
893
  describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate", () => {
@@ -933,8 +976,9 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
933
976
  ],
934
977
  });
935
978
 
936
- expect(context).toContain("| Daemon | Pool | Host | Value |");
937
- expect(context).toContain("| `osd.3` | - | `ceph-node-1` | **0** |");
979
+ expect(context).toContain(
980
+ "1. **Daemon** `osd.3` — **0**\n - Host: `ceph-node-1`",
981
+ );
938
982
  expect(context).not.toContain("osd.4");
939
983
  expect(context).not.toContain("osd.5");
940
984
  expect(context).toContain("**Affected Resources** (1 total)");
@@ -960,8 +1004,8 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
960
1004
  });
961
1005
 
962
1006
  expect(context).toContain("**Affected Resources** (2 total)");
963
- expect(context).toContain("`osd.1`");
964
- expect(context).toContain("`osd.2`");
1007
+ expect(context).toContain("1. **Daemon** `osd.1` — **0**");
1008
+ expect(context).toContain("2. **Daemon** `osd.2` — **0**");
965
1009
  expect(context).not.toContain("`osd.10`");
966
1010
  expect(context).not.toContain("*... and");
967
1011
  });
@@ -1009,8 +1053,13 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1009
1053
  ],
1010
1054
  });
1011
1055
 
1012
- expect(context).toContain("| `osd.3` | - | `ceph-node-1` | **250 ms** |");
1013
- expect(context).toContain("| - | `rbd` (`2`) | - | **91 ms** |");
1056
+ expect(context).toContain(
1057
+ [
1058
+ "1. **Daemon** `osd.3` — **250 ms**",
1059
+ " - Host: `ceph-node-1`",
1060
+ "2. **Pool** `rbd` (`2`) — **91 ms**",
1061
+ ].join("\n"),
1062
+ );
1014
1063
  expect(context).not.toContain("osd.5");
1015
1064
  expect(context).toContain("**Affected Resources** (2 total)");
1016
1065
 
@@ -1020,7 +1069,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1020
1069
  expect(poolIndex).toBeGreaterThan(osdIndex);
1021
1070
  });
1022
1071
 
1023
- test("an `= 0` recovery criteria does NOT turn the all-clear into a table of zeroes", () => {
1072
+ test("an `= 0` recovery criteria does NOT turn the all-clear into a list of zeroes", () => {
1024
1073
  const context: string | null = cephContext({
1025
1074
  metricName: "ceph_pg_degraded",
1026
1075
  criteriaInstance: syntheticCriteria([
@@ -1036,7 +1085,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1036
1085
  ],
1037
1086
  });
1038
1087
 
1039
- expect(context).not.toContain("| Daemon | Pool | Host | Value |");
1088
+ expect(context).not.toContain("**Pool**");
1040
1089
  expect(context).not.toContain("**Affected Resources**");
1041
1090
  });
1042
1091
 
@@ -1049,7 +1098,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1049
1098
  ],
1050
1099
  });
1051
1100
 
1052
- expect(context).toContain("| `osd.4` | - | - | **1** |");
1101
+ expect(context).toContain("1. **Daemon** `osd.4` — **1**");
1053
1102
  expect(context).not.toContain("osd.3");
1054
1103
  });
1055
1104
 
@@ -1061,7 +1110,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1061
1110
  ],
1062
1111
  });
1063
1112
 
1064
- expect(context).toContain("| `osd.4` | - | - | **1** |");
1113
+ expect(context).toContain("1. **Daemon** `osd.4` — **1**");
1065
1114
  expect(context).not.toContain("osd.3");
1066
1115
  });
1067
1116
 
@@ -1080,7 +1129,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1080
1129
  ],
1081
1130
  });
1082
1131
 
1083
- expect(context).toContain("| `osd.4` | - | - | **1** |");
1132
+ expect(context).toContain("1. **Daemon** `osd.4` — **1**");
1084
1133
  expect(context).not.toContain("osd.3");
1085
1134
  });
1086
1135
 
@@ -1104,7 +1153,7 @@ describe("MonitorCriteriaEvaluator - Ceph affected-resources breach predicate",
1104
1153
  ],
1105
1154
  });
1106
1155
 
1107
- expect(context).toContain("| `osd.4` | - | - | **1** |");
1156
+ expect(context).toContain("1. **Daemon** `osd.4` — **1**");
1108
1157
  expect(context).not.toContain("osd.3");
1109
1158
  });
1110
1159
  });
@@ -1333,3 +1382,792 @@ describe("MonitorCriteriaEvaluator - Kubernetes root cause analysis scoping", ()
1333
1382
  });
1334
1383
  });
1335
1384
  });
1385
+
1386
+ /*
1387
+ * The Kubernetes "Affected Resources" block — the one the table-to-list
1388
+ * change was made for. A pod-restart incident on a GKE cluster used to
1389
+ * carry a six-column table (Namespace / Workload Type / Workload / Pod /
1390
+ * Node / Value) that wrapped every identifier in the email card. Each
1391
+ * resource is now one numbered item titled by the most specific object
1392
+ * the series names, with the rest of its identity as nested bullets.
1393
+ */
1394
+ describe("MonitorCriteriaEvaluator - Kubernetes affected resources list", () => {
1395
+ // The first line of a top-level list item: "1. ", "10. ", ...
1396
+ const NUMBERED_ITEM: RegExp = /^\d+\. /;
1397
+
1398
+ async function kubernetesContext(input: {
1399
+ affectedResources: Array<KubernetesAffectedResource>;
1400
+ metricName?: string;
1401
+ clusterName?: string;
1402
+ }): Promise<string | null> {
1403
+ return Evaluator.buildKubernetesRootCauseContext({
1404
+ dataToProcess: metricResponse({
1405
+ kubernetesResourceBreakdown: {
1406
+ clusterName:
1407
+ input.clusterName === undefined ? "prod-gke" : input.clusterName,
1408
+ metricName: input.metricName || "k8s.container.restarts",
1409
+ metricFriendlyName: "Container Restarts",
1410
+ affectedResources: input.affectedResources,
1411
+ attributes: {},
1412
+ },
1413
+ }),
1414
+ monitorStep: new MonitorStep(),
1415
+ // No projectId, so the restart branch never reaches for container logs.
1416
+ monitor: new Monitor(),
1417
+ });
1418
+ }
1419
+
1420
+ function podRestart(
1421
+ overrides: Partial<KubernetesAffectedResource>,
1422
+ ): KubernetesAffectedResource {
1423
+ return {
1424
+ namespace: "default",
1425
+ workloadType: "Deployment",
1426
+ workloadName: "oneuptime-worker",
1427
+ podName: "oneuptime-worker-6b67db69c9-gtcp6",
1428
+ nodeName: "gke-prod-default-pool-662f6819-e9gq",
1429
+ metricValue: 2,
1430
+ ...overrides,
1431
+ };
1432
+ }
1433
+
1434
+ test("renders the screenshot's pod restarts as a ranked list, not a table", async () => {
1435
+ const affectedResources: Array<KubernetesAffectedResource> = [
1436
+ podRestart({ metricValue: 2 }),
1437
+ podRestart({
1438
+ workloadType: "Job",
1439
+ workloadName: "oneuptime-migrate-267",
1440
+ podName: "oneuptime-migrate-267-k64qm",
1441
+ nodeName: "gke-prod-default-pool-662f6819-c6w3",
1442
+ metricValue: 3,
1443
+ }),
1444
+ // kube-proxy is a static pod: no owning workload at all.
1445
+ podRestart({
1446
+ namespace: "kube-system",
1447
+ workloadType: undefined,
1448
+ workloadName: undefined,
1449
+ podName: "kube-proxy-gke-prod-default-pool-662f6819-c6w3",
1450
+ nodeName: "gke-prod-default-pool-662f6819-c6w3",
1451
+ metricValue: 1,
1452
+ }),
1453
+ ];
1454
+
1455
+ const context: string | null = await kubernetesContext({
1456
+ affectedResources,
1457
+ });
1458
+
1459
+ expect(context).toContain("**Kubernetes Cluster Details**");
1460
+ expect(context).toContain(
1461
+ [
1462
+ "**Affected Resources** (3 total)",
1463
+ "",
1464
+ "1. **Pod** `oneuptime-migrate-267-k64qm` — **3**",
1465
+ " - Namespace: `default`",
1466
+ " - Job: `oneuptime-migrate-267`",
1467
+ " - Node: `gke-prod-default-pool-662f6819-c6w3`",
1468
+ "2. **Pod** `oneuptime-worker-6b67db69c9-gtcp6` — **2**",
1469
+ " - Namespace: `default`",
1470
+ " - Deployment: `oneuptime-worker`",
1471
+ " - Node: `gke-prod-default-pool-662f6819-e9gq`",
1472
+ "3. **Pod** `kube-proxy-gke-prod-default-pool-662f6819-c6w3` — **1**",
1473
+ " - Namespace: `kube-system`",
1474
+ " - Node: `gke-prod-default-pool-662f6819-c6w3`",
1475
+ ].join("\n"),
1476
+ );
1477
+
1478
+ // None of the old table survives.
1479
+ expect(context).not.toContain("| Namespace |");
1480
+ expect(context).not.toContain("Workload Type");
1481
+ expect(context).not.toContain("| --- |");
1482
+ expect(context).not.toMatch(/^\|/m);
1483
+ // A missing workload is left out, not printed as "-".
1484
+ expect(context).not.toContain(": -");
1485
+ expect(context).not.toContain("| - |");
1486
+ });
1487
+
1488
+ test("caps the list at 10 with an '... and N more' summary, like the 83-pod incident", async () => {
1489
+ const affectedResources: Array<KubernetesAffectedResource> = [];
1490
+ for (let i: number = 1; i <= 83; i++) {
1491
+ affectedResources.push(
1492
+ podRestart({ podName: `worker-${i}`, metricValue: i }),
1493
+ );
1494
+ }
1495
+
1496
+ const context: string | null = await kubernetesContext({
1497
+ affectedResources,
1498
+ });
1499
+
1500
+ expect(context).toContain("**Affected Resources** (83 total)");
1501
+ expect(context).toContain("1. **Pod** `worker-83` — **83**");
1502
+ expect(context).toContain(
1503
+ [
1504
+ "10. **Pod** `worker-74` — **74**",
1505
+ " - Namespace: `default`",
1506
+ " - Deployment: `oneuptime-worker`",
1507
+ " - Node: `gke-prod-default-pool-662f6819-e9gq`",
1508
+ "",
1509
+ "*... and 73 more affected resources*",
1510
+ ].join("\n"),
1511
+ );
1512
+ expect(context).not.toContain("11. ");
1513
+ expect(context).not.toContain("`worker-73`");
1514
+ });
1515
+
1516
+ test("a container-level series is titled by the container and its pod", async () => {
1517
+ const context: string | null = await kubernetesContext({
1518
+ affectedResources: [
1519
+ podRestart({ containerName: "app", podName: "checkout-7d9f" }),
1520
+ ],
1521
+ });
1522
+
1523
+ expect(context).toContain(
1524
+ [
1525
+ "1. **Container** `app` in pod `checkout-7d9f` — **2**",
1526
+ " - Namespace: `default`",
1527
+ " - Deployment: `oneuptime-worker`",
1528
+ " - Node: `gke-prod-default-pool-662f6819-e9gq`",
1529
+ ].join("\n"),
1530
+ );
1531
+ // The pod is in the title, so it is not repeated as a bullet.
1532
+ expect(context).not.toContain("- Pod:");
1533
+ });
1534
+
1535
+ /*
1536
+ * Every replica of a workload runs a container of the same name. Titled
1537
+ * by the container alone, three crash-looping replicas would read
1538
+ * "Container `checkout`" three times over.
1539
+ */
1540
+ test("replicas that share a container name still get distinct titles", async () => {
1541
+ const context: string | null = await kubernetesContext({
1542
+ affectedResources: [
1543
+ podRestart({
1544
+ containerName: "checkout",
1545
+ podName: "checkout-7d9f6c5b8-abcde",
1546
+ metricValue: 5,
1547
+ }),
1548
+ podRestart({
1549
+ containerName: "checkout",
1550
+ podName: "checkout-7d9f6c5b8-fghij",
1551
+ metricValue: 4,
1552
+ }),
1553
+ podRestart({
1554
+ containerName: "checkout",
1555
+ podName: "checkout-7d9f6c5b8-klmno",
1556
+ metricValue: 3,
1557
+ }),
1558
+ ],
1559
+ });
1560
+
1561
+ const titles: Array<string> = (context || "")
1562
+ .split("\n")
1563
+ .filter((line: string) => {
1564
+ return NUMBERED_ITEM.test(line);
1565
+ });
1566
+
1567
+ expect(titles).toEqual([
1568
+ "1. **Container** `checkout` in pod `checkout-7d9f6c5b8-abcde` — **5**",
1569
+ "2. **Container** `checkout` in pod `checkout-7d9f6c5b8-fghij` — **4**",
1570
+ "3. **Container** `checkout` in pod `checkout-7d9f6c5b8-klmno` — **3**",
1571
+ ]);
1572
+ expect(new Set(titles).size).toBe(3);
1573
+ });
1574
+
1575
+ test("a container without a pod is titled by the container alone", async () => {
1576
+ const context: string | null = await kubernetesContext({
1577
+ affectedResources: [
1578
+ { containerName: "app", namespace: "default", metricValue: 2 },
1579
+ ],
1580
+ });
1581
+
1582
+ expect(context).toContain(
1583
+ "1. **Container** `app` — **2**\n - Namespace: `default`",
1584
+ );
1585
+ });
1586
+
1587
+ test("a node-level series is titled by the node and has nothing to list under it", async () => {
1588
+ const context: string | null = await kubernetesContext({
1589
+ metricName: "k8s.node.memory.usage",
1590
+ affectedResources: [
1591
+ { nodeName: "gke-prod-pool-a", metricValue: 8589934592 },
1592
+ { nodeName: "gke-prod-pool-b", metricValue: 1073741824 },
1593
+ ],
1594
+ });
1595
+
1596
+ expect(context).toContain(
1597
+ [
1598
+ "1. **Node** `gke-prod-pool-a` — **8.59 GB**",
1599
+ "2. **Node** `gke-prod-pool-b` — **1.07 GB**",
1600
+ ].join("\n"),
1601
+ );
1602
+ expect(context).not.toContain("- Node:");
1603
+ });
1604
+
1605
+ test("a workload-level series is titled by its workload type and does not repeat it", async () => {
1606
+ const context: string | null = await kubernetesContext({
1607
+ metricName: "k8s.deployment.unavailable_replicas",
1608
+ affectedResources: [
1609
+ {
1610
+ namespace: "payments",
1611
+ workloadType: "StatefulSet",
1612
+ workloadName: "ledger",
1613
+ metricValue: 2,
1614
+ },
1615
+ ],
1616
+ });
1617
+
1618
+ expect(context).toContain(
1619
+ "1. **StatefulSet** `ledger` — **2**\n - Namespace: `payments`",
1620
+ );
1621
+ expect(context).not.toContain("- StatefulSet:");
1622
+ });
1623
+
1624
+ test("a workload name without a type falls back to the generic 'Workload' label", async () => {
1625
+ const context: string | null = await kubernetesContext({
1626
+ affectedResources: [
1627
+ podRestart({ workloadType: undefined, workloadName: "mystery" }),
1628
+ ],
1629
+ });
1630
+
1631
+ expect(context).toContain(" - Workload: `mystery`");
1632
+ });
1633
+
1634
+ test("a workload-level series without a type is titled 'Workload'", async () => {
1635
+ const context: string | null = await kubernetesContext({
1636
+ affectedResources: [
1637
+ { namespace: "payments", workloadName: "ledger", metricValue: 1 },
1638
+ ],
1639
+ });
1640
+
1641
+ expect(context).toContain(
1642
+ "1. **Workload** `ledger` — **1**\n - Namespace: `payments`",
1643
+ );
1644
+ });
1645
+
1646
+ test("a namespace-level series is titled by the namespace", async () => {
1647
+ const context: string | null = await kubernetesContext({
1648
+ affectedResources: [{ namespace: "payments", metricValue: 4 }],
1649
+ });
1650
+
1651
+ expect(context).toContain("1. **Namespace** `payments` — **4**");
1652
+ expect(context).not.toContain("- Namespace:");
1653
+ });
1654
+
1655
+ test("a series with no Kubernetes identity is a cluster-level series and is titled by the cluster", async () => {
1656
+ const context: string | null = await kubernetesContext({
1657
+ affectedResources: [{ metricValue: 5 }],
1658
+ });
1659
+
1660
+ expect(context).toContain("1. **Cluster** `prod-gke` — **5**");
1661
+ });
1662
+
1663
+ test("items of different shapes in one breakdown are each titled by their own kind", async () => {
1664
+ const context: string | null = await kubernetesContext({
1665
+ affectedResources: [
1666
+ podRestart({ containerName: "app", metricValue: 4 }),
1667
+ podRestart({ metricValue: 3 }),
1668
+ { nodeName: "gke-prod-pool-a", metricValue: 2 },
1669
+ { metricValue: 1 },
1670
+ ],
1671
+ });
1672
+
1673
+ const titles: Array<string> = (context || "")
1674
+ .split("\n")
1675
+ .filter((line: string) => {
1676
+ return NUMBERED_ITEM.test(line);
1677
+ });
1678
+
1679
+ expect(titles).toEqual([
1680
+ "1. **Container** `app` in pod `oneuptime-worker-6b67db69c9-gtcp6` — **4**",
1681
+ "2. **Pod** `oneuptime-worker-6b67db69c9-gtcp6` — **3**",
1682
+ "3. **Node** `gke-prod-pool-a` — **2**",
1683
+ "4. **Cluster** `prod-gke` — **1**",
1684
+ ]);
1685
+ });
1686
+
1687
+ test("zero-value resources are dropped, and an all-zero breakdown renders no list", async () => {
1688
+ const some: string | null = await kubernetesContext({
1689
+ affectedResources: [
1690
+ podRestart({ podName: "healthy", metricValue: 0 }),
1691
+ podRestart({ podName: "crashing", metricValue: 7 }),
1692
+ ],
1693
+ });
1694
+
1695
+ expect(some).toContain("**Affected Resources** (1 total)");
1696
+ expect(some).toContain("1. **Pod** `crashing` — **7**");
1697
+ expect(some).not.toContain("healthy");
1698
+
1699
+ const none: string | null = await kubernetesContext({
1700
+ affectedResources: [podRestart({ metricValue: 0 })],
1701
+ });
1702
+
1703
+ expect(none).toContain("**Kubernetes Cluster Details**");
1704
+ expect(none).not.toContain("**Affected Resources**");
1705
+ expect(none).not.toContain("1. ");
1706
+ });
1707
+
1708
+ test("the Root Cause Analysis still names the worst resource and follows the list as its own block", async () => {
1709
+ const context: string | null = await kubernetesContext({
1710
+ affectedResources: [
1711
+ podRestart({ containerName: "app", podName: "checkout-7d9f" }),
1712
+ ],
1713
+ });
1714
+
1715
+ expect(context).toContain(
1716
+ " - Node: `gke-prod-default-pool-662f6819-e9gq`\n\n\n**Root Cause Analysis**",
1717
+ );
1718
+ expect(context).toContain("kubectl logs checkout-7d9f -c app --previous");
1719
+ });
1720
+
1721
+ test("a name containing a backtick cannot break out of its code span", async () => {
1722
+ const context: string | null = await kubernetesContext({
1723
+ affectedResources: [podRestart({ podName: "odd`pod" })],
1724
+ });
1725
+
1726
+ expect(context).toContain("1. **Pod** `` odd`pod `` — **2**");
1727
+ });
1728
+
1729
+ test("the whole root cause renders to an email as nested lists, not a table", async () => {
1730
+ const context: string | null = await kubernetesContext({
1731
+ affectedResources: [
1732
+ podRestart({ metricValue: 3 }),
1733
+ podRestart({ podName: "second", metricValue: 2 }),
1734
+ ],
1735
+ });
1736
+
1737
+ const html: string = await Markdown.convertToHTML(
1738
+ context!,
1739
+ MarkdownContentType.Email,
1740
+ );
1741
+
1742
+ expect(html).not.toContain("<table");
1743
+ expect(html).not.toContain("<th");
1744
+ expect((html.match(/<ol /g) || []).length).toBe(1);
1745
+ // Cluster details list + one nested details list per resource.
1746
+ expect((html.match(/<ul /g) || []).length).toBe(3);
1747
+ expect(html).toMatch(
1748
+ /<li style="[^"]+"><strong>Pod<\/strong> <code[^>]*>oneuptime-worker-6b67db69c9-gtcp6<\/code> — <strong>3<\/strong><ul style="[^"]+"><li style="[^"]+">Namespace: <code[^>]*>default<\/code><\/li>/,
1749
+ );
1750
+ });
1751
+ });
1752
+
1753
+ describe("MonitorCriteriaEvaluator - Docker Swarm affected tasks list", () => {
1754
+ function swarmContext(input: {
1755
+ affectedResources: Array<DockerSwarmAffectedResource>;
1756
+ metricResult?: Array<any>;
1757
+ }): string | null {
1758
+ return Evaluator.buildDockerSwarmRootCauseContext({
1759
+ dataToProcess: metricResponse({
1760
+ dockerSwarmResourceBreakdown: {
1761
+ clusterName: "swarm-prod",
1762
+ metricName: "container.pids.count",
1763
+ metricFriendlyName: "Process Count",
1764
+ affectedResources: input.affectedResources,
1765
+ attributes: {},
1766
+ },
1767
+ metricResult: input.metricResult || [],
1768
+ }),
1769
+ monitorStep: new MonitorStep(),
1770
+ monitor: new Monitor(),
1771
+ });
1772
+ }
1773
+
1774
+ test("renders each task with its service and node beneath it, worst first", () => {
1775
+ const context: string | null = swarmContext({
1776
+ affectedResources: [
1777
+ {
1778
+ containerName: "web.1.abc123",
1779
+ serviceName: "web",
1780
+ nodeName: "worker-1",
1781
+ metricValue: 40,
1782
+ },
1783
+ {
1784
+ containerName: "api.2.def456",
1785
+ serviceName: "api",
1786
+ nodeName: "worker-2",
1787
+ metricValue: 90,
1788
+ },
1789
+ // Zero row dropped.
1790
+ {
1791
+ containerName: "idle.1.xyz",
1792
+ serviceName: "idle",
1793
+ nodeName: "worker-3",
1794
+ metricValue: 0,
1795
+ },
1796
+ ],
1797
+ });
1798
+
1799
+ expect(context).toContain("**Docker Swarm Cluster Details**");
1800
+ expect(context).toContain(
1801
+ [
1802
+ "**Affected Tasks** (2 total)",
1803
+ "",
1804
+ "1. **Task** `api.2.def456` — **90**",
1805
+ " - Service: `api`",
1806
+ " - Node: `worker-2`",
1807
+ "2. **Task** `web.1.abc123` — **40**",
1808
+ " - Service: `web`",
1809
+ " - Node: `worker-1`",
1810
+ ].join("\n"),
1811
+ );
1812
+ expect(context).not.toContain("idle");
1813
+ expect(context).not.toContain("Task / Container");
1814
+ expect(context).not.toContain("| --- |");
1815
+ });
1816
+
1817
+ test("a series without a task is titled by its service, then its node", () => {
1818
+ const context: string | null = swarmContext({
1819
+ affectedResources: [
1820
+ { serviceName: "web", nodeName: "worker-1", metricValue: 2 },
1821
+ { nodeName: "worker-2", metricValue: 1 },
1822
+ ],
1823
+ });
1824
+
1825
+ expect(context).toContain(
1826
+ [
1827
+ "1. **Service** `web` — **2**",
1828
+ " - Node: `worker-1`",
1829
+ "2. **Node** `worker-2` — **1**",
1830
+ ].join("\n"),
1831
+ );
1832
+ });
1833
+
1834
+ test("overflow is counted in tasks", () => {
1835
+ const affectedResources: Array<DockerSwarmAffectedResource> = [];
1836
+ for (let i: number = 1; i <= 13; i++) {
1837
+ affectedResources.push({
1838
+ containerName: `web.${i}.task`,
1839
+ serviceName: "web",
1840
+ metricValue: i,
1841
+ });
1842
+ }
1843
+
1844
+ const context: string | null = swarmContext({ affectedResources });
1845
+
1846
+ expect(context).toContain("**Affected Tasks** (13 total)");
1847
+ expect(context).toContain("*... and 3 more affected tasks*");
1848
+ expect(context).toContain("10. **Task** `web.4.task` — **4**");
1849
+ });
1850
+
1851
+ test("a row with no identity inside an attributed breakdown is titled by the cluster", () => {
1852
+ const context: string | null = swarmContext({
1853
+ affectedResources: [
1854
+ { containerName: "web.1", serviceName: "web", metricValue: 2 },
1855
+ { metricValue: 1 },
1856
+ ],
1857
+ });
1858
+
1859
+ expect(context).toContain(
1860
+ [
1861
+ "1. **Task** `web.1` — **2**",
1862
+ " - Service: `web`",
1863
+ "2. **Cluster** `swarm-prod` — **1**",
1864
+ ].join("\n"),
1865
+ );
1866
+ });
1867
+
1868
+ test("an identity-less breakdown renders no list and falls back to the metric summary", () => {
1869
+ const context: string | null = swarmContext({
1870
+ affectedResources: [{ metricValue: 3 }],
1871
+ metricResult: [{ data: [{}, {}] }],
1872
+ });
1873
+
1874
+ expect(context).not.toContain("**Affected Tasks**");
1875
+ expect(context).toContain("**Metric Summary**");
1876
+ });
1877
+ });
1878
+
1879
+ describe("MonitorCriteriaEvaluator - per-platform resource kinds in the list", () => {
1880
+ function proxmoxContext(
1881
+ affectedResources: Array<ProxmoxAffectedResource>,
1882
+ ): string | null {
1883
+ return Evaluator.buildProxmoxRootCauseContext({
1884
+ dataToProcess: metricResponse({
1885
+ proxmoxResourceBreakdown: {
1886
+ clusterName: "prod-cluster",
1887
+ metricName: "pve_replication_failed_syncs",
1888
+ metricFriendlyName: "Failed Replication Syncs",
1889
+ affectedResources,
1890
+ attributes: {},
1891
+ },
1892
+ }),
1893
+ monitorStep: proxmoxStep(),
1894
+ monitor: new Monitor(),
1895
+ });
1896
+ }
1897
+
1898
+ test("Proxmox: each pve.type gets a readable kind, and a node does not list itself as its node", () => {
1899
+ const context: string | null = proxmoxContext([
1900
+ {
1901
+ resourceId: "lxc/200",
1902
+ resourceName: "cache",
1903
+ resourceType: "lxc",
1904
+ scope: "guest",
1905
+ nodeName: "pve1",
1906
+ metricValue: 5,
1907
+ },
1908
+ {
1909
+ resourceId: "node/pve1",
1910
+ resourceName: "pve1",
1911
+ resourceType: "node",
1912
+ scope: "node",
1913
+ nodeName: "pve1",
1914
+ metricValue: 4,
1915
+ },
1916
+ {
1917
+ resourceId: "storage/pve1/local-lvm",
1918
+ resourceName: "local-lvm",
1919
+ resourceType: "storage",
1920
+ scope: "storage",
1921
+ nodeName: "pve1",
1922
+ metricValue: 3,
1923
+ },
1924
+ ]);
1925
+
1926
+ expect(context).toContain(
1927
+ [
1928
+ "1. **Container** `cache` (`lxc/200`) — **5**",
1929
+ " - Node: `pve1`",
1930
+ "2. **Node** `pve1` (`node/pve1`) — **4**",
1931
+ "3. **Storage** `local-lvm` (`storage/pve1/local-lvm`) — **3**",
1932
+ " - Node: `pve1`",
1933
+ ].join("\n"),
1934
+ );
1935
+ });
1936
+
1937
+ test("Proxmox: an unknown pve.type is shown as it arrived, and scope fills in when type is missing", () => {
1938
+ const context: string | null = proxmoxContext([
1939
+ { resourceId: "sdn/zone1", resourceType: "sdn", metricValue: 2 },
1940
+ { resourceId: "qemu/300", scope: "guest", metricValue: 1 },
1941
+ ]);
1942
+
1943
+ expect(context).toContain("1. **sdn** `sdn/zone1` — **2**");
1944
+ expect(context).toContain("2. **Guest** `qemu/300` — **1**");
1945
+ });
1946
+
1947
+ /*
1948
+ * The agent's transform/pve-identity processor sets pve.scope=cluster
1949
+ * and leaves pve.type unset on `cluster/*` series, so this is the one
1950
+ * scope-only input that really arrives.
1951
+ */
1952
+ test("Proxmox: cluster/* series (scope only) are titled Cluster; no type or scope falls back to Resource", () => {
1953
+ const context: string | null = proxmoxContext([
1954
+ { resourceId: "cluster/prod", scope: "cluster", metricValue: 4 },
1955
+ { resourceId: "node/pve3", scope: "node", metricValue: 3 },
1956
+ { resourceId: "pool/backups", scope: "pool", metricValue: 2 },
1957
+ { resourceId: "qemu/100", metricValue: 1 },
1958
+ ]);
1959
+
1960
+ expect(context).toContain(
1961
+ [
1962
+ "1. **Cluster** `cluster/prod` — **4**",
1963
+ "2. **Node** `node/pve3` — **3**",
1964
+ "3. **pool** `pool/backups` — **2**",
1965
+ "4. **Resource** `qemu/100` — **1**",
1966
+ ].join("\n"),
1967
+ );
1968
+ });
1969
+
1970
+ test("Proxmox: a node-only row is titled by the node; a row with no identity by the cluster", () => {
1971
+ const context: string | null = proxmoxContext([
1972
+ { nodeName: "pve2", metricValue: 2 },
1973
+ { metricValue: 1 },
1974
+ ]);
1975
+
1976
+ expect(context).toContain(
1977
+ "1. **Node** `pve2` — **2**\n2. **Cluster** `prod-cluster` — **1**",
1978
+ );
1979
+ });
1980
+
1981
+ test("VMware: a VM with no display name is named by its instance UUID", () => {
1982
+ const context: string | null = Evaluator.buildVMwareRootCauseContext({
1983
+ dataToProcess: metricResponse({
1984
+ vmwareResourceBreakdown: {
1985
+ vcenterName: "vcsa-prod",
1986
+ metricName: "vcenter.vm.disk.usage",
1987
+ metricFriendlyName: "VM Disk Usage",
1988
+ affectedResources: [
1989
+ {
1990
+ datacenterName: "DC1",
1991
+ hostName: "esx-01",
1992
+ vmId: "5029abcd-1111-2222-3333-444455556666",
1993
+ metricValue: 1073741824,
1994
+ },
1995
+ ],
1996
+ attributes: {},
1997
+ },
1998
+ }),
1999
+ monitorStep: vmwareStep(),
2000
+ monitor: new Monitor(),
2001
+ });
2002
+
2003
+ expect(context).toContain(
2004
+ "1. **Virtual Machine** `5029abcd-1111-2222-3333-444455556666` — **1.07 GB**\n - Host: `esx-01`",
2005
+ );
2006
+ });
2007
+
2008
+ test("VMware: a pool on a standalone host lists its owner host; a row with no identity is the vCenter", () => {
2009
+ const context: string | null = Evaluator.buildVMwareRootCauseContext({
2010
+ dataToProcess: metricResponse({
2011
+ vmwareResourceBreakdown: {
2012
+ vcenterName: "vcsa-prod",
2013
+ metricName: "vcenter.datacenter.vm.count",
2014
+ metricFriendlyName: "Datacenter VM Count",
2015
+ affectedResources: [
2016
+ {
2017
+ datacenterName: "DC1",
2018
+ hostName: "esx-standalone",
2019
+ resourcePoolName: "Resources",
2020
+ resourcePoolPath: "/DC1/host/esx-standalone/Resources",
2021
+ metricValue: 2,
2022
+ },
2023
+ { metricValue: 1 },
2024
+ ],
2025
+ attributes: {},
2026
+ },
2027
+ }),
2028
+ monitorStep: vmwareStep(),
2029
+ monitor: new Monitor(),
2030
+ });
2031
+
2032
+ expect(context).toContain(
2033
+ [
2034
+ "1. **Resource Pool** `Resources` (`/DC1/host/esx-standalone/Resources`) — **2**",
2035
+ " - Host: `esx-standalone`",
2036
+ "2. **vCenter** `vcsa-prod` — **1**",
2037
+ ].join("\n"),
2038
+ );
2039
+ });
2040
+
2041
+ /*
2042
+ * The worker stores every Ceph series' `name` label as poolName. On
2043
+ * ceph_health_detail that label is the health check — RECENT_CRASH,
2044
+ * OSD_NEARFULL — not a pool, and there is no pool_id beside it.
2045
+ */
2046
+ test("Ceph: a ceph_health_detail series is titled as a health check, not a pool", () => {
2047
+ const template: CephAlertTemplate | undefined =
2048
+ getCephAlertTemplateById("ceph-daemon-crash");
2049
+ if (!template) {
2050
+ throw new Error("ceph-daemon-crash template missing");
2051
+ }
2052
+
2053
+ const step: MonitorStep = template.getMonitorStep(templateArgs());
2054
+
2055
+ const context: string | null = Evaluator.buildCephRootCauseContext({
2056
+ dataToProcess: metricResponse({
2057
+ cephResourceBreakdown: {
2058
+ clusterName: "prod-cluster",
2059
+ metricName: "ceph_health_detail",
2060
+ metricFriendlyName: "Health Detail",
2061
+ affectedResources: [{ poolName: "RECENT_CRASH", metricValue: 1 }],
2062
+ attributes: {},
2063
+ },
2064
+ }),
2065
+ monitorStep: step,
2066
+ monitor: new Monitor(),
2067
+ criteriaInstance:
2068
+ step.data!.monitorCriteria!.data!.monitorCriteriaInstanceArray[0],
2069
+ });
2070
+
2071
+ expect(context).toContain(
2072
+ "**Affected Resources** (1 total)\n\n1. **Health Check** `RECENT_CRASH` — **1**",
2073
+ );
2074
+ expect(context).not.toContain("**Pool**");
2075
+ expect(context).not.toContain("- Pool:");
2076
+ });
2077
+
2078
+ test("Ceph: a name without a pool id on another metric is a generic resource, and a daemon keeps it as a detail", () => {
2079
+ const context: string | null = Evaluator.buildCephRootCauseContext({
2080
+ dataToProcess: metricResponse({
2081
+ cephResourceBreakdown: {
2082
+ clusterName: "prod-cluster",
2083
+ metricName: "ceph_fs_metadata",
2084
+ metricFriendlyName: "Filesystem Metadata",
2085
+ affectedResources: [
2086
+ { poolName: "cephfs", metricValue: 2 },
2087
+ {
2088
+ daemon: "mds.a",
2089
+ poolName: "cephfs",
2090
+ hostname: "ceph-node-1",
2091
+ metricValue: 1,
2092
+ },
2093
+ ],
2094
+ attributes: {},
2095
+ },
2096
+ }),
2097
+ monitorStep: cephStep(),
2098
+ monitor: new Monitor(),
2099
+ });
2100
+
2101
+ expect(context).toContain(
2102
+ [
2103
+ "1. **Resource** `cephfs` — **2**",
2104
+ "2. **Daemon** `mds.a` — **1**",
2105
+ " - Name: `cephfs`",
2106
+ " - Host: `ceph-node-1`",
2107
+ ].join("\n"),
2108
+ );
2109
+ expect(context).not.toContain("**Pool**");
2110
+ });
2111
+
2112
+ test("Ceph: a pool id alone still makes a pool", () => {
2113
+ const context: string | null = Evaluator.buildCephRootCauseContext({
2114
+ dataToProcess: metricResponse({
2115
+ cephResourceBreakdown: {
2116
+ clusterName: "prod-cluster",
2117
+ metricName: "ceph_pool_stored",
2118
+ metricFriendlyName: "Pool Stored",
2119
+ affectedResources: [{ poolId: "7", metricValue: 3 }],
2120
+ attributes: {},
2121
+ },
2122
+ }),
2123
+ monitorStep: cephStep(),
2124
+ monitor: new Monitor(),
2125
+ });
2126
+
2127
+ expect(context).toContain("1. **Pool** `7` — **3 B**");
2128
+ });
2129
+
2130
+ test("Ceph: a daemon lists its pool and host; a pool lists its host; a host and a cluster stand alone", () => {
2131
+ const context: string | null = Evaluator.buildCephRootCauseContext({
2132
+ dataToProcess: metricResponse({
2133
+ cephResourceBreakdown: {
2134
+ clusterName: "prod-cluster",
2135
+ metricName: "ceph_osd_apply_latency_ms",
2136
+ metricFriendlyName: "OSD Apply Latency",
2137
+ affectedResources: [
2138
+ {
2139
+ daemon: "osd.7",
2140
+ poolId: "2",
2141
+ poolName: "rbd",
2142
+ hostname: "ceph-node-1",
2143
+ metricValue: 400,
2144
+ },
2145
+ {
2146
+ poolId: "3",
2147
+ poolName: "cephfs_data",
2148
+ hostname: "ceph-node-2",
2149
+ metricValue: 300,
2150
+ },
2151
+ { hostname: "ceph-node-3", metricValue: 200 },
2152
+ { metricValue: 100 },
2153
+ ],
2154
+ attributes: {},
2155
+ },
2156
+ }),
2157
+ monitorStep: cephStep(),
2158
+ monitor: new Monitor(),
2159
+ });
2160
+
2161
+ expect(context).toContain(
2162
+ [
2163
+ "1. **Daemon** `osd.7` — **400 ms**",
2164
+ " - Pool: `rbd` (`2`)",
2165
+ " - Host: `ceph-node-1`",
2166
+ "2. **Pool** `cephfs_data` (`3`) — **300 ms**",
2167
+ " - Host: `ceph-node-2`",
2168
+ "3. **Host** `ceph-node-3` — **200 ms**",
2169
+ "4. **Cluster** `prod-cluster` — **100 ms**",
2170
+ ].join("\n"),
2171
+ );
2172
+ });
2173
+ });