@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.
- package/Models/DatabaseModels/Incident.ts +267 -1
- package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/RunnerJob.ts +75 -0
- package/Models/DatabaseModels/StatusPage.ts +55 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
- package/Server/API/AIChatAPI.ts +6 -3
- package/Server/API/AIInvestigationAPI.ts +17 -10
- package/Server/API/AutoRemediationAPI.ts +207 -25
- package/Server/API/IncidentAPI.ts +162 -0
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/API/StatusPageAPI.ts +487 -171
- package/Server/API/StatusPageSubscriberAPI.ts +63 -15
- package/Server/Infrastructure/GlobalCache.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
- package/Server/Infrastructure/Semaphore.ts +10 -0
- package/Server/Middleware/IdentityRateLimit.ts +108 -3
- package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
- package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
- package/Server/Middleware/TelemetryIngest.ts +41 -18
- package/Server/Services/AIService.ts +70 -1
- package/Server/Services/AlertService.ts +352 -11
- package/Server/Services/AlertSeverityService.ts +65 -15
- package/Server/Services/AutoRemediationRuleService.ts +1 -1
- package/Server/Services/DatabaseService.ts +93 -10
- package/Server/Services/IncidentCustomFieldService.ts +276 -1
- package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
- package/Server/Services/IncidentEpisodeService.ts +15 -0
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
- package/Server/Services/IncidentPublicNoteService.ts +30 -1
- package/Server/Services/IncidentService.ts +1788 -142
- package/Server/Services/IncidentSeverityService.ts +66 -15
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
- package/Server/Services/KubernetesAiAgentService.ts +1634 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
- package/Server/Services/KubernetesClusterService.ts +129 -21
- package/Server/Services/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/RunnerJobService.ts +241 -104
- package/Server/Services/RunnerService.ts +1 -1
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +805 -19
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/SmsService.ts +10 -0
- package/Server/Services/StatusPageService.ts +65 -8
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
- package/Server/Services/StatusPageSubscriberService.ts +1161 -64
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- package/Server/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
- package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
- package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
- package/Server/Utils/SSRFProtection.ts +76 -16
- package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
- package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
- package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
- package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
- package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
- package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
- package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
- package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
- package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
- package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
- package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
- package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
- package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
- package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
- package/Server/Utils/StatusPageResource.ts +51 -20
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
- package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
- package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
- package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
- package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
- package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
- package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
- package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
- package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
- package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
- package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
- package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
- package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
- package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
- package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
- package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
- package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
- package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
- package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
- package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
- package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
- package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
- package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
- package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
- package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
- package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
- package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
- package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
- package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
- package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
- package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
- package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
- package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
- package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
- package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
- package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
- package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
- package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
- package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
- package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
- package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
- package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
- package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
- package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
- package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
- package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
- package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
- package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
- package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
- package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
- package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
- package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
- package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
- package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
- package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
- package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
- package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
- package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
- package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
- package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
- package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
- package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
- package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
- package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
- package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
- package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
- package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
- package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
- package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
- package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
- package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
- package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
- package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
- package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
- package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
- package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
- package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
- package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
- package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
- package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
- package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
- package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
- package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
- package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
- package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
- package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
- package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
- package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
- package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
- package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
- package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
- package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
- package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
- package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
- package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
- package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
- package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
- package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
- package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
- package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
- package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
- package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
- package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
- package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
- package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
- package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
- package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
- package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
- package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
- package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
- package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
- package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
- package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
- package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
- package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
- package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
- package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
- package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
- package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
- package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
- package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
- package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
- package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
- package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
- package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
- package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
- package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
- package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
- package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
- package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
- package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
- package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
- package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
- package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
- package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
- package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
- package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
- package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
- package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
- package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
- package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
- package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
- package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
- package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
- package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
- package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
- package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
- package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
- package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
- package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
- package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
- package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
- package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
- package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
- package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
- package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
- package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
- package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
- package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
- package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
- package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
- package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
- package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
- package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
- package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
- package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
- package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
- package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
- package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
- package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
- package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
- package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
- package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
- package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
- package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
- package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
- package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
- package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
- package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
- package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
- package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
- package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
- package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
- package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
- package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
- package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
- package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
- package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
- package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- package/Tests/Types/SafeHtml.test.ts +102 -0
- package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
- package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
- package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
- package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
- package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
- package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
- package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
- package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
- package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
- package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
- package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
- package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
- package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
- package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
- package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
- package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
- package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
- package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
- package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
- package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
- package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
- package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
- package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
- package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
- package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
- package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
- package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
- package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
- package/Tests/UI/Components/Input.test.tsx +80 -1
- package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
- package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
- package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
- package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
- package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
- package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
- package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
- package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
- package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
- package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
- package/Tests/UI/Components/Toggle.test.tsx +66 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
- package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
- package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
- package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
- package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
- package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
- package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
- package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
- package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
- package/Types/CustomField/CustomFieldOrder.ts +55 -0
- package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
- package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
- package/Types/CustomField/CustomFieldType.ts +14 -0
- package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
- package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
- package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
- package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
- package/Types/SafeHtml.ts +93 -0
- package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
- package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
- package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
- package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
- package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
- package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
- package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
- package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
- package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
- package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
- package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
- package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
- package/Types/Team/ComplianceNotificationChannel.ts +22 -0
- package/Types/Team/ComplianceRule.ts +434 -0
- package/Types/Team/ComplianceRuleType.ts +21 -0
- package/Types/Team/TeamComplianceStatus.ts +81 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
- package/UI/Components/ActionButton/RowActions.tsx +238 -0
- package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
- package/UI/Components/Charts/Line/LineChart.tsx +32 -2
- package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
- package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
- package/UI/Components/Charts/Utils/XAxis.ts +75 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
- package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- package/UI/Components/Forms/ModelForm.tsx +70 -8
- package/UI/Components/Forms/Validation.ts +1 -1
- package/UI/Components/Input/Input.tsx +90 -57
- package/UI/Components/List/ListRow.tsx +8 -74
- package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
- package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
- package/UI/Components/ModelTable/ModelTable.tsx +6 -7
- package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
- package/UI/Components/OrderedStatesList/Item.tsx +7 -72
- package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
- package/UI/Components/Table/TableBody.tsx +8 -2
- package/UI/Components/Table/TableRow.tsx +20 -140
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
- package/UI/Components/TelemetryViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
- package/UI/Components/Toggle/Toggle.tsx +26 -0
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Styles/Theme.css +14 -2
- package/UI/Utils/SensitiveUrlToken.ts +10 -5
- package/Utils/API.ts +247 -2
- package/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
- package/Utils/Markdown/MarkdownEscape.ts +65 -0
- package/Utils/ProbeNetworkFailureUtil.ts +673 -0
- package/build/dist/Models/DatabaseModels/Incident.js +267 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +6 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +100 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +417 -176
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
- package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +59 -1
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +248 -13
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +51 -15
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1251 -72
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +178 -92
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -1
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +2 -22
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +47 -9
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +110 -6
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +48 -6
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageResource.js +39 -7
- package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/SafeHtml.js +84 -0
- package/build/dist/Types/SafeHtml.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRule.js +304 -0
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
- package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
- package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
- package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
- package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +1 -1
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +69 -54
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -39
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +6 -2
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +12 -75
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
- package/build/dist/Utils/API.js +177 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
- package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
- package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import KubernetesClusterAiAccessService, {
|
|
2
|
+
AI_AGENT_INSTALL_COMMAND,
|
|
3
|
+
AI_AGENT_NOT_CONNECTED_NEXT_STEP,
|
|
4
|
+
AI_BALANCE_INSUFFICIENT_NEXT_STEP,
|
|
5
|
+
CREDENTIAL_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
|
|
6
|
+
LEGACY_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
|
|
2
7
|
KubernetesAgentRegistrationRefusedException,
|
|
3
8
|
KubernetesClusterAiAccessProjectGates,
|
|
4
9
|
MAX_KUBERNETES_AGENT_RUNNERS_PER_PROJECT,
|
|
@@ -9,6 +14,8 @@ import KubernetesClusterAiAccessService, {
|
|
|
9
14
|
getKubernetesAgentRunnerNameForCluster,
|
|
10
15
|
getPreviousInstanceRetryAfterSeconds,
|
|
11
16
|
} from "../../../Server/Services/KubernetesClusterAiAccessService";
|
|
17
|
+
import AIService from "../../../Server/Services/AIService";
|
|
18
|
+
import KubernetesAiAgentService from "../../../Server/Services/KubernetesAiAgentService";
|
|
12
19
|
import KubernetesClusterFeedService from "../../../Server/Services/KubernetesClusterFeedService";
|
|
13
20
|
import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
|
|
14
21
|
import LlmProviderService from "../../../Server/Services/LlmProviderService";
|
|
@@ -17,6 +24,7 @@ import RunbookCredentialService from "../../../Server/Services/RunbookCredential
|
|
|
17
24
|
import RunbookSecretService from "../../../Server/Services/RunbookSecretService";
|
|
18
25
|
import RunnerService from "../../../Server/Services/RunnerService";
|
|
19
26
|
import logger from "../../../Server/Utils/Logger";
|
|
27
|
+
import KubernetesAiAgent from "../../../Models/DatabaseModels/KubernetesAiAgent";
|
|
20
28
|
import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
|
|
21
29
|
import LlmProvider from "../../../Models/DatabaseModels/LlmProvider";
|
|
22
30
|
import Project from "../../../Models/DatabaseModels/Project";
|
|
@@ -133,6 +141,32 @@ function fakeRunner(overrides: Partial<Record<string, unknown>> = {}): Runner {
|
|
|
133
141
|
} as unknown as Runner;
|
|
134
142
|
}
|
|
135
143
|
|
|
144
|
+
const AGENT_ID: ObjectID = new ObjectID("77777777-7777-4777-8777-777777777777");
|
|
145
|
+
|
|
146
|
+
// This cluster's Kubernetes AI agent row, online and able to write.
|
|
147
|
+
function fakeAgent(
|
|
148
|
+
overrides: Partial<Record<string, unknown>> = {},
|
|
149
|
+
): KubernetesAiAgent {
|
|
150
|
+
return {
|
|
151
|
+
id: AGENT_ID,
|
|
152
|
+
_id: AGENT_ID.toString(),
|
|
153
|
+
projectId: PROJECT_ID,
|
|
154
|
+
kubernetesClusterId: CLUSTER_ID,
|
|
155
|
+
connectionStatus: "connected",
|
|
156
|
+
lastAliveAt: OneUptimeDate.getCurrentDate(),
|
|
157
|
+
lastRegisteredAt: OneUptimeDate.getSomeMinutesAgo(60),
|
|
158
|
+
agentVersion: "14.1.0",
|
|
159
|
+
posture: {
|
|
160
|
+
inCluster: true,
|
|
161
|
+
allowWrites: true,
|
|
162
|
+
clusterIdentifier: "prod-us",
|
|
163
|
+
podNamespace: "oneuptime-agent",
|
|
164
|
+
kubectlVersion: "v1.36.4",
|
|
165
|
+
},
|
|
166
|
+
...overrides,
|
|
167
|
+
} as unknown as KubernetesAiAgent;
|
|
168
|
+
}
|
|
169
|
+
|
|
136
170
|
function gapCodes(
|
|
137
171
|
status: KubernetesClusterAiAccessStatus,
|
|
138
172
|
): Array<KubernetesAiAccessGapCode> {
|
|
@@ -150,6 +184,7 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
150
184
|
let credentialCountSpy: jest.SpyInstance;
|
|
151
185
|
let secretCountSpy: jest.SpyInstance;
|
|
152
186
|
let boundClusterCountSpy: jest.SpyInstance;
|
|
187
|
+
let agentLookup: jest.SpyInstance;
|
|
153
188
|
|
|
154
189
|
beforeEach(() => {
|
|
155
190
|
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
@@ -164,6 +199,10 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
164
199
|
boundClusterCountSpy = jest
|
|
165
200
|
.spyOn(KubernetesClusterService, "countBy")
|
|
166
201
|
.mockResolvedValue(new PositiveNumber(0));
|
|
202
|
+
// No Kubernetes AI agent unless a test says otherwise.
|
|
203
|
+
agentLookup = jest
|
|
204
|
+
.spyOn(KubernetesAiAgentService, "findForCluster")
|
|
205
|
+
.mockResolvedValue(null);
|
|
167
206
|
});
|
|
168
207
|
|
|
169
208
|
afterEach(() => {
|
|
@@ -220,11 +259,11 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
220
259
|
expect(status.runner?.posture?.allowNodeOperations).toBe(false);
|
|
221
260
|
});
|
|
222
261
|
|
|
223
|
-
it("reports
|
|
262
|
+
it("reports ai_agent_not_connected with the install command and blocks both when nothing can reach the cluster", async () => {
|
|
224
263
|
/*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
264
|
+
* No binding and no agent row: nothing to read but the agent. The
|
|
265
|
+
* status no longer looks for an unbound previous in-cluster Runner —
|
|
266
|
+
* the chart upgrade that installs the agent is the one step.
|
|
228
267
|
*/
|
|
229
268
|
const findOneBy: jest.SpyInstance = jest
|
|
230
269
|
.spyOn(RunnerService, "findOneBy")
|
|
@@ -236,17 +275,44 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
236
275
|
gates: READY_GATES,
|
|
237
276
|
});
|
|
238
277
|
|
|
239
|
-
expect(findOneBy).
|
|
240
|
-
expect(
|
|
278
|
+
expect(findOneBy).not.toHaveBeenCalled();
|
|
279
|
+
expect(agentLookup).toHaveBeenCalledWith({
|
|
280
|
+
projectId: PROJECT_ID,
|
|
281
|
+
kubernetesClusterId: CLUSTER_ID,
|
|
282
|
+
});
|
|
283
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
241
284
|
expect(status.gaps[0]?.blocks).toBe("both");
|
|
242
|
-
expect(status.gaps[0]?.title).toBe(
|
|
243
|
-
|
|
285
|
+
expect(status.gaps[0]?.title).toBe(
|
|
286
|
+
"The Kubernetes AI agent is not connected",
|
|
287
|
+
);
|
|
288
|
+
expect(status.gaps[0]?.nextStep).toBe(AI_AGENT_NOT_CONNECTED_NEXT_STEP);
|
|
289
|
+
expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
|
|
290
|
+
expect(status.gaps[0]?.nextStep).not.toContain("aiAccess");
|
|
291
|
+
// Nothing selected, so nothing said about an ignored Runner.
|
|
292
|
+
expect(status.gaps[0]?.description).toBe(
|
|
293
|
+
"OneUptime AI runs kubectl on this cluster through the Kubernetes AI agent, and no agent has connected for this cluster yet.",
|
|
294
|
+
);
|
|
244
295
|
expect(status.isInvestigationReady).toBe(false);
|
|
245
296
|
expect(status.isRemediationReady).toBe(false);
|
|
246
297
|
expect(status.runner).toBeNull();
|
|
298
|
+
expect(status.aiAgent).toBeNull();
|
|
247
299
|
expect(status.accessMethod).toBe("none");
|
|
248
300
|
});
|
|
249
301
|
|
|
302
|
+
it("never produces no_runner_bound any more", async () => {
|
|
303
|
+
for (const aiAccessRunnerId of [undefined, RUNNER_ID]) {
|
|
304
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(null);
|
|
305
|
+
|
|
306
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
307
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
308
|
+
cluster: fakeCluster({ aiAccessRunnerId }),
|
|
309
|
+
gates: READY_GATES,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
expect(gapCodes(status)).not.toContain("no_runner_bound");
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
250
316
|
/*
|
|
251
317
|
* The FK on aiAccessRunnerId is ON DELETE SET NULL, so deleting the bound
|
|
252
318
|
* Runner clears the binding in the same statement: the only way to see a
|
|
@@ -265,25 +331,37 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
265
331
|
expect(gapCodes(status)).toEqual(["runner_missing"]);
|
|
266
332
|
expect(status.gaps[0]?.title).toBe("The bound Runner was just deleted");
|
|
267
333
|
/*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* page instead (was "Reload this page").
|
|
334
|
+
* Gap next steps are shown verbatim on incident pages too, where "this
|
|
335
|
+
* page" would be the incident. They name the cluster's AI agent page.
|
|
271
336
|
*/
|
|
272
|
-
expect(status.gaps[0]?.nextStep).
|
|
337
|
+
expect(status.gaps[0]?.nextStep).toBe(
|
|
338
|
+
"Reload the cluster's AI agent page (AI → Agent).",
|
|
339
|
+
);
|
|
273
340
|
expect(status.gaps[0]?.nextStep).not.toContain("this page");
|
|
274
341
|
expect(status.isInvestigationReady).toBe(false);
|
|
275
342
|
});
|
|
276
343
|
|
|
344
|
+
it("negative control: a bound Runner that vanished while the cluster has an agent reads as the agent", async () => {
|
|
345
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(null);
|
|
346
|
+
agentLookup.mockResolvedValue(fakeAgent());
|
|
347
|
+
|
|
348
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
349
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
350
|
+
cluster: fakeCluster(),
|
|
351
|
+
gates: READY_GATES,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(gapCodes(status)).toEqual([]);
|
|
355
|
+
expect(status.runner?.kind).toBe("ai_agent");
|
|
356
|
+
});
|
|
357
|
+
|
|
277
358
|
/*
|
|
278
|
-
* A cluster
|
|
279
|
-
* binding was cleared,
|
|
280
|
-
*
|
|
281
|
-
* would change nothing there — a registering Runner never re-binds a
|
|
282
|
-
* cluster that was configured before — so the step that works is
|
|
283
|
-
* selecting the Runner on the AI page.
|
|
359
|
+
* A previous in-cluster Runner that is registered but not bound (its
|
|
360
|
+
* binding was cleared, or it was deleted and registered again) is not an
|
|
361
|
+
* access target: only a bound Runner is. The cluster uses its AI agent.
|
|
284
362
|
*/
|
|
285
|
-
describe("
|
|
286
|
-
it("
|
|
363
|
+
describe("an unbound previous in-cluster Runner", () => {
|
|
364
|
+
it("is not looked up or used: the install step is the next step", async () => {
|
|
287
365
|
const findOneBy: jest.SpyInstance = jest
|
|
288
366
|
.spyOn(RunnerService, "findOneBy")
|
|
289
367
|
.mockResolvedValue(fakeRunner());
|
|
@@ -294,56 +372,13 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
294
372
|
gates: READY_GATES,
|
|
295
373
|
});
|
|
296
374
|
|
|
297
|
-
expect(
|
|
298
|
-
expect(status.
|
|
299
|
-
"The in-cluster Runner is installed but not selected",
|
|
300
|
-
);
|
|
301
|
-
expect(status.gaps[0]?.description).toContain("and online");
|
|
302
|
-
expect(status.gaps[0]?.nextStep).toContain(
|
|
303
|
-
'Select the kubernetes-agent Runner "kubernetes-agent/prod-us"',
|
|
304
|
-
);
|
|
305
|
-
// Round three: named, not "on this page" (shown on incident pages too).
|
|
306
|
-
expect(status.gaps[0]?.nextStep).toContain("on the cluster's AI page");
|
|
307
|
-
expect(status.gaps[0]?.nextStep).not.toContain("this page");
|
|
308
|
-
expect(status.gaps[0]?.nextStep).not.toContain("aiAccess.enabled=true");
|
|
309
|
-
expect(status.gaps[0]?.blocks).toBe("both");
|
|
310
|
-
expect(status.isInvestigationReady).toBe(false);
|
|
375
|
+
expect(findOneBy).not.toHaveBeenCalled();
|
|
376
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
311
377
|
expect(status.runner).toBeNull();
|
|
312
|
-
|
|
313
|
-
// Looked up in this project, by the agent name, case-insensitively.
|
|
314
|
-
const query: Record<string, unknown> = (
|
|
315
|
-
findOneBy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
316
|
-
).query;
|
|
317
|
-
expect(query["projectId"]).toBe(PROJECT_ID);
|
|
318
|
-
expect(typeof query["name"]).not.toBe("string");
|
|
319
|
-
expect(query["name"]).toBeDefined();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("says the Runner is not online when it is not, and still points at selecting it", async () => {
|
|
323
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
324
|
-
fakeRunner({
|
|
325
|
-
lastAlive: OneUptimeDate.getSomeMinutesAgo(30),
|
|
326
|
-
}),
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
330
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
331
|
-
cluster: fakeCluster({ aiAccessRunnerId: undefined }),
|
|
332
|
-
gates: READY_GATES,
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
expect(status.gaps[0]?.description).toContain("not online right now");
|
|
336
|
-
expect(status.gaps[0]?.nextStep).toContain("Select the kubernetes-agent");
|
|
337
378
|
});
|
|
338
379
|
|
|
339
|
-
it("
|
|
340
|
-
|
|
341
|
-
fakeRunner({
|
|
342
|
-
hostInfo: {
|
|
343
|
-
kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
|
|
344
|
-
},
|
|
345
|
-
}),
|
|
346
|
-
);
|
|
380
|
+
it("never throws: a failed agent lookup reads as no agent", async () => {
|
|
381
|
+
agentLookup.mockRejectedValue(new Error("db down"));
|
|
347
382
|
|
|
348
383
|
const status: KubernetesClusterAiAccessStatus =
|
|
349
384
|
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
@@ -351,52 +386,32 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
351
386
|
gates: READY_GATES,
|
|
352
387
|
});
|
|
353
388
|
|
|
354
|
-
expect(status
|
|
355
|
-
expect(status.
|
|
389
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
390
|
+
expect(status.aiAgent).toBeNull();
|
|
356
391
|
});
|
|
357
392
|
|
|
358
|
-
it("never throws: a failed lookup
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
.mockRejectedValue(new Error("db down"));
|
|
362
|
-
|
|
363
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
364
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
365
|
-
cluster: fakeCluster({ aiAccessRunnerId: undefined }),
|
|
366
|
-
gates: READY_GATES,
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
expect(gapCodes(status)).toEqual(["no_runner_bound"]);
|
|
370
|
-
expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it("does not look anything up for a cluster row without an identifier", async () => {
|
|
374
|
-
const findOneBy: jest.SpyInstance = jest.spyOn(
|
|
375
|
-
RunnerService,
|
|
376
|
-
"findOneBy",
|
|
377
|
-
);
|
|
393
|
+
it("never throws: with a failed agent lookup a bound, online previous Runner still serves", async () => {
|
|
394
|
+
agentLookup.mockRejectedValue(new Error("db down"));
|
|
395
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
|
|
378
396
|
|
|
379
397
|
const status: KubernetesClusterAiAccessStatus =
|
|
380
398
|
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
381
|
-
cluster: fakeCluster(
|
|
382
|
-
aiAccessRunnerId: undefined,
|
|
383
|
-
clusterIdentifier: " ",
|
|
384
|
-
}),
|
|
399
|
+
cluster: fakeCluster(),
|
|
385
400
|
gates: READY_GATES,
|
|
386
401
|
});
|
|
387
402
|
|
|
388
|
-
expect(
|
|
389
|
-
expect(status.
|
|
403
|
+
expect(gapCodes(status)).toEqual([]);
|
|
404
|
+
expect(status.runner?.kind).toBe("runner");
|
|
390
405
|
});
|
|
391
406
|
});
|
|
392
407
|
|
|
393
408
|
/*
|
|
394
409
|
* A Runner that signs off (/runner-ingest/disconnect — what a helm
|
|
395
|
-
*
|
|
396
|
-
* at once, not "Connected" until its last heartbeat ages out.
|
|
410
|
+
* upgrade or uninstall does to the previous in-cluster Runner's pod) is
|
|
411
|
+
* offline at once, not "Connected" until its last heartbeat ages out.
|
|
397
412
|
*/
|
|
398
413
|
describe("a Runner that signed off", () => {
|
|
399
|
-
it("
|
|
414
|
+
it("the previous in-cluster Runner: offline, blocks both, and the step is the chart upgrade to the AI agent", async () => {
|
|
400
415
|
const findOneBy: jest.SpyInstance = jest
|
|
401
416
|
.spyOn(RunnerService, "findOneBy")
|
|
402
417
|
.mockResolvedValue(
|
|
@@ -413,16 +428,18 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
413
428
|
});
|
|
414
429
|
|
|
415
430
|
expect(status.runner?.isOnline).toBe(false);
|
|
431
|
+
expect(status.runner?.kind).toBe("runner");
|
|
416
432
|
expect(status.isInvestigationReady).toBe(false);
|
|
417
433
|
expect(status.isRemediationReady).toBe(false);
|
|
418
434
|
expect(gapCodes(status)).toEqual(["runner_offline"]);
|
|
419
|
-
expect(status.gaps[0]?.title).toBe(
|
|
420
|
-
|
|
421
|
-
|
|
435
|
+
expect(status.gaps[0]?.title).toBe(
|
|
436
|
+
"The previous in-cluster Runner signed off",
|
|
437
|
+
);
|
|
422
438
|
expect(status.gaps[0]?.nextStep).toContain(
|
|
423
|
-
"
|
|
439
|
+
"Upgrade the Kubernetes agent chart: the Kubernetes AI agent replaces this Runner",
|
|
424
440
|
);
|
|
425
|
-
expect(status.gaps[0]?.nextStep).toContain(
|
|
441
|
+
expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
|
|
442
|
+
expect(status.gaps[0]?.nextStep).not.toContain("aiAccess.enabled=true");
|
|
426
443
|
expect(status.gaps[0]?.nextStep).not.toContain("component=ai-runner");
|
|
427
444
|
|
|
428
445
|
// The sign-off is read, not guessed.
|
|
@@ -450,6 +467,9 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
450
467
|
expect(status.gaps[0]?.code).toBe("runner_offline");
|
|
451
468
|
expect(status.gaps[0]?.title).toBe("The Runner signed off");
|
|
452
469
|
expect(status.gaps[0]?.nextStep).toContain("Start the Runner container");
|
|
470
|
+
expect(status.gaps[0]?.nextStep).toContain(
|
|
471
|
+
"clear the Runner on the cluster's AI agent page (AI → Agent) to use the Kubernetes AI agent",
|
|
472
|
+
);
|
|
453
473
|
});
|
|
454
474
|
|
|
455
475
|
it("negative control: the same Runner Connected is online and ready", async () => {
|
|
@@ -475,6 +495,8 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
475
495
|
it("negative control: a row that never heartbeated is 'never connected', not 'signed off'", async () => {
|
|
476
496
|
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
477
497
|
fakeRunner({
|
|
498
|
+
name: "office-runner",
|
|
499
|
+
hostInfo: {},
|
|
478
500
|
lastAlive: undefined,
|
|
479
501
|
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
480
502
|
}),
|
|
@@ -489,152 +511,51 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
489
511
|
expect(status.gaps[0]?.title).toBe("The Runner has never connected");
|
|
490
512
|
});
|
|
491
513
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
500
|
-
fakeRunner({
|
|
501
|
-
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
502
|
-
canRunRunbooks: true,
|
|
503
|
-
}),
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
507
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
508
|
-
cluster: fakeCluster(),
|
|
509
|
-
gates: READY_GATES,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
expect(gapCodes(status)).toEqual(["runner_offline"]);
|
|
513
|
-
const gap: KubernetesAiAccessGap = status.gaps[0]!;
|
|
514
|
-
expect(gap.title).toContain("cannot re-register");
|
|
515
|
-
expect(gap.description).toContain('"Runs Runbooks" is on');
|
|
516
|
-
expect(gap.nextStep).toContain(
|
|
517
|
-
'on Runner "kubernetes-agent/prod-us": turn off "Runs Runbooks"',
|
|
518
|
-
);
|
|
519
|
-
expect(gap.nextStep).toContain("delete the Runner");
|
|
520
|
-
/*
|
|
521
|
-
* Round four: deleting the Runner leaves this cluster unbound (the
|
|
522
|
-
* FK nulls the binding and registration never re-binds a cluster
|
|
523
|
-
* that had a Runner), so the delete path names its second step.
|
|
524
|
-
* Was "... — or delete the Runner, and the in-cluster Runner
|
|
525
|
-
* registers a fresh one within a minute."
|
|
526
|
-
*/
|
|
527
|
-
expect(gap.nextStep).toContain(
|
|
528
|
-
"select it on the cluster's AI page as its Runner",
|
|
529
|
-
);
|
|
530
|
-
expect(gap.nextStep).toContain("still bound to this cluster");
|
|
531
|
-
expect(gap.nextStep).not.toContain("reconnects within a minute");
|
|
532
|
-
expect(gap.nextStep).not.toContain("aiAccess.enabled=true");
|
|
533
|
-
expect(gap.blocks).toBe("both");
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
it("names assigned credentials, secrets and other clusters bound to it, counted for THIS cluster", async () => {
|
|
537
|
-
credentialCountSpy.mockResolvedValue(new PositiveNumber(1));
|
|
538
|
-
secretCountSpy.mockResolvedValue(new PositiveNumber(2));
|
|
539
|
-
boundClusterCountSpy.mockResolvedValue(new PositiveNumber(1));
|
|
540
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
541
|
-
fakeRunner({
|
|
542
|
-
lastAlive: OneUptimeDate.getSomeMinutesAgo(30),
|
|
543
|
-
}),
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
547
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
548
|
-
cluster: fakeCluster(),
|
|
549
|
-
gates: READY_GATES,
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
const gap: KubernetesAiAccessGap = status.gaps[0]!;
|
|
553
|
-
expect(gap.description).toContain("1 Runner credential(s) assigned");
|
|
554
|
-
expect(gap.description).toContain("2 runbook secret(s) assigned");
|
|
555
|
-
expect(gap.description).toContain(
|
|
556
|
-
"the AI access Runner of 1 other cluster(s)",
|
|
557
|
-
);
|
|
558
|
-
// "Runs Runbooks" is off on this row, so it is not named.
|
|
559
|
-
expect(gap.description).not.toContain("Runs Runbooks");
|
|
560
|
-
|
|
561
|
-
const clusterQuery: Record<string, unknown> = (
|
|
562
|
-
boundClusterCountSpy.mock.calls[0]![0] as {
|
|
563
|
-
query: Record<string, unknown>;
|
|
564
|
-
}
|
|
565
|
-
).query;
|
|
566
|
-
expect(clusterQuery["_id"]).toBeDefined();
|
|
567
|
-
expect(typeof clusterQuery["_id"]).not.toBe("string");
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it("negative control: a signed-off agent row with nothing extra keeps the reconnect wording", async () => {
|
|
571
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
572
|
-
fakeRunner({
|
|
573
|
-
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
574
|
-
}),
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
578
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
579
|
-
cluster: fakeCluster(),
|
|
580
|
-
gates: READY_GATES,
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
|
|
584
|
-
expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it("negative control: an online agent row is never counted", async () => {
|
|
588
|
-
jest
|
|
589
|
-
.spyOn(RunnerService, "findOneBy")
|
|
590
|
-
.mockResolvedValue(fakeRunner({ canRunRunbooks: true }));
|
|
514
|
+
it("negative control: a previous in-cluster Runner that never heartbeated is offline, not 'signed off'", async () => {
|
|
515
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
516
|
+
fakeRunner({
|
|
517
|
+
lastAlive: undefined,
|
|
518
|
+
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
591
521
|
|
|
522
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
592
523
|
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
593
524
|
cluster: fakeCluster(),
|
|
594
525
|
gates: READY_GATES,
|
|
595
526
|
});
|
|
596
527
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
602
|
-
fakeRunner({
|
|
603
|
-
name: "kubernetes-agent/prod-eu",
|
|
604
|
-
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
605
|
-
canRunRunbooks: true,
|
|
606
|
-
hostInfo: {
|
|
607
|
-
kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
|
|
608
|
-
},
|
|
609
|
-
}),
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
613
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
614
|
-
cluster: fakeCluster(),
|
|
615
|
-
gates: READY_GATES,
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
|
|
619
|
-
expect(credentialCountSpy).not.toHaveBeenCalled();
|
|
620
|
-
});
|
|
528
|
+
expect(status.gaps[0]?.title).toBe(
|
|
529
|
+
"The previous in-cluster Runner is offline",
|
|
530
|
+
);
|
|
531
|
+
});
|
|
621
532
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
533
|
+
/*
|
|
534
|
+
* An offline previous in-cluster Runner that holds more than the agent
|
|
535
|
+
* defaults could not re-register — but it no longer has to: the chart
|
|
536
|
+
* upgrade installs the AI agent, which needs none of it. So the status
|
|
537
|
+
* no longer counts what the row holds.
|
|
538
|
+
*/
|
|
539
|
+
it("never counts what an offline previous in-cluster Runner holds: the step is the chart upgrade", async () => {
|
|
540
|
+
credentialCountSpy.mockResolvedValue(new PositiveNumber(1));
|
|
541
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
542
|
+
fakeRunner({
|
|
543
|
+
connectionStatus: RunnerConnectionStatus.Disconnected,
|
|
544
|
+
canRunRunbooks: true,
|
|
545
|
+
}),
|
|
546
|
+
);
|
|
629
547
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
548
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
549
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
550
|
+
cluster: fakeCluster(),
|
|
551
|
+
gates: READY_GATES,
|
|
552
|
+
});
|
|
635
553
|
|
|
636
|
-
|
|
637
|
-
|
|
554
|
+
expect(gapCodes(status)).toEqual(["runner_offline"]);
|
|
555
|
+
expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
|
|
556
|
+
expect(credentialCountSpy).not.toHaveBeenCalled();
|
|
557
|
+
expect(secretCountSpy).not.toHaveBeenCalled();
|
|
558
|
+
expect(boundClusterCountSpy).not.toHaveBeenCalled();
|
|
638
559
|
});
|
|
639
560
|
|
|
640
561
|
it("still reports the sign-off once the last heartbeat has also aged out", async () => {
|
|
@@ -652,15 +573,21 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
652
573
|
});
|
|
653
574
|
|
|
654
575
|
// Signed off AND stale: the sign-off is the more precise explanation.
|
|
655
|
-
expect(status.gaps[0]?.title).toBe(
|
|
576
|
+
expect(status.gaps[0]?.title).toBe(
|
|
577
|
+
"The previous in-cluster Runner signed off",
|
|
578
|
+
);
|
|
656
579
|
expect(status.runner?.isOnline).toBe(false);
|
|
657
580
|
});
|
|
658
581
|
});
|
|
659
582
|
|
|
660
583
|
it("reports runner_offline (never connected vs stale) and keeps the Runner summary", async () => {
|
|
661
|
-
jest
|
|
662
|
-
|
|
663
|
-
|
|
584
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
585
|
+
fakeRunner({
|
|
586
|
+
name: "office-runner",
|
|
587
|
+
hostInfo: {},
|
|
588
|
+
lastAlive: undefined,
|
|
589
|
+
}),
|
|
590
|
+
);
|
|
664
591
|
|
|
665
592
|
const never: KubernetesClusterAiAccessStatus =
|
|
666
593
|
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
@@ -668,11 +595,10 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
668
595
|
gates: READY_GATES,
|
|
669
596
|
});
|
|
670
597
|
|
|
671
|
-
expect(gapCodes(never)).
|
|
598
|
+
expect(gapCodes(never)).toContain("runner_offline");
|
|
672
599
|
expect(never.gaps[0]?.title).toContain("never connected");
|
|
673
600
|
expect(never.runner?.isOnline).toBe(false);
|
|
674
601
|
|
|
675
|
-
jest.restoreAllMocks();
|
|
676
602
|
jest
|
|
677
603
|
.spyOn(RunnerService, "findOneBy")
|
|
678
604
|
.mockResolvedValue(
|
|
@@ -686,8 +612,11 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
686
612
|
});
|
|
687
613
|
|
|
688
614
|
expect(gapCodes(stale)).toEqual(["runner_offline"]);
|
|
689
|
-
expect(stale.gaps[0]?.title).toBe(
|
|
690
|
-
|
|
615
|
+
expect(stale.gaps[0]?.title).toBe(
|
|
616
|
+
"The previous in-cluster Runner is offline",
|
|
617
|
+
);
|
|
618
|
+
expect(stale.gaps[0]?.description).toContain("last reported in at");
|
|
619
|
+
expect(stale.runner?.name).toBe("kubernetes-agent/prod-us");
|
|
691
620
|
});
|
|
692
621
|
|
|
693
622
|
it("reports runner_ai_commands_disabled when the capability is off", async () => {
|
|
@@ -773,7 +702,14 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
773
702
|
* exists only for the Runner whose posture names THIS cluster.
|
|
774
703
|
*/
|
|
775
704
|
describe("in-cluster access is only the in-cluster Runner of this cluster", () => {
|
|
776
|
-
|
|
705
|
+
/*
|
|
706
|
+
* A kubernetes-agent Runner of ANOTHER cluster bound here (the binding
|
|
707
|
+
* guard refuses it for new writes; older rows may still hold one) is
|
|
708
|
+
* never an access target: its ServiceAccount reaches only its own
|
|
709
|
+
* cluster, and it is neither the advanced Runner an operator chose nor
|
|
710
|
+
* this cluster's previous in-cluster Runner.
|
|
711
|
+
*/
|
|
712
|
+
it("never uses the in-cluster Runner of a different cluster: without an agent nothing reaches this one", async () => {
|
|
777
713
|
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
778
714
|
fakeRunner({
|
|
779
715
|
name: "kubernetes-agent/prod-eu",
|
|
@@ -793,19 +729,39 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
793
729
|
gates: READY_GATES,
|
|
794
730
|
});
|
|
795
731
|
|
|
796
|
-
expect(gapCodes(status)).toEqual(["
|
|
732
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
797
733
|
expect(status.gaps[0]?.blocks).toBe("both");
|
|
798
|
-
expect(status.gaps[0]?.title).toContain("different cluster");
|
|
799
|
-
expect(status.gaps[0]?.description).toContain('"prod-eu"');
|
|
800
|
-
expect(status.gaps[0]?.description).toContain('"prod-us"');
|
|
801
|
-
expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
|
|
802
734
|
expect(status.accessMethod).toBe("none");
|
|
803
735
|
expect(status.credentialId).toBeUndefined();
|
|
736
|
+
expect(status.runner).toBeNull();
|
|
804
737
|
expect(status.isInvestigationReady).toBe(false);
|
|
805
738
|
expect(status.isRemediationReady).toBe(false);
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it("with this cluster's AI agent, the other cluster's bound Runner is ignored and the agent serves", async () => {
|
|
742
|
+
agentLookup.mockResolvedValue(fakeAgent());
|
|
743
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
744
|
+
fakeRunner({
|
|
745
|
+
name: "kubernetes-agent/prod-eu",
|
|
746
|
+
hostInfo: {
|
|
747
|
+
kubernetes: {
|
|
748
|
+
inCluster: true,
|
|
749
|
+
allowWrites: true,
|
|
750
|
+
clusterIdentifier: "prod-eu",
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
757
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
758
|
+
cluster: fakeCluster(),
|
|
759
|
+
gates: READY_GATES,
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
expect(status.gaps).toEqual([]);
|
|
763
|
+
expect(status.runner?.kind).toBe("ai_agent");
|
|
764
|
+
expect(status.runner?.id).toBe(AGENT_ID.toString());
|
|
809
765
|
});
|
|
810
766
|
|
|
811
767
|
it("refuses a pod Runner that never said which cluster it runs in", async () => {
|
|
@@ -823,7 +779,12 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
823
779
|
});
|
|
824
780
|
|
|
825
781
|
expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
|
|
826
|
-
expect(status.gaps[0]?.title).
|
|
782
|
+
expect(status.gaps[0]?.title).toBe(
|
|
783
|
+
"The bound Runner did not report which cluster it runs in",
|
|
784
|
+
);
|
|
785
|
+
expect(status.gaps[0]?.description).toBe(
|
|
786
|
+
'Runner "pod-runner" reports that it runs inside a Kubernetes cluster but not which one, so OneUptime AI cannot tell whether that is this cluster.',
|
|
787
|
+
);
|
|
827
788
|
expect(status.accessMethod).toBe("none");
|
|
828
789
|
expect(status.isInvestigationReady).toBe(false);
|
|
829
790
|
});
|
|
@@ -847,7 +808,8 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
847
808
|
gates: READY_GATES,
|
|
848
809
|
});
|
|
849
810
|
|
|
850
|
-
|
|
811
|
+
// A cluster without an identifier has no previous in-cluster Runner.
|
|
812
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
851
813
|
expect(status.accessMethod).toBe("none");
|
|
852
814
|
});
|
|
853
815
|
|
|
@@ -876,16 +838,16 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
876
838
|
});
|
|
877
839
|
|
|
878
840
|
/*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
* instead of promising access that would fail on every command.
|
|
841
|
+
* A kubernetes-agent Runner is minted and re-keyed with the telemetry
|
|
842
|
+
* ingestion key, so it is never handed credential material. Such a row
|
|
843
|
+
* of another cluster — by its name marker, its posture, or a case
|
|
844
|
+
* variant of the marker — is not an access target at all now, so the
|
|
845
|
+
* credential selected with it is never even read, let alone used.
|
|
885
846
|
*/
|
|
886
|
-
it(
|
|
887
|
-
|
|
888
|
-
|
|
847
|
+
it.each([
|
|
848
|
+
[
|
|
849
|
+
"another cluster's agent row",
|
|
850
|
+
{
|
|
889
851
|
name: "kubernetes-agent/prod-eu",
|
|
890
852
|
hostInfo: {
|
|
891
853
|
kubernetes: {
|
|
@@ -894,72 +856,17 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
894
856
|
clusterIdentifier: "prod-eu",
|
|
895
857
|
},
|
|
896
858
|
},
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
910
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
911
|
-
cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
912
|
-
gates: READY_GATES,
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
|
|
916
|
-
expect(status.gaps[0]?.blocks).toBe("both");
|
|
917
|
-
expect(status.gaps[0]?.description).toContain(
|
|
918
|
-
'the in-cluster Runner of cluster "prod-eu"',
|
|
919
|
-
);
|
|
920
|
-
expect(status.gaps[0]?.nextStep).toContain(
|
|
921
|
-
"Create a Runner under Project Settings → Runners",
|
|
922
|
-
);
|
|
923
|
-
expect(status.accessMethod).toBe("none");
|
|
924
|
-
expect(status.credentialId).toBeUndefined();
|
|
925
|
-
expect(status.isInvestigationReady).toBe(false);
|
|
926
|
-
expect(status.isRemediationReady).toBe(false);
|
|
927
|
-
// The credential's details are moot: an agent row never carries one.
|
|
928
|
-
expect(credentialLookup).not.toHaveBeenCalled();
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
it("keyed on the server-owned NAME: an agent row whose heartbeat dropped its posture still cannot carry a credential", async () => {
|
|
932
|
-
jest
|
|
933
|
-
.spyOn(RunnerService, "findOneBy")
|
|
934
|
-
.mockResolvedValue(
|
|
935
|
-
fakeRunner({ name: "kubernetes-agent/prod-eu", hostInfo: {} }),
|
|
936
|
-
);
|
|
937
|
-
jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
|
|
938
|
-
id: CREDENTIAL_ID,
|
|
939
|
-
_id: CREDENTIAL_ID.toString(),
|
|
940
|
-
name: "prod-us kubeconfig",
|
|
941
|
-
credentialType: "Kubernetes",
|
|
942
|
-
runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
|
|
943
|
-
} as unknown as RunbookCredential);
|
|
944
|
-
|
|
945
|
-
const status: KubernetesClusterAiAccessStatus =
|
|
946
|
-
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
947
|
-
cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
948
|
-
gates: READY_GATES,
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
|
|
952
|
-
expect(status.accessMethod).toBe("none");
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
/*
|
|
956
|
-
* An agent row renamed (only root can rename one now) keeps its agent
|
|
957
|
-
* posture, and the posture alone marks it: the one "is an agent row"
|
|
958
|
-
* rule fails closed on the name OR the posture.
|
|
959
|
-
*/
|
|
960
|
-
it("keyed on the name OR the posture: a row with another cluster's agent posture but no marker cannot carry a credential", async () => {
|
|
961
|
-
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
962
|
-
fakeRunner({
|
|
859
|
+
},
|
|
860
|
+
' (it reports cluster "prod-eu")',
|
|
861
|
+
],
|
|
862
|
+
[
|
|
863
|
+
"an agent row whose heartbeat dropped its posture (the server-owned NAME)",
|
|
864
|
+
{ name: "kubernetes-agent/prod-eu", hostInfo: {} },
|
|
865
|
+
"",
|
|
866
|
+
],
|
|
867
|
+
[
|
|
868
|
+
"a row with another cluster's agent posture but no marker (name OR posture)",
|
|
869
|
+
{
|
|
963
870
|
name: "prod-eu in-cluster runner",
|
|
964
871
|
hostInfo: {
|
|
965
872
|
kubernetes: {
|
|
@@ -968,42 +875,57 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
968
875
|
clusterIdentifier: "prod-eu",
|
|
969
876
|
},
|
|
970
877
|
},
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
name: "prod-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
878
|
+
},
|
|
879
|
+
' (it reports cluster "prod-eu")',
|
|
880
|
+
],
|
|
881
|
+
[
|
|
882
|
+
"a case variant of the marker",
|
|
883
|
+
{ name: "Kubernetes-Agent/prod-eu", hostInfo: {} },
|
|
884
|
+
"",
|
|
885
|
+
],
|
|
886
|
+
])(
|
|
887
|
+
"never uses a credential with %s: it is no access target",
|
|
888
|
+
async (
|
|
889
|
+
_label: string,
|
|
890
|
+
runner: Record<string, unknown>,
|
|
891
|
+
reported: string,
|
|
892
|
+
) => {
|
|
893
|
+
jest
|
|
894
|
+
.spyOn(RunnerService, "findOneBy")
|
|
895
|
+
.mockResolvedValue(fakeRunner(runner));
|
|
896
|
+
const credentialLookup: jest.SpyInstance = jest
|
|
897
|
+
.spyOn(RunbookCredentialService, "findOneBy")
|
|
898
|
+
.mockResolvedValue({
|
|
899
|
+
id: CREDENTIAL_ID,
|
|
900
|
+
_id: CREDENTIAL_ID.toString(),
|
|
901
|
+
name: "prod-us kubeconfig",
|
|
902
|
+
credentialType: "Kubernetes",
|
|
903
|
+
runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
|
|
904
|
+
} as unknown as RunbookCredential);
|
|
986
905
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
906
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
907
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
908
|
+
cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
909
|
+
gates: READY_GATES,
|
|
910
|
+
});
|
|
991
911
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
912
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
913
|
+
expect(gapCodes(status)).not.toContain("credential_on_agent_runner");
|
|
914
|
+
/*
|
|
915
|
+
* The status still says a Runner is selected and why it is not
|
|
916
|
+
* used, rather than reading as if nothing were bound.
|
|
917
|
+
*/
|
|
918
|
+
expect(status.gaps[0]?.description).toContain(
|
|
919
|
+
`Runner "${String(runner["name"])}" is still selected for this cluster, but it is not this cluster's in-cluster Runner${reported}, so OneUptime AI does not use it.`,
|
|
997
920
|
);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
});
|
|
921
|
+
expect(status.gaps[0]?.nextStep).toBe(AI_AGENT_NOT_CONNECTED_NEXT_STEP);
|
|
922
|
+
expect(status.runner).toBeNull();
|
|
923
|
+
expect(status.accessMethod).toBe("none");
|
|
924
|
+
expect(status.credentialId).toBeUndefined();
|
|
925
|
+
expect(status.isInvestigationReady).toBe(false);
|
|
926
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
927
|
+
},
|
|
928
|
+
);
|
|
1007
929
|
|
|
1008
930
|
/*
|
|
1009
931
|
* Before round two this fixture reported a cluster identity too (an
|
|
@@ -1093,7 +1015,7 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
1093
1015
|
expect(status.accessMethod).toBe("none");
|
|
1094
1016
|
});
|
|
1095
1017
|
|
|
1096
|
-
it("
|
|
1018
|
+
it("a row named for this cluster whose posture names another is not this cluster's previous Runner", async () => {
|
|
1097
1019
|
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
1098
1020
|
fakeRunner({
|
|
1099
1021
|
hostInfo: {
|
|
@@ -1112,16 +1034,13 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
1112
1034
|
gates: READY_GATES,
|
|
1113
1035
|
});
|
|
1114
1036
|
|
|
1115
|
-
expect(gapCodes(status)).toEqual([
|
|
1116
|
-
"runner_cluster_mismatch",
|
|
1117
|
-
"remediation_write_access_missing",
|
|
1118
|
-
]);
|
|
1037
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
1119
1038
|
expect(status.isInvestigationReady).toBe(false);
|
|
1120
1039
|
expect(status.isRemediationReady).toBe(false);
|
|
1121
1040
|
});
|
|
1122
1041
|
});
|
|
1123
1042
|
|
|
1124
|
-
it("blocks only remediation when the in-cluster Runner is read-only", async () => {
|
|
1043
|
+
it("blocks only remediation when the previous in-cluster Runner is read-only, and says to upgrade to the AI agent", async () => {
|
|
1125
1044
|
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
1126
1045
|
fakeRunner({
|
|
1127
1046
|
hostInfo: {
|
|
@@ -1142,36 +1061,66 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
1142
1061
|
|
|
1143
1062
|
expect(gapCodes(status)).toEqual(["remediation_write_access_missing"]);
|
|
1144
1063
|
expect(status.gaps[0]?.blocks).toBe("remediation");
|
|
1064
|
+
expect(status.gaps[0]?.title).toBe(
|
|
1065
|
+
"The previous in-cluster Runner is read-only",
|
|
1066
|
+
);
|
|
1067
|
+
expect(status.gaps[0]?.nextStep).toBe(
|
|
1068
|
+
LEGACY_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
|
|
1069
|
+
);
|
|
1145
1070
|
expect(status.gaps[0]?.nextStep).toContain(
|
|
1146
|
-
"
|
|
1071
|
+
"--set aiAgent.remediation.enabled=true",
|
|
1147
1072
|
);
|
|
1148
|
-
expect(status.gaps[0]?.nextStep).toBe(REMEDIATION_WRITE_ACCESS_NEXT_STEP);
|
|
1149
1073
|
expect(status.isInvestigationReady).toBe(true);
|
|
1150
1074
|
expect(status.isRemediationReady).toBe(false);
|
|
1151
1075
|
});
|
|
1152
1076
|
|
|
1077
|
+
it("an in-cluster pod Runner outside the chart that cannot write is told to use its own environment", async () => {
|
|
1078
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
|
|
1079
|
+
fakeRunner({
|
|
1080
|
+
name: "pod-runner",
|
|
1081
|
+
hostInfo: { kubernetes: { inCluster: true, allowWrites: false } },
|
|
1082
|
+
}),
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1086
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1087
|
+
cluster: fakeCluster(),
|
|
1088
|
+
gates: READY_GATES,
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
const gap: KubernetesAiAccessGap | undefined = status.gaps.find(
|
|
1092
|
+
(candidate: KubernetesAiAccessGap) => {
|
|
1093
|
+
return candidate.code === "remediation_write_access_missing";
|
|
1094
|
+
},
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
expect(gap?.title).toBe("The Runner is read-only");
|
|
1098
|
+
expect(gap?.nextStep).toBe(
|
|
1099
|
+
CREDENTIAL_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
|
|
1100
|
+
);
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1153
1103
|
/*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* cluster
|
|
1157
|
-
* write-access section (the complete commands) and names the two values
|
|
1158
|
-
* that bound where the write role reaches.
|
|
1104
|
+
* The read-only gap names the value that grants writes and the two that
|
|
1105
|
+
* bound where the write role reaches; the complete command lives on the
|
|
1106
|
+
* cluster's AI agent page, which it names (never "this page").
|
|
1159
1107
|
*/
|
|
1160
|
-
it("points the read-only gap at the AI page
|
|
1108
|
+
it("points the read-only gap at the AI agent page and the aiAgent values that scope it", () => {
|
|
1161
1109
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
|
|
1162
|
-
"--set
|
|
1110
|
+
"--set aiAgent.remediation.enabled=true",
|
|
1163
1111
|
);
|
|
1164
1112
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
|
|
1165
|
-
|
|
1113
|
+
"the cluster's AI agent page (AI → Agent)",
|
|
1166
1114
|
);
|
|
1167
1115
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
|
|
1168
|
-
"
|
|
1116
|
+
"aiAgent.remediation.namespaces",
|
|
1169
1117
|
);
|
|
1170
1118
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
|
|
1171
|
-
"
|
|
1119
|
+
"aiAgent.remediation.nodeOperations=false",
|
|
1172
1120
|
);
|
|
1173
1121
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain("cluster-wide");
|
|
1174
1122
|
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).not.toContain("this page");
|
|
1123
|
+
expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).not.toContain("aiAccess");
|
|
1175
1124
|
});
|
|
1176
1125
|
|
|
1177
1126
|
it("does not raise the read-only gap when remediation is off", async () => {
|
|
@@ -1225,19 +1174,139 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
|
|
|
1225
1174
|
isAutoRemediationEnabled: false,
|
|
1226
1175
|
isAiCommandExecutionEnabled: false,
|
|
1227
1176
|
hasLlmProvider: false,
|
|
1177
|
+
aiBalanceBlocker: "This project's AI credit balance is used up.",
|
|
1228
1178
|
},
|
|
1229
1179
|
});
|
|
1230
1180
|
|
|
1181
|
+
/*
|
|
1182
|
+
* The previous in-cluster Runner is a cluster target the command
|
|
1183
|
+
* execution opt-in no longer gates (the cluster's Fixes setting and
|
|
1184
|
+
* the chart's write RBAC do), so there is no command-execution gap.
|
|
1185
|
+
*/
|
|
1231
1186
|
expect(gapCodes(status)).toEqual([
|
|
1232
1187
|
"project_ai_disabled",
|
|
1233
1188
|
"llm_provider_missing",
|
|
1189
|
+
"ai_balance_insufficient",
|
|
1234
1190
|
"project_auto_remediation_disabled",
|
|
1235
|
-
"project_ai_command_execution_disabled",
|
|
1236
1191
|
]);
|
|
1237
1192
|
expect(status.isInvestigationReady).toBe(false);
|
|
1238
1193
|
expect(status.isRemediationReady).toBe(false);
|
|
1239
1194
|
});
|
|
1240
1195
|
|
|
1196
|
+
it("ai_balance_insufficient carries the reason, blocks both and says where credits are added", async () => {
|
|
1197
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
|
|
1198
|
+
|
|
1199
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1200
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1201
|
+
cluster: fakeCluster(),
|
|
1202
|
+
gates: {
|
|
1203
|
+
...READY_GATES,
|
|
1204
|
+
aiBalanceBlocker: "This project's AI credit balance is used up.",
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
expect(gapCodes(status)).toEqual(["ai_balance_insufficient"]);
|
|
1209
|
+
expect(status.gaps[0]).toEqual({
|
|
1210
|
+
code: "ai_balance_insufficient",
|
|
1211
|
+
title: "The project is out of AI credits",
|
|
1212
|
+
description: "This project's AI credit balance is used up.",
|
|
1213
|
+
nextStep:
|
|
1214
|
+
"Add AI credits under Project Settings → AI Credits (or enable auto-recharge).",
|
|
1215
|
+
blocks: "both",
|
|
1216
|
+
});
|
|
1217
|
+
expect(status.gaps[0]?.nextStep).toBe(AI_BALANCE_INSUFFICIENT_NEXT_STEP);
|
|
1218
|
+
expect(status.isInvestigationReady).toBe(false);
|
|
1219
|
+
expect(status.isRemediationReady).toBe(false);
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
it("negative control: no blocker (null or absent) is no balance gap", async () => {
|
|
1223
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
|
|
1224
|
+
|
|
1225
|
+
for (const aiBalanceBlocker of [null, undefined, ""]) {
|
|
1226
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1227
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1228
|
+
cluster: fakeCluster(),
|
|
1229
|
+
gates: { ...READY_GATES, aiBalanceBlocker },
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
expect(gapCodes(status)).toEqual([]);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
describe("the project's command-execution opt-in gates only an advanced Runner", () => {
|
|
1237
|
+
const CLOSED_OPT_IN: KubernetesClusterAiAccessProjectGates = {
|
|
1238
|
+
...READY_GATES,
|
|
1239
|
+
isAiCommandExecutionEnabled: false,
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
it("an advanced Runner with a credential: project_ai_command_execution_disabled blocks remediation", async () => {
|
|
1243
|
+
jest
|
|
1244
|
+
.spyOn(RunnerService, "findOneBy")
|
|
1245
|
+
.mockResolvedValue(
|
|
1246
|
+
fakeRunner({ name: "office-runner", hostInfo: { hostname: "x" } }),
|
|
1247
|
+
);
|
|
1248
|
+
jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
|
|
1249
|
+
id: CREDENTIAL_ID,
|
|
1250
|
+
_id: CREDENTIAL_ID.toString(),
|
|
1251
|
+
name: "prod kubeconfig",
|
|
1252
|
+
credentialType: "Kubernetes",
|
|
1253
|
+
runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
|
|
1254
|
+
} as unknown as RunbookCredential);
|
|
1255
|
+
|
|
1256
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1257
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1258
|
+
cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
1259
|
+
gates: CLOSED_OPT_IN,
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
expect(gapCodes(status)).toEqual([
|
|
1263
|
+
"project_ai_command_execution_disabled",
|
|
1264
|
+
]);
|
|
1265
|
+
expect(status.gaps[0]?.blocks).toBe("remediation");
|
|
1266
|
+
expect(status.gaps[0]?.nextStep).toBe(
|
|
1267
|
+
'Turn on "Enable AI Command Execution" under Project Settings → AI Features.',
|
|
1268
|
+
);
|
|
1269
|
+
expect(status.isInvestigationReady).toBe(true);
|
|
1270
|
+
expect(status.isRemediationReady).toBe(false);
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
it("the Kubernetes AI agent: no command-execution gap, remediation ready", async () => {
|
|
1274
|
+
agentLookup.mockResolvedValue(fakeAgent());
|
|
1275
|
+
|
|
1276
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1277
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1278
|
+
cluster: fakeCluster({ aiAccessRunnerId: undefined }),
|
|
1279
|
+
gates: CLOSED_OPT_IN,
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
expect(gapCodes(status)).toEqual([]);
|
|
1283
|
+
expect(status.isRemediationReady).toBe(true);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
it("the previous in-cluster Runner: no command-execution gap either", async () => {
|
|
1287
|
+
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
|
|
1288
|
+
|
|
1289
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1290
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1291
|
+
cluster: fakeCluster(),
|
|
1292
|
+
gates: CLOSED_OPT_IN,
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
expect(gapCodes(status)).toEqual([]);
|
|
1296
|
+
expect(status.isRemediationReady).toBe(true);
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
it("nothing connected: no command-execution gap, the connection gap says it all", async () => {
|
|
1300
|
+
const status: KubernetesClusterAiAccessStatus =
|
|
1301
|
+
await KubernetesClusterAiAccessService.getStatusForClusterModel({
|
|
1302
|
+
cluster: fakeCluster({ aiAccessRunnerId: undefined }),
|
|
1303
|
+
gates: CLOSED_OPT_IN,
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1241
1310
|
it("is remediation-ready in BypassApproval mode with a write-capable Runner and reports the mode verbatim", async () => {
|
|
1242
1311
|
jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
|
|
1243
1312
|
|
|
@@ -1412,6 +1481,7 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
|
|
|
1412
1481
|
jest
|
|
1413
1482
|
.spyOn(LlmProviderService, "getLLMProviderForProject")
|
|
1414
1483
|
.mockResolvedValue({ id: ObjectID.generate() } as unknown as LlmProvider);
|
|
1484
|
+
jest.spyOn(AIService, "getAiBalanceBlocker").mockResolvedValue(null);
|
|
1415
1485
|
|
|
1416
1486
|
const gates: KubernetesClusterAiAccessProjectGates =
|
|
1417
1487
|
await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
|
|
@@ -1421,10 +1491,13 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
|
|
|
1421
1491
|
isAutoRemediationEnabled: true,
|
|
1422
1492
|
isAiCommandExecutionEnabled: false,
|
|
1423
1493
|
hasLlmProvider: true,
|
|
1494
|
+
aiBalanceBlocker: null,
|
|
1495
|
+
// Absent reads as off: the investigation opt-ins count only when true.
|
|
1496
|
+
automaticInvestigation: { incidents: false, alerts: false },
|
|
1424
1497
|
});
|
|
1425
1498
|
});
|
|
1426
1499
|
|
|
1427
|
-
it("treats a provider lookup failure as no provider", async () => {
|
|
1500
|
+
it("treats a provider lookup failure as no provider, and does not check the balance without one", async () => {
|
|
1428
1501
|
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
1429
1502
|
return undefined;
|
|
1430
1503
|
});
|
|
@@ -1434,12 +1507,85 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
|
|
|
1434
1507
|
jest
|
|
1435
1508
|
.spyOn(LlmProviderService, "getLLMProviderForProject")
|
|
1436
1509
|
.mockRejectedValue(new Error("boom"));
|
|
1510
|
+
const blocker: jest.SpyInstance = jest
|
|
1511
|
+
.spyOn(AIService, "getAiBalanceBlocker")
|
|
1512
|
+
.mockResolvedValue("out of credits");
|
|
1437
1513
|
|
|
1438
1514
|
const gates: KubernetesClusterAiAccessProjectGates =
|
|
1439
1515
|
await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
|
|
1440
1516
|
|
|
1441
1517
|
expect(gates.hasLlmProvider).toBe(false);
|
|
1442
1518
|
expect(gates.isAiCommandExecutionEnabled).toBe(true);
|
|
1519
|
+
// Unknown, so unsaid: not a second (failing) provider lookup.
|
|
1520
|
+
expect(blocker).not.toHaveBeenCalled();
|
|
1521
|
+
expect(gates.aiBalanceBlocker).toBeNull();
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it("reads the project's automatic-investigation opt-ins (exactly true) for the AI agent page", async () => {
|
|
1525
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
|
|
1526
|
+
enableAutomaticIncidentInvestigation: true,
|
|
1527
|
+
enableAutomaticAlertInvestigation: false,
|
|
1528
|
+
} as unknown as Project);
|
|
1529
|
+
jest
|
|
1530
|
+
.spyOn(LlmProviderService, "getLLMProviderForProject")
|
|
1531
|
+
.mockResolvedValue(null);
|
|
1532
|
+
jest.spyOn(AIService, "getAiBalanceBlocker").mockResolvedValue(null);
|
|
1533
|
+
|
|
1534
|
+
const gates: KubernetesClusterAiAccessProjectGates =
|
|
1535
|
+
await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
|
|
1536
|
+
|
|
1537
|
+
expect(gates.automaticInvestigation).toEqual({
|
|
1538
|
+
incidents: true,
|
|
1539
|
+
alerts: false,
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
it("asks AIService for the AI balance blocker with the provider it already resolved, and carries its reason", async () => {
|
|
1544
|
+
const provider: LlmProvider = {
|
|
1545
|
+
id: ObjectID.generate(),
|
|
1546
|
+
isGlobalLlm: true,
|
|
1547
|
+
costPerMillionTokensInUSDCents: 100,
|
|
1548
|
+
} as unknown as LlmProvider;
|
|
1549
|
+
jest
|
|
1550
|
+
.spyOn(ProjectService, "findOneById")
|
|
1551
|
+
.mockResolvedValue({} as unknown as Project);
|
|
1552
|
+
const providerLookup: jest.SpyInstance = jest
|
|
1553
|
+
.spyOn(LlmProviderService, "getLLMProviderForProject")
|
|
1554
|
+
.mockResolvedValue(provider);
|
|
1555
|
+
const blocker: jest.SpyInstance = jest
|
|
1556
|
+
.spyOn(AIService, "getAiBalanceBlocker")
|
|
1557
|
+
.mockResolvedValue("out of credits");
|
|
1558
|
+
|
|
1559
|
+
const gates: KubernetesClusterAiAccessProjectGates =
|
|
1560
|
+
await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
|
|
1561
|
+
|
|
1562
|
+
expect(blocker).toHaveBeenCalledWith({
|
|
1563
|
+
projectId: PROJECT_ID,
|
|
1564
|
+
llmProvider: provider,
|
|
1565
|
+
});
|
|
1566
|
+
expect(gates.aiBalanceBlocker).toBe("out of credits");
|
|
1567
|
+
expect(providerLookup).toHaveBeenCalledTimes(1);
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
it("treats a balance check that fails as no blocker (the AI call still refuses on its own)", async () => {
|
|
1571
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
1572
|
+
return undefined;
|
|
1573
|
+
});
|
|
1574
|
+
jest
|
|
1575
|
+
.spyOn(ProjectService, "findOneById")
|
|
1576
|
+
.mockResolvedValue({} as unknown as Project);
|
|
1577
|
+
jest
|
|
1578
|
+
.spyOn(LlmProviderService, "getLLMProviderForProject")
|
|
1579
|
+
.mockResolvedValue({ id: ObjectID.generate() } as unknown as LlmProvider);
|
|
1580
|
+
jest
|
|
1581
|
+
.spyOn(AIService, "getAiBalanceBlocker")
|
|
1582
|
+
.mockRejectedValue(new Error("db down"));
|
|
1583
|
+
|
|
1584
|
+
const gates: KubernetesClusterAiAccessProjectGates =
|
|
1585
|
+
await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
|
|
1586
|
+
|
|
1587
|
+
expect(gates.aiBalanceBlocker).toBeNull();
|
|
1588
|
+
expect(gates.hasLlmProvider).toBe(true);
|
|
1443
1589
|
});
|
|
1444
1590
|
});
|
|
1445
1591
|
|
|
@@ -1608,6 +1754,10 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
1608
1754
|
.mockImplementation(async (args: unknown): Promise<void> => {
|
|
1609
1755
|
feedItems.push(args as Record<string, unknown>);
|
|
1610
1756
|
});
|
|
1757
|
+
// No Kubernetes AI agent unless a test says otherwise.
|
|
1758
|
+
jest
|
|
1759
|
+
.spyOn(KubernetesAiAgentService, "findForCluster")
|
|
1760
|
+
.mockResolvedValue(null);
|
|
1611
1761
|
});
|
|
1612
1762
|
|
|
1613
1763
|
afterEach(() => {
|
|
@@ -2672,7 +2822,11 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
2672
2822
|
"the binding was cleared by an operator, or the Runner it was bound to was deleted",
|
|
2673
2823
|
);
|
|
2674
2824
|
expect(feed).toContain("no AI switch was changed");
|
|
2675
|
-
|
|
2825
|
+
// The way back is the AI agent, not selecting this Runner.
|
|
2826
|
+
expect(feed).toContain(
|
|
2827
|
+
"Upgrade the Kubernetes agent chart to use the Kubernetes AI agent instead.",
|
|
2828
|
+
);
|
|
2829
|
+
expect(feed).not.toContain("Select **kubernetes-agent/prod-us**");
|
|
2676
2830
|
});
|
|
2677
2831
|
|
|
2678
2832
|
it("keeps an operator's enabled switches exactly as they are, too", async () => {
|
|
@@ -2696,7 +2850,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
2696
2850
|
expect(clusterUpdates).toHaveLength(0);
|
|
2697
2851
|
});
|
|
2698
2852
|
|
|
2699
|
-
it("afterwards, the cluster's status points at
|
|
2853
|
+
it("afterwards, the cluster's status points at installing the AI agent, not at selecting the Runner", async () => {
|
|
2700
2854
|
jest
|
|
2701
2855
|
.spyOn(KubernetesClusterService, "findOneBy")
|
|
2702
2856
|
.mockResolvedValue(clusterAfterRunnerDelete());
|
|
@@ -2708,7 +2862,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
2708
2862
|
posture: { allowWrites: true },
|
|
2709
2863
|
});
|
|
2710
2864
|
|
|
2711
|
-
// The row registration just created
|
|
2865
|
+
// The row registration just created exists, but nothing binds it.
|
|
2712
2866
|
mockRunnerLookups({ byName: createdRunner });
|
|
2713
2867
|
|
|
2714
2868
|
const status: KubernetesClusterAiAccessStatus =
|
|
@@ -2717,16 +2871,15 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
2717
2871
|
gates: READY_GATES,
|
|
2718
2872
|
});
|
|
2719
2873
|
|
|
2720
|
-
expect(gapCodes(status)).toContain("
|
|
2874
|
+
expect(gapCodes(status)).toContain("ai_agent_not_connected");
|
|
2875
|
+
expect(gapCodes(status)).not.toContain("no_runner_bound");
|
|
2721
2876
|
const gap: KubernetesAiAccessGap = status.gaps.find(
|
|
2722
2877
|
(candidate: KubernetesAiAccessGap) => {
|
|
2723
|
-
return candidate.code === "
|
|
2878
|
+
return candidate.code === "ai_agent_not_connected";
|
|
2724
2879
|
},
|
|
2725
2880
|
)!;
|
|
2726
|
-
expect(gap.nextStep).toContain(
|
|
2727
|
-
|
|
2728
|
-
);
|
|
2729
|
-
expect(gap.nextStep).not.toContain("aiAccess.enabled=true");
|
|
2881
|
+
expect(gap.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
|
|
2882
|
+
expect(gap.nextStep).not.toContain("Select the kubernetes-agent Runner");
|
|
2730
2883
|
});
|
|
2731
2884
|
|
|
2732
2885
|
it("negative control: without either marker (and no other history) the same cluster still first-binds", async () => {
|
|
@@ -3034,11 +3187,11 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
3034
3187
|
expect(thrown).toBeInstanceOf(ForbiddenException);
|
|
3035
3188
|
const message: string = (thrown as Error).message;
|
|
3036
3189
|
expect(message).toContain(holding.expectInMessage);
|
|
3037
|
-
|
|
3038
|
-
// Round four: the delete path names its second step.
|
|
3190
|
+
// The chart upgrade to the AI agent comes first: it needs no Runner.
|
|
3039
3191
|
expect(message).toContain(
|
|
3040
|
-
"
|
|
3192
|
+
"Upgrade the Kubernetes agent chart: its Kubernetes AI agent replaces this in-cluster Runner.",
|
|
3041
3193
|
);
|
|
3194
|
+
expect(message).not.toContain("select it on the cluster's AI page");
|
|
3042
3195
|
expect(message).not.toContain(CURRENT_KEY);
|
|
3043
3196
|
|
|
3044
3197
|
expect(runnerUpdates).toHaveLength(0);
|
|
@@ -3519,11 +3672,15 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
3519
3672
|
isTransientKubernetesAgentRegistrationRefusal(refusal.reason),
|
|
3520
3673
|
).toBe(false);
|
|
3521
3674
|
expect(refusal.retryAfterSeconds).toBeUndefined();
|
|
3675
|
+
// The chart upgrade to the AI agent leads: it needs no Runner at all.
|
|
3522
3676
|
expect(
|
|
3523
3677
|
refusal.message.startsWith(
|
|
3524
|
-
|
|
3678
|
+
"Upgrade the Kubernetes agent chart: its Kubernetes AI agent replaces this in-cluster Runner.",
|
|
3525
3679
|
),
|
|
3526
3680
|
).toBe(true);
|
|
3681
|
+
expect(refusal.message).toContain(
|
|
3682
|
+
'Or, under Project Settings → Runners, on Runner "kubernetes-agent/prod-us": turn off "Runs Runbooks"',
|
|
3683
|
+
);
|
|
3527
3684
|
// Only what it actually holds is asked for.
|
|
3528
3685
|
expect(refusal.message).not.toContain('turn off "Runs AI Code Fixes"');
|
|
3529
3686
|
expect(refusal.message).not.toContain("unassign");
|
|
@@ -3566,11 +3723,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
3566
3723
|
);
|
|
3567
3724
|
|
|
3568
3725
|
const head: string = refusal.message.slice(0, 500);
|
|
3569
|
-
expect(head).toContain("
|
|
3570
|
-
// Round four: the delete path's second step is part of the instruction.
|
|
3571
|
-
expect(head).toContain(
|
|
3572
|
-
"select it on the cluster's AI page as its Runner",
|
|
3573
|
-
);
|
|
3726
|
+
expect(head).toContain("Upgrade the Kubernetes agent chart");
|
|
3574
3727
|
expect(head).toContain('"Runs Runbooks"');
|
|
3575
3728
|
expect(head).toContain('"Runs AI Code Fixes"');
|
|
3576
3729
|
expect(head).toContain("3 Runner credential(s)");
|
|
@@ -3602,15 +3755,18 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
|
|
|
3602
3755
|
expect(refusal.reason).toBe("runner_belongs_to_another_cluster");
|
|
3603
3756
|
expect(refusal.retryAfterSeconds).toBeUndefined();
|
|
3604
3757
|
/*
|
|
3605
|
-
*
|
|
3606
|
-
*
|
|
3607
|
-
* an operator can take is the delete — and then selecting the fresh
|
|
3608
|
-
* Runner, since a deleted Runner's cluster is left unbound.
|
|
3758
|
+
* The step that works is the chart upgrade: the Kubernetes AI agent
|
|
3759
|
+
* replaces the in-cluster Runner and does not use the Runner name.
|
|
3609
3760
|
*/
|
|
3610
|
-
expect(
|
|
3761
|
+
expect(
|
|
3762
|
+
refusal.message.startsWith(
|
|
3763
|
+
'Upgrade the Kubernetes agent chart of cluster "prod-us"',
|
|
3764
|
+
),
|
|
3765
|
+
).toBe(true);
|
|
3611
3766
|
expect(refusal.message).toContain(
|
|
3612
|
-
'
|
|
3767
|
+
'does not need Runner "kubernetes-agent/prod-us"',
|
|
3613
3768
|
);
|
|
3769
|
+
expect(refusal.message).not.toContain("AI page");
|
|
3614
3770
|
});
|
|
3615
3771
|
|
|
3616
3772
|
it("getPreviousInstanceRetryAfterSeconds counts down to the end of the alive window, never below 1s", () => {
|