@oneuptime/common 14.0.8 → 14.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -1,10 +1,96 @@
1
+ import AlertSeverityService from "./AlertSeverityService";
1
2
  import DatabaseService from "./DatabaseService";
3
+ import IncidentSeverityService from "./IncidentSeverityService";
4
+ import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
5
+ import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
2
6
  import Model from "../../Models/DatabaseModels/TeamComplianceSetting";
3
7
  import CreateBy from "../Types/Database/CreateBy";
4
- import { OnCreate } from "../Types/Database/Hooks";
8
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
9
+ import ModelPermission from "../Types/Database/Permissions/Index";
10
+ import QueryHelper from "../Types/Database/QueryHelper";
11
+ import UpdateBy from "../Types/Database/UpdateBy";
12
+ import Includes from "../../Types/BaseDatabase/Includes";
13
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
5
14
  import BadDataException from "../../Types/Exception/BadDataException";
15
+ import { JSONObject } from "../../Types/JSON";
16
+ import ObjectID from "../../Types/ObjectID";
17
+ import PositiveNumber from "../../Types/PositiveNumber";
18
+ import ComplianceNotificationChannel from "../../Types/Team/ComplianceNotificationChannel";
19
+ import ComplianceRule, {
20
+ ComplianceSeverityKind,
21
+ } from "../../Types/Team/ComplianceRule";
22
+ import ComplianceRuleType from "../../Types/Team/ComplianceRuleType";
6
23
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
7
24
 
25
+ /*
26
+ * The part of a compliance rule that decides what it checks: its type, the
27
+ * channel it insists on (null = any) and the severities it is scoped to
28
+ * (empty = every severity of its kind). Two rules with the same scope check
29
+ * exactly the same thing, which is what "duplicate" means here.
30
+ */
31
+ export interface ComplianceRuleScope {
32
+ ruleType: ComplianceRuleType;
33
+ notificationChannel: ComplianceNotificationChannel | null;
34
+ severityKind: ComplianceSeverityKind | null;
35
+ // Sorted, de-duplicated ids of the severity list the rule's kind uses.
36
+ severityIds: Array<string>;
37
+ }
38
+
39
+ // A severity about to be deleted, as the severity services read it.
40
+ export interface DeletedSeverity {
41
+ _id?: string | undefined;
42
+ projectId?: ObjectID | undefined;
43
+ }
44
+
45
+ export const DUPLICATE_COMPLIANCE_RULE_MESSAGE: string =
46
+ "This team already has a compliance rule that checks exactly the same thing. Edit that rule instead of adding another.";
47
+
48
+ /*
49
+ * The key a severity delete sets to true in TeamComplianceSetting.options on
50
+ * a rule it left with none of the severities it was scoped to (see SEVERITY
51
+ * DELETES below). Such a rule checks nothing until an admin picks new
52
+ * severities: it is paused, it cannot be turned back on as it is, it is no
53
+ * duplicate of anything, and the compliance page says why. Any update that
54
+ * changes the rule's scope removes the key.
55
+ */
56
+ export const SEVERITIES_DELETED_OPTION: string = "severitiesDeleted";
57
+
58
+ export const SEVERITIES_DELETED_ENABLE_MESSAGE: string =
59
+ "Every severity this rule was scoped to has been deleted. Edit the rule to choose new severities before turning it back on.";
60
+
61
+ export const OPTIONS_DIFFER_MESSAGE: string =
62
+ "These compliance rules carry different options, so their scope cannot be changed in one update. Update them one at a time.";
63
+
64
+ /*
65
+ * The two severity relations, and the model each one's items are written as.
66
+ * Every list a payload carries for them is rewritten through
67
+ * normaliseSeverityLists before anything is checked.
68
+ */
69
+ const SEVERITY_LISTS: ReadonlyArray<{
70
+ key: "incidentSeverities" | "alertSeverities";
71
+ kind: ComplianceSeverityKind;
72
+ buildItem: (id: string) => IncidentSeverity | AlertSeverity;
73
+ }> = [
74
+ {
75
+ key: "incidentSeverities",
76
+ kind: ComplianceSeverityKind.Incident,
77
+ buildItem: (id: string): IncidentSeverity => {
78
+ const severity: IncidentSeverity = new IncidentSeverity();
79
+ severity._id = id;
80
+ return severity;
81
+ },
82
+ },
83
+ {
84
+ key: "alertSeverities",
85
+ kind: ComplianceSeverityKind.Alert,
86
+ buildItem: (id: string): AlertSeverity => {
87
+ const severity: AlertSeverity = new AlertSeverity();
88
+ severity._id = id;
89
+ return severity;
90
+ },
91
+ },
92
+ ];
93
+
8
94
  export class TeamComplianceSettingService extends DatabaseService<Model> {
9
95
  public constructor() {
10
96
  super(Model);
@@ -14,27 +100,1057 @@ export class TeamComplianceSettingService extends DatabaseService<Model> {
14
100
  protected override async onBeforeCreate(
15
101
  createBy: CreateBy<Model>,
16
102
  ): Promise<OnCreate<Model>> {
17
- // Check if a compliance setting with the same teamId and ruleType already exists
18
- const existingSetting: Model | null = await this.findOneBy({
19
- query: {
20
- teamId: createBy.data.teamId!,
21
- ruleType: createBy.data.ruleType!,
103
+ /*
104
+ * Authorisation first. DatabaseService.create only checks the caller's
105
+ * create permission AFTER this hook, and every check below reads as root
106
+ * in the project the request names - which a caller picks with a header,
107
+ * member of that project or not. Answering "that severity is not in this
108
+ * project" or "that team already has this rule" before the permission
109
+ * refusal would describe another project's severities and rules to
110
+ * anyone signed in. The columns this hook clears later carry the same
111
+ * create permissions, so the check DatabaseService repeats still passes.
112
+ */
113
+ if (!createBy.props.isRoot && !createBy.props.isMasterAdmin) {
114
+ ModelPermission.checkCreatePermissions(
115
+ this.modelType,
116
+ createBy.data,
117
+ createBy.props,
118
+ );
119
+ }
120
+
121
+ const ruleType: ComplianceRuleType = this.assertKnownRuleType(
122
+ createBy.data.ruleType,
123
+ );
124
+
125
+ this.assertValidChannel(createBy.data.notificationChannel);
126
+
127
+ /*
128
+ * Normalise before anything is stored: a method rule carries no channel
129
+ * or severities, and an incident rule carries no alert severities. The
130
+ * form hides the fields that do not apply, but the API accepts whatever it
131
+ * is sent, and a stray alert severity on an incident rule would otherwise
132
+ * sit in the database looking like part of the rule.
133
+ */
134
+ this.clearOptionsThatDoNotApply(createBy.data, ruleType, {
135
+ clearUnsentOptions: false,
136
+ });
137
+
138
+ TeamComplianceSettingService.normaliseSeverityLists(createBy.data);
139
+
140
+ const projectId: ObjectID | undefined =
141
+ createBy.data.projectId || createBy.props.tenantId;
142
+
143
+ if (!projectId) {
144
+ throw new BadDataException("Project ID is required.");
145
+ }
146
+
147
+ if (!createBy.data.teamId) {
148
+ throw new BadDataException("Team ID is required.");
149
+ }
150
+
151
+ const scope: ComplianceRuleScope = TeamComplianceSettingService.getScope({
152
+ ruleType: ruleType,
153
+ notificationChannel: createBy.data.notificationChannel,
154
+ incidentSeverities: createBy.data.incidentSeverities,
155
+ alertSeverities: createBy.data.alertSeverities,
156
+ });
157
+
158
+ await this.assertSeveritiesBelongToProject(projectId, scope);
159
+
160
+ await this.assertNoIdenticalRule({
161
+ teamId: createBy.data.teamId,
162
+ projectId: projectId,
163
+ scope: scope,
164
+ excludeSettingId: undefined,
165
+ });
166
+
167
+ return { createBy, carryForward: null };
168
+ }
169
+
170
+ /*
171
+ * Toggling a rule on or off touches none of the fields that define it, so a
172
+ * disable returns before reading anything: it must never fail because of
173
+ * the rule's configuration. Turning a rule ON reads one thing - whether a
174
+ * severity delete left it with nothing to check (SEVERITIES_DELETED_OPTION)
175
+ * - because turning such a rule on as it is would make it check every
176
+ * severity of its kind, a rule nobody wrote.
177
+ */
178
+ @CaptureSpan()
179
+ protected override async onBeforeUpdate(
180
+ updateBy: UpdateBy<Model>,
181
+ ): Promise<OnUpdate<Model>> {
182
+ const data: JSONObject = updateBy.data as JSONObject;
183
+
184
+ const changesScope: boolean =
185
+ data["ruleType"] !== undefined ||
186
+ data["notificationChannel"] !== undefined ||
187
+ data["incidentSeverities"] !== undefined ||
188
+ data["alertSeverities"] !== undefined;
189
+
190
+ if (!changesScope) {
191
+ if (data["enabled"] === true) {
192
+ await this.assertNoRuleLeftWithoutSeveritiesIsTurnedOn(updateBy);
193
+ }
194
+
195
+ return { updateBy, carryForward: null };
196
+ }
197
+
198
+ /*
199
+ * As on create: _updateBy checks the caller's update permission only
200
+ * after this hook, and updateBy / updateOneBy do not check it before. The
201
+ * reads below are scoped to the caller's project, but that project is
202
+ * whatever the request named, so a caller who may not change rules there
203
+ * is refused before any of them can answer.
204
+ */
205
+ if (!updateBy.props.isRoot && !updateBy.props.isMasterAdmin) {
206
+ await this.assertMayUpdate(updateBy);
207
+ }
208
+
209
+ const newRuleType: ComplianceRuleType | undefined =
210
+ data["ruleType"] !== undefined
211
+ ? this.assertKnownRuleType(data["ruleType"])
212
+ : undefined;
213
+
214
+ this.assertValidChannel(data["notificationChannel"]);
215
+
216
+ if (newRuleType) {
217
+ /*
218
+ * A new type makes the row's STORED options stale too, not only the ones
219
+ * this update sends: switching "Call for Critical incidents" to
220
+ * "Verified email" must not leave Call and Critical on the row, to come
221
+ * back as the scope if the type is ever switched again.
222
+ */
223
+ this.clearOptionsThatDoNotApply(data, newRuleType, {
224
+ clearUnsentOptions: true,
225
+ });
226
+ }
227
+
228
+ /*
229
+ * The rows being changed, read to work out what each will check once the
230
+ * update lands. Scoped to the caller's project when there is one, so the
231
+ * rows of another project are never checked, let alone described.
232
+ */
233
+ const existingSettings: Array<Model> = await this.findBy({
234
+ query: TeamComplianceSettingService.getTargetQuery(updateBy),
235
+ select: {
236
+ _id: true,
237
+ teamId: true,
238
+ projectId: true,
239
+ ruleType: true,
240
+ notificationChannel: true,
241
+ options: true,
242
+ incidentSeverities: {
243
+ _id: true,
244
+ },
245
+ alertSeverities: {
246
+ _id: true,
247
+ },
22
248
  },
249
+ limit: LIMIT_PER_PROJECT,
250
+ skip: 0,
23
251
  props: {
24
252
  isRoot: true,
25
253
  },
254
+ });
255
+
256
+ if (!newRuleType) {
257
+ /*
258
+ * The type stays, so drop whatever this update sends that the type does
259
+ * not use - as create does - rather than store alert severities on an
260
+ * incident rule unvalidated. Only when every row being updated has the
261
+ * same type, because `data` is written to all of them.
262
+ */
263
+ const storedRuleTypes: Set<ComplianceRuleType> =
264
+ new Set<ComplianceRuleType>();
265
+
266
+ for (const existing of existingSettings) {
267
+ if (ComplianceRule.isKnownRuleType(existing.ruleType)) {
268
+ storedRuleTypes.add(existing.ruleType);
269
+ }
270
+ }
271
+
272
+ const storedRuleType: ComplianceRuleType | undefined =
273
+ Array.from(storedRuleTypes)[0];
274
+
275
+ if (storedRuleTypes.size === 1 && storedRuleType) {
276
+ this.clearOptionsThatDoNotApply(data, storedRuleType, {
277
+ clearUnsentOptions: false,
278
+ });
279
+ }
280
+ }
281
+
282
+ /*
283
+ * `data` is written to every row the update matches, so the lists it
284
+ * sends are normalised once, here, and each row below is checked against
285
+ * exactly what will be written to it.
286
+ */
287
+ TeamComplianceSettingService.normaliseSeverityLists(data);
288
+
289
+ /*
290
+ * Every list this update sends is written to every row it matches, so it
291
+ * is checked against the project of every one of those rows - by the
292
+ * list's own kind, not by the row's type. A row whose stored type this
293
+ * build does not recognise (legacy data, or written by a newer build) is
294
+ * skipped by the scope checks below, and the relation save would
295
+ * otherwise link it to another project's severity unchecked.
296
+ */
297
+ const projectIds: Map<string, ObjectID> = new Map<string, ObjectID>();
298
+
299
+ for (const existing of existingSettings) {
300
+ if (existing.projectId) {
301
+ projectIds.set(existing.projectId.toString(), existing.projectId);
302
+ }
303
+ }
304
+
305
+ for (const projectId of projectIds.values()) {
306
+ for (const list of SEVERITY_LISTS) {
307
+ if (data[list.key] === undefined) {
308
+ continue;
309
+ }
310
+
311
+ await this.assertSeveritiesBelongToProject(projectId, {
312
+ severityKind: list.kind,
313
+ severityIds: TeamComplianceSettingService.getIds(data[list.key]),
314
+ });
315
+ }
316
+ }
317
+
318
+ for (const existing of existingSettings) {
319
+ const ruleType: ComplianceRuleType | undefined =
320
+ newRuleType ||
321
+ (ComplianceRule.isKnownRuleType(existing.ruleType)
322
+ ? existing.ruleType
323
+ : undefined);
324
+
325
+ if (!ruleType || !existing.teamId || !existing.projectId) {
326
+ continue;
327
+ }
328
+
329
+ const scope: ComplianceRuleScope = TeamComplianceSettingService.getScope({
330
+ ruleType: ruleType,
331
+ notificationChannel:
332
+ data["notificationChannel"] !== undefined
333
+ ? data["notificationChannel"]
334
+ : existing.notificationChannel,
335
+ incidentSeverities:
336
+ data["incidentSeverities"] !== undefined
337
+ ? data["incidentSeverities"]
338
+ : existing.incidentSeverities,
339
+ alertSeverities:
340
+ data["alertSeverities"] !== undefined
341
+ ? data["alertSeverities"]
342
+ : existing.alertSeverities,
343
+ });
344
+
345
+ /*
346
+ * A list this update sends was checked above; one it keeps is the
347
+ * stored list, re-checked here because a type change can make a list
348
+ * the rule did not use until now its scope.
349
+ */
350
+ const isScopeListSent: boolean = SEVERITY_LISTS.some(
351
+ (list: { key: string; kind: ComplianceSeverityKind }): boolean => {
352
+ return (
353
+ list.kind === scope.severityKind && data[list.key] !== undefined
354
+ );
355
+ },
356
+ );
357
+
358
+ if (!isScopeListSent) {
359
+ await this.assertSeveritiesBelongToProject(existing.projectId, scope);
360
+ }
361
+
362
+ await this.assertNoIdenticalRule({
363
+ teamId: existing.teamId,
364
+ projectId: existing.projectId,
365
+ scope: scope,
366
+ excludeSettingId: existing.id || undefined,
367
+ });
368
+ }
369
+
370
+ /*
371
+ * The admin has chosen what the rule checks now - new severities, or
372
+ * deliberately every severity - so a rule a severity delete left empty
373
+ * is an ordinary rule again, in the same write.
374
+ */
375
+ TeamComplianceSettingService.clearSeveritiesDeletedMark(
376
+ data,
377
+ existingSettings,
378
+ );
379
+
380
+ return { updateBy, carryForward: null };
381
+ }
382
+
383
+ /*
384
+ * An update that turns a rule on without re-scoping it is refused when a
385
+ * severity delete left the rule with nothing to check: turned on as it is,
386
+ * it would check every severity of its kind. The caller's permission is
387
+ * checked first, as for a scope change, so the refusal describes nothing
388
+ * to a caller who may not change the rule.
389
+ */
390
+ private async assertNoRuleLeftWithoutSeveritiesIsTurnedOn(
391
+ updateBy: UpdateBy<Model>,
392
+ ): Promise<void> {
393
+ if (!updateBy.props.isRoot && !updateBy.props.isMasterAdmin) {
394
+ await this.assertMayUpdate(updateBy);
395
+ }
396
+
397
+ const rules: Array<Model> = await this.findBy({
398
+ query: TeamComplianceSettingService.getTargetQuery(updateBy),
26
399
  select: {
27
400
  _id: true,
401
+ ruleType: true,
402
+ options: true,
403
+ incidentSeverities: {
404
+ _id: true,
405
+ },
406
+ alertSeverities: {
407
+ _id: true,
408
+ },
409
+ },
410
+ limit: LIMIT_PER_PROJECT,
411
+ skip: 0,
412
+ props: {
413
+ isRoot: true,
28
414
  },
29
415
  });
30
416
 
31
- if (existingSetting) {
417
+ if (
418
+ rules.some((rule: Model): boolean => {
419
+ return TeamComplianceSettingService.isLeftWithoutSeverities(rule);
420
+ })
421
+ ) {
422
+ throw new BadDataException(SEVERITIES_DELETED_ENABLE_MESSAGE);
423
+ }
424
+ }
425
+
426
+ /*
427
+ * The update's own query, narrowed to the caller's project when there is
428
+ * one - a copy, so the caller's query object is left as it was.
429
+ */
430
+ private static getTargetQuery(updateBy: UpdateBy<Model>): JSONObject {
431
+ const query: JSONObject = { ...(updateBy.query as JSONObject) };
432
+
433
+ if (updateBy.props.tenantId) {
434
+ query["projectId"] = updateBy.props.tenantId;
435
+ }
436
+
437
+ return query;
438
+ }
439
+
440
+ /*
441
+ * True when TeamComplianceSetting.options carries SEVERITIES_DELETED_OPTION.
442
+ * Exported for the compliance status, which lists such a rule as paused
443
+ * with nothing to check.
444
+ */
445
+ public static hasSeveritiesDeletedMark(options: unknown): boolean {
446
+ return (
447
+ TeamComplianceSettingService.isPlainObject(options) &&
448
+ options[SEVERITIES_DELETED_OPTION] === true
449
+ );
450
+ }
451
+
452
+ /*
453
+ * A rule a severity delete left with nothing to check: marked, of a type
454
+ * scoped by severity, and still without a severity of its kind. A mark on a
455
+ * rule that has severities again - it lost a race with a re-scope - is
456
+ * stale, and the rule is judged by the severities it has.
457
+ */
458
+ public static isLeftWithoutSeverities(rule: Model): boolean {
459
+ const kind: ComplianceSeverityKind | undefined =
460
+ ComplianceRule.getSeverityKind(rule.ruleType);
461
+
462
+ return (
463
+ Boolean(kind) &&
464
+ TeamComplianceSettingService.hasSeveritiesDeletedMark(rule.options) &&
465
+ TeamComplianceSettingService.getSeverityIdsOfKind(rule, kind!).length ===
466
+ 0
467
+ );
468
+ }
469
+
470
+ /*
471
+ * Writes `options` without SEVERITIES_DELETED_OPTION, keeping every other
472
+ * key: the options sent with the update when it sends any, else the
473
+ * options stored on the rows it changes. `data` is written to every row
474
+ * the update matches, so stored options are only rewritten when those rows
475
+ * agree on them - otherwise one row's options would overwrite another's.
476
+ */
477
+ private static clearSeveritiesDeletedMark(
478
+ data: JSONObject,
479
+ existingSettings: Array<Model>,
480
+ ): void {
481
+ if (data["options"] !== undefined) {
482
+ if (
483
+ TeamComplianceSettingService.isPlainObject(data["options"]) &&
484
+ SEVERITIES_DELETED_OPTION in data["options"]
485
+ ) {
486
+ data["options"] = TeamComplianceSettingService.withoutMark(
487
+ data["options"],
488
+ );
489
+ }
490
+
491
+ return;
492
+ }
493
+
494
+ const isAnyMarked: boolean = existingSettings.some(
495
+ (existing: Model): boolean => {
496
+ return TeamComplianceSettingService.hasSeveritiesDeletedMark(
497
+ existing.options,
498
+ );
499
+ },
500
+ );
501
+
502
+ if (!isAnyMarked) {
503
+ return;
504
+ }
505
+
506
+ const cleared: Array<JSONObject | null> = existingSettings.map(
507
+ (existing: Model): JSONObject | null => {
508
+ return TeamComplianceSettingService.isPlainObject(existing.options)
509
+ ? TeamComplianceSettingService.withoutMark(existing.options)
510
+ : null;
511
+ },
512
+ );
513
+
514
+ const first: string = JSON.stringify(cleared[0] ?? null);
515
+
516
+ if (
517
+ cleared.some((options: JSONObject | null): boolean => {
518
+ return JSON.stringify(options) !== first;
519
+ })
520
+ ) {
521
+ throw new BadDataException(OPTIONS_DIFFER_MESSAGE);
522
+ }
523
+
524
+ data["options"] = cleared[0] ?? null;
525
+ }
526
+
527
+ // A copy of `options` without the mark; null when nothing else is left.
528
+ private static withoutMark(options: JSONObject): JSONObject | null {
529
+ const rest: JSONObject = { ...options };
530
+ delete rest[SEVERITIES_DELETED_OPTION];
531
+
532
+ return Object.keys(rest).length > 0 ? rest : null;
533
+ }
534
+
535
+ private static isPlainObject(value: unknown): value is JSONObject {
536
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
537
+ }
538
+
539
+ /*
540
+ * SEVERITY DELETES. A rule's severity scope is stored only as join rows,
541
+ * and those cascade away with the severity. A rule left with no severity
542
+ * reads exactly like one created for every severity of its kind, so
543
+ * deleting the only severity "Call for Critical incidents" was scoped to
544
+ * would quietly turn it into "Call for every incident severity" - failing
545
+ * members against a rule nobody wrote.
546
+ *
547
+ * The severity services therefore ask, before the delete and while the join
548
+ * rows still say what each rule was scoped to, which rules reference any of
549
+ * the severities about to go (getRulesScopedToAnyOf), and once the delete
550
+ * has actually happened they have those that were left with none paused
551
+ * and marked (pauseRulesLeftWithoutSeverities, SEVERITIES_DELETED_OPTION).
552
+ * The Compliance page lists a marked rule as having lost its severities,
553
+ * for an admin to re-scope or delete; it cannot be turned back on as it is.
554
+ * Pausing only after the delete means a delete that is refused - by the
555
+ * permission layer, or because incidents still use the severity - pauses
556
+ * nothing.
557
+ *
558
+ * ANY reference is carried forward, not only rules scoped to nothing but
559
+ * the severities this delete removes: two deletes running side by side -
560
+ * the two severities of a "Critical and Major" rule, deleted by Terraform
561
+ * in parallel - each see the other's severity still attached before
562
+ * either commits, so neither would carry the rule, and it would be left
563
+ * scoped to nothing and checking everything. Each request re-reads after
564
+ * its own delete has committed, so whichever commits last sees the rule
565
+ * empty and pauses it. A rule that still has another severity is left
566
+ * alone by the re-read.
567
+ *
568
+ * Paused rules are carried too: turning one on later must not make it
569
+ * check every severity either.
570
+ */
571
+ @CaptureSpan()
572
+ public async getRulesScopedToAnyOf(data: {
573
+ severityKind: ComplianceSeverityKind;
574
+ severities: Array<DeletedSeverity>;
575
+ }): Promise<Array<string>> {
576
+ const deletedIdsByProject: Map<string, Set<string>> = new Map<
577
+ string,
578
+ Set<string>
579
+ >();
580
+
581
+ for (const severity of data.severities) {
582
+ const id: string | undefined = severity._id?.toString().toLowerCase();
583
+ const projectId: string | undefined = severity.projectId?.toString();
584
+
585
+ if (!id || !projectId) {
586
+ continue;
587
+ }
588
+
589
+ const ids: Set<string> =
590
+ deletedIdsByProject.get(projectId) || new Set<string>();
591
+ ids.add(id);
592
+ deletedIdsByProject.set(projectId, ids);
593
+ }
594
+
595
+ const ruleTypes: Array<ComplianceRuleType> = Object.values(
596
+ ComplianceRuleType,
597
+ ).filter((ruleType: ComplianceRuleType): boolean => {
598
+ return ComplianceRule.getSeverityKind(ruleType) === data.severityKind;
599
+ });
600
+
601
+ const settingIds: Array<string> = [];
602
+
603
+ for (const [projectId, deletedIds] of deletedIdsByProject) {
604
+ const rules: Array<Model> = await this.findBy({
605
+ query: {
606
+ projectId: new ObjectID(projectId),
607
+ ruleType: new Includes(ruleTypes),
608
+ },
609
+ select: {
610
+ _id: true,
611
+ ruleType: true,
612
+ incidentSeverities: {
613
+ _id: true,
614
+ },
615
+ alertSeverities: {
616
+ _id: true,
617
+ },
618
+ },
619
+ limit: LIMIT_PER_PROJECT,
620
+ skip: 0,
621
+ props: {
622
+ isRoot: true,
623
+ },
624
+ });
625
+
626
+ for (const rule of rules) {
627
+ if (
628
+ rule._id &&
629
+ ComplianceRule.getSeverityKind(rule.ruleType) === data.severityKind &&
630
+ TeamComplianceSettingService.getSeverityIdsOfKind(
631
+ rule,
632
+ data.severityKind,
633
+ ).some((id: string): boolean => {
634
+ return deletedIds.has(id);
635
+ })
636
+ ) {
637
+ settingIds.push(rule._id.toString());
638
+ }
639
+ }
640
+ }
641
+
642
+ return settingIds;
643
+ }
644
+
645
+ /*
646
+ * The second half of a severity delete: of the rules getRulesScopedToAnyOf
647
+ * named, pause and mark (SEVERITIES_DELETED_OPTION) the ones now left with
648
+ * no severity of their kind, in one write per rule's own options so every
649
+ * other key they hold is kept. Re-read rather than assumed, so a rule
650
+ * whose severity survived (the delete matched fewer rows than the lookup
651
+ * did, another of its severities is still there, or it was re-scoped in the
652
+ * meantime) stays as it is.
653
+ */
654
+ @CaptureSpan()
655
+ public async pauseRulesLeftWithoutSeverities(data: {
656
+ severityKind: ComplianceSeverityKind;
657
+ settingIds: Array<string>;
658
+ }): Promise<Array<string>> {
659
+ if (data.settingIds.length === 0) {
660
+ return [];
661
+ }
662
+
663
+ const rules: Array<Model> = await this.findBy({
664
+ query: {
665
+ _id: new Includes(data.settingIds),
666
+ },
667
+ select: {
668
+ _id: true,
669
+ ruleType: true,
670
+ enabled: true,
671
+ options: true,
672
+ incidentSeverities: {
673
+ _id: true,
674
+ },
675
+ alertSeverities: {
676
+ _id: true,
677
+ },
678
+ },
679
+ limit: LIMIT_PER_PROJECT,
680
+ skip: 0,
681
+ props: {
682
+ isRoot: true,
683
+ },
684
+ });
685
+
686
+ // Rule ids by the options they are written with, marked.
687
+ const toPause: Map<string, { options: JSONObject; ids: Array<string> }> =
688
+ new Map<string, { options: JSONObject; ids: Array<string> }>();
689
+ const paused: Array<string> = [];
690
+
691
+ for (const rule of rules) {
692
+ if (
693
+ !rule._id ||
694
+ ComplianceRule.getSeverityKind(rule.ruleType) !== data.severityKind ||
695
+ TeamComplianceSettingService.getSeverityIdsOfKind(
696
+ rule,
697
+ data.severityKind,
698
+ ).length > 0
699
+ ) {
700
+ continue;
701
+ }
702
+
703
+ const isAlreadyPausedAndMarked: boolean =
704
+ rule.enabled === false &&
705
+ TeamComplianceSettingService.hasSeveritiesDeletedMark(rule.options);
706
+
707
+ if (isAlreadyPausedAndMarked) {
708
+ continue;
709
+ }
710
+
711
+ const options: JSONObject = {
712
+ ...(TeamComplianceSettingService.isPlainObject(rule.options)
713
+ ? rule.options
714
+ : {}),
715
+ [SEVERITIES_DELETED_OPTION]: true,
716
+ };
717
+
718
+ const key: string = JSON.stringify(options);
719
+ const group: { options: JSONObject; ids: Array<string> } = toPause.get(
720
+ key,
721
+ ) || { options: options, ids: [] };
722
+
723
+ group.ids.push(rule._id.toString());
724
+ toPause.set(key, group);
725
+ paused.push(rule._id.toString());
726
+ }
727
+
728
+ for (const group of toPause.values()) {
729
+ /*
730
+ * Neither the type, the channel nor a severity list: the update hook
731
+ * reads nothing for it. (Cast: the write type of a JSON column is too
732
+ * deep for the compiler to spell out.)
733
+ */
734
+ const pause: JSONObject = {
735
+ enabled: false,
736
+ options: group.options,
737
+ };
738
+
739
+ await this.updateBy({
740
+ query: {
741
+ _id: new Includes(group.ids),
742
+ },
743
+ data: pause as unknown as UpdateBy<Model>["data"],
744
+ limit: group.ids.length,
745
+ skip: 0,
746
+ props: {
747
+ isRoot: true,
748
+ },
749
+ });
750
+ }
751
+
752
+ return paused;
753
+ }
754
+
755
+ /*
756
+ * What a rule checks, from its stored (or about-to-be-stored) fields. Only
757
+ * the options its type uses count: a channel on a method rule, or alert
758
+ * severities on an incident rule, change nothing about what is checked.
759
+ */
760
+ public static getScope(data: {
761
+ ruleType: ComplianceRuleType;
762
+ notificationChannel: unknown;
763
+ incidentSeverities: unknown;
764
+ alertSeverities: unknown;
765
+ }): ComplianceRuleScope {
766
+ const severityKind: ComplianceSeverityKind | undefined =
767
+ ComplianceRule.getSeverityKind(data.ruleType);
768
+
769
+ const notificationChannel: ComplianceNotificationChannel | null =
770
+ ComplianceRule.supportsChannel(data.ruleType) &&
771
+ ComplianceRule.isKnownChannel(data.notificationChannel)
772
+ ? data.notificationChannel
773
+ : null;
774
+
775
+ let severityIds: Array<string> = [];
776
+
777
+ if (severityKind === ComplianceSeverityKind.Incident) {
778
+ severityIds = TeamComplianceSettingService.getIds(
779
+ data.incidentSeverities,
780
+ );
781
+ } else if (severityKind === ComplianceSeverityKind.Alert) {
782
+ severityIds = TeamComplianceSettingService.getIds(data.alertSeverities);
783
+ }
784
+
785
+ return {
786
+ ruleType: data.ruleType,
787
+ notificationChannel: notificationChannel,
788
+ severityKind: severityKind || null,
789
+ severityIds: severityIds,
790
+ };
791
+ }
792
+
793
+ public static isSameScope(
794
+ a: ComplianceRuleScope,
795
+ b: ComplianceRuleScope,
796
+ ): boolean {
797
+ return (
798
+ a.ruleType === b.ruleType &&
799
+ a.notificationChannel === b.notificationChannel &&
800
+ a.severityIds.join(",") === b.severityIds.join(",")
801
+ );
802
+ }
803
+
804
+ /*
805
+ * Severity ids from a severity list as it is STORED (or once a payload has
806
+ * been through normaliseSeverityLists): an id string, an ObjectID, a model,
807
+ * or `{_id}` / `{id}` JSON. Sorted and de-duplicated so two lists compare by
808
+ * content, and lower-cased because Postgres compares uuids without case and
809
+ * hands them back lower case: "ABC..." sent by a client is the stored
810
+ * "abc...", and must not slip past the duplicate check as a different id.
811
+ *
812
+ * Lenient on purpose - an unreadable item is skipped - so it is NOT the
813
+ * reader for a payload: what the relation save writes is decided by
814
+ * DatabaseService.sanitizeCreateOrUpdate, and resolveSentSeverityIds reads
815
+ * a payload exactly the way that does.
816
+ */
817
+ public static getIds(value: unknown): Array<string> {
818
+ if (!Array.isArray(value)) {
819
+ return [];
820
+ }
821
+
822
+ const ids: Set<string> = new Set<string>();
823
+
824
+ for (const item of value) {
825
+ let id: string | undefined = undefined;
826
+
827
+ if (typeof item === "string") {
828
+ id = item;
829
+ } else if (item instanceof ObjectID) {
830
+ id = item.toString();
831
+ } else if (item && typeof item === "object") {
832
+ const candidate: unknown =
833
+ (item as JSONObject)["_id"] || (item as JSONObject)["id"];
834
+
835
+ if (typeof candidate === "string") {
836
+ id = candidate;
837
+ } else if (candidate instanceof ObjectID) {
838
+ id = candidate.toString();
839
+ }
840
+ }
841
+
842
+ if (id && id.trim()) {
843
+ ids.add(id.trim().toLowerCase());
844
+ }
845
+ }
846
+
847
+ return Array.from(ids).sort();
848
+ }
849
+
850
+ /*
851
+ * The severity ids a SENT list asks the relation save to write, read item
852
+ * by item with exactly the precedence DatabaseService.sanitizeCreateOrUpdate
853
+ * uses - an id string or ObjectID, else a string `_id`, else a string `id`,
854
+ * else a model's own `_id` - so the ids this service validates are the ids
855
+ * that would be written. An item that yields no valid uuid is refused
856
+ * rather than skipped: the sanitizer would drop it (or write garbage), and
857
+ * a list that silently lost its only item is a rule for EVERY severity.
858
+ *
859
+ * Trimmed, lower-cased (Postgres hands uuids back lower case, and TypeORM
860
+ * compares join rows as case-sensitive strings) and de-duplicated (the join
861
+ * table's primary key refuses the same severity twice), first occurrence
862
+ * kept. Null is an empty list, as the relation save treats it; anything
863
+ * else that is not a list is refused.
864
+ */
865
+ public static resolveSentSeverityIds(
866
+ value: unknown,
867
+ kind: ComplianceSeverityKind,
868
+ ): Array<string> {
869
+ if (value === null) {
870
+ return [];
871
+ }
872
+
873
+ const noun: string =
874
+ kind === ComplianceSeverityKind.Incident ? "incident" : "alert";
875
+
876
+ if (!Array.isArray(value)) {
32
877
  throw new BadDataException(
33
- `A compliance setting for rule type "${createBy.data.ruleType}" already exists for this team.`,
878
+ `The ${noun} severities of a compliance rule must be a list of severity ids.`,
34
879
  );
35
880
  }
36
881
 
37
- return { createBy, carryForward: null };
882
+ const ids: Array<string> = [];
883
+
884
+ for (const item of value) {
885
+ const id: string | undefined =
886
+ TeamComplianceSettingService.resolveWrittenId(item);
887
+
888
+ if (id === undefined) {
889
+ throw new BadDataException(
890
+ `Every ${noun} severity of a compliance rule must be a severity id.`,
891
+ );
892
+ }
893
+
894
+ const normalised: string = id.trim().toLowerCase();
895
+
896
+ ObjectID.validateUUID(normalised);
897
+
898
+ if (!ids.includes(normalised)) {
899
+ ids.push(normalised);
900
+ }
901
+ }
902
+
903
+ return ids;
904
+ }
905
+
906
+ /*
907
+ * Replaces each severity list a payload sends with the list
908
+ * resolveSentSeverityIds read from it, as models carrying only `_id`. From
909
+ * here on the scope that is validated and compared, and the join rows the
910
+ * relation save writes, are one and the same list. Lists that are not sent
911
+ * stay unsent.
912
+ */
913
+ public static normaliseSeverityLists(data: Model | JSONObject): void {
914
+ const target: Record<string, unknown> = data as unknown as Record<
915
+ string,
916
+ unknown
917
+ >;
918
+
919
+ for (const list of SEVERITY_LISTS) {
920
+ if (target[list.key] === undefined) {
921
+ continue;
922
+ }
923
+
924
+ target[list.key] = TeamComplianceSettingService.resolveSentSeverityIds(
925
+ target[list.key],
926
+ list.kind,
927
+ ).map((id: string): IncidentSeverity | AlertSeverity => {
928
+ return list.buildItem(id);
929
+ });
930
+ }
931
+ }
932
+
933
+ // DatabaseService.sanitizeCreateOrUpdate's reading of one EntityArray item.
934
+ private static resolveWrittenId(item: unknown): string | undefined {
935
+ if (typeof item === "string" || item instanceof ObjectID) {
936
+ return item.toString();
937
+ }
938
+
939
+ if (!item || typeof item !== "object") {
940
+ return undefined;
941
+ }
942
+
943
+ const json: JSONObject = item as JSONObject;
944
+
945
+ if (json["_id"] && typeof json["_id"] === "string") {
946
+ return json["_id"];
947
+ }
948
+
949
+ if (json["id"] && typeof json["id"] === "string") {
950
+ return json["id"];
951
+ }
952
+
953
+ /*
954
+ * A model reaches this line only when its `_id` is empty or not a string,
955
+ * and the sanitizer writes it as it is - with no id a uuid column takes.
956
+ */
957
+ return undefined;
958
+ }
959
+
960
+ private static getSeverityIdsOfKind(
961
+ rule: Model,
962
+ kind: ComplianceSeverityKind,
963
+ ): Array<string> {
964
+ return TeamComplianceSettingService.getIds(
965
+ kind === ComplianceSeverityKind.Incident
966
+ ? rule.incidentSeverities
967
+ : rule.alertSeverities,
968
+ );
969
+ }
970
+
971
+ /*
972
+ * The update permission check _updateBy makes after this hook, made first.
973
+ * It narrows a copy of the query; this hook's own reads add the project
974
+ * themselves, so only the refusal matters here.
975
+ */
976
+ private async assertMayUpdate(updateBy: UpdateBy<Model>): Promise<void> {
977
+ await ModelPermission.checkUpdateQueryPermissions(
978
+ this.modelType,
979
+ { ...updateBy.query },
980
+ updateBy.data,
981
+ updateBy.props,
982
+ );
983
+ }
984
+
985
+ private assertKnownRuleType(value: unknown): ComplianceRuleType {
986
+ if (!ComplianceRule.isKnownRuleType(value)) {
987
+ throw new BadDataException(
988
+ `"${String(value)}" is not a compliance rule type.`,
989
+ );
990
+ }
991
+
992
+ return value;
993
+ }
994
+
995
+ // Null / undefined is "any channel" and always valid.
996
+ private assertValidChannel(value: unknown): void {
997
+ if (value === undefined || value === null) {
998
+ return;
999
+ }
1000
+
1001
+ if (!ComplianceRule.isKnownChannel(value)) {
1002
+ throw new BadDataException(
1003
+ `"${String(value)}" is not a notification channel a compliance rule can require.`,
1004
+ );
1005
+ }
1006
+ }
1007
+
1008
+ /*
1009
+ * `clearUnsentOptions` also clears the options `data` does not mention, so
1010
+ * an update writes them empty on the row instead of leaving what is stored.
1011
+ */
1012
+ private clearOptionsThatDoNotApply(
1013
+ data: Model | JSONObject,
1014
+ ruleType: ComplianceRuleType,
1015
+ options: { clearUnsentOptions: boolean },
1016
+ ): void {
1017
+ const target: JSONObject = data as JSONObject;
1018
+ const severityKind: ComplianceSeverityKind | undefined =
1019
+ ComplianceRule.getSeverityKind(ruleType);
1020
+
1021
+ if (!ComplianceRule.supportsChannel(ruleType)) {
1022
+ if (
1023
+ options.clearUnsentOptions ||
1024
+ (target["notificationChannel"] !== undefined &&
1025
+ target["notificationChannel"] !== null)
1026
+ ) {
1027
+ target["notificationChannel"] = null;
1028
+ }
1029
+ }
1030
+
1031
+ if (
1032
+ severityKind !== ComplianceSeverityKind.Incident &&
1033
+ (options.clearUnsentOptions || target["incidentSeverities"] !== undefined)
1034
+ ) {
1035
+ target["incidentSeverities"] = [];
1036
+ }
1037
+
1038
+ if (
1039
+ severityKind !== ComplianceSeverityKind.Alert &&
1040
+ (options.clearUnsentOptions || target["alertSeverities"] !== undefined)
1041
+ ) {
1042
+ target["alertSeverities"] = [];
1043
+ }
1044
+ }
1045
+
1046
+ /*
1047
+ * The severity relation accepts any id, and the compliance status is read as
1048
+ * root, so a severity from another project would otherwise be stored against
1049
+ * this rule and have its name shown on this project's page.
1050
+ */
1051
+ private async assertSeveritiesBelongToProject(
1052
+ projectId: ObjectID,
1053
+ scope: {
1054
+ severityKind: ComplianceSeverityKind | null;
1055
+ severityIds: Array<string>;
1056
+ },
1057
+ ): Promise<void> {
1058
+ if (scope.severityIds.length === 0 || !scope.severityKind) {
1059
+ return;
1060
+ }
1061
+
1062
+ for (const id of scope.severityIds) {
1063
+ ObjectID.validateUUID(id);
1064
+ }
1065
+
1066
+ const count: PositiveNumber =
1067
+ scope.severityKind === ComplianceSeverityKind.Incident
1068
+ ? await IncidentSeverityService.countBy({
1069
+ query: {
1070
+ _id: new Includes(scope.severityIds),
1071
+ projectId: projectId,
1072
+ },
1073
+ props: {
1074
+ isRoot: true,
1075
+ },
1076
+ })
1077
+ : await AlertSeverityService.countBy({
1078
+ query: {
1079
+ _id: new Includes(scope.severityIds),
1080
+ projectId: projectId,
1081
+ },
1082
+ props: {
1083
+ isRoot: true,
1084
+ },
1085
+ });
1086
+
1087
+ if (count.toNumber() !== scope.severityIds.length) {
1088
+ throw new BadDataException(
1089
+ `One or more of the selected ${scope.severityKind === ComplianceSeverityKind.Incident ? "incident" : "alert"} severities do not exist in this project.`,
1090
+ );
1091
+ }
1092
+ }
1093
+
1094
+ private async assertNoIdenticalRule(data: {
1095
+ teamId: ObjectID;
1096
+ projectId: ObjectID;
1097
+ scope: ComplianceRuleScope;
1098
+ excludeSettingId: ObjectID | undefined;
1099
+ }): Promise<void> {
1100
+ const query: JSONObject = {
1101
+ teamId: data.teamId,
1102
+ projectId: data.projectId,
1103
+ ruleType: data.scope.ruleType,
1104
+ };
1105
+
1106
+ if (data.excludeSettingId) {
1107
+ query["_id"] = QueryHelper.notEquals(data.excludeSettingId.toString());
1108
+ }
1109
+
1110
+ const siblings: Array<Model> = await this.findBy({
1111
+ query: query,
1112
+ select: {
1113
+ _id: true,
1114
+ ruleType: true,
1115
+ notificationChannel: true,
1116
+ options: true,
1117
+ incidentSeverities: {
1118
+ _id: true,
1119
+ },
1120
+ alertSeverities: {
1121
+ _id: true,
1122
+ },
1123
+ },
1124
+ limit: LIMIT_PER_PROJECT,
1125
+ skip: 0,
1126
+ props: {
1127
+ isRoot: true,
1128
+ },
1129
+ });
1130
+
1131
+ for (const sibling of siblings) {
1132
+ /*
1133
+ * A rule a severity delete left with nothing to check is stored with
1134
+ * no severities, which is also how a rule for EVERY severity is
1135
+ * stored. It checks nothing and cannot be turned on until it is
1136
+ * re-scoped - when it is compared again - so it duplicates nothing.
1137
+ */
1138
+ if (TeamComplianceSettingService.isLeftWithoutSeverities(sibling)) {
1139
+ continue;
1140
+ }
1141
+
1142
+ const siblingScope: ComplianceRuleScope =
1143
+ TeamComplianceSettingService.getScope({
1144
+ ruleType: data.scope.ruleType,
1145
+ notificationChannel: sibling.notificationChannel,
1146
+ incidentSeverities: sibling.incidentSeverities,
1147
+ alertSeverities: sibling.alertSeverities,
1148
+ });
1149
+
1150
+ if (TeamComplianceSettingService.isSameScope(siblingScope, data.scope)) {
1151
+ throw new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1152
+ }
1153
+ }
38
1154
  }
39
1155
  }
40
1156