@oneuptime/common 14.0.7 → 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 (1064) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  7. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  8. package/Models/DatabaseModels/Project.ts +55 -5
  9. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  10. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  11. package/Models/DatabaseModels/StatusPage.ts +55 -0
  12. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  13. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  14. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  15. package/Server/API/AIChatAPI.ts +6 -3
  16. package/Server/API/AIInvestigationAPI.ts +17 -10
  17. package/Server/API/AutoRemediationAPI.ts +207 -25
  18. package/Server/API/IncidentAPI.ts +162 -0
  19. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  20. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  21. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  22. package/Server/API/StatusPageAPI.ts +487 -171
  23. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  24. package/Server/Infrastructure/GlobalCache.ts +32 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  36. package/Server/Infrastructure/Semaphore.ts +10 -0
  37. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  38. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  39. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  40. package/Server/Middleware/TelemetryIngest.ts +41 -18
  41. package/Server/Services/AIService.ts +70 -1
  42. package/Server/Services/AlertService.ts +352 -11
  43. package/Server/Services/AlertSeverityService.ts +65 -15
  44. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  45. package/Server/Services/DatabaseService.ts +93 -10
  46. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  47. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  48. package/Server/Services/IncidentEpisodeService.ts +15 -0
  49. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  50. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  51. package/Server/Services/IncidentService.ts +1788 -142
  52. package/Server/Services/IncidentSeverityService.ts +66 -15
  53. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  54. package/Server/Services/IncidentTemplateService.ts +109 -0
  55. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  56. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  57. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  58. package/Server/Services/KubernetesClusterService.ts +129 -21
  59. package/Server/Services/ProjectService.ts +32 -0
  60. package/Server/Services/ProxmoxResourceService.ts +322 -5
  61. package/Server/Services/RunnerJobService.ts +241 -104
  62. package/Server/Services/RunnerService.ts +1 -1
  63. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  64. package/Server/Services/ScheduledMaintenanceService.ts +805 -19
  65. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  66. package/Server/Services/SmsService.ts +10 -0
  67. package/Server/Services/StatusPageService.ts +65 -8
  68. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  69. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  70. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  71. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  72. package/Server/Types/Markdown.ts +153 -6
  73. package/Server/Types/Workflow/Components/Email.ts +61 -2
  74. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  75. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  76. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  77. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  78. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  79. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  80. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  81. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  82. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  83. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  84. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  85. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  86. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  87. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  88. package/Server/Utils/SSRFProtection.ts +76 -16
  89. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  90. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  91. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  92. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  93. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  94. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  95. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  96. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  97. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  98. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  99. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  100. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  101. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  102. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  103. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  104. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  105. package/Server/Utils/StatusPageResource.ts +51 -20
  106. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  107. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  108. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  109. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  110. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  111. package/Server/Utils/VM/VMAPI.ts +1 -0
  112. package/Server/Utils/VM/VMRunner.ts +10 -0
  113. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  114. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  115. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  116. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  117. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  118. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  119. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  120. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  121. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  122. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  123. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  124. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  125. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  126. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  127. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  128. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  129. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  130. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  131. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  132. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  133. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  134. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  135. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  136. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  137. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  138. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  139. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  140. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  141. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  142. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  143. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  144. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  145. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  146. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  147. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  148. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  149. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  150. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  151. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  152. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  153. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  154. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  155. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  156. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  157. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  158. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  159. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  160. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  161. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  162. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  163. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  164. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  165. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  166. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  167. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  168. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  169. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  170. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  171. package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
  172. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  173. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  174. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
  175. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  176. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  177. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  178. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  179. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  180. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  181. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  182. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  183. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  184. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  185. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  186. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  187. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  188. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  189. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  190. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  191. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  192. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  193. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  194. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  195. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  196. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  197. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  198. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  199. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  200. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  201. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  202. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  203. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  204. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  205. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  206. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  207. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  208. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  209. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  210. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  211. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  212. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  213. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  214. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  215. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  216. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  217. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  218. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  219. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  220. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  221. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  222. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  223. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  224. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  225. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  226. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  227. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  228. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  229. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  230. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  231. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  232. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  233. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  234. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  235. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  236. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  237. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  238. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  239. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  240. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  241. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  242. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  243. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  244. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  245. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  246. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  247. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  248. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  249. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  250. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  251. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  252. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  253. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  254. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  255. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  256. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  257. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  258. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  259. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  260. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  261. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  262. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  263. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  264. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  265. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  266. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  267. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  268. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  269. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  270. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  271. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  272. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  273. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  274. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  275. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  276. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  277. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  278. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  279. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  280. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  281. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  282. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  283. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  284. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  285. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  286. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  287. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  288. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  289. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  290. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  291. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  292. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  293. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  294. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  295. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  296. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  297. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  298. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  299. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  300. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  301. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  302. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  303. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  304. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  305. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  306. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  307. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  308. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  309. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  310. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  311. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  312. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  313. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  314. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  315. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  316. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  317. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  318. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  319. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  320. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  321. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  322. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  323. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  324. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  325. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  326. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  327. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  328. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  329. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  330. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  331. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  332. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  333. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  334. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  335. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  336. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  337. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  338. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  339. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  340. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  341. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  342. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  343. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  344. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  345. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  346. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  347. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  348. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  349. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  350. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  351. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  352. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  353. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  354. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  355. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  356. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  357. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  358. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  359. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  360. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  361. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  362. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  363. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  364. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  365. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  366. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  367. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  368. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  369. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  370. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  371. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  372. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  373. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  374. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  375. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  376. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  377. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  378. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  379. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  380. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  381. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  382. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  383. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  384. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  385. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  386. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  387. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  388. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  389. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  390. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  391. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  392. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  393. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  394. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  395. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  396. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  397. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  398. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  399. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  400. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  401. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  402. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  403. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  404. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  405. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  406. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  407. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  408. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  409. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  410. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  411. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  412. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  413. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  414. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  415. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  416. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  417. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  418. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  419. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  420. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  421. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  422. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  423. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  424. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  425. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  426. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  427. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  428. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  429. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  430. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  431. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  432. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  433. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  434. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  435. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  436. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  437. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  438. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  439. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  440. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  441. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  442. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  443. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  444. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  445. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  446. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  447. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  448. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  449. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  450. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  451. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  452. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  453. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  454. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  455. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  456. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  457. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  458. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  459. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  460. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  461. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  462. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  463. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  464. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  465. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  466. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  467. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  468. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  469. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  470. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  471. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  472. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  473. package/Tests/Types/SafeHtml.test.ts +102 -0
  474. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  475. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  476. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  477. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  478. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  479. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  480. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  481. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  482. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  483. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  484. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  485. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  486. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  487. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  488. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  489. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  490. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  491. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  492. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  493. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  494. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  495. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  496. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  497. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  498. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  499. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  500. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  501. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  502. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  503. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  504. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  505. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  506. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  507. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  508. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  509. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  510. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  511. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  512. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  513. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  514. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  515. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  516. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  517. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  518. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  519. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  520. package/Tests/UI/Components/Input.test.tsx +80 -1
  521. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  522. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  523. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  524. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  525. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  526. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  527. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  528. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  529. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  530. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  531. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  532. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  533. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  534. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  535. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  536. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  537. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  538. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  539. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  540. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  541. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  542. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  543. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  544. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  545. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  546. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  547. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  548. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  549. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  550. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  551. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  552. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  553. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  554. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  555. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  556. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  557. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  558. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  559. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  560. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  561. package/Types/AI/AIRunType.ts +9 -4
  562. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  563. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  564. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  565. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  566. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  567. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  568. package/Types/CustomField/CustomFieldType.ts +14 -0
  569. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  570. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  571. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  572. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  573. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  574. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  575. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  576. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  577. package/Types/SafeHtml.ts +93 -0
  578. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  579. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  580. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  581. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  582. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  583. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  584. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  585. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  586. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  587. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  588. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  589. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  590. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  591. package/Types/Team/ComplianceRule.ts +434 -0
  592. package/Types/Team/ComplianceRuleType.ts +21 -0
  593. package/Types/Team/TeamComplianceStatus.ts +81 -0
  594. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  595. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  596. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  597. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  598. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  599. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  600. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  601. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  602. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  603. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  604. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  605. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  606. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  607. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  608. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  609. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  610. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  611. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  612. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  613. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  614. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  615. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  616. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  617. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  618. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  619. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  620. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  621. package/UI/Components/Forms/ModelForm.tsx +70 -8
  622. package/UI/Components/Forms/Validation.ts +1 -1
  623. package/UI/Components/Input/Input.tsx +90 -57
  624. package/UI/Components/List/ListRow.tsx +8 -74
  625. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  626. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  627. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  628. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  629. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  630. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  631. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  632. package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
  633. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  634. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  635. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  636. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  637. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  638. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  639. package/UI/Components/Table/TableBody.tsx +8 -2
  640. package/UI/Components/Table/TableRow.tsx +20 -140
  641. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  642. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  643. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  644. package/UI/Components/TelemetryViewer/types.ts +2 -0
  645. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  646. package/UI/Components/Toggle/Toggle.tsx +26 -0
  647. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  648. package/UI/Styles/Theme.css +14 -2
  649. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  650. package/Utils/API.ts +247 -2
  651. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  652. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  653. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  654. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  655. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  656. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  657. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  658. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  659. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  660. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  662. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  664. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  666. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  668. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  670. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  671. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  672. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  673. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  674. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  675. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  676. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  677. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  678. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  680. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  682. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  684. package/build/dist/Server/API/AIChatAPI.js +6 -1
  685. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  686. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  687. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  688. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  689. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  690. package/build/dist/Server/API/IncidentAPI.js +100 -1
  691. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  692. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  693. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  694. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  695. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  696. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  697. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  698. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  699. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  700. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  701. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  702. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  703. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  704. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  705. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  706. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  707. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  708. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  709. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  710. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  711. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  712. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  713. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  714. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  715. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  716. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  717. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  718. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  719. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  720. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  721. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  722. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  723. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  724. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  725. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  726. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  727. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  728. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  729. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  730. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  731. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  732. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  733. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  734. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  735. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  736. package/build/dist/Server/Services/AIService.js +59 -1
  737. package/build/dist/Server/Services/AIService.js.map +1 -1
  738. package/build/dist/Server/Services/AlertService.js +248 -13
  739. package/build/dist/Server/Services/AlertService.js.map +1 -1
  740. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  741. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  742. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  743. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  744. package/build/dist/Server/Services/DatabaseService.js +56 -7
  745. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  746. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  747. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  748. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  749. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  750. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  751. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  752. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  753. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  754. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  755. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/IncidentService.js +1251 -72
  757. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  758. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  759. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  760. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  761. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  762. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  763. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  764. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  765. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  766. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  767. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  768. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  769. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  770. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  771. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  772. package/build/dist/Server/Services/ProjectService.js +22 -0
  773. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  774. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  775. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  776. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  777. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  778. package/build/dist/Server/Services/RunnerService.js +1 -1
  779. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  780. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  781. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  782. package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
  783. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  784. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  785. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  786. package/build/dist/Server/Services/SmsService.js +2 -22
  787. package/build/dist/Server/Services/SmsService.js.map +1 -1
  788. package/build/dist/Server/Services/StatusPageService.js +47 -9
  789. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  790. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  791. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  792. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  793. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  794. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  795. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  796. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  797. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  798. package/build/dist/Server/Types/Markdown.js +110 -6
  799. package/build/dist/Server/Types/Markdown.js.map +1 -1
  800. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  801. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  803. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  805. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  807. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  809. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  811. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  813. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  814. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  815. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  816. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  817. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  818. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  819. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  820. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  821. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  822. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  823. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  824. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  825. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  826. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  828. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  829. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  830. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  831. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  832. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  833. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  834. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  835. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  836. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  837. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  838. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  839. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  840. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  841. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  842. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  843. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  844. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  845. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  846. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  847. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  848. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  849. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  850. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  851. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  852. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  853. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  854. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  855. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  856. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  857. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  858. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  859. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  860. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  861. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  862. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  863. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  864. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  865. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  866. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  867. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  868. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  869. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  870. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  871. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  872. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  873. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  874. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  875. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  876. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  877. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  878. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  879. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  881. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  882. package/build/dist/Types/AI/AIRunType.js +9 -4
  883. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  884. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  885. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  886. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  887. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  888. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  889. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  890. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  891. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  892. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  893. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  894. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  895. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  896. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  897. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  898. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  899. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  900. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  901. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  902. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  903. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  904. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  905. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  906. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  907. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  908. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  909. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  910. package/build/dist/Types/SafeHtml.js +84 -0
  911. package/build/dist/Types/SafeHtml.js.map +1 -0
  912. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  913. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  914. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  915. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  916. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  917. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  918. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  919. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  920. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  921. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  922. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  923. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  924. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  925. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  926. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  927. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  928. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  929. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  930. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  931. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  932. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  933. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  934. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  935. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  936. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  937. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  938. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  939. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  940. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  941. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  942. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  943. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  944. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  945. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  946. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  947. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  948. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  949. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  950. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  951. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  952. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  953. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  954. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  955. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  956. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  957. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  958. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  959. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  960. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  961. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  962. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  963. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  964. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  965. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  966. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  967. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  968. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  969. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  970. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  971. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  972. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  973. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  974. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  975. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  976. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  977. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  978. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  979. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  980. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  981. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  982. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  983. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  984. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  985. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  986. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  987. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  988. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  989. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  990. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  991. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  992. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  993. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  994. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  995. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  996. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  997. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  998. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  999. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  1000. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  1001. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  1002. package/build/dist/UI/Components/Input/Input.js +69 -54
  1003. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1004. package/build/dist/UI/Components/List/ListRow.js +4 -39
  1005. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  1007. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  1009. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  1011. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  1013. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  1015. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1016. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  1017. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  1018. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
  1019. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1020. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  1021. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  1022. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  1023. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  1024. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  1025. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  1026. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  1027. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  1028. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  1029. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  1030. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  1031. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  1032. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  1033. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  1034. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  1035. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1036. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  1037. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1038. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  1039. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1040. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  1041. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  1042. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  1043. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  1044. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  1045. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  1046. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  1047. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  1048. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  1049. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  1050. package/build/dist/Utils/API.js +177 -1
  1051. package/build/dist/Utils/API.js.map +1 -1
  1052. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  1053. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  1054. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  1055. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  1056. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  1057. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  1058. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  1059. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  1060. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  1061. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  1062. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  1063. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  1064. package/package.json +1 -1
@@ -0,0 +1,1634 @@
1
+ import AutoRemediationRuleService from "./AutoRemediationRuleService";
2
+ import DatabaseService from "./DatabaseService";
3
+ import KubernetesClusterAiAccessService from "./KubernetesClusterAiAccessService";
4
+ import KubernetesClusterFeedService from "./KubernetesClusterFeedService";
5
+ import KubernetesClusterService from "./KubernetesClusterService";
6
+ import RunnerService from "./RunnerService";
7
+ import UserService from "./UserService";
8
+ import Model from "../../Models/DatabaseModels/KubernetesAiAgent";
9
+ import KubernetesCluster from "../../Models/DatabaseModels/KubernetesCluster";
10
+ import { KubernetesClusterFeedEventType } from "../../Models/DatabaseModels/KubernetesClusterFeed";
11
+ import Runner from "../../Models/DatabaseModels/Runner";
12
+ import QueryHelper from "../Types/Database/QueryHelper";
13
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
14
+ import logger from "../Utils/Logger";
15
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
16
+ import Select from "../Types/Database/Select";
17
+ import ColumnLength from "../../Types/Database/ColumnLength";
18
+ import OneUptimeDate from "../../Types/Date";
19
+ import BadDataException from "../../Types/Exception/BadDataException";
20
+ import ForbiddenException from "../../Types/Exception/ForbiddenException";
21
+ import { Blue500, Green500 } from "../../Types/BrandColors";
22
+ import { JSONObject } from "../../Types/JSON";
23
+ import {
24
+ KUBERNETES_AI_AGENT_ALIVE_WINDOW_IN_MINUTES,
25
+ KUBERNETES_AI_AGENT_DISPLAY_NAME,
26
+ KubernetesAgentPosture,
27
+ KubernetesAiAgentConnectionStatus,
28
+ KubernetesAiAgentRegistrationRefusalReason,
29
+ KubernetesAiAgentSummary,
30
+ KubernetesAiRemediationMode,
31
+ parseKubernetesAgentPosture,
32
+ } from "../../Types/Kubernetes/KubernetesClusterAiAccess";
33
+ import ObjectID from "../../Types/ObjectID";
34
+ import crypto from "crypto";
35
+
36
+ /*
37
+ * Every column of a KubernetesAiAgent row a reader may need — everything
38
+ * except keyHash. The key hash is read only by the agent authentication
39
+ * path, which selects it explicitly; nothing that builds a status, a list
40
+ * or a response ever loads it, so it cannot leak into one by accident.
41
+ */
42
+ export const KUBERNETES_AI_AGENT_SELECT_WITHOUT_KEY: Select<Model> = {
43
+ _id: true,
44
+ createdAt: true,
45
+ updatedAt: true,
46
+ projectId: true,
47
+ kubernetesClusterId: true,
48
+ agentVersion: true,
49
+ posture: true,
50
+ lastAliveAt: true,
51
+ connectionStatus: true,
52
+ lastRegisteredAt: true,
53
+ registeredWithIngestionKeyId: true,
54
+ lastRefusedRegistrationAt: true,
55
+ lastRefusedRegistrationReason: true,
56
+ };
57
+
58
+ // 32 random bytes, hex: 64 characters, 256 bits.
59
+ const AGENT_KEY_BYTES: number = 32;
60
+
61
+ // What a stored key hash looks like: sha256, hex.
62
+ const SHA256_HEX_PATTERN: RegExp = /^[0-9a-f]{64}$/i;
63
+
64
+ /*
65
+ * The subset of a row isOnline / toSummary read. Dates may arrive as Date
66
+ * objects (a hydrated model) or ISO strings (a JSON payload).
67
+ */
68
+ export interface KubernetesAiAgentPresenceRow {
69
+ connectionStatus?: string | null | undefined;
70
+ lastAliveAt?: Date | string | null | undefined;
71
+ }
72
+
73
+ type DateLike = Date | string | null | undefined;
74
+
75
+ function toDate(value: DateLike): Date | null {
76
+ if (!value) {
77
+ return null;
78
+ }
79
+
80
+ const date: Date = value instanceof Date ? value : new Date(value);
81
+
82
+ return Number.isNaN(date.getTime()) ? null : date;
83
+ }
84
+
85
+ function toIsoString(value: DateLike): string | undefined {
86
+ const date: Date | null = toDate(value);
87
+
88
+ return date ? OneUptimeDate.toString(date) : undefined;
89
+ }
90
+
91
+ /*
92
+ * Ceiling on agent rows one project may hold, and on how many new ones may
93
+ * be created per hour. Registration is authenticated by the project's
94
+ * telemetry ingestion key — a credential every collector and CI job holds —
95
+ * and every distinct clusterName creates a row that is handed kubectl jobs,
96
+ * so the rows are bounded like anything else an ingestion key can create.
97
+ * The same numbers as the in-cluster Runner's brakes: a fleet-wide install
98
+ * that trips the hourly one is only delayed, the agent keeps retrying.
99
+ */
100
+ export const MAX_KUBERNETES_AI_AGENTS_PER_PROJECT: number = 250;
101
+ export const MAX_NEW_KUBERNETES_AI_AGENTS_PER_PROJECT_PER_HOUR: number = 30;
102
+
103
+ /*
104
+ * When a transient refusal (previous_instance_online, legacy_runner_online)
105
+ * tells the agent to try again. Both clear within seconds on a helm upgrade
106
+ * or a pod restart, and an agent that is refused for longer just keeps
107
+ * asking at this pace.
108
+ */
109
+ export const KUBERNETES_AI_AGENT_REGISTRATION_RETRY_AFTER_SECONDS: number = 20;
110
+
111
+ /*
112
+ * The longest cluster name registration accepts: KubernetesCluster's
113
+ * clusterIdentifier is a ShortText column, so a longer name could never
114
+ * become a cluster row.
115
+ */
116
+ export const MAX_KUBERNETES_AI_AGENT_CLUSTER_NAME_LENGTH: number =
117
+ ColumnLength.ShortText;
118
+
119
+ /*
120
+ * A 403 from POST /kubernetes-ai-agent-ingest/register that says WHICH
121
+ * refusal it is, so the agent can tell a wait that clears on its own (the
122
+ * transient reasons, which also carry retryAfterSeconds) from one that
123
+ * needs an operator. The ingress sends { message, reason,
124
+ * retryAfterSeconds? } and a Retry-After header for the transient ones.
125
+ */
126
+ export class KubernetesAiAgentRegistrationRefusedException extends ForbiddenException {
127
+ public readonly reason: KubernetesAiAgentRegistrationRefusalReason;
128
+ // Set only for a refusal that clears on its own: when to try again.
129
+ public readonly retryAfterSeconds: number | undefined;
130
+
131
+ public constructor(data: {
132
+ message: string;
133
+ reason: KubernetesAiAgentRegistrationRefusalReason;
134
+ retryAfterSeconds?: number | undefined;
135
+ }) {
136
+ super(data.message);
137
+ this.reason = data.reason;
138
+ this.retryAfterSeconds = data.retryAfterSeconds;
139
+ }
140
+ }
141
+
142
+ /*
143
+ * How a registration came to hold the cluster's agent key.
144
+ *
145
+ * created: the cluster had no agent row; this registration created it.
146
+ * continuity: it presented the row's current key — the same agent.
147
+ * reset: an admin reset the agent (no key is valid), so the first
148
+ * registration after that takes the row.
149
+ * signed_off: the previous instance signed off (/disconnect), as a pod
150
+ * does on a clean shutdown.
151
+ * offline: the previous instance stopped heartbeating.
152
+ *
153
+ * Only "continuity" is proof. The rest are what a restarted pod looks like —
154
+ * and also what anyone holding the ingestion key looks like, which is why
155
+ * none of them can take a row whose agent is online.
156
+ */
157
+ export type KubernetesAiAgentRegistrationAdmission =
158
+ | "created"
159
+ | "continuity"
160
+ | "reset"
161
+ | "signed_off"
162
+ | "offline";
163
+
164
+ export interface KubernetesAiAgentRegistrationResult {
165
+ agentId: ObjectID;
166
+ // The new agent key. Returned once, here; only its hash is stored.
167
+ agentKey: string;
168
+ clusterId: ObjectID;
169
+ admission: KubernetesAiAgentRegistrationAdmission;
170
+ }
171
+
172
+ /*
173
+ * What the server changed on the cluster when it applied the agent's
174
+ * first-connection defaults. Empty when nothing needed changing.
175
+ */
176
+ export interface KubernetesAiAgentDefaultsApplied {
177
+ turnedOnInvestigation: boolean;
178
+ remediationMode?: KubernetesAiRemediationMode | undefined;
179
+ }
180
+
181
+ // What registration and heartbeat read off the agent's cluster.
182
+ const CLUSTER_SELECT: Select<KubernetesCluster> = {
183
+ _id: true,
184
+ projectId: true,
185
+ clusterIdentifier: true,
186
+ isAiInvestigationEnabled: true,
187
+ aiRemediationMode: true,
188
+ aiAccessConfiguredAt: true,
189
+ };
190
+
191
+ /*
192
+ * A key hash that no key can have (its preimage is unknown), compared
193
+ * against when the presented agent id matches no row, so an unknown id and
194
+ * a wrong key cost the same.
195
+ */
196
+ const UNMATCHABLE_KEY_HASH: string = "0".repeat(64);
197
+
198
+ /*
199
+ * When the previous in-cluster Runner of a cluster may be retired (see
200
+ * retireLegacyRunnerIfUnused): the agent has existed for a day, the Runner
201
+ * has been silent for a week, and this is checked at most hourly.
202
+ */
203
+ export const LEGACY_RUNNER_RETIREMENT_AGENT_AGE_HOURS: number = 24;
204
+ export const LEGACY_RUNNER_RETIREMENT_OFFLINE_DAYS: number = 7;
205
+ export const LEGACY_RUNNER_RETIREMENT_CHECK_INTERVAL_MS: number =
206
+ 60 * 60 * 1000;
207
+ const MAX_LEGACY_RUNNER_RETIREMENT_CHECKS_REMEMBERED: number = 10_000;
208
+
209
+ // What retireLegacyRunnerIfUnused decided.
210
+ export type LegacyRunnerRetirementOutcome =
211
+ | "not_due"
212
+ | "agent_too_new"
213
+ | "no_legacy_runner"
214
+ | "legacy_runner_recently_online"
215
+ | "legacy_runner_holds_more"
216
+ | "legacy_runner_in_rule"
217
+ | "retired"
218
+ | "failed";
219
+
220
+ /*
221
+ * The Kubernetes AI agent of a cluster (see the model): its identity, and
222
+ * everything the server does about it.
223
+ *
224
+ * - Shared helpers every caller needs: key minting and hashing, the one
225
+ * online rule, the summary the dashboard sees and the reads the status
226
+ * builder uses.
227
+ * - The identity lifecycle behind /kubernetes-ai-agent-ingest: register
228
+ * (with the project's telemetry ingestion key; hands out an agent key),
229
+ * authenticate (every later request, by agent id and key), heartbeat and
230
+ * sign-off — plus the admin reset on the cluster's AI agent page.
231
+ *
232
+ * Registration never deletes or unbinds anything: the cluster's settings,
233
+ * its Runner binding and the previous in-cluster Runner are left as they
234
+ * are. The only cluster setting it ever writes is the first-connection
235
+ * defaults, and only on a cluster nobody configured. The previous
236
+ * in-cluster Runner is removed only later, lazily, once it is certainly
237
+ * unused (retireLegacyRunnerIfUnused).
238
+ */
239
+ export class Service extends DatabaseService<Model> {
240
+ // Agent id → when this process last checked its previous Runner (ms).
241
+ private legacyRunnerRetirementCheckedAt: Map<string, number> = new Map<
242
+ string,
243
+ number
244
+ >();
245
+
246
+ public constructor() {
247
+ super(Model);
248
+ }
249
+
250
+ /*
251
+ * sha256 (hex) of an agent key: what KubernetesAiAgent.keyHash stores.
252
+ * The key itself is never stored.
253
+ */
254
+ public static hashKey(key: string): string {
255
+ return crypto.createHash("sha256").update(key, "utf8").digest("hex");
256
+ }
257
+
258
+ // A fresh agent key: 32 cryptographically random bytes, hex encoded.
259
+ public static generateKey(): string {
260
+ return crypto.randomBytes(AGENT_KEY_BYTES).toString("hex");
261
+ }
262
+
263
+ /*
264
+ * Does this presented key match the stored hash? Timing-safe, and false —
265
+ * never a throw — for anything that is not a non-empty key and a stored
266
+ * sha256 hex hash (a reset row's hash is null, which nothing matches).
267
+ */
268
+ public static doesKeyMatchHash(key: unknown, keyHash: unknown): boolean {
269
+ if (typeof key !== "string" || key.length === 0) {
270
+ return false;
271
+ }
272
+
273
+ if (typeof keyHash !== "string" || !SHA256_HEX_PATTERN.test(keyHash)) {
274
+ return false;
275
+ }
276
+
277
+ const presented: Buffer = Buffer.from(Service.hashKey(key), "hex");
278
+ const stored: Buffer = Buffer.from(keyHash.toLowerCase(), "hex");
279
+
280
+ return (
281
+ presented.length === stored.length &&
282
+ crypto.timingSafeEqual(presented, stored)
283
+ );
284
+ }
285
+
286
+ // Instance forms of the static helpers, for callers holding the default export.
287
+ public hashKey(key: string): string {
288
+ return Service.hashKey(key);
289
+ }
290
+
291
+ public generateKey(): string {
292
+ return Service.generateKey();
293
+ }
294
+
295
+ public doesKeyMatchHash(key: unknown, keyHash: unknown): boolean {
296
+ return Service.doesKeyMatchHash(key, keyHash);
297
+ }
298
+
299
+ /*
300
+ * The one "is the agent online?" rule: it says it is connected (it
301
+ * registered or heartbeated and has not signed off or been reset) AND it
302
+ * was heard from within KUBERNETES_AI_AGENT_ALIVE_WINDOW_IN_MINUTES. A
303
+ * heartbeat stamped slightly in the future (another server's clock) still
304
+ * counts as recent.
305
+ */
306
+ public isOnline(row: KubernetesAiAgentPresenceRow, now?: Date): boolean {
307
+ if (row.connectionStatus !== "connected") {
308
+ return false;
309
+ }
310
+
311
+ const lastAliveAt: Date | null = toDate(row.lastAliveAt);
312
+
313
+ if (!lastAliveAt) {
314
+ return false;
315
+ }
316
+
317
+ const nowTime: number = (now || OneUptimeDate.getCurrentDate()).getTime();
318
+
319
+ return (
320
+ nowTime - lastAliveAt.getTime() <=
321
+ KUBERNETES_AI_AGENT_ALIVE_WINDOW_IN_MINUTES * 60 * 1000
322
+ );
323
+ }
324
+
325
+ /*
326
+ * The row as the dashboard and the status builder see it. Never carries
327
+ * the key hash; the posture is re-validated on the way out, so a stored
328
+ * value of the wrong shape is dropped rather than trusted.
329
+ */
330
+ public toSummary(
331
+ row: Pick<
332
+ Model,
333
+ | "id"
334
+ | "agentVersion"
335
+ | "posture"
336
+ | "lastRegisteredAt"
337
+ | "lastRefusedRegistrationAt"
338
+ | "lastRefusedRegistrationReason"
339
+ > &
340
+ KubernetesAiAgentPresenceRow,
341
+ now?: Date,
342
+ ): KubernetesAiAgentSummary {
343
+ const connectionStatus: KubernetesAiAgentConnectionStatus =
344
+ row.connectionStatus === "connected" ? "connected" : "disconnected";
345
+
346
+ const posture: KubernetesAgentPosture | undefined =
347
+ parseKubernetesAgentPosture(row.posture);
348
+
349
+ return {
350
+ id: row.id ? row.id.toString() : "",
351
+ isOnline: this.isOnline(row, now),
352
+ connectionStatus,
353
+ lastAliveAt: toIsoString(row.lastAliveAt),
354
+ lastRegisteredAt: toIsoString(row.lastRegisteredAt),
355
+ agentVersion: row.agentVersion || undefined,
356
+ posture,
357
+ lastRefusedRegistrationAt: toIsoString(row.lastRefusedRegistrationAt),
358
+ lastRefusedRegistrationReason:
359
+ row.lastRefusedRegistrationReason || undefined,
360
+ };
361
+ }
362
+
363
+ // The cluster's agent row (as root, without the key hash), or null.
364
+ @CaptureSpan()
365
+ public async findForCluster(data: {
366
+ projectId: ObjectID;
367
+ kubernetesClusterId: ObjectID;
368
+ }): Promise<Model | null> {
369
+ return await this.findOneBy({
370
+ query: {
371
+ projectId: data.projectId,
372
+ kubernetesClusterId: data.kubernetesClusterId,
373
+ },
374
+ select: KUBERNETES_AI_AGENT_SELECT_WITHOUT_KEY,
375
+ props: {
376
+ isRoot: true,
377
+ },
378
+ });
379
+ }
380
+
381
+ /*
382
+ * The agent rows of several clusters of one project in one query, keyed
383
+ * by cluster id (string). Clusters without an agent are simply absent.
384
+ */
385
+ @CaptureSpan()
386
+ public async findForClusters(data: {
387
+ projectId: ObjectID;
388
+ kubernetesClusterIds: Array<ObjectID>;
389
+ }): Promise<Map<string, Model>> {
390
+ const byClusterId: Map<string, Model> = new Map<string, Model>();
391
+
392
+ const clusterIds: Array<string> = Array.from(
393
+ new Set(
394
+ data.kubernetesClusterIds.map((id: ObjectID): string => {
395
+ return id.toString();
396
+ }),
397
+ ),
398
+ );
399
+
400
+ if (clusterIds.length === 0) {
401
+ return byClusterId;
402
+ }
403
+
404
+ const rows: Array<Model> = await this.findBy({
405
+ query: {
406
+ projectId: data.projectId,
407
+ kubernetesClusterId: QueryHelper.any(clusterIds),
408
+ },
409
+ select: KUBERNETES_AI_AGENT_SELECT_WITHOUT_KEY,
410
+ limit: LIMIT_MAX,
411
+ skip: 0,
412
+ props: {
413
+ isRoot: true,
414
+ },
415
+ });
416
+
417
+ for (const row of rows) {
418
+ const clusterId: string | undefined = row.kubernetesClusterId?.toString();
419
+
420
+ if (clusterId && clusterIds.includes(clusterId)) {
421
+ byClusterId.set(clusterId, row);
422
+ }
423
+ }
424
+
425
+ return byClusterId;
426
+ }
427
+
428
+ /*
429
+ * ------------------------------------------------------------------
430
+ * Registration
431
+ * ------------------------------------------------------------------
432
+ */
433
+
434
+ /*
435
+ * The agent registering for its cluster (POST
436
+ * /kubernetes-ai-agent-ingest/register, authenticated by the project's
437
+ * telemetry ingestion key). Hands out a fresh agent key; only its hash is
438
+ * kept.
439
+ *
440
+ * Refused (KubernetesAiAgentRegistrationRefusedException, a 403 with the
441
+ * reason) when:
442
+ * - the cluster name is empty or too long (cluster_name_invalid);
443
+ * - this cluster's previous in-cluster Runner is still online
444
+ * (legacy_runner_online) — a helm upgrade stops it within seconds, and
445
+ * an ingestion key alone must never take a cluster away from a live
446
+ * Runner;
447
+ * - the cluster's agent is online and the request did not present its
448
+ * current key (previous_instance_online) — recorded on the row so the
449
+ * AI agent page can say another agent tried;
450
+ * - a new row would pass the project's caps (agent_cap_reached).
451
+ *
452
+ * An existing row is re-keyed when the request proves continuity (its
453
+ * current key as previousAgentKey), or when the row was reset, or its
454
+ * agent signed off or went quiet: what a restarted pod looks like.
455
+ *
456
+ * On a cluster nobody configured it applies the first-connection defaults
457
+ * (applyFirstConnectionDefaults). It never deletes, unbinds or rebinds
458
+ * anything, and writes ONE feed item when a cluster's agent first
459
+ * connects.
460
+ */
461
+ @CaptureSpan()
462
+ public async register(data: {
463
+ projectId: ObjectID;
464
+ clusterName: unknown;
465
+ agentVersion?: string | undefined;
466
+ // The key the agent currently holds, when it still has one.
467
+ previousAgentKey?: string | undefined;
468
+ // The bare posture the body reports; parsed here, never trusted as is.
469
+ posture?: unknown;
470
+ // The TelemetryIngestionKey the request was admitted with, when known.
471
+ ingestionKeyId?: ObjectID | undefined;
472
+ }): Promise<KubernetesAiAgentRegistrationResult> {
473
+ const clusterName: string = this.getValidClusterName(data.clusterName);
474
+
475
+ const cluster: KubernetesCluster = await this.findOrCreateCluster({
476
+ projectId: data.projectId,
477
+ clusterName,
478
+ });
479
+
480
+ // The cluster row's own spelling of its name, whatever the chart sent.
481
+ const clusterIdentifier: string =
482
+ cluster.clusterIdentifier?.trim() || clusterName;
483
+
484
+ await this.assertLegacyRunnerIsNotOnline({
485
+ projectId: data.projectId,
486
+ cluster,
487
+ clusterIdentifier,
488
+ });
489
+
490
+ const posture: KubernetesAgentPosture = this.buildStoredPosture({
491
+ reported: data.posture,
492
+ clusterIdentifier,
493
+ });
494
+
495
+ const agentVersion: string | undefined = Service.normalizeAgentVersion(
496
+ data.agentVersion,
497
+ );
498
+
499
+ const agentKey: string = Service.generateKey();
500
+ const now: Date = OneUptimeDate.getCurrentDate();
501
+
502
+ const existing: Model | null = await this.findOneBy({
503
+ query: {
504
+ projectId: data.projectId,
505
+ kubernetesClusterId: cluster.id!,
506
+ },
507
+ select: { ...KUBERNETES_AI_AGENT_SELECT_WITHOUT_KEY, keyHash: true },
508
+ props: { isRoot: true },
509
+ });
510
+
511
+ let agentId: ObjectID;
512
+ let admission: KubernetesAiAgentRegistrationAdmission;
513
+
514
+ if (existing && existing.id) {
515
+ admission = await this.admitReRegistration({
516
+ agent: existing,
517
+ previousAgentKey: data.previousAgentKey,
518
+ clusterIdentifier,
519
+ projectId: data.projectId,
520
+ });
521
+
522
+ await this.updateOneById({
523
+ id: existing.id,
524
+ data: {
525
+ keyHash: Service.hashKey(agentKey),
526
+ posture: posture as JSONObject,
527
+ lastAliveAt: now,
528
+ connectionStatus: "connected",
529
+ lastRegisteredAt: now,
530
+ registeredWithIngestionKeyId: data.ingestionKeyId || null,
531
+ ...(agentVersion ? { agentVersion } : {}),
532
+ } as never,
533
+ props: { isRoot: true },
534
+ });
535
+
536
+ agentId = existing.id;
537
+ } else {
538
+ await this.assertAgentMayBeCreated({
539
+ projectId: data.projectId,
540
+ clusterIdentifier,
541
+ });
542
+
543
+ const row: Model = new Model();
544
+ row.projectId = data.projectId;
545
+ row.kubernetesClusterId = cluster.id!;
546
+ row.keyHash = Service.hashKey(agentKey);
547
+ row.posture = posture as JSONObject;
548
+ row.lastAliveAt = now;
549
+ row.connectionStatus = "connected";
550
+ row.lastRegisteredAt = now;
551
+ if (agentVersion) {
552
+ row.agentVersion = agentVersion;
553
+ }
554
+ if (data.ingestionKeyId) {
555
+ row.registeredWithIngestionKeyId = data.ingestionKeyId;
556
+ }
557
+
558
+ const created: Model = await this.createAgentRow({
559
+ row,
560
+ clusterIdentifier,
561
+ });
562
+
563
+ agentId = created.id!;
564
+ admission = "created";
565
+ }
566
+
567
+ logger.info(
568
+ `KubernetesAiAgent: the Kubernetes AI agent of cluster "${clusterIdentifier}" in project ${data.projectId.toString()} registered (${admission}).`,
569
+ );
570
+
571
+ /*
572
+ * The new key's hash is stored: from here on nothing may fail the
573
+ * registration, or the agent never receives a key the row now demands
574
+ * (see applyFirstConnectionDefaultsSafely). The feed items are safe
575
+ * already — KubernetesClusterFeedService never throws.
576
+ */
577
+ const defaultsApplied: KubernetesAiAgentDefaultsApplied =
578
+ await this.applyFirstConnectionDefaultsSafely({
579
+ cluster,
580
+ allowWrites: posture.allowWrites === true,
581
+ });
582
+
583
+ if (admission === "created") {
584
+ await this.writeConnectedFeedItem({
585
+ cluster,
586
+ agentId,
587
+ posture,
588
+ agentVersion,
589
+ defaultsApplied,
590
+ });
591
+ } else if (Service.didApplyDefaults(defaultsApplied)) {
592
+ await this.writeDefaultsFeedItem({ cluster, posture, defaultsApplied });
593
+ }
594
+
595
+ return {
596
+ agentId,
597
+ agentKey,
598
+ clusterId: cluster.id!,
599
+ admission,
600
+ };
601
+ }
602
+
603
+ /*
604
+ * How a registration for a cluster that already has an agent row is
605
+ * admitted, or null when it must be refused (the row's agent is online
606
+ * and the request did not prove it is that agent). A reset row admits
607
+ * anyone: no key is valid for it, and the admin reset is exactly the
608
+ * "let the real pod back in" action.
609
+ */
610
+ public getReRegistrationAdmission(data: {
611
+ agent: {
612
+ keyHash?: string | null | undefined;
613
+ } & KubernetesAiAgentPresenceRow;
614
+ previousAgentKey?: string | undefined;
615
+ now?: Date | undefined;
616
+ }): KubernetesAiAgentRegistrationAdmission | null {
617
+ if (!data.agent.keyHash) {
618
+ return "reset";
619
+ }
620
+
621
+ if (Service.doesKeyMatchHash(data.previousAgentKey, data.agent.keyHash)) {
622
+ return "continuity";
623
+ }
624
+
625
+ if (data.agent.connectionStatus !== "connected") {
626
+ return "signed_off";
627
+ }
628
+
629
+ if (!this.isOnline(data.agent, data.now)) {
630
+ return "offline";
631
+ }
632
+
633
+ return null;
634
+ }
635
+
636
+ /*
637
+ * The first-connection defaults, applied ONLY to a cluster nobody
638
+ * configured (aiAccessConfiguredAt is null — an operator's write of any AI
639
+ * setting stamps it): investigating with kubectl on, and, when the chart
640
+ * granted the agent write RBAC, fixes "Ask for approval" instead of Off.
641
+ * The chart's write RBAC is the cluster operator's consent for that; a
642
+ * human still approves every fix. Never sets aiAccessConfiguredAt, so
643
+ * these stay defaults — and never touches a mode an operator chose.
644
+ *
645
+ * Registration applies them every time; a heartbeat applies them when the
646
+ * agent's write access appears (the chart was upgraded to allow writes).
647
+ * The update is conditioned on the cluster still being unconfigured, so
648
+ * an operator's choice made a moment earlier is never overwritten.
649
+ */
650
+ @CaptureSpan()
651
+ public async applyFirstConnectionDefaults(data: {
652
+ cluster: KubernetesCluster;
653
+ allowWrites: boolean;
654
+ }): Promise<KubernetesAiAgentDefaultsApplied> {
655
+ const defaults: KubernetesAiAgentDefaultsApplied =
656
+ this.getFirstConnectionDefaults(data);
657
+
658
+ if (!Service.didApplyDefaults(defaults) || !data.cluster.id) {
659
+ return { turnedOnInvestigation: false };
660
+ }
661
+
662
+ const updated: number = await KubernetesClusterService.updateOneBy({
663
+ query: {
664
+ _id: data.cluster.id.toString(),
665
+ aiAccessConfiguredAt: QueryHelper.isNull(),
666
+ ...(data.cluster.projectId
667
+ ? { projectId: data.cluster.projectId }
668
+ : {}),
669
+ },
670
+ data: {
671
+ ...(defaults.turnedOnInvestigation
672
+ ? { isAiInvestigationEnabled: true }
673
+ : {}),
674
+ ...(defaults.remediationMode
675
+ ? { aiRemediationMode: defaults.remediationMode }
676
+ : {}),
677
+ } as never,
678
+ props: { isRoot: true },
679
+ });
680
+
681
+ if (updated === 0) {
682
+ return { turnedOnInvestigation: false };
683
+ }
684
+
685
+ logger.info(
686
+ `KubernetesAiAgent: applied the first-connection defaults to cluster ${data.cluster.id.toString()} (${[
687
+ defaults.turnedOnInvestigation ? "investigation on" : "",
688
+ defaults.remediationMode
689
+ ? `remediation ${defaults.remediationMode}`
690
+ : "",
691
+ ]
692
+ .filter(Boolean)
693
+ .join(", ")}).`,
694
+ );
695
+
696
+ return defaults;
697
+ }
698
+
699
+ /*
700
+ * applyFirstConnectionDefaults for a request whose real work is already
701
+ * written — registration stored the new key's hash and marked the row
702
+ * connected, a heartbeat stored the liveness. A failure here must not
703
+ * fail that request: a registration that answered 500 after storing the
704
+ * hash would leave a row that reads online and holds a key nobody
705
+ * received, so every retry would be refused as previous_instance_online
706
+ * (and recorded as another agent's attempt) until the alive window
707
+ * lapsed. Nothing is lost by skipping the defaults: the next
708
+ * registration applies them again. Logged, and reported as "nothing
709
+ * changed", so no feed item claims a change that did not happen.
710
+ */
711
+ private async applyFirstConnectionDefaultsSafely(data: {
712
+ cluster: KubernetesCluster;
713
+ allowWrites: boolean;
714
+ }): Promise<KubernetesAiAgentDefaultsApplied> {
715
+ try {
716
+ return await this.applyFirstConnectionDefaults(data);
717
+ } catch (error) {
718
+ logger.error(
719
+ `KubernetesAiAgent: could not apply the first-connection defaults to cluster ${data.cluster.id?.toString()}; the next registration tries again: ${error}`,
720
+ );
721
+ return { turnedOnInvestigation: false };
722
+ }
723
+ }
724
+
725
+ // What applyFirstConnectionDefaults would change, without writing it.
726
+ public getFirstConnectionDefaults(data: {
727
+ cluster: Pick<
728
+ KubernetesCluster,
729
+ "aiAccessConfiguredAt" | "isAiInvestigationEnabled" | "aiRemediationMode"
730
+ >;
731
+ allowWrites: boolean;
732
+ }): KubernetesAiAgentDefaultsApplied {
733
+ if (data.cluster.aiAccessConfiguredAt) {
734
+ return { turnedOnInvestigation: false };
735
+ }
736
+
737
+ const isRemediationOff: boolean =
738
+ KubernetesClusterAiAccessService.normalizeRemediationMode(
739
+ data.cluster.aiRemediationMode,
740
+ ) === KubernetesAiRemediationMode.Disabled;
741
+
742
+ return {
743
+ turnedOnInvestigation: data.cluster.isAiInvestigationEnabled !== true,
744
+ ...(data.allowWrites && isRemediationOff
745
+ ? { remediationMode: KubernetesAiRemediationMode.RequireApproval }
746
+ : {}),
747
+ };
748
+ }
749
+
750
+ public static didApplyDefaults(
751
+ defaults: KubernetesAiAgentDefaultsApplied,
752
+ ): boolean {
753
+ return defaults.turnedOnInvestigation || Boolean(defaults.remediationMode);
754
+ }
755
+
756
+ /*
757
+ * The posture a row stores: what the agent reported, validated field by
758
+ * field, with the two facts the server decides itself forced — it runs in
759
+ * the cluster (inCluster), and WHICH cluster (the cluster row's
760
+ * identifier). A body can never make the agent the in-cluster executor of
761
+ * another cluster.
762
+ */
763
+ public buildStoredPosture(data: {
764
+ reported: unknown;
765
+ clusterIdentifier: string;
766
+ }): KubernetesAgentPosture {
767
+ const reported: KubernetesAgentPosture =
768
+ parseKubernetesAgentPosture(data.reported) || {};
769
+
770
+ const posture: KubernetesAgentPosture = {
771
+ ...reported,
772
+ clusterIdentifier: data.clusterIdentifier.trim() || undefined,
773
+ inCluster: true,
774
+ allowWrites: reported.allowWrites === true,
775
+ };
776
+
777
+ // A JSON column: store only what is known, never `undefined` keys.
778
+ for (const key of Object.keys(posture) as Array<
779
+ keyof KubernetesAgentPosture
780
+ >) {
781
+ if (posture[key] === undefined) {
782
+ delete posture[key];
783
+ }
784
+ }
785
+
786
+ return posture;
787
+ }
788
+
789
+ /*
790
+ * The version string the agent reports, bounded to its ShortText column
791
+ * (an over-long value would fail the whole write); undefined when absent.
792
+ */
793
+ public static normalizeAgentVersion(value: unknown): string | undefined {
794
+ if (typeof value !== "string") {
795
+ return undefined;
796
+ }
797
+
798
+ const trimmed: string = value.trim();
799
+
800
+ return trimmed ? trimmed.substring(0, ColumnLength.ShortText) : undefined;
801
+ }
802
+
803
+ private getValidClusterName(raw: unknown): string {
804
+ const clusterName: string = typeof raw === "string" ? raw.trim() : "";
805
+
806
+ if (!clusterName) {
807
+ throw new KubernetesAiAgentRegistrationRefusedException({
808
+ reason: "cluster_name_invalid",
809
+ message:
810
+ "The Kubernetes AI agent did not send a cluster name. Set clusterName on the kubernetes-agent chart.",
811
+ });
812
+ }
813
+
814
+ if (clusterName.length > MAX_KUBERNETES_AI_AGENT_CLUSTER_NAME_LENGTH) {
815
+ throw new KubernetesAiAgentRegistrationRefusedException({
816
+ reason: "cluster_name_invalid",
817
+ message: `The cluster name is ${clusterName.length} characters long; the limit is ${MAX_KUBERNETES_AI_AGENT_CLUSTER_NAME_LENGTH}. Set a shorter clusterName on the kubernetes-agent chart.`,
818
+ });
819
+ }
820
+
821
+ return clusterName;
822
+ }
823
+
824
+ /*
825
+ * The cluster the agent names, created when OneUptime has not seen it yet
826
+ * (the agent may register before the collector's first telemetry), then
827
+ * re-read for the AI columns registration needs.
828
+ */
829
+ private async findOrCreateCluster(data: {
830
+ projectId: ObjectID;
831
+ clusterName: string;
832
+ }): Promise<KubernetesCluster> {
833
+ const found: KubernetesCluster =
834
+ await KubernetesClusterService.findOrCreateByClusterIdentifier({
835
+ projectId: data.projectId,
836
+ clusterIdentifier: data.clusterName,
837
+ });
838
+
839
+ const cluster: KubernetesCluster | null =
840
+ await KubernetesClusterService.findOneBy({
841
+ query: { _id: found.id!.toString(), projectId: data.projectId },
842
+ select: CLUSTER_SELECT,
843
+ props: { isRoot: true },
844
+ });
845
+
846
+ if (!cluster || !cluster.id) {
847
+ throw new BadDataException("Cluster could not be resolved.");
848
+ }
849
+
850
+ return cluster;
851
+ }
852
+
853
+ /*
854
+ * The previous in-cluster Runner (the kubernetes-agent chart's ai-runner,
855
+ * which this agent replaces) keeps its cluster while it is online. During
856
+ * a helm upgrade the old pod stops within seconds of the new one starting,
857
+ * so the agent is only asked to wait; an ingestion key alone can never
858
+ * take a cluster away from a Runner that is still working.
859
+ */
860
+ private async assertLegacyRunnerIsNotOnline(data: {
861
+ projectId: ObjectID;
862
+ cluster: KubernetesCluster;
863
+ clusterIdentifier: string;
864
+ }): Promise<void> {
865
+ const legacyRunner: Runner | null =
866
+ await KubernetesClusterAiAccessService.getLegacyAgentRunnerForCluster({
867
+ projectId: data.projectId,
868
+ kubernetesClusterId: data.cluster.id!,
869
+ clusterIdentifier: data.clusterIdentifier,
870
+ });
871
+
872
+ if (
873
+ !legacyRunner ||
874
+ !KubernetesClusterAiAccessService.isRunnerOnline(legacyRunner)
875
+ ) {
876
+ return;
877
+ }
878
+
879
+ logger.info(
880
+ `KubernetesAiAgent: asked the Kubernetes AI agent of cluster "${data.clusterIdentifier}" in project ${data.projectId.toString()} to wait: its previous in-cluster Runner (${legacyRunner.id?.toString()}) is still online.`,
881
+ );
882
+
883
+ throw new KubernetesAiAgentRegistrationRefusedException({
884
+ reason: "legacy_runner_online",
885
+ retryAfterSeconds: KUBERNETES_AI_AGENT_REGISTRATION_RETRY_AFTER_SECONDS,
886
+ message: `The previous in-cluster Runner of cluster "${data.clusterIdentifier}" is still online. This is normal for a few seconds during a helm upgrade, and the agent retries on its own. If it lasts, remove the old ai-runner pod.`,
887
+ });
888
+ }
889
+
890
+ /*
891
+ * An existing row: admitted per getReRegistrationAdmission, or refused
892
+ * with previous_instance_online — which is recorded on the row (when, and
893
+ * why) so the AI agent page can warn that another agent tried to register
894
+ * while this one was online. Nothing about the current key is disclosed.
895
+ */
896
+ private async admitReRegistration(data: {
897
+ agent: Model;
898
+ previousAgentKey?: string | undefined;
899
+ clusterIdentifier: string;
900
+ projectId: ObjectID;
901
+ }): Promise<KubernetesAiAgentRegistrationAdmission> {
902
+ const admission: KubernetesAiAgentRegistrationAdmission | null =
903
+ this.getReRegistrationAdmission({
904
+ agent: data.agent,
905
+ previousAgentKey: data.previousAgentKey,
906
+ });
907
+
908
+ if (admission) {
909
+ return admission;
910
+ }
911
+
912
+ logger.warn(
913
+ `KubernetesAiAgent: refused a registration for cluster "${data.clusterIdentifier}" in project ${data.projectId.toString()}: its agent (${data.agent.id?.toString()}) is online and the request did not present its current key.`,
914
+ );
915
+
916
+ await this.recordRefusedRegistration({
917
+ agentId: data.agent.id!,
918
+ reason: "previous_instance_online",
919
+ });
920
+
921
+ throw this.getPreviousInstanceOnlineRefusal(data.clusterIdentifier);
922
+ }
923
+
924
+ private getPreviousInstanceOnlineRefusal(
925
+ clusterIdentifier: string,
926
+ ): KubernetesAiAgentRegistrationRefusedException {
927
+ return new KubernetesAiAgentRegistrationRefusedException({
928
+ reason: "previous_instance_online",
929
+ retryAfterSeconds: KUBERNETES_AI_AGENT_REGISTRATION_RETRY_AFTER_SECONDS,
930
+ message: `Another Kubernetes AI agent for cluster "${clusterIdentifier}" is online. This is normal for a moment while its pod restarts, and the agent retries on its own. If it lasts, make sure only one kubernetes-agent release uses clusterName "${clusterIdentifier}".`,
931
+ });
932
+ }
933
+
934
+ // Best-effort: recording a refusal must never turn it into a 500.
935
+ private async recordRefusedRegistration(data: {
936
+ agentId: ObjectID;
937
+ reason: KubernetesAiAgentRegistrationRefusalReason;
938
+ }): Promise<void> {
939
+ try {
940
+ await this.updateColumnsByIdWithoutHooks({
941
+ id: data.agentId,
942
+ data: {
943
+ lastRefusedRegistrationAt: OneUptimeDate.getCurrentDate(),
944
+ lastRefusedRegistrationReason: data.reason,
945
+ },
946
+ });
947
+ } catch (error) {
948
+ logger.error(
949
+ `KubernetesAiAgent: could not record a refused registration on agent ${data.agentId.toString()}: ${error}`,
950
+ );
951
+ }
952
+ }
953
+
954
+ // Bounds what an ingestion key can create (see the caps above).
955
+ private async assertAgentMayBeCreated(data: {
956
+ projectId: ObjectID;
957
+ clusterIdentifier: string;
958
+ }): Promise<void> {
959
+ const total: number = (
960
+ await this.countBy({
961
+ query: { projectId: data.projectId },
962
+ props: { isRoot: true },
963
+ })
964
+ ).toNumber();
965
+
966
+ if (total >= MAX_KUBERNETES_AI_AGENTS_PER_PROJECT) {
967
+ logger.warn(
968
+ `KubernetesAiAgent: refused to create an agent for cluster "${data.clusterIdentifier}" in project ${data.projectId.toString()}: the project already has ${total} (limit ${MAX_KUBERNETES_AI_AGENTS_PER_PROJECT}).`,
969
+ );
970
+
971
+ throw new KubernetesAiAgentRegistrationRefusedException({
972
+ reason: "agent_cap_reached",
973
+ message: `This project already has ${total} Kubernetes AI agents, which is its limit (${MAX_KUBERNETES_AI_AGENTS_PER_PROJECT}). Delete the clusters you no longer use under Kubernetes; the agent then connects on its next retry.`,
974
+ });
975
+ }
976
+
977
+ const createdInLastHour: number = (
978
+ await this.countBy({
979
+ query: {
980
+ projectId: data.projectId,
981
+ createdAt: QueryHelper.greaterThan(OneUptimeDate.getSomeHoursAgo(1)),
982
+ },
983
+ props: { isRoot: true },
984
+ })
985
+ ).toNumber();
986
+
987
+ if (
988
+ createdInLastHour >= MAX_NEW_KUBERNETES_AI_AGENTS_PER_PROJECT_PER_HOUR
989
+ ) {
990
+ logger.warn(
991
+ `KubernetesAiAgent: refused to create an agent for cluster "${data.clusterIdentifier}" in project ${data.projectId.toString()}: ${createdInLastHour} were created in the last hour (limit ${MAX_NEW_KUBERNETES_AI_AGENTS_PER_PROJECT_PER_HOUR}).`,
992
+ );
993
+
994
+ throw new KubernetesAiAgentRegistrationRefusedException({
995
+ reason: "agent_cap_reached",
996
+ message: `${createdInLastHour} Kubernetes AI agents connected to this project in the last hour, which is its limit (${MAX_NEW_KUBERNETES_AI_AGENTS_PER_PROJECT_PER_HOUR}). The agent connects on a later retry.`,
997
+ });
998
+ }
999
+ }
1000
+
1001
+ /*
1002
+ * Creates the cluster's agent row. The database allows one per cluster,
1003
+ * so when two pods register for a new cluster at once one create loses;
1004
+ * that pod is told the other agent is online (it is — it just
1005
+ * registered), exactly as if it had arrived a moment later.
1006
+ */
1007
+ private async createAgentRow(data: {
1008
+ row: Model;
1009
+ clusterIdentifier: string;
1010
+ }): Promise<Model> {
1011
+ try {
1012
+ return await this.create({
1013
+ data: data.row,
1014
+ props: { isRoot: true },
1015
+ });
1016
+ } catch (error) {
1017
+ const winner: Model | null = await this.findOneBy({
1018
+ query: {
1019
+ projectId: data.row.projectId!,
1020
+ kubernetesClusterId: data.row.kubernetesClusterId!,
1021
+ },
1022
+ select: { _id: true },
1023
+ props: { isRoot: true },
1024
+ });
1025
+
1026
+ if (!winner) {
1027
+ throw error;
1028
+ }
1029
+
1030
+ logger.info(
1031
+ `KubernetesAiAgent: two agents registered for cluster "${data.clusterIdentifier}" at once; the other one was admitted.`,
1032
+ );
1033
+
1034
+ throw this.getPreviousInstanceOnlineRefusal(data.clusterIdentifier);
1035
+ }
1036
+ }
1037
+
1038
+ /*
1039
+ * ------------------------------------------------------------------
1040
+ * The registered agent
1041
+ * ------------------------------------------------------------------
1042
+ */
1043
+
1044
+ /*
1045
+ * The agent a request's id and key belong to, or null. Every request
1046
+ * after registration is authenticated here: the key's sha256 must match
1047
+ * the row's hash (timing-safe). A reset row (null hash) matches nothing,
1048
+ * and an unknown id costs the same comparison as a wrong key. The row is
1049
+ * returned without its key hash.
1050
+ */
1051
+ @CaptureSpan()
1052
+ public async authenticate(data: {
1053
+ agentId: ObjectID | string;
1054
+ agentKey: string;
1055
+ }): Promise<Model | null> {
1056
+ const agentId: string =
1057
+ typeof data.agentId === "string"
1058
+ ? data.agentId
1059
+ : data.agentId?.toString() || "";
1060
+
1061
+ if (
1062
+ !agentId ||
1063
+ !ObjectID.isValidUUID(agentId) ||
1064
+ typeof data.agentKey !== "string" ||
1065
+ !data.agentKey
1066
+ ) {
1067
+ return null;
1068
+ }
1069
+
1070
+ const agent: Model | null = await this.findOneBy({
1071
+ query: { _id: agentId },
1072
+ select: { ...KUBERNETES_AI_AGENT_SELECT_WITHOUT_KEY, keyHash: true },
1073
+ props: { isRoot: true },
1074
+ });
1075
+
1076
+ const matches: boolean = Service.doesKeyMatchHash(
1077
+ data.agentKey,
1078
+ agent?.keyHash || UNMATCHABLE_KEY_HASH,
1079
+ );
1080
+
1081
+ if (!agent || !matches) {
1082
+ return null;
1083
+ }
1084
+
1085
+ delete agent.keyHash;
1086
+
1087
+ return agent;
1088
+ }
1089
+
1090
+ /*
1091
+ * A heartbeat from the authenticated agent: it is alive and connected,
1092
+ * and this is what it can do now. The posture is stored the way
1093
+ * registration stores it (validated, with inCluster and the cluster's
1094
+ * identifier forced); a heartbeat without one keeps the stored posture.
1095
+ *
1096
+ * When the agent's write access appears (the chart was upgraded with
1097
+ * aiAgent.remediation.enabled=true) on a cluster nobody configured, the
1098
+ * first-connection defaults apply again, so fixes move from Off to "Ask
1099
+ * for approval" without anyone visiting the AI agent page. It is also
1100
+ * where the previous in-cluster Runner's retirement is checked (hourly).
1101
+ *
1102
+ * This runs every 30 seconds per cluster, so a normal heartbeat is one
1103
+ * UPDATE without hooks and no read: the cluster's identifier comes from
1104
+ * the stored posture, which registration took from the cluster row. The
1105
+ * cluster row is read only when the stored posture lacks it or write
1106
+ * access just appeared; the hourly Runner check reads it itself.
1107
+ */
1108
+ @CaptureSpan()
1109
+ public async heartbeat(data: {
1110
+ // The row authenticate returned for this request.
1111
+ agent: Model;
1112
+ agentVersion?: string | undefined;
1113
+ posture?: unknown;
1114
+ }): Promise<void> {
1115
+ const agentId: ObjectID | null = data.agent.id;
1116
+
1117
+ if (!agentId) {
1118
+ throw new BadDataException("The agent is missing its id.");
1119
+ }
1120
+
1121
+ const storedPosture: KubernetesAgentPosture | undefined =
1122
+ parseKubernetesAgentPosture(data.agent.posture);
1123
+
1124
+ const isPostureReported: boolean = Boolean(
1125
+ data.posture && typeof data.posture === "object",
1126
+ );
1127
+
1128
+ let cluster: KubernetesCluster | null = null;
1129
+ let clusterIdentifier: string =
1130
+ storedPosture?.clusterIdentifier?.trim() || "";
1131
+
1132
+ if (isPostureReported && !clusterIdentifier) {
1133
+ cluster = await this.findAgentCluster(data.agent);
1134
+ clusterIdentifier = cluster?.clusterIdentifier?.trim() || "";
1135
+ }
1136
+
1137
+ const posture: KubernetesAgentPosture | undefined = isPostureReported
1138
+ ? this.buildStoredPosture({
1139
+ reported: data.posture,
1140
+ clusterIdentifier,
1141
+ })
1142
+ : undefined;
1143
+
1144
+ const agentVersion: string | undefined = Service.normalizeAgentVersion(
1145
+ data.agentVersion,
1146
+ );
1147
+
1148
+ const update: JSONObject = {
1149
+ lastAliveAt: OneUptimeDate.getCurrentDate(),
1150
+ connectionStatus: "connected",
1151
+ };
1152
+
1153
+ if (posture) {
1154
+ update["posture"] = posture as JSONObject;
1155
+ }
1156
+
1157
+ if (agentVersion) {
1158
+ update["agentVersion"] = agentVersion;
1159
+ }
1160
+
1161
+ await this.updateColumnsByIdWithoutHooks({
1162
+ id: agentId,
1163
+ data: update as never,
1164
+ });
1165
+
1166
+ /*
1167
+ * Off the heartbeat's path: it decides at most once an hour per agent
1168
+ * and never throws, and a heartbeat must not wait on (or fail because
1169
+ * of) housekeeping.
1170
+ */
1171
+ this.retireLegacyRunnerIfUnused({
1172
+ agent: data.agent,
1173
+ ...(cluster ? { cluster } : {}),
1174
+ }).catch((error: unknown) => {
1175
+ logger.error(
1176
+ `KubernetesAiAgent: the previous in-cluster Runner check for agent ${agentId.toString()} failed: ${error}`,
1177
+ );
1178
+ });
1179
+
1180
+ const writesAppeared: boolean =
1181
+ posture?.allowWrites === true && storedPosture?.allowWrites !== true;
1182
+
1183
+ if (!writesAppeared || !posture) {
1184
+ return;
1185
+ }
1186
+
1187
+ // The defaults read the cluster's current AI settings.
1188
+ cluster = cluster || (await this.findAgentCluster(data.agent));
1189
+
1190
+ if (!cluster) {
1191
+ return;
1192
+ }
1193
+
1194
+ // The liveness is written; a failure here must not fail the heartbeat.
1195
+ const defaultsApplied: KubernetesAiAgentDefaultsApplied =
1196
+ await this.applyFirstConnectionDefaultsSafely({
1197
+ cluster,
1198
+ allowWrites: true,
1199
+ });
1200
+
1201
+ if (Service.didApplyDefaults(defaultsApplied)) {
1202
+ await this.writeDefaultsFeedItem({ cluster, posture, defaultsApplied });
1203
+ }
1204
+ }
1205
+
1206
+ // The agent's own cluster (in its project, as root), or null.
1207
+ private async findAgentCluster(
1208
+ agent: Pick<Model, "kubernetesClusterId" | "projectId">,
1209
+ ): Promise<KubernetesCluster | null> {
1210
+ if (!agent.kubernetesClusterId) {
1211
+ return null;
1212
+ }
1213
+
1214
+ return await KubernetesClusterService.findOneBy({
1215
+ query: {
1216
+ _id: agent.kubernetesClusterId.toString(),
1217
+ ...(agent.projectId ? { projectId: agent.projectId } : {}),
1218
+ },
1219
+ select: CLUSTER_SELECT,
1220
+ props: { isRoot: true },
1221
+ });
1222
+ }
1223
+
1224
+ /*
1225
+ * ------------------------------------------------------------------
1226
+ * Retiring the previous in-cluster Runner
1227
+ * ------------------------------------------------------------------
1228
+ */
1229
+
1230
+ /*
1231
+ * The kubernetes-agent Runner row an older chart registered for this
1232
+ * cluster, deleted once it is certainly no longer needed — so the
1233
+ * Runners page stops listing a dead Runner nobody created by hand. Every
1234
+ * condition must hold, and anything else leaves it alone:
1235
+ *
1236
+ * - this agent has existed for LEGACY_RUNNER_RETIREMENT_AGENT_AGE_HOURS
1237
+ * and is online (it is heartbeating) — a rollback in the first day
1238
+ * after an upgrade still finds its Runner;
1239
+ * - the Runner has not been heard from for
1240
+ * LEGACY_RUNNER_RETIREMENT_OFFLINE_DAYS (a Runner that never
1241
+ * heartbeated is left alone: its age is unknown);
1242
+ * - it holds nothing beyond an in-cluster Runner's defaults (no
1243
+ * credentials or secrets, no runbooks or code fixes, not another
1244
+ * cluster's Runner) — what an operator entrusted to it is theirs;
1245
+ * - no auto-remediation rule names it: a rule scoped to only that Runner
1246
+ * would silently widen to "any Runner" once it is gone.
1247
+ *
1248
+ * Checked at most once an hour per agent per process, from the heartbeat.
1249
+ * The agent's cluster is read (once the check is due) unless the caller
1250
+ * already has it. Never throws; says what it decided.
1251
+ */
1252
+ @CaptureSpan()
1253
+ public async retireLegacyRunnerIfUnused(data: {
1254
+ agent: Model;
1255
+ cluster?: KubernetesCluster | undefined;
1256
+ now?: Date | undefined;
1257
+ }): Promise<LegacyRunnerRetirementOutcome> {
1258
+ const now: Date = data.now || OneUptimeDate.getCurrentDate();
1259
+ const agentId: string = data.agent.id?.toString() || "";
1260
+
1261
+ if (!agentId || !this.isLegacyRunnerRetirementCheckDue(agentId, now)) {
1262
+ return "not_due";
1263
+ }
1264
+
1265
+ const clusterId: string =
1266
+ data.cluster?.id?.toString() ||
1267
+ data.agent.kubernetesClusterId?.toString() ||
1268
+ "";
1269
+
1270
+ try {
1271
+ if (!Service.isOldEnoughToRetireLegacyRunner(data.agent, now)) {
1272
+ return "agent_too_new";
1273
+ }
1274
+
1275
+ const cluster: KubernetesCluster | null =
1276
+ data.cluster || (await this.findAgentCluster(data.agent));
1277
+
1278
+ const projectId: ObjectID | undefined =
1279
+ cluster?.projectId || data.agent.projectId;
1280
+
1281
+ if (!cluster || !cluster.id || !projectId) {
1282
+ return "no_legacy_runner";
1283
+ }
1284
+
1285
+ const legacyRunner: Runner | null =
1286
+ await KubernetesClusterAiAccessService.getLegacyAgentRunnerForCluster({
1287
+ projectId,
1288
+ kubernetesClusterId: cluster.id,
1289
+ clusterIdentifier: cluster.clusterIdentifier,
1290
+ });
1291
+
1292
+ if (!legacyRunner || !legacyRunner.id) {
1293
+ return "no_legacy_runner";
1294
+ }
1295
+
1296
+ if (
1297
+ KubernetesClusterAiAccessService.isRunnerOnline(legacyRunner) ||
1298
+ !Service.hasBeenOfflineLongEnoughToRetire(legacyRunner, now)
1299
+ ) {
1300
+ return "legacy_runner_recently_online";
1301
+ }
1302
+
1303
+ const holdings: Array<{ description: string }> =
1304
+ await KubernetesClusterAiAccessService.getRunnerHoldingsBeyondDefaults({
1305
+ runner: legacyRunner,
1306
+ clusterId: cluster.id,
1307
+ projectId,
1308
+ });
1309
+
1310
+ if (holdings.length > 0) {
1311
+ logger.info(
1312
+ `KubernetesAiAgent: kept the previous in-cluster Runner ${legacyRunner.id.toString()} of cluster ${cluster.id.toString()}: it holds more than its defaults.`,
1313
+ );
1314
+ return "legacy_runner_holds_more";
1315
+ }
1316
+
1317
+ const rulesNamingIt: number = (
1318
+ await AutoRemediationRuleService.countBy({
1319
+ query: {
1320
+ projectId,
1321
+ commandRunners: QueryHelper.inRelationArray([legacyRunner.id]),
1322
+ },
1323
+ props: { isRoot: true },
1324
+ })
1325
+ ).toNumber();
1326
+
1327
+ if (rulesNamingIt > 0) {
1328
+ logger.info(
1329
+ `KubernetesAiAgent: kept the previous in-cluster Runner ${legacyRunner.id.toString()} of cluster ${cluster.id.toString()}: ${rulesNamingIt} auto-remediation rule(s) name it.`,
1330
+ );
1331
+ return "legacy_runner_in_rule";
1332
+ }
1333
+
1334
+ await RunnerService.deleteOneBy({
1335
+ query: { _id: legacyRunner.id.toString(), projectId },
1336
+ props: { isRoot: true },
1337
+ });
1338
+
1339
+ logger.info(
1340
+ `KubernetesAiAgent: removed the previous in-cluster Runner ${legacyRunner.id.toString()} of cluster ${cluster.id.toString()}; the Kubernetes AI agent replaced it.`,
1341
+ );
1342
+
1343
+ await KubernetesClusterFeedService.createKubernetesClusterFeedItem({
1344
+ kubernetesClusterId: cluster.id,
1345
+ projectId,
1346
+ kubernetesClusterFeedEventType:
1347
+ KubernetesClusterFeedEventType.KubernetesClusterUpdated,
1348
+ displayColor: Blue500,
1349
+ feedInfoInMarkdown: `🧹 The previous in-cluster Runner **${
1350
+ legacyRunner.name || "kubernetes-agent"
1351
+ }** was removed. The ${KUBERNETES_AI_AGENT_DISPLAY_NAME} replaced it, and it had been offline for over ${LEGACY_RUNNER_RETIREMENT_OFFLINE_DAYS} days.`,
1352
+ });
1353
+
1354
+ return "retired";
1355
+ } catch (error) {
1356
+ logger.error(
1357
+ `KubernetesAiAgent: could not check the previous in-cluster Runner of cluster ${clusterId}: ${error}`,
1358
+ );
1359
+ return "failed";
1360
+ }
1361
+ }
1362
+
1363
+ // The agent has existed long enough that a rollback is no longer likely.
1364
+ public static isOldEnoughToRetireLegacyRunner(
1365
+ agent: Pick<Model, "createdAt">,
1366
+ now: Date,
1367
+ ): boolean {
1368
+ const createdAt: Date | null = toDate(agent.createdAt);
1369
+
1370
+ return Boolean(
1371
+ createdAt &&
1372
+ now.getTime() - createdAt.getTime() >=
1373
+ LEGACY_RUNNER_RETIREMENT_AGENT_AGE_HOURS * 60 * 60 * 1000,
1374
+ );
1375
+ }
1376
+
1377
+ // The Runner was last heard from long enough ago (never: not eligible).
1378
+ public static hasBeenOfflineLongEnoughToRetire(
1379
+ runner: Pick<Runner, "lastAlive">,
1380
+ now: Date,
1381
+ ): boolean {
1382
+ const lastAlive: Date | null = toDate(runner.lastAlive);
1383
+
1384
+ return Boolean(
1385
+ lastAlive &&
1386
+ now.getTime() - lastAlive.getTime() >=
1387
+ LEGACY_RUNNER_RETIREMENT_OFFLINE_DAYS * 24 * 60 * 60 * 1000,
1388
+ );
1389
+ }
1390
+
1391
+ /*
1392
+ * At most one check an hour per agent in this process. Stamped before the
1393
+ * check runs, so heartbeats that overlap it do not start a second one.
1394
+ */
1395
+ private isLegacyRunnerRetirementCheckDue(
1396
+ agentId: string,
1397
+ now: Date,
1398
+ ): boolean {
1399
+ const lastCheckedAt: number | undefined =
1400
+ this.legacyRunnerRetirementCheckedAt.get(agentId);
1401
+
1402
+ if (
1403
+ lastCheckedAt !== undefined &&
1404
+ now.getTime() - lastCheckedAt < LEGACY_RUNNER_RETIREMENT_CHECK_INTERVAL_MS
1405
+ ) {
1406
+ return false;
1407
+ }
1408
+
1409
+ // Bounded: a long-lived process forgets old agents rather than growing.
1410
+ if (
1411
+ this.legacyRunnerRetirementCheckedAt.size >=
1412
+ MAX_LEGACY_RUNNER_RETIREMENT_CHECKS_REMEMBERED
1413
+ ) {
1414
+ this.legacyRunnerRetirementCheckedAt.clear();
1415
+ }
1416
+
1417
+ this.legacyRunnerRetirementCheckedAt.set(agentId, now.getTime());
1418
+
1419
+ return true;
1420
+ }
1421
+
1422
+ /*
1423
+ * The agent signing off on a clean shutdown. lastAliveAt is left alone
1424
+ * (it is when the agent was last heard from); the disconnected status is
1425
+ * what lets its replacement pod register at once instead of waiting for
1426
+ * the alive window to lapse, and what makes the AI agent page say
1427
+ * "Offline" straight away. Its next heartbeat or registration flips it
1428
+ * back.
1429
+ */
1430
+ @CaptureSpan()
1431
+ public async markDisconnected(data: {
1432
+ kubernetesAiAgentId: ObjectID;
1433
+ }): Promise<void> {
1434
+ if (!data.kubernetesAiAgentId) {
1435
+ throw new BadDataException("kubernetesAiAgentId is required");
1436
+ }
1437
+
1438
+ await this.updateColumnsByIdWithoutHooks({
1439
+ id: data.kubernetesAiAgentId,
1440
+ data: { connectionStatus: "disconnected" },
1441
+ });
1442
+ }
1443
+
1444
+ /*
1445
+ * An admin's "Reset agent" on the cluster's AI agent page: the agent's key
1446
+ * stops working at once (keyHash null matches nothing) and the row reads
1447
+ * as disconnected. The real pod fails authentication on its next request,
1448
+ * registers again and — the row being reset — is admitted, so it comes
1449
+ * back by itself within a few minutes. Whoever else held the key (a pod
1450
+ * that registered with a leaked ingestion key) is locked out the same
1451
+ * way; rotating that ingestion key keeps it out.
1452
+ */
1453
+ @CaptureSpan()
1454
+ public async resetAgent(data: {
1455
+ projectId: ObjectID;
1456
+ kubernetesClusterId: ObjectID;
1457
+ userId?: ObjectID | undefined;
1458
+ }): Promise<void> {
1459
+ const agent: Model | null = await this.findForCluster({
1460
+ projectId: data.projectId,
1461
+ kubernetesClusterId: data.kubernetesClusterId,
1462
+ });
1463
+
1464
+ if (!agent || !agent.id) {
1465
+ throw new BadDataException(
1466
+ "This cluster has no Kubernetes AI agent to reset.",
1467
+ );
1468
+ }
1469
+
1470
+ await this.updateOneById({
1471
+ id: agent.id,
1472
+ data: {
1473
+ keyHash: null,
1474
+ connectionStatus: "disconnected",
1475
+ } as never,
1476
+ props: { isRoot: true },
1477
+ });
1478
+
1479
+ logger.info(
1480
+ `KubernetesAiAgent: the Kubernetes AI agent of cluster ${data.kubernetesClusterId.toString()} in project ${data.projectId.toString()} was reset${
1481
+ data.userId ? ` by user ${data.userId.toString()}` : ""
1482
+ }.`,
1483
+ );
1484
+
1485
+ const resetBy: string = data.userId
1486
+ ? await this.getUserMarkdownForFeed({
1487
+ userId: data.userId,
1488
+ projectId: data.projectId,
1489
+ })
1490
+ : "";
1491
+
1492
+ await KubernetesClusterFeedService.createKubernetesClusterFeedItem({
1493
+ kubernetesClusterId: data.kubernetesClusterId,
1494
+ projectId: data.projectId,
1495
+ kubernetesClusterFeedEventType:
1496
+ KubernetesClusterFeedEventType.KubernetesClusterUpdated,
1497
+ displayColor: Blue500,
1498
+ feedInfoInMarkdown: `🔄 The ${KUBERNETES_AI_AGENT_DISPLAY_NAME} was reset${
1499
+ resetBy ? ` by **${resetBy}**` : ""
1500
+ }. It reconnects on its own within a few minutes.`,
1501
+ moreInformationInMarkdown: `**Agent**: ${KUBERNETES_AI_AGENT_DISPLAY_NAME} (${agent.id.toString()})`,
1502
+ ...(data.userId ? { userId: data.userId } : {}),
1503
+ });
1504
+ }
1505
+
1506
+ /*
1507
+ * The user's name as a feed link, or "" when it cannot be had (no such
1508
+ * user, or the lookup failed). Only ever wording: the action it describes
1509
+ * is already done, so a failed lookup must not fail it.
1510
+ */
1511
+ private async getUserMarkdownForFeed(data: {
1512
+ userId: ObjectID;
1513
+ projectId: ObjectID;
1514
+ }): Promise<string> {
1515
+ try {
1516
+ return await UserService.getUserMarkdownString(data);
1517
+ } catch (error) {
1518
+ logger.error(
1519
+ `KubernetesAiAgent: could not look up user ${data.userId.toString()} for a feed item: ${error}`,
1520
+ );
1521
+ return "";
1522
+ }
1523
+ }
1524
+
1525
+ /*
1526
+ * ------------------------------------------------------------------
1527
+ * Feed
1528
+ * ------------------------------------------------------------------
1529
+ */
1530
+
1531
+ // What the agent may change, in the feed's words.
1532
+ public static describeWriteAccess(posture: KubernetesAgentPosture): string {
1533
+ if (posture.allowWrites !== true) {
1534
+ return "read-only";
1535
+ }
1536
+
1537
+ return `can make changes ${Service.describeWriteScope(posture)}`;
1538
+ }
1539
+
1540
+ // Where a writing agent may change things: its namespaces, or everywhere.
1541
+ public static describeWriteScope(posture: KubernetesAgentPosture): string {
1542
+ const namespaces: Array<string> = posture.writeNamespaces || [];
1543
+
1544
+ return namespaces.length > 0
1545
+ ? `in ${namespaces.join(", ")}`
1546
+ : "anywhere in the cluster";
1547
+ }
1548
+
1549
+ // The first successful registration of a cluster's agent. Best-effort.
1550
+ private async writeConnectedFeedItem(data: {
1551
+ cluster: KubernetesCluster;
1552
+ agentId: ObjectID;
1553
+ posture: KubernetesAgentPosture;
1554
+ agentVersion?: string | undefined;
1555
+ defaultsApplied: KubernetesAiAgentDefaultsApplied;
1556
+ }): Promise<void> {
1557
+ const isInvestigationOn: boolean =
1558
+ data.cluster.isAiInvestigationEnabled === true ||
1559
+ data.defaultsApplied.turnedOnInvestigation;
1560
+
1561
+ const sentences: Array<string> = [
1562
+ `🤖 The ${KUBERNETES_AI_AGENT_DISPLAY_NAME} connected (${Service.describeWriteAccess(
1563
+ data.posture,
1564
+ )}).`,
1565
+ isInvestigationOn
1566
+ ? "AI can now use it to investigate this cluster."
1567
+ : "Investigating with kubectl is off for this cluster; turn it on on the cluster's AI agent page.",
1568
+ ];
1569
+
1570
+ if (data.defaultsApplied.remediationMode) {
1571
+ sentences.push(
1572
+ 'Fixes are set to "Ask for approval", because the chart allows changes.',
1573
+ );
1574
+ }
1575
+
1576
+ await KubernetesClusterFeedService.createKubernetesClusterFeedItem({
1577
+ kubernetesClusterId: data.cluster.id!,
1578
+ projectId: data.cluster.projectId!,
1579
+ kubernetesClusterFeedEventType:
1580
+ KubernetesClusterFeedEventType.KubernetesClusterUpdated,
1581
+ displayColor: Green500,
1582
+ feedInfoInMarkdown: sentences.join(" "),
1583
+ moreInformationInMarkdown: [
1584
+ `**Agent**: ${KUBERNETES_AI_AGENT_DISPLAY_NAME} (${data.agentId.toString()})`,
1585
+ `**Cluster identifier**: \`${data.posture.clusterIdentifier || ""}\``,
1586
+ `**Agent version**: ${data.agentVersion || "unknown"}`,
1587
+ `**kubectl**: ${data.posture.kubectlVersion || "not detected"}`,
1588
+ `**Agent chart**: ${data.posture.agentChartVersion || "unknown"}`,
1589
+ ].join("\n\n"),
1590
+ });
1591
+ }
1592
+
1593
+ /*
1594
+ * The first-connection defaults changed a setting after the first
1595
+ * connection (the agent re-registered with write access, or its write
1596
+ * access appeared on a heartbeat). A setting never changes silently.
1597
+ */
1598
+ private async writeDefaultsFeedItem(data: {
1599
+ cluster: KubernetesCluster;
1600
+ posture: KubernetesAgentPosture;
1601
+ defaultsApplied: KubernetesAiAgentDefaultsApplied;
1602
+ }): Promise<void> {
1603
+ const sentences: Array<string> = [];
1604
+
1605
+ if (data.defaultsApplied.turnedOnInvestigation) {
1606
+ sentences.push(
1607
+ `AI can now use the ${KUBERNETES_AI_AGENT_DISPLAY_NAME} to investigate this cluster.`,
1608
+ );
1609
+ }
1610
+
1611
+ if (data.defaultsApplied.remediationMode) {
1612
+ sentences.push(
1613
+ `The ${KUBERNETES_AI_AGENT_DISPLAY_NAME} can now make changes ${Service.describeWriteScope(
1614
+ data.posture,
1615
+ )}, so fixes are set to "Ask for approval".`,
1616
+ );
1617
+ }
1618
+
1619
+ sentences.push(
1620
+ "Nobody had chosen AI settings for this cluster yet; change them on the cluster's AI agent page.",
1621
+ );
1622
+
1623
+ await KubernetesClusterFeedService.createKubernetesClusterFeedItem({
1624
+ kubernetesClusterId: data.cluster.id!,
1625
+ projectId: data.cluster.projectId!,
1626
+ kubernetesClusterFeedEventType:
1627
+ KubernetesClusterFeedEventType.KubernetesClusterUpdated,
1628
+ displayColor: Blue500,
1629
+ feedInfoInMarkdown: `🤖 ${sentences.join(" ")}`,
1630
+ });
1631
+ }
1632
+ }
1633
+
1634
+ export default new Service();