@oneuptime/common 14.0.8 → 14.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/Infrastructure/GlobalCache.ts +32 -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 +6 -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/IncidentService.ts +280 -104
- package/Server/Services/IncidentSeverityService.ts +66 -15
- 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/ScheduledMaintenanceService.ts +737 -1
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- 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/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/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
- 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/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 +2 -2
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
- 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/IncidentRootCauseTimeRangeZoom.test.tsx +358 -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/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/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/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -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/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/Infrastructure/GlobalCache.test.ts +195 -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/AddTeamComplianceRuleScopeMigration.test.ts +622 -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/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/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -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/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/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- 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/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/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/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- 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/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/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/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/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/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/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/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/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- 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/ModelTable/BaseModelTable.tsx +12 -1
- 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/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- 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/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/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/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- 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 +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- 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/IncidentService.js +199 -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/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/ScheduledMaintenanceService.js +535 -3
- 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/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.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/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/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/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/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/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/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/ModelTable/BaseModelTable.js +10 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.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/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/package.json +1 -1
|
@@ -331,7 +331,15 @@ import OneUptimeDate from "../../../Types/Date";
|
|
|
331
331
|
import ObjectID from "../../../Types/ObjectID";
|
|
332
332
|
import StatusPageSubscriberNotificationStatus from "../../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
|
|
333
333
|
import IncidentCreatedResend from "../../../Types/StatusPage/IncidentCreatedResend";
|
|
334
|
-
import {
|
|
334
|
+
import ModelForm, {
|
|
335
|
+
FormType,
|
|
336
|
+
ModelField,
|
|
337
|
+
} from "../../../UI/Components/Forms/ModelForm";
|
|
338
|
+
import { ModalWidth } from "../../../UI/Components/Modal/Modal";
|
|
339
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
340
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
341
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
342
|
+
import JSONFunctions from "../../../Types/JSONFunctions";
|
|
335
343
|
import SubscriberNotificationResendCopy from "../../../../App/FeatureSet/Dashboard/src/Components/StatusPageSubscribers/SubscriberNotificationResendCopy";
|
|
336
344
|
import { DetailStyle } from "../../../UI/Components/Detail/Detail";
|
|
337
345
|
import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
@@ -1233,6 +1241,24 @@ describe.each([
|
|
|
1233
1241
|
latestProps<{ seriesLabels: unknown }>("SeriesResource").seriesLabels,
|
|
1234
1242
|
).toEqual({ "host.name": "prod-01" });
|
|
1235
1243
|
});
|
|
1244
|
+
|
|
1245
|
+
/*
|
|
1246
|
+
* The Affected Resources editor holds a picker whose placeholder names a
|
|
1247
|
+
* dozen resource types. In the default 512px modal it was cut off and
|
|
1248
|
+
* every chip cramped, so the card edits in a Medium one.
|
|
1249
|
+
*/
|
|
1250
|
+
test("the Affected Resources card edits in a Medium modal", async () => {
|
|
1251
|
+
serve(pageCase, { timeline: REOPENED_TIMELINE, title: "Checkout slow" });
|
|
1252
|
+
|
|
1253
|
+
pageCase.renderPage();
|
|
1254
|
+
await waitForPage();
|
|
1255
|
+
|
|
1256
|
+
expect(
|
|
1257
|
+
latestProps<{ createEditModalWidth?: ModalWidth }>(
|
|
1258
|
+
"CardModelDetail:Affected Resources",
|
|
1259
|
+
).createEditModalWidth,
|
|
1260
|
+
).toBe(ModalWidth.Medium);
|
|
1261
|
+
});
|
|
1236
1262
|
});
|
|
1237
1263
|
|
|
1238
1264
|
describe("background refresh", () => {
|
|
@@ -1886,7 +1912,13 @@ describe.each([
|
|
|
1886
1912
|
*/
|
|
1887
1913
|
interface ResourcesFormFieldProps {
|
|
1888
1914
|
field?: Record<string, unknown>;
|
|
1915
|
+
title?: string;
|
|
1916
|
+
description?: string;
|
|
1889
1917
|
fieldType?: FormFieldSchemaType;
|
|
1918
|
+
required?: boolean;
|
|
1919
|
+
placeholder?: string;
|
|
1920
|
+
disabled?: boolean;
|
|
1921
|
+
dropdownModal?: { type: unknown; labelField: string; valueField: string };
|
|
1890
1922
|
showIf?: (values: unknown) => boolean;
|
|
1891
1923
|
getCustomElement?: (values: unknown, elementProps: unknown) => ReactElement;
|
|
1892
1924
|
}
|
|
@@ -2908,11 +2940,11 @@ describe("alert-only behaviour", () => {
|
|
|
2908
2940
|
});
|
|
2909
2941
|
|
|
2910
2942
|
/*
|
|
2911
|
-
*
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2943
|
+
* Alert.monitor is a single relation, so it is edited with a dropdown of
|
|
2944
|
+
* its own above the picker (as on the Create page), never as a picker
|
|
2945
|
+
* chip. ModelForm loads and saves only the fields registered here.
|
|
2914
2946
|
*/
|
|
2915
|
-
test("its edit form
|
|
2947
|
+
test("its edit form offers the monitor in a dropdown of its own, above the picker", () => {
|
|
2916
2948
|
const formFields: Array<ResourcesFormFieldProps> =
|
|
2917
2949
|
resourcesCard().formFields;
|
|
2918
2950
|
const keysOf: (field: ResourcesFormFieldProps) => Array<string> = (
|
|
@@ -2923,8 +2955,13 @@ describe("alert-only behaviour", () => {
|
|
|
2923
2955
|
|
|
2924
2956
|
const formKeys: Array<string> = formFields.flatMap(keysOf);
|
|
2925
2957
|
|
|
2926
|
-
|
|
2958
|
+
// The singular relation, never the incident's plural list.
|
|
2927
2959
|
expect(formKeys).not.toContain("monitors");
|
|
2960
|
+
/*
|
|
2961
|
+
* The lock is read by the page, not registered: ModelForm submits every
|
|
2962
|
+
* registered field, and nobody may update this column.
|
|
2963
|
+
*/
|
|
2964
|
+
expect(formKeys).not.toContain("isCreatedAutomatically");
|
|
2928
2965
|
|
|
2929
2966
|
const visibleFields: Array<ResourcesFormFieldProps> = formFields.filter(
|
|
2930
2967
|
(field: ResourcesFormFieldProps): boolean => {
|
|
@@ -2937,11 +2974,30 @@ describe("alert-only behaviour", () => {
|
|
|
2937
2974
|
},
|
|
2938
2975
|
);
|
|
2939
2976
|
|
|
2940
|
-
// The
|
|
2941
|
-
expect(visibleFields.map(keysOf)).toEqual([["hosts"]]);
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2977
|
+
// The monitor dropdown first, then the resource picker on hosts.
|
|
2978
|
+
expect(visibleFields.map(keysOf)).toEqual([["monitor"], ["hosts"]]);
|
|
2979
|
+
|
|
2980
|
+
const monitorField: ResourcesFormFieldProps = visibleFields[0]!;
|
|
2981
|
+
|
|
2982
|
+
expect(monitorField.title).toBe("Monitor");
|
|
2983
|
+
expect(monitorField.fieldType).toBe(FormFieldSchemaType.Dropdown);
|
|
2984
|
+
expect(monitorField.dropdownModal).toEqual({
|
|
2985
|
+
type: Monitor,
|
|
2986
|
+
labelField: "name",
|
|
2987
|
+
valueField: "_id",
|
|
2988
|
+
});
|
|
2989
|
+
expect(monitorField.required).toBe(false);
|
|
2990
|
+
/*
|
|
2991
|
+
* The Create page's placeholder: the dashboard translates it by its
|
|
2992
|
+
* English text, and every locale has this spelling.
|
|
2993
|
+
*/
|
|
2994
|
+
expect(monitorField.placeholder).toBe("Select Monitor");
|
|
2995
|
+
|
|
2996
|
+
const pickerField: ResourcesFormFieldProps = visibleFields[1]!;
|
|
2997
|
+
|
|
2998
|
+
// Read as a pair with the dropdown above it.
|
|
2999
|
+
expect(pickerField.title).toBe("Other Affected Resources");
|
|
3000
|
+
expect(pickerField.fieldType).toBe(FormFieldSchemaType.CustomComponent);
|
|
2945
3001
|
// Hidden registrations so the form still loads the other relations.
|
|
2946
3002
|
expect(hiddenFields.flatMap(keysOf)).toEqual([
|
|
2947
3003
|
"kubernetesClusters",
|
|
@@ -2956,8 +3012,8 @@ describe("alert-only behaviour", () => {
|
|
|
2956
3012
|
"services",
|
|
2957
3013
|
]);
|
|
2958
3014
|
|
|
2959
|
-
// The picker
|
|
2960
|
-
render(
|
|
3015
|
+
// The picker does not offer monitors: the dropdown is the one place.
|
|
3016
|
+
render(pickerField.getCustomElement!({}, {}));
|
|
2961
3017
|
|
|
2962
3018
|
const picker: { monitors?: unknown; resourceTypes?: Array<string> } =
|
|
2963
3019
|
latestProps<{ monitors?: unknown; resourceTypes?: Array<string> }>(
|
|
@@ -2975,4 +3031,522 @@ describe("alert-only behaviour", () => {
|
|
|
2975
3031
|
);
|
|
2976
3032
|
});
|
|
2977
3033
|
});
|
|
3034
|
+
|
|
3035
|
+
/*
|
|
3036
|
+
* An alert raised automatically keeps the monitor it was raised with, and
|
|
3037
|
+
* the server refuses to set, change or clear it (AlertService.
|
|
3038
|
+
* onBeforeUpdate). Raised by a monitor, the alert shows that monitor
|
|
3039
|
+
* locked, with the reason; raised without one (an SLO burn-rate or
|
|
3040
|
+
* security-event alert, or one whose monitor was deleted), it has no
|
|
3041
|
+
* Monitor field at all. The page reads which case it is with its own row,
|
|
3042
|
+
* so the lock is known before any card renders.
|
|
3043
|
+
*/
|
|
3044
|
+
describe("the Affected Resources card's monitor lock", () => {
|
|
3045
|
+
interface ServedAlert {
|
|
3046
|
+
isCreatedAutomatically: boolean;
|
|
3047
|
+
monitorId?: string | undefined;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
type ServeAlertFunction = (served: ServedAlert) => void;
|
|
3051
|
+
|
|
3052
|
+
const serveAlert: ServeAlertFunction = (served: ServedAlert): void => {
|
|
3053
|
+
serve(ALERT_CASE, {
|
|
3054
|
+
timeline: REOPENED_TIMELINE,
|
|
3055
|
+
title: "Checkout slow",
|
|
3056
|
+
});
|
|
3057
|
+
|
|
3058
|
+
getItemMock.mockImplementation(() => {
|
|
3059
|
+
const alert: Alert = ALERT_CASE.buildEvent("Checkout slow") as Alert;
|
|
3060
|
+
alert.isCreatedAutomatically = served.isCreatedAutomatically;
|
|
3061
|
+
|
|
3062
|
+
if (served.monitorId) {
|
|
3063
|
+
alert.monitorId = new ObjectID(served.monitorId);
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
return Promise.resolve(alert);
|
|
3067
|
+
});
|
|
3068
|
+
};
|
|
3069
|
+
|
|
3070
|
+
type MonitorFieldFunction = () => ResourcesFormFieldProps;
|
|
3071
|
+
|
|
3072
|
+
const monitorField: MonitorFieldFunction = (): ResourcesFormFieldProps => {
|
|
3073
|
+
const field: ResourcesFormFieldProps | undefined =
|
|
3074
|
+
resourcesCard().formFields.find(
|
|
3075
|
+
(formField: ResourcesFormFieldProps): boolean => {
|
|
3076
|
+
return Boolean(formField.field?.["monitor"]);
|
|
3077
|
+
},
|
|
3078
|
+
);
|
|
3079
|
+
|
|
3080
|
+
if (!field) {
|
|
3081
|
+
throw new Error("The Affected Resources form has no monitor field");
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
return field;
|
|
3085
|
+
};
|
|
3086
|
+
|
|
3087
|
+
type IsMonitorFieldShownFunction = (values: unknown) => boolean;
|
|
3088
|
+
|
|
3089
|
+
// What the form decides for the Monitor field, given its current values.
|
|
3090
|
+
const isMonitorFieldShown: IsMonitorFieldShownFunction = (
|
|
3091
|
+
values: unknown,
|
|
3092
|
+
): boolean => {
|
|
3093
|
+
const field: ResourcesFormFieldProps = monitorField();
|
|
3094
|
+
|
|
3095
|
+
return !field.showIf || field.showIf(values);
|
|
3096
|
+
};
|
|
3097
|
+
|
|
3098
|
+
const LOCKED_DESCRIPTION: string =
|
|
3099
|
+
"This alert was raised by this monitor, which resolves it automatically when the monitor recovers, so it can't be moved to another monitor or removed.";
|
|
3100
|
+
|
|
3101
|
+
test("the page reads whether the alert was raised automatically, and its monitor, with its own row", async () => {
|
|
3102
|
+
serveAlert({ isCreatedAutomatically: false });
|
|
3103
|
+
|
|
3104
|
+
ALERT_CASE.renderPage();
|
|
3105
|
+
await waitForPage();
|
|
3106
|
+
|
|
3107
|
+
const pageRead: { select: Record<string, unknown> } | undefined =
|
|
3108
|
+
getItemMock.mock.calls
|
|
3109
|
+
.map((call: Array<unknown>) => {
|
|
3110
|
+
return call[0] as { select: Record<string, unknown> };
|
|
3111
|
+
})
|
|
3112
|
+
.find((request: { select: Record<string, unknown> }): boolean => {
|
|
3113
|
+
return Boolean(request.select["telemetryQuery"]);
|
|
3114
|
+
});
|
|
3115
|
+
|
|
3116
|
+
expect(pageRead).toBeDefined();
|
|
3117
|
+
expect(pageRead!.select["isCreatedAutomatically"]).toBe(true);
|
|
3118
|
+
expect(pageRead!.select["monitorId"]).toBe(true);
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
test("an alert its monitor raised shows the dropdown locked, and says why", async () => {
|
|
3122
|
+
serveAlert({ isCreatedAutomatically: true, monitorId: MONITOR_ID });
|
|
3123
|
+
|
|
3124
|
+
ALERT_CASE.renderPage();
|
|
3125
|
+
await waitForPage();
|
|
3126
|
+
|
|
3127
|
+
expect(isMonitorFieldShown({ monitor: MONITOR_ID })).toBe(true);
|
|
3128
|
+
expect(monitorField().disabled).toBe(true);
|
|
3129
|
+
expect(monitorField().description).toBe(LOCKED_DESCRIPTION);
|
|
3130
|
+
});
|
|
3131
|
+
|
|
3132
|
+
test("a manual alert's monitor can be changed or cleared", async () => {
|
|
3133
|
+
serveAlert({ isCreatedAutomatically: false, monitorId: MONITOR_ID });
|
|
3134
|
+
|
|
3135
|
+
ALERT_CASE.renderPage();
|
|
3136
|
+
await waitForPage();
|
|
3137
|
+
|
|
3138
|
+
expect(isMonitorFieldShown({ monitor: MONITOR_ID })).toBe(true);
|
|
3139
|
+
// Cleared in the open form, it stays there to pick another.
|
|
3140
|
+
expect(isMonitorFieldShown({ monitor: null })).toBe(true);
|
|
3141
|
+
expect(monitorField().disabled).toBe(false);
|
|
3142
|
+
expect(monitorField().description).toBe(
|
|
3143
|
+
"Select the monitor affected by this alert.",
|
|
3144
|
+
);
|
|
3145
|
+
});
|
|
3146
|
+
|
|
3147
|
+
test("a manual alert with no monitor can be given one", async () => {
|
|
3148
|
+
serveAlert({ isCreatedAutomatically: false });
|
|
3149
|
+
|
|
3150
|
+
ALERT_CASE.renderPage();
|
|
3151
|
+
await waitForPage();
|
|
3152
|
+
|
|
3153
|
+
expect(isMonitorFieldShown({})).toBe(true);
|
|
3154
|
+
expect(monitorField().disabled).toBe(false);
|
|
3155
|
+
expect(monitorField().description).toBe(
|
|
3156
|
+
"Select the monitor affected by this alert.",
|
|
3157
|
+
);
|
|
3158
|
+
});
|
|
3159
|
+
|
|
3160
|
+
/*
|
|
3161
|
+
* Nothing to show, and nothing that may be picked: the field is left out
|
|
3162
|
+
* rather than shown empty and locked with a reason that names a monitor
|
|
3163
|
+
* the alert does not have.
|
|
3164
|
+
*/
|
|
3165
|
+
test("an automatic alert with no monitor (an SLO burn-rate alert) has no Monitor field", async () => {
|
|
3166
|
+
serveAlert({ isCreatedAutomatically: true });
|
|
3167
|
+
|
|
3168
|
+
ALERT_CASE.renderPage();
|
|
3169
|
+
await waitForPage();
|
|
3170
|
+
|
|
3171
|
+
expect(isMonitorFieldShown({})).toBe(false);
|
|
3172
|
+
expect(isMonitorFieldShown({ monitor: null })).toBe(false);
|
|
3173
|
+
// Still registered: ModelForm loads it and saves back the null it holds.
|
|
3174
|
+
expect(monitorField().field).toEqual({ monitor: true });
|
|
3175
|
+
});
|
|
3176
|
+
|
|
3177
|
+
/*
|
|
3178
|
+
* Saving the card can set, move or clear a manual alert's monitor. The
|
|
3179
|
+
* details card shows it (and reports it for the header's Monitor fact),
|
|
3180
|
+
* and the lock depends on the page's own row, so both are read again
|
|
3181
|
+
* without a reload. Here, an automatic alert's monitor was deleted while
|
|
3182
|
+
* the page was open: after the save, the row the page reads again has no
|
|
3183
|
+
* monitor, and the field locked on it is left out.
|
|
3184
|
+
*/
|
|
3185
|
+
test("saving the card re-reads the details card, the page's lock and the feed", async () => {
|
|
3186
|
+
serveAlert({ isCreatedAutomatically: true, monitorId: MONITOR_ID });
|
|
3187
|
+
|
|
3188
|
+
ALERT_CASE.renderPage();
|
|
3189
|
+
await waitForPage();
|
|
3190
|
+
|
|
3191
|
+
expect(isMonitorFieldShown({})).toBe(true);
|
|
3192
|
+
expect(monitorField().disabled).toBe(true);
|
|
3193
|
+
|
|
3194
|
+
const refresherBefore: boolean | undefined =
|
|
3195
|
+
latestProps<CardModelDetailProps>(
|
|
3196
|
+
"CardModelDetail:Alert Details",
|
|
3197
|
+
).refresher;
|
|
3198
|
+
const feedTokenBefore: number = latestProps<{ refreshToken: number }>(
|
|
3199
|
+
"Feed",
|
|
3200
|
+
).refreshToken;
|
|
3201
|
+
const readsBefore: number = countItemReads();
|
|
3202
|
+
|
|
3203
|
+
// Its monitor was deleted, which leaves the alert's monitorId NULL.
|
|
3204
|
+
serveAlert({ isCreatedAutomatically: true });
|
|
3205
|
+
|
|
3206
|
+
act(() => {
|
|
3207
|
+
latestProps<CardModelDetailProps>("CardModelDetail:Affected Resources")
|
|
3208
|
+
.onSaveSuccess!();
|
|
3209
|
+
});
|
|
3210
|
+
|
|
3211
|
+
await waitFor(() => {
|
|
3212
|
+
expect(isMonitorFieldShown({})).toBe(false);
|
|
3213
|
+
});
|
|
3214
|
+
|
|
3215
|
+
expect(
|
|
3216
|
+
latestProps<CardModelDetailProps>("CardModelDetail:Alert Details")
|
|
3217
|
+
.refresher,
|
|
3218
|
+
).toBe(!refresherBefore);
|
|
3219
|
+
expect(latestProps<{ refreshToken: number }>("Feed").refreshToken).toBe(
|
|
3220
|
+
feedTokenBefore + 1,
|
|
3221
|
+
);
|
|
3222
|
+
// One background read of the page's own row, and nothing remounted.
|
|
3223
|
+
expect(countItemReads()).toBe(readsBefore + 1);
|
|
3224
|
+
expect(mountCounts["InvestigationPanel"]).toBe(1);
|
|
3225
|
+
expect(mountCounts["Feed"]).toBe(1);
|
|
3226
|
+
});
|
|
3227
|
+
});
|
|
3228
|
+
|
|
3229
|
+
/*
|
|
3230
|
+
* The card's form fields, rendered in the real ModelForm its Edit modal
|
|
3231
|
+
* opens (EntityDropdown included), against the row as the API hands it to
|
|
3232
|
+
* the form: the monitor as `{ _id }` only.
|
|
3233
|
+
*/
|
|
3234
|
+
describe("the Affected Resources edit form, rendered", () => {
|
|
3235
|
+
const OTHER_MONITOR_ID: string = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbb2";
|
|
3236
|
+
const HOST_ID: string = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee1";
|
|
3237
|
+
const OTHER_HOST_ID: string = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee2";
|
|
3238
|
+
|
|
3239
|
+
const MONITOR_NAMES: Record<string, string> = {
|
|
3240
|
+
[MONITOR_ID]: "Developer portal",
|
|
3241
|
+
[OTHER_MONITOR_ID]: "Payments API",
|
|
3242
|
+
};
|
|
3243
|
+
|
|
3244
|
+
let formLoadSelect: Record<string, unknown> | null = null;
|
|
3245
|
+
|
|
3246
|
+
interface EditFormOptions {
|
|
3247
|
+
isCreatedAutomatically: boolean;
|
|
3248
|
+
// Whether the alert has a monitor. It has MONITOR_ID unless this is false.
|
|
3249
|
+
hasMonitor?: boolean | undefined;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
type RenderEditFormFunction = (options: EditFormOptions) => Promise<void>;
|
|
3253
|
+
|
|
3254
|
+
const renderEditForm: RenderEditFormFunction = async (
|
|
3255
|
+
options: EditFormOptions,
|
|
3256
|
+
): Promise<void> => {
|
|
3257
|
+
const hasMonitor: boolean = options.hasMonitor !== false;
|
|
3258
|
+
|
|
3259
|
+
serve(ALERT_CASE, {
|
|
3260
|
+
timeline: REOPENED_TIMELINE,
|
|
3261
|
+
title: "Checkout slow",
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
getItemMock.mockImplementation(() => {
|
|
3265
|
+
const alert: Alert = ALERT_CASE.buildEvent("Checkout slow") as Alert;
|
|
3266
|
+
alert.isCreatedAutomatically = options.isCreatedAutomatically;
|
|
3267
|
+
|
|
3268
|
+
if (hasMonitor) {
|
|
3269
|
+
alert.monitorId = new ObjectID(MONITOR_ID);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
return Promise.resolve(alert);
|
|
3273
|
+
});
|
|
3274
|
+
|
|
3275
|
+
ALERT_CASE.renderPage();
|
|
3276
|
+
await waitForPage();
|
|
3277
|
+
|
|
3278
|
+
const fields: Array<ModelField<Alert>> = resourcesCard()
|
|
3279
|
+
.formFields as unknown as Array<ModelField<Alert>>;
|
|
3280
|
+
|
|
3281
|
+
// The page has handed over its fields; only the form is needed now.
|
|
3282
|
+
cleanup();
|
|
3283
|
+
|
|
3284
|
+
// The form is about wiring, not about who may edit which column.
|
|
3285
|
+
jest.spyOn(User, "isMasterAdmin").mockReturnValue(true);
|
|
3286
|
+
|
|
3287
|
+
formLoadSelect = null;
|
|
3288
|
+
|
|
3289
|
+
getItemMock.mockImplementation((...args: Array<unknown>) => {
|
|
3290
|
+
formLoadSelect = (args[0] as { select: Record<string, unknown> })
|
|
3291
|
+
.select;
|
|
3292
|
+
|
|
3293
|
+
return Promise.resolve(
|
|
3294
|
+
BaseModel.fromJSON(
|
|
3295
|
+
{
|
|
3296
|
+
_id: EVENT_ID,
|
|
3297
|
+
// The API answers a relation the row does not hold with null.
|
|
3298
|
+
monitor: hasMonitor ? { _id: MONITOR_ID } : null,
|
|
3299
|
+
hosts: [{ _id: HOST_ID }],
|
|
3300
|
+
services: [],
|
|
3301
|
+
},
|
|
3302
|
+
Alert,
|
|
3303
|
+
) as Alert,
|
|
3304
|
+
);
|
|
3305
|
+
});
|
|
3306
|
+
|
|
3307
|
+
getListMock.mockImplementation((...args: Array<unknown>) => {
|
|
3308
|
+
const request: {
|
|
3309
|
+
modelType: unknown;
|
|
3310
|
+
query: Record<string, unknown>;
|
|
3311
|
+
} = args[0] as {
|
|
3312
|
+
modelType: unknown;
|
|
3313
|
+
query: Record<string, unknown>;
|
|
3314
|
+
};
|
|
3315
|
+
|
|
3316
|
+
if (request.modelType !== Monitor) {
|
|
3317
|
+
return Promise.resolve(listResult([]));
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
const idFilter: unknown = request.query["_id"];
|
|
3321
|
+
const ids: Array<string> =
|
|
3322
|
+
idFilter instanceof Includes
|
|
3323
|
+
? (idFilter.values as Array<unknown>).map((id: unknown) => {
|
|
3324
|
+
return String(id);
|
|
3325
|
+
})
|
|
3326
|
+
: Object.keys(MONITOR_NAMES);
|
|
3327
|
+
|
|
3328
|
+
return Promise.resolve(
|
|
3329
|
+
listResult(
|
|
3330
|
+
ids
|
|
3331
|
+
.filter((id: string): boolean => {
|
|
3332
|
+
return Boolean(MONITOR_NAMES[id]);
|
|
3333
|
+
})
|
|
3334
|
+
.map((id: string): Monitor => {
|
|
3335
|
+
const monitor: Monitor = new Monitor();
|
|
3336
|
+
monitor._id = id;
|
|
3337
|
+
monitor.name = MONITOR_NAMES[id]!;
|
|
3338
|
+
return monitor;
|
|
3339
|
+
}),
|
|
3340
|
+
),
|
|
3341
|
+
);
|
|
3342
|
+
});
|
|
3343
|
+
|
|
3344
|
+
createOrUpdateMock.mockResolvedValue({
|
|
3345
|
+
data: new Alert(),
|
|
3346
|
+
miscData: undefined,
|
|
3347
|
+
});
|
|
3348
|
+
|
|
3349
|
+
render(
|
|
3350
|
+
<ModelForm<Alert>
|
|
3351
|
+
modelType={Alert}
|
|
3352
|
+
id="edit-alert-affected-resources"
|
|
3353
|
+
name="Affected Resources"
|
|
3354
|
+
fields={fields}
|
|
3355
|
+
formType={FormType.Update}
|
|
3356
|
+
modelIdToEdit={new ObjectID(EVENT_ID)}
|
|
3357
|
+
submitButtonText="Save Changes"
|
|
3358
|
+
onSuccess={() => {
|
|
3359
|
+
// asserted through createOrUpdateMock
|
|
3360
|
+
}}
|
|
3361
|
+
/>,
|
|
3362
|
+
);
|
|
3363
|
+
|
|
3364
|
+
if (hasMonitor) {
|
|
3365
|
+
await screen.findByText("Developer portal");
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
// No monitor name to wait for: the picker renders with the fields.
|
|
3370
|
+
await waitFor(() => {
|
|
3371
|
+
expect(propsHistory("AffectedResourcesPicker").length).toBeGreaterThan(
|
|
3372
|
+
0,
|
|
3373
|
+
);
|
|
3374
|
+
});
|
|
3375
|
+
};
|
|
3376
|
+
|
|
3377
|
+
type SaveFunction = () => Promise<Alert>;
|
|
3378
|
+
|
|
3379
|
+
const save: SaveFunction = async (): Promise<Alert> => {
|
|
3380
|
+
fireEvent.click(screen.getByRole("button", { name: "Save Changes" }));
|
|
3381
|
+
|
|
3382
|
+
await waitFor(() => {
|
|
3383
|
+
expect(createOrUpdateMock).toHaveBeenCalledTimes(1);
|
|
3384
|
+
});
|
|
3385
|
+
|
|
3386
|
+
return (createOrUpdateMock.mock.calls[0]![0] as { model: Alert }).model;
|
|
3387
|
+
};
|
|
3388
|
+
|
|
3389
|
+
// The body ModelAPI.createOrUpdate sends for this model.
|
|
3390
|
+
type RequestBodyFunction = (model: Alert) => JSONObject;
|
|
3391
|
+
|
|
3392
|
+
const requestBodyOf: RequestBodyFunction = (model: Alert): JSONObject => {
|
|
3393
|
+
return JSONFunctions.serialize(BaseModel.toJSON(model, Alert));
|
|
3394
|
+
};
|
|
3395
|
+
|
|
3396
|
+
type MonitorButtonFunction = () => HTMLButtonElement;
|
|
3397
|
+
|
|
3398
|
+
// The dropdown shows its selection as a button until it is opened.
|
|
3399
|
+
const monitorButton: MonitorButtonFunction = (): HTMLButtonElement => {
|
|
3400
|
+
const button: HTMLButtonElement | null = screen
|
|
3401
|
+
.getByText("Developer portal")
|
|
3402
|
+
.closest("button");
|
|
3403
|
+
|
|
3404
|
+
if (!button) {
|
|
3405
|
+
throw new Error("The monitor dropdown shows no selection");
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
return button;
|
|
3409
|
+
};
|
|
3410
|
+
|
|
3411
|
+
test("loads the monitor as an id and shows its name", async () => {
|
|
3412
|
+
await renderEditForm({ isCreatedAutomatically: false });
|
|
3413
|
+
|
|
3414
|
+
expect(formLoadSelect).not.toBeNull();
|
|
3415
|
+
expect(formLoadSelect!["monitor"]).toBeTruthy();
|
|
3416
|
+
expect(monitorButton()).not.toBeDisabled();
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
test("saving without touching the monitor keeps it", async () => {
|
|
3420
|
+
await renderEditForm({ isCreatedAutomatically: false });
|
|
3421
|
+
|
|
3422
|
+
const saved: Alert = await save();
|
|
3423
|
+
|
|
3424
|
+
expect(saved.monitor?._id?.toString()).toBe(MONITOR_ID);
|
|
3425
|
+
expect(requestBodyOf(saved)["monitor"]).toEqual(
|
|
3426
|
+
expect.objectContaining({ _id: MONITOR_ID }),
|
|
3427
|
+
);
|
|
3428
|
+
});
|
|
3429
|
+
|
|
3430
|
+
test("an edit to the other resources keeps the monitor", async () => {
|
|
3431
|
+
await renderEditForm({ isCreatedAutomatically: false });
|
|
3432
|
+
|
|
3433
|
+
act(() => {
|
|
3434
|
+
latestProps<{ onChange: (payload: unknown) => void }>(
|
|
3435
|
+
"AffectedResourcesPicker",
|
|
3436
|
+
).onChange([OTHER_HOST_ID]);
|
|
3437
|
+
});
|
|
3438
|
+
|
|
3439
|
+
const saved: Alert = await save();
|
|
3440
|
+
|
|
3441
|
+
expect(saved.monitor?._id?.toString()).toBe(MONITOR_ID);
|
|
3442
|
+
expect(
|
|
3443
|
+
(saved.hosts || []).map((host: BaseModel): string => {
|
|
3444
|
+
return String(host._id);
|
|
3445
|
+
}),
|
|
3446
|
+
).toEqual([OTHER_HOST_ID]);
|
|
3447
|
+
});
|
|
3448
|
+
|
|
3449
|
+
test("choosing another monitor saves the new one", async () => {
|
|
3450
|
+
await renderEditForm({ isCreatedAutomatically: false });
|
|
3451
|
+
|
|
3452
|
+
fireEvent.click(monitorButton());
|
|
3453
|
+
fireEvent.click(
|
|
3454
|
+
await screen.findByRole("option", { name: /Payments API/ }),
|
|
3455
|
+
);
|
|
3456
|
+
|
|
3457
|
+
const saved: Alert = await save();
|
|
3458
|
+
|
|
3459
|
+
expect(saved.monitor?._id?.toString()).toBe(OTHER_MONITOR_ID);
|
|
3460
|
+
expect(requestBodyOf(saved)["monitor"]).toEqual(
|
|
3461
|
+
expect.objectContaining({ _id: OTHER_MONITOR_ID }),
|
|
3462
|
+
);
|
|
3463
|
+
});
|
|
3464
|
+
|
|
3465
|
+
/*
|
|
3466
|
+
* Clear selection hands the form null. It has to reach the request as an
|
|
3467
|
+
* explicit null: an absent key would leave the monitor where it is.
|
|
3468
|
+
*/
|
|
3469
|
+
test("clearing it saves the alert with no monitor", async () => {
|
|
3470
|
+
await renderEditForm({ isCreatedAutomatically: false });
|
|
3471
|
+
|
|
3472
|
+
fireEvent.click(screen.getByRole("button", { name: "Clear selection" }));
|
|
3473
|
+
|
|
3474
|
+
await waitFor(() => {
|
|
3475
|
+
expect(screen.queryByText("Developer portal")).toBeNull();
|
|
3476
|
+
});
|
|
3477
|
+
|
|
3478
|
+
const saved: Alert = await save();
|
|
3479
|
+
const body: JSONObject = requestBodyOf(saved);
|
|
3480
|
+
|
|
3481
|
+
expect(saved.monitor).toBeNull();
|
|
3482
|
+
expect(Object.prototype.hasOwnProperty.call(body, "monitor")).toBe(true);
|
|
3483
|
+
expect(body["monitor"]).toBeNull();
|
|
3484
|
+
});
|
|
3485
|
+
|
|
3486
|
+
test("for an alert its monitor raised, the dropdown cannot be opened or cleared, and saving keeps the monitor", async () => {
|
|
3487
|
+
await renderEditForm({ isCreatedAutomatically: true });
|
|
3488
|
+
|
|
3489
|
+
expect(monitorButton()).toBeDisabled();
|
|
3490
|
+
expect(screen.queryByRole("button", { name: "Clear selection" })).toBe(
|
|
3491
|
+
null,
|
|
3492
|
+
);
|
|
3493
|
+
expect(
|
|
3494
|
+
screen.getByText(/so it can't be moved to another monitor/),
|
|
3495
|
+
).toBeInTheDocument();
|
|
3496
|
+
|
|
3497
|
+
fireEvent.click(monitorButton());
|
|
3498
|
+
|
|
3499
|
+
expect(screen.queryByRole("listbox")).toBeNull();
|
|
3500
|
+
expect(screen.queryByRole("option", { name: /Payments API/ })).toBeNull();
|
|
3501
|
+
|
|
3502
|
+
const saved: Alert = await save();
|
|
3503
|
+
|
|
3504
|
+
expect(saved.monitor?._id?.toString()).toBe(MONITOR_ID);
|
|
3505
|
+
});
|
|
3506
|
+
|
|
3507
|
+
/*
|
|
3508
|
+
* An automatic alert raised without a monitor has no Monitor field. The
|
|
3509
|
+
* form still loads the hidden field and submits the null it holds, which
|
|
3510
|
+
* the server reads as no change (AlertMonitorEditGuard.test.ts), so the
|
|
3511
|
+
* rest of the card still saves.
|
|
3512
|
+
*/
|
|
3513
|
+
test("for an automatic alert with no monitor, the field is left out, and saving keeps it without one", async () => {
|
|
3514
|
+
await renderEditForm({ isCreatedAutomatically: true, hasMonitor: false });
|
|
3515
|
+
|
|
3516
|
+
expect(formLoadSelect!["monitor"]).toBeTruthy();
|
|
3517
|
+
expect(screen.queryByText("Monitor")).toBeNull();
|
|
3518
|
+
expect(screen.queryByPlaceholderText("Select Monitor")).toBeNull();
|
|
3519
|
+
expect(screen.queryByText(/raised by this monitor/)).toBeNull();
|
|
3520
|
+
expect(screen.queryByRole("button", { name: "Clear selection" })).toBe(
|
|
3521
|
+
null,
|
|
3522
|
+
);
|
|
3523
|
+
|
|
3524
|
+
act(() => {
|
|
3525
|
+
latestProps<{ onChange: (payload: unknown) => void }>(
|
|
3526
|
+
"AffectedResourcesPicker",
|
|
3527
|
+
).onChange([OTHER_HOST_ID]);
|
|
3528
|
+
});
|
|
3529
|
+
|
|
3530
|
+
const saved: Alert = await save();
|
|
3531
|
+
const body: JSONObject = requestBodyOf(saved);
|
|
3532
|
+
|
|
3533
|
+
expect(saved.monitor).toBeNull();
|
|
3534
|
+
expect(body["monitor"]).toBeNull();
|
|
3535
|
+
expect(
|
|
3536
|
+
(saved.hosts || []).map((host: BaseModel): string => {
|
|
3537
|
+
return String(host._id);
|
|
3538
|
+
}),
|
|
3539
|
+
).toEqual([OTHER_HOST_ID]);
|
|
3540
|
+
});
|
|
3541
|
+
|
|
3542
|
+
test("for a manual alert with no monitor, the dropdown is offered", async () => {
|
|
3543
|
+
await renderEditForm({
|
|
3544
|
+
isCreatedAutomatically: false,
|
|
3545
|
+
hasMonitor: false,
|
|
3546
|
+
});
|
|
3547
|
+
|
|
3548
|
+
expect(screen.getByText("Monitor")).toBeInTheDocument();
|
|
3549
|
+
expect(screen.getByPlaceholderText("Select Monitor")).not.toBeDisabled();
|
|
3550
|
+
});
|
|
3551
|
+
});
|
|
2978
3552
|
});
|