@oneuptime/common 11.7.4 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/ExceptionInstance.ts +67 -0
- package/Models/AnalyticsModels/Index.ts +10 -0
- package/Models/AnalyticsModels/Log.ts +60 -0
- package/Models/AnalyticsModels/RumSession.ts +1401 -0
- package/Models/AnalyticsModels/RumSessionChunk.ts +798 -0
- package/Models/AnalyticsModels/Span.ts +60 -0
- package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +52 -0
- package/Models/DatabaseModels/AIRun.ts +31 -0
- package/Models/DatabaseModels/Alert.ts +29 -0
- package/Models/DatabaseModels/AlertCustomField.ts +1 -1
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/AutoRemediationRule.ts +989 -0
- package/Models/DatabaseModels/AutoRemediationSuggestion.ts +874 -0
- package/Models/DatabaseModels/CodeRepository.ts +129 -0
- package/Models/DatabaseModels/GlobalConfig.ts +312 -0
- package/Models/DatabaseModels/Incident.ts +29 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +1 -1
- package/Models/DatabaseModels/IncidentFeed.ts +1 -0
- package/Models/DatabaseModels/Index.ts +21 -8
- package/Models/DatabaseModels/InstanceHealthLog.ts +8 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +1 -1
- package/Models/DatabaseModels/MonitorStatus.ts +1 -1
- package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +2 -2
- package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +2 -2
- package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +1 -1
- package/Models/DatabaseModels/Project.ts +231 -0
- package/Models/DatabaseModels/RumApplication.ts +906 -0
- package/Models/DatabaseModels/RumSessionErasureRequest.ts +561 -0
- package/Models/DatabaseModels/RumSessionPin.ts +457 -0
- package/Models/DatabaseModels/RumSessionReplayView.ts +406 -0
- package/Models/DatabaseModels/RunbookCredential.ts +673 -0
- package/Models/DatabaseModels/RunbookSecret.ts +7 -7
- package/Models/DatabaseModels/{RunbookAgent.ts → Runner.ts} +206 -46
- package/Models/DatabaseModels/{RunbookAgentJob.ts → RunnerJob.ts} +151 -18
- package/Models/DatabaseModels/{RunbookAgentOwnerTeam.ts → RunnerOwnerTeam.ts} +41 -43
- package/Models/DatabaseModels/{RunbookAgentOwnerUser.ts → RunnerOwnerUser.ts} +41 -43
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +1 -1
- package/Models/DatabaseModels/StatusPageCustomField.ts +1 -1
- package/Models/DatabaseModels/TeamCustomField.ts +1 -1
- package/Models/DatabaseModels/TeamMemberCustomField.ts +1 -1
- package/Server/API/AIAgentDataAPI.ts +179 -48
- package/Server/API/AIAgentTaskAPI.ts +36 -37
- package/Server/API/AIAgentTaskLogAPI.ts +19 -33
- package/Server/API/AutoRemediationAPI.ts +557 -0
- package/Server/API/BaseAnalyticsAPI.ts +4 -1
- package/Server/API/CommonAPI.ts +35 -0
- package/Server/API/DashboardAPI.ts +340 -15
- package/Server/API/TelemetryAPI.ts +1622 -3
- package/Server/API/UserIncomingCallNumberAPI.ts +31 -0
- package/Server/DatabaseConfig.ts +26 -1
- package/Server/EnvironmentConfig.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.ts +293 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.ts +54 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.ts +104 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.ts +349 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.ts +152 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.ts +151 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.ts +597 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.ts +71 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +32 -0
- package/Server/Services/AIRunService.ts +10 -0
- package/Server/Services/AIService.ts +30 -0
- package/Server/Services/AlertService.ts +41 -4
- package/Server/Services/AlertSeverityService.ts +25 -11
- package/Server/Services/AlertStateService.ts +25 -11
- package/Server/Services/AutoRemediationRuleEngineService.ts +1101 -0
- package/Server/Services/{RunbookAgentOwnerTeamService.ts → AutoRemediationRuleService.ts} +1 -1
- package/Server/Services/AutoRemediationSuggestionService.ts +183 -0
- package/Server/Services/BillingService.ts +50 -1
- package/Server/Services/DatabaseService.ts +92 -4
- package/Server/Services/IncidentService.ts +56 -4
- package/Server/Services/IncidentSeverityService.ts +25 -11
- package/Server/Services/IncidentStateService.ts +25 -11
- package/Server/Services/Index.ts +19 -0
- package/Server/Services/MonitorService.ts +24 -0
- package/Server/Services/MonitorStatusService.ts +25 -11
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +18 -19
- package/Server/Services/NetworkDeviceLabelRuleService.ts +39 -0
- package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +18 -19
- package/Server/Services/NetworkDeviceOwnerRuleService.ts +37 -0
- package/Server/Services/NetworkDeviceService.ts +172 -21
- package/Server/Services/NetworkSiteAssignmentRuleService.ts +92 -12
- package/Server/Services/NetworkSiteService.ts +27 -10
- package/Server/Services/ProjectService.ts +47 -0
- package/Server/Services/RumApplicationService.ts +240 -1
- package/Server/Services/RumSessionChunkService.ts +11 -0
- package/Server/Services/RumSessionErasureRequestService.ts +197 -0
- package/Server/Services/RumSessionPinService.ts +213 -0
- package/Server/Services/RumSessionReplayViewService.ts +201 -0
- package/Server/Services/RumSessionService.ts +123 -0
- package/Server/Services/RunbookCredentialService.ts +76 -0
- package/Server/Services/{RunbookAgentJobService.ts → RunnerJobService.ts} +202 -51
- package/Server/Services/{RunbookAgentOwnerUserService.ts → RunnerOwnerTeamService.ts} +1 -1
- package/Server/Services/RunnerOwnerUserService.ts +10 -0
- package/Server/Services/RunnerService.ts +172 -0
- package/Server/Services/ScheduledMaintenanceStateService.ts +25 -11
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +15 -1
- package/Server/Services/ServiceService.ts +8 -2
- package/Server/Services/StatusPageService.ts +240 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +20 -1
- package/Server/Services/TelemetryExceptionService.ts +3 -16
- package/Server/Services/TelemetryUsageBillingService.ts +33 -2
- package/Server/Services/TraceAggregationService.ts +196 -32
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +17 -0
- package/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.ts +10 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +63 -7
- package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +128 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.ts +54 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +17 -10
- package/Server/Utils/AI/CodeFix/CodeFixRunQueue.ts +8 -6
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +943 -0
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +866 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +823 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +103 -12
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +75 -17
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +28 -6
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +202 -12
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +264 -14
- package/Server/Utils/AI/SRE/Insights/Triage.ts +2 -7
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +5 -24
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +259 -20
- package/Server/Utils/AI/SRE/PostedRootCause.ts +82 -0
- package/Server/Utils/AI/SRE/README.md +3 -2
- package/Server/Utils/AI/SRE/RemediationHandoff.ts +82 -0
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +6 -19
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +2 -2
- package/Server/Utils/Alert/AlertPrivacyFilter.ts +58 -0
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +604 -0
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +619 -0
- package/Server/Utils/CorsOptions.ts +52 -0
- package/Server/Utils/Database/RelationIdUtil.ts +68 -0
- package/Server/Utils/Incident/IncidentPrivacyFilter.ts +60 -0
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +286 -0
- package/Server/Utils/InstanceHealth/RedisHealth.ts +434 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +20 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +20 -0
- package/Server/Utils/Monitor/MonitorResource.ts +19 -0
- package/Server/Utils/Monitor/MonitorSummaryCapture.ts +104 -0
- package/Server/Utils/NetworkDevice/RulePatternValidator.ts +44 -0
- package/Server/Utils/OpenAPI.ts +2 -1
- package/Server/Utils/Runbook/RunbookExecutePermission.ts +94 -0
- package/Server/Utils/SessionReplay/SessionReplayErasureTombstone.ts +128 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +586 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +188 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1449 -0
- package/Server/Utils/SessionReplay/SessionReplayRetention.ts +114 -0
- package/Server/Utils/SessionReplay/SessionReplayTargeting.ts +144 -0
- package/Server/Utils/SessionReplay/SessionReplayUsage.ts +92 -0
- package/Server/Utils/StartServer.ts +47 -3
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +44 -10
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +557 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +479 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +343 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +320 -0
- package/Tests/Models/AnalyticsModels/RumSessionSchema.test.ts +455 -0
- package/Tests/Models/AnalyticsModels/SessionIdCorrelation.test.ts +158 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +447 -0
- package/Tests/Models/RunnerVersionLabel.test.ts +223 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +1287 -0
- package/Tests/Server/API/LegacyRunbookAgentRoutes.test.ts +117 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +2093 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +429 -0
- package/Tests/Server/API/UserSmsApi.test.ts +68 -2
- package/Tests/Server/DatabaseConfig.test.ts +150 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +2 -2
- package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +2 -3
- package/Tests/Server/Services/AIRunCodeFixClaimScoping.test.ts +100 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +8 -0
- package/Tests/Server/Services/AutoRemediationCommandRuleEngine.test.ts +334 -0
- package/Tests/Server/Services/AutoRemediationRuleEngineService.test.ts +517 -0
- package/Tests/Server/Services/BillingService.test.ts +158 -1
- package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +24 -2
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +17 -10
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +128 -0
- package/Tests/Server/Services/DatabaseServiceUpdateWriteRouting.test.ts +178 -0
- package/Tests/Server/Services/DatabaseServiceUpdateWriteRoutingMonitor.test.ts +206 -0
- package/Tests/Server/Services/MonitorServiceOperationalStatusSelection.test.ts +145 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +726 -0
- package/Tests/Server/Services/NetworkDeviceSiteAssignment.test.ts +570 -9
- package/Tests/Server/Services/NetworkSiteAssignmentRuleService.test.ts +94 -1
- package/Tests/Server/Services/NetworkSiteService.test.ts +85 -0
- package/Tests/Server/Services/RumApplicationReplayHealth.test.ts +114 -0
- package/Tests/Server/Services/RunbookCredentialService.test.ts +657 -0
- package/Tests/Server/Services/SessionReplayCreatePath.test.ts +376 -0
- package/Tests/Server/Services/SessionReplayGateCacheInvalidation.test.ts +211 -0
- package/Tests/Server/Services/SessionReplayMetering.test.ts +174 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +555 -0
- package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +25 -4
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +17 -2
- package/Tests/Server/Services/TraceAggregationService.test.ts +48 -0
- package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +855 -0
- package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +38 -27
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +44 -0
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +475 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +257 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.test.ts +236 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +44 -6
- package/Tests/Server/Utils/AI/CodeFixRunQueue.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +2 -2
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +294 -0
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +47 -3
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +233 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +463 -0
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationAllowlistNormalization.test.ts +248 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +1008 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +887 -0
- package/Tests/Server/Utils/AI/RemediationHandoff.test.ts +306 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +745 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +291 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1050 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +350 -0
- package/Tests/Server/Utils/AutoRemediation/RemediationVerifier.test.ts +341 -0
- package/Tests/Server/Utils/CorsOptions.test.ts +119 -0
- package/Tests/Server/Utils/Database/RelationIdUtil.test.ts +120 -0
- package/Tests/Server/Utils/Database/ResolveReferenceId.test.ts +60 -0
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +337 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +671 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryCapture.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +313 -0
- package/Tests/Server/Utils/PushNotificationUtil.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +177 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +108 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayRetention.test.ts +229 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayTargeting.test.ts +246 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +110 -0
- package/Tests/Types/AI/AIRunStatus.test.ts +25 -0
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +1063 -0
- package/Tests/Types/BaseDatabase/AggregationType.test.ts +56 -0
- package/Tests/Types/CustomField/CustomFieldDropdownOption.test.ts +227 -0
- package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +811 -0
- package/Tests/Types/Dashboard/RumDashboardTemplate.test.ts +1238 -0
- package/Tests/Types/Day/DayOfWeek.test.ts +46 -0
- package/Tests/Types/Docker/DockerInventoryExtractor.test.ts +249 -0
- package/Tests/Types/Kubernetes/KubernetesInventoryExtractor.test.ts +231 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +323 -0
- package/Tests/Types/Metrics/MetricDownsamplingRetentionDays.test.ts +77 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +42 -14
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +2 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +3 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +6 -3
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +285 -0
- package/Tests/Types/Podman/PodmanInventoryExtractor.test.ts +159 -0
- package/Tests/Types/Runbook/RunbookExecutionDeadline.test.ts +268 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +242 -0
- package/Tests/Types/Telemetry/TelemetryRetentionConfig.test.ts +225 -0
- package/Tests/Types/Text.test.ts +60 -0
- package/Tests/UI/Components/AutocompleteTextInputMenu.test.tsx +555 -0
- package/Tests/UI/Components/BulkLabelActions.test.tsx +1502 -0
- package/Tests/UI/Components/BulkOwnerActions.test.tsx +1255 -0
- package/Tests/UI/Components/CustomFields/DropdownOptionsInput.test.tsx +228 -0
- package/Tests/UI/Components/Detail/DropdownColors.test.tsx +128 -0
- package/Tests/UI/Components/Dropdown/DropdownValueBadge.test.tsx +60 -0
- package/Tests/UI/Components/FilterModalOperatorMenu.test.tsx +674 -0
- package/Tests/UI/Components/Forms/ColorPicker.test.tsx +48 -0
- package/Tests/UI/Components/Forms/ColorPickerPopup.test.tsx +222 -0
- package/Tests/UI/Components/Forms/IconPickerPopup.test.tsx +270 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +125 -6
- package/Tests/UI/Components/OperatorSelectorFilterTypes.test.tsx +1595 -0
- package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +851 -0
- package/Tests/UI/Components/OperatorSelectorMenu.test.tsx +1229 -0
- package/Tests/UI/Components/Pagination.test.tsx +85 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +248 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +790 -0
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +236 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +606 -0
- package/Tests/UI/Utils/Timezone.test.ts +86 -0
- package/Tests/UI/Utils/UseClockTick.test.tsx +276 -0
- package/Tests/Utils/AiRemediation/CommandPolicy.test.ts +1063 -0
- package/Tests/Utils/Dashboard/ClockWidgetFormat.test.ts +1374 -0
- package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +362 -0
- package/Tests/Utils/Dashboard/DashboardHtmlComponent.test.ts +217 -0
- package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +656 -0
- package/Tests/Utils/Dashboard/ValueWidgetLayout.test.ts +1007 -0
- package/Tests/Utils/Metrics/MetricSeriesScope.test.ts +1197 -0
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +839 -0
- package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +69 -0
- package/Tests/Utils/Rules/RulePatternMatchUtil.test.ts +274 -0
- package/Tests/Utils/Rum/ChunkMath.test.ts +286 -0
- package/Tests/Utils/Rum/Masking.test.ts +186 -0
- package/Tests/Utils/Rum/SessionIdentity.test.ts +272 -0
- package/Tests/Utils/Rum/SessionSampling.test.ts +164 -0
- package/Tests/Utils/Rum/UrlScrubber.test.ts +175 -0
- package/Tests/Utils/StatusPage/Report.test.ts +708 -0
- package/Tests/Utils/ValueFormatter.test.ts +299 -1
- package/Types/AI/AIRunStatus.ts +18 -7
- package/Types/AI/AIRunType.ts +18 -0
- package/Types/AI/FixVerificationStatus.ts +21 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +16 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +304 -0
- package/Types/AutoRemediation/AutoRemediationExecutionMode.ts +17 -0
- package/Types/AutoRemediation/AutoRemediationSuggestionStatus.ts +35 -0
- package/Types/AutoRemediation/AutoRemediationSuggestionType.ts +17 -0
- package/Types/AutoRemediation/AutoRemediationTriggerEntity.ts +11 -0
- package/Types/AutoRemediation/AutoRemediationVerificationStatus.ts +21 -0
- package/Types/CustomField/CustomFieldDropdownOption.ts +190 -0
- package/Types/Dashboard/DashboardComponentType.ts +2 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +4 -0
- package/Types/Dashboard/DashboardComponents/DashboardClockComponent.ts +45 -0
- package/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts +8 -0
- package/Types/Dashboard/DashboardTemplates.ts +516 -0
- package/Types/Date.ts +6 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/Kubernetes/KubernetesRightSizing.ts +353 -0
- package/Types/MeteredPlan/ProductType.ts +1 -0
- package/Types/Monitor/MonitorStepMetricMonitor.ts +27 -1
- package/Types/Monitor/MonitorSteps.ts +5 -0
- package/Types/Monitor/MonitorSummarySnapshot.ts +88 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +52 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +7 -5
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +101 -20
- package/Types/Monitor/RumAlertTemplates.ts +386 -0
- package/Types/Permission.ts +204 -24
- package/Types/Rum/SessionReplay.ts +452 -0
- package/Types/Rum/SessionReplayCaptureTrigger.ts +26 -0
- package/Types/Rum/SessionReplayConsentMode.ts +27 -0
- package/Types/Rum/SessionReplayMaskingMode.ts +86 -0
- package/Types/Rum/SessionReplayTriggerReason.ts +31 -0
- package/Types/Runbook/RunbookCredentialType.ts +6 -0
- package/Types/Runbook/RunbookStep.ts +66 -1
- package/Types/Runbook/RunbookStepType.ts +26 -0
- package/Types/Runbook/RunnerJobOrigin.ts +20 -0
- package/Types/Runbook/{RunbookAgentJobStatus.ts → RunnerJobStatus.ts} +2 -2
- package/Types/StatusPage/StatusPageReport.ts +99 -0
- package/Types/Telemetry/TelemetryRetentionConfig.ts +18 -1
- package/Types/Text.ts +33 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +260 -75
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +99 -9
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +11 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +19 -14
- package/UI/Components/CustomFields/DropdownOptionsInput.tsx +144 -54
- package/UI/Components/Detail/Detail.tsx +9 -22
- package/UI/Components/Dropdown/Dropdown.tsx +2 -5
- package/UI/Components/Dropdown/DropdownMenuZIndex.ts +7 -0
- package/UI/Components/Dropdown/DropdownValueBadge.tsx +61 -0
- package/UI/Components/Filters/OperatorSelector.tsx +350 -39
- package/UI/Components/Forms/Fields/ColorPicker.tsx +60 -27
- package/UI/Components/Forms/Fields/IconPicker.tsx +89 -60
- package/UI/Components/Forms/Validation.ts +9 -4
- package/UI/Components/List/List.tsx +1 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +41 -32
- package/UI/Components/Pagination/Pagination.tsx +33 -10
- package/UI/Components/Table/Table.tsx +1 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +321 -0
- package/UI/Utils/Dropdown.ts +26 -0
- package/UI/Utils/Timezone.ts +18 -2
- package/UI/Utils/UseClockTick.ts +78 -0
- package/Utils/AiRemediation/CommandPolicy.ts +398 -0
- package/Utils/Dashboard/ClockWidgetFormat.ts +604 -0
- package/Utils/Dashboard/Components/DashboardClockComponent.ts +181 -0
- package/Utils/Dashboard/Components/DashboardHtmlComponent.ts +162 -0
- package/Utils/Dashboard/Components/DashboardValueComponent.ts +12 -1
- package/Utils/Dashboard/Components/Index.ts +14 -0
- package/Utils/Dashboard/HtmlWidgetDocument.ts +309 -0
- package/Utils/Dashboard/ValueWidgetLayout.ts +471 -0
- package/Utils/Metrics/MetricSeriesScope.ts +301 -0
- package/Utils/Monitor/MonitorSummarySnapshotUtil.ts +593 -0
- package/Utils/NetworkSite/CidrMatchUtil.ts +30 -17
- package/Utils/Rules/RulePatternMatchUtil.ts +103 -0
- package/Utils/Rum/ChunkMath.ts +211 -0
- package/Utils/Rum/Masking.ts +168 -0
- package/Utils/Rum/SessionIdentity.ts +230 -0
- package/Utils/Rum/SessionSampling.ts +108 -0
- package/Utils/Rum/UrlScrubber.ts +185 -0
- package/Utils/Schema/ModelSchema.ts +27 -3
- package/Utils/StatusPage/Report.ts +304 -0
- package/Utils/ValueFormatter.ts +300 -54
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +63 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Index.js +10 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +56 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/RumSession.js +1159 -0
- package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +670 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js +56 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +54 -0
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +32 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Alert.js +30 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AutoRemediationRule.js +998 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +917 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js +132 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +327 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +30 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +20 -8
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InstanceHealthLog.js +8 -0
- package/build/dist/Models/DatabaseModels/InstanceHealthLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatus.js +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatus.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +2 -2
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +2 -2
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +235 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +930 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js +591 -0
- package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumSessionPin.js +483 -0
- package/build/dist/Models/DatabaseModels/RumSessionPin.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumSessionReplayView.js +433 -0
- package/build/dist/Models/DatabaseModels/RumSessionReplayView.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunbookCredential.js +693 -0
- package/build/dist/Models/DatabaseModels/RunbookCredential.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunbookSecret.js +8 -8
- package/build/dist/Models/DatabaseModels/RunbookSecret.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{RunbookAgent.js → Runner.js} +235 -70
- package/build/dist/Models/DatabaseModels/Runner.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentJob.js → RunnerJob.js} +173 -40
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerTeam.js → RunnerOwnerTeam.js} +57 -57
- package/build/dist/Models/DatabaseModels/RunnerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerUser.js → RunnerOwnerUser.js} +57 -57
- package/build/dist/Models/DatabaseModels/RunnerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +130 -45
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +26 -36
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskLogAPI.js +11 -28
- package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +416 -0
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -0
- package/build/dist/Server/API/BaseAnalyticsAPI.js +4 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +24 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +265 -6
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +940 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +18 -0
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/DatabaseConfig.js +23 -4
- package/build/dist/Server/DatabaseConfig.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +8 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js +128 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js +88 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js +76 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js +214 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +1 -4
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +26 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +37 -4
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +24 -11
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateService.js +24 -11
- package/build/dist/Server/Services/AlertStateService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +917 -0
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentOwnerTeamService.js → AutoRemediationRuleService.js} +2 -2
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -0
- package/build/dist/Server/Services/AutoRemediationSuggestionService.js +127 -0
- package/build/dist/Server/Services/AutoRemediationSuggestionService.js.map +1 -0
- package/build/dist/Server/Services/BillingService.js +45 -1
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +72 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +52 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +24 -11
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateService.js +24 -11
- package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +18 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +24 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +25 -11
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +12 -11
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js +43 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +12 -11
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js +41 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +129 -20
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js +67 -10
- package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +19 -8
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +43 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +202 -0
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionChunkService.js +9 -0
- package/build/dist/Server/Services/RumSessionChunkService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionErasureRequestService.js +190 -0
- package/build/dist/Server/Services/RumSessionErasureRequestService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionPinService.js +208 -0
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js +180 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionService.js +102 -0
- package/build/dist/Server/Services/RumSessionService.js.map +1 -0
- package/build/dist/Server/Services/RunbookCredentialService.js +67 -0
- package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentJobService.js → RunnerJobService.js} +158 -46
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentOwnerUserService.js → RunnerOwnerTeamService.js} +2 -2
- package/build/dist/Server/Services/RunnerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/RunnerOwnerUserService.js +9 -0
- package/build/dist/Server/Services/RunnerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/RunnerService.js +168 -0
- package/build/dist/Server/Services/RunnerService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateService.js +24 -11
- package/build/dist/Server/Services/ScheduledMaintenanceStateService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +15 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +8 -2
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +173 -16
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +3 -16
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +31 -3
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +106 -2
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +16 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js +9 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +38 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +104 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js +54 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +11 -7
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js +3 -4
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +724 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +668 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +657 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +52 -7
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +53 -5
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +25 -6
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +131 -17
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +200 -5
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js +2 -7
- package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +4 -17
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +203 -19
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +89 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js +82 -0
- package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +4 -19
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +2 -2
- package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js +36 -0
- package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +498 -0
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -0
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +492 -0
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -0
- package/build/dist/Server/Utils/CorsOptions.js +47 -0
- package/build/dist/Server/Utils/CorsOptions.js.map +1 -0
- package/build/dist/Server/Utils/Database/RelationIdUtil.js +51 -0
- package/build/dist/Server/Utils/Database/RelationIdUtil.js.map +1 -0
- package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js +38 -0
- package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +194 -0
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +292 -0
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +16 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js +92 -0
- package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js.map +1 -0
- package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js +27 -0
- package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/OpenAPI.js +2 -1
- package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +67 -0
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js +98 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +378 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +137 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +998 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js +59 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js +98 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +69 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +40 -3
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +27 -6
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
- package/build/dist/Types/AI/AIRunStatus.js +17 -5
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +18 -0
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AI/FixVerificationStatus.js +22 -0
- package/build/dist/Types/AI/FixVerificationStatus.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +16 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +192 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js +18 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js +31 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js +18 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js +12 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js +22 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDropdownOption.js +110 -0
- package/build/dist/Types/CustomField/CustomFieldDropdownOption.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +4 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js +19 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +487 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Date.js +6 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesRightSizing.js +229 -0
- package/build/dist/Types/Kubernetes/KubernetesRightSizing.js.map +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +12 -2
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorSteps.js +5 -0
- package/build/dist/Types/Monitor/MonitorSteps.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorSummarySnapshot.js +13 -0
- package/build/dist/Types/Monitor/MonitorSummarySnapshot.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +40 -8
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +4 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +50 -10
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/RumAlertTemplates.js +297 -0
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +184 -24
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +155 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +26 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayConsentMode.js +27 -0
- package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayMaskingMode.js +79 -0
- package/build/dist/Types/Rum/SessionReplayMaskingMode.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayTriggerReason.js +28 -0
- package/build/dist/Types/Rum/SessionReplayTriggerReason.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookCredentialType.js +7 -0
- package/build/dist/Types/Runbook/RunbookCredentialType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStep.js +20 -1
- package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
- package/build/dist/Types/Runbook/RunbookStepType.js +24 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Runbook/RunnerJobOrigin.js +21 -0
- package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -0
- package/build/dist/Types/Runbook/RunnerJobStatus.js +22 -0
- package/build/dist/Types/Runbook/RunnerJobStatus.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageReport.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReport.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
- package/build/dist/Types/Text.js +29 -0
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +143 -28
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +82 -9
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +11 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +11 -13
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +76 -45
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +3 -8
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +2 -5
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js +7 -0
- package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js +32 -0
- package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js.map +1 -0
- package/build/dist/UI/Components/Filters/OperatorSelector.js +210 -20
- package/build/dist/UI/Components/Filters/OperatorSelector.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +26 -10
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -13
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +9 -5
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +1 -1
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +21 -19
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +14 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +1 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +197 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -0
- package/build/dist/UI/Utils/Dropdown.js +18 -0
- package/build/dist/UI/Utils/Dropdown.js.map +1 -1
- package/build/dist/UI/Utils/Timezone.js +13 -2
- package/build/dist/UI/Utils/Timezone.js.map +1 -1
- package/build/dist/UI/Utils/UseClockTick.js +41 -0
- package/build/dist/UI/Utils/UseClockTick.js.map +1 -0
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +303 -0
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -0
- package/build/dist/Utils/Dashboard/ClockWidgetFormat.js +232 -0
- package/build/dist/Utils/Dashboard/ClockWidgetFormat.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +146 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js +133 -0
- package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js +10 -1
- package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +198 -0
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -0
- package/build/dist/Utils/Dashboard/ValueWidgetLayout.js +276 -0
- package/build/dist/Utils/Dashboard/ValueWidgetLayout.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricSeriesScope.js +203 -0
- package/build/dist/Utils/Metrics/MetricSeriesScope.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js +382 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +13 -7
- package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +87 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js.map +1 -0
- package/build/dist/Utils/Rum/ChunkMath.js +147 -0
- package/build/dist/Utils/Rum/ChunkMath.js.map +1 -0
- package/build/dist/Utils/Rum/Masking.js +143 -0
- package/build/dist/Utils/Rum/Masking.js.map +1 -0
- package/build/dist/Utils/Rum/SessionIdentity.js +161 -0
- package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -0
- package/build/dist/Utils/Rum/SessionSampling.js +91 -0
- package/build/dist/Utils/Rum/SessionSampling.js.map +1 -0
- package/build/dist/Utils/Rum/UrlScrubber.js +144 -0
- package/build/dist/Utils/Rum/UrlScrubber.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +28 -3
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/build/dist/Utils/StatusPage/Report.js +194 -0
- package/build/dist/Utils/StatusPage/Report.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +239 -46
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/jest.config.json +3 -0
- package/package.json +1 -1
- package/tsconfig.json +19 -1
- package/Server/Services/RunbookAgentService.ts +0 -93
- package/build/dist/Models/DatabaseModels/RunbookAgent.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentJob.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentOwnerTeam.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentOwnerUser.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentJobService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentOwnerTeamService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentOwnerUserService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentService.js +0 -91
- package/build/dist/Server/Services/RunbookAgentService.js.map +0 -1
- package/build/dist/Types/Runbook/RunbookAgentJobStatus.js +0 -22
- package/build/dist/Types/Runbook/RunbookAgentJobStatus.js.map +0 -1
|
@@ -5,6 +5,7 @@ import Express, {
|
|
|
5
5
|
ExpressRouter,
|
|
6
6
|
NextFunction,
|
|
7
7
|
RequestHandler,
|
|
8
|
+
getClientIp,
|
|
8
9
|
} from "../Utils/Express";
|
|
9
10
|
import Response from "../Utils/Response";
|
|
10
11
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
@@ -30,6 +31,7 @@ import TraceAggregationService, {
|
|
|
30
31
|
FacetValue as TraceFacetValue,
|
|
31
32
|
MultiFacetRequest as TraceMultiFacetRequest,
|
|
32
33
|
TraceFilters,
|
|
34
|
+
TraceAttributeFilters,
|
|
33
35
|
TraceAnalyticsChartType,
|
|
34
36
|
TraceAnalyticsRequest,
|
|
35
37
|
TraceAnalyticsTimeseriesRow,
|
|
@@ -69,14 +71,53 @@ import ProfileSample from "../../Models/AnalyticsModels/ProfileSample";
|
|
|
69
71
|
import ProfileService from "../Services/ProfileService";
|
|
70
72
|
import ProfileSampleService from "../Services/ProfileSampleService";
|
|
71
73
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
72
|
-
import Permission
|
|
74
|
+
import Permission, {
|
|
75
|
+
PermissionHelper,
|
|
76
|
+
UserPermission,
|
|
77
|
+
} from "../../Types/Permission";
|
|
78
|
+
import DatabaseCommonInteractionPropsUtil, {
|
|
79
|
+
PermissionType,
|
|
80
|
+
} from "../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
|
|
81
|
+
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
|
|
82
|
+
import PaymentRequiredException from "../../Types/Exception/PaymentRequiredException";
|
|
83
|
+
import PermissionScope from "../../Types/Database/AccessControl/PermissionScope";
|
|
84
|
+
import SubscriptionPlan, {
|
|
85
|
+
PlanType,
|
|
86
|
+
} from "../../Types/Billing/SubscriptionPlan";
|
|
87
|
+
import { IsBillingEnabled, getAllEnvVars } from "../EnvironmentConfig";
|
|
88
|
+
import RumSession from "../../Models/AnalyticsModels/RumSession";
|
|
73
89
|
import ObjectID from "../../Types/ObjectID";
|
|
74
90
|
import OneUptimeDate from "../../Types/Date";
|
|
75
|
-
import { JSONObject } from "../../Types/JSON";
|
|
91
|
+
import { JSONArray, JSONObject } from "../../Types/JSON";
|
|
76
92
|
import ResourceFacetResolver, {
|
|
77
93
|
ResolvedFacetValue,
|
|
78
94
|
ResourceFacetSpec,
|
|
79
95
|
} from "../Utils/Telemetry/ResourceFacetResolver";
|
|
96
|
+
import Label from "../../Models/DatabaseModels/Label";
|
|
97
|
+
import RumApplication from "../../Models/DatabaseModels/RumApplication";
|
|
98
|
+
import RumApplicationService from "../Services/RumApplicationService";
|
|
99
|
+
import Project from "../../Models/DatabaseModels/Project";
|
|
100
|
+
import ProjectService from "../Services/ProjectService";
|
|
101
|
+
import SessionReplayTargeting from "../Utils/SessionReplay/SessionReplayTargeting";
|
|
102
|
+
import SessionReplayUsage from "../Utils/SessionReplay/SessionReplayUsage";
|
|
103
|
+
import RumSessionReplayView from "../../Models/DatabaseModels/RumSessionReplayView";
|
|
104
|
+
import RumSessionReplayViewService from "../Services/RumSessionReplayViewService";
|
|
105
|
+
import SessionReplayReadService, {
|
|
106
|
+
DEFAULT_SESSION_REPLAY_LIST_LIMIT,
|
|
107
|
+
MAX_SESSION_REPLAY_FOR_EXCEPTION_LIMIT,
|
|
108
|
+
SessionReplayChunkPayload,
|
|
109
|
+
SessionReplayExceptionSession,
|
|
110
|
+
SessionReplayListCursor,
|
|
111
|
+
SessionReplayListFilters,
|
|
112
|
+
SessionReplayListResult,
|
|
113
|
+
SessionReplayManifest,
|
|
114
|
+
SessionReplaySessionHeader,
|
|
115
|
+
} from "../Utils/SessionReplay/SessionReplayReadService";
|
|
116
|
+
import {
|
|
117
|
+
DEFAULT_SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY,
|
|
118
|
+
MAX_SESSION_REPLAY_CHUNKS_PER_READ,
|
|
119
|
+
MAX_SESSION_REPLAY_READ_BYTES,
|
|
120
|
+
} from "../../Types/Rum/SessionReplay";
|
|
80
121
|
|
|
81
122
|
const router: ExpressRouter = Express.getRouter();
|
|
82
123
|
|
|
@@ -668,6 +709,42 @@ function parseTraceFilterBody(body: JSONObject): TraceFilters {
|
|
|
668
709
|
return Object.fromEntries(entries);
|
|
669
710
|
};
|
|
670
711
|
|
|
712
|
+
/*
|
|
713
|
+
* Exact attribute predicates accept a single value or a list of values
|
|
714
|
+
* (`IN (...)`) — a multi-select dashboard variable resolves to the latter.
|
|
715
|
+
* Non-string array entries are dropped, and an array left empty by that
|
|
716
|
+
* filtering is dropped entirely so it cannot narrow to nothing.
|
|
717
|
+
*/
|
|
718
|
+
const attributeFilterRecord: () => TraceAttributeFilters | undefined = ():
|
|
719
|
+
| TraceAttributeFilters
|
|
720
|
+
| undefined => {
|
|
721
|
+
const raw: unknown = body["attributes"];
|
|
722
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
const filters: TraceAttributeFilters = {};
|
|
726
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
727
|
+
if (typeof value === "string") {
|
|
728
|
+
filters[key] = value;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (Array.isArray(value)) {
|
|
732
|
+
const values: Array<string> = (value as Array<unknown>).filter(
|
|
733
|
+
(v: unknown): v is string => {
|
|
734
|
+
return typeof v === "string";
|
|
735
|
+
},
|
|
736
|
+
);
|
|
737
|
+
if (values.length > 0) {
|
|
738
|
+
filters[key] = values;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (Object.keys(filters).length === 0) {
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
return filters;
|
|
746
|
+
};
|
|
747
|
+
|
|
671
748
|
return {
|
|
672
749
|
serviceIds,
|
|
673
750
|
entityKeys: stringArray("entityKeys"),
|
|
@@ -714,7 +791,7 @@ function parseTraceFilterBody(body: JSONObject): TraceFilters {
|
|
|
714
791
|
: undefined,
|
|
715
792
|
rootOnly:
|
|
716
793
|
body["rootOnly"] === undefined ? undefined : Boolean(body["rootOnly"]),
|
|
717
|
-
attributes:
|
|
794
|
+
attributes: attributeFilterRecord(),
|
|
718
795
|
attributeSearches: stringRecord("attributeSearches"),
|
|
719
796
|
};
|
|
720
797
|
}
|
|
@@ -2714,4 +2791,1546 @@ router.post(
|
|
|
2714
2791
|
},
|
|
2715
2792
|
);
|
|
2716
2793
|
|
|
2794
|
+
/*
|
|
2795
|
+
* ---------------------------------------------------------------------
|
|
2796
|
+
* Session replay playback
|
|
2797
|
+
* ---------------------------------------------------------------------
|
|
2798
|
+
*
|
|
2799
|
+
* These five routes are the ONLY reader of RumSessionV1 / RumSessionChunkV1:
|
|
2800
|
+
* both analytics models deliberately omit `crudApiPath`, so there is no
|
|
2801
|
+
* generic CRUD surface for them and ModelPermission is NEVER invoked on
|
|
2802
|
+
* this path.
|
|
2803
|
+
*
|
|
2804
|
+
* That has a consequence which is easy to get wrong and expensive to get
|
|
2805
|
+
* wrong: the `payload` column's own read ACL and the narrower ACL on
|
|
2806
|
+
* `identifiedUserLabel` are, on their own, decorative here. Nothing
|
|
2807
|
+
* enforces them unless this file does. Hence a dedicated guard rather
|
|
2808
|
+
* than createTelemetryReadAccessGuard above, whose OR-list already
|
|
2809
|
+
* contains ProjectMember, Viewer and TelemetryViewer - reusing it would
|
|
2810
|
+
* let every read-only member of a project watch recordings of that
|
|
2811
|
+
* project's real end users, which is precisely the outcome the design
|
|
2812
|
+
* rejected.
|
|
2813
|
+
*
|
|
2814
|
+
* Declared before the route registrations below for the same
|
|
2815
|
+
* temporal-dead-zone reason the guards at the top of this file are.
|
|
2816
|
+
*/
|
|
2817
|
+
|
|
2818
|
+
/*
|
|
2819
|
+
* Listing sessions. Mirrors RumSession's table-level read ACL exactly:
|
|
2820
|
+
* knowing WHICH sessions errored is triage.
|
|
2821
|
+
*/
|
|
2822
|
+
const requireSessionReplayListAccess: Array<RequestHandler> = [
|
|
2823
|
+
UserMiddleware.getUserMiddleware,
|
|
2824
|
+
UserMiddleware.requireUserAuthentication,
|
|
2825
|
+
UserMiddleware.requirePermission({
|
|
2826
|
+
permissions: [
|
|
2827
|
+
Permission.ProjectOwner,
|
|
2828
|
+
Permission.ProjectAdmin,
|
|
2829
|
+
Permission.TelemetryAdmin,
|
|
2830
|
+
Permission.ReadRumSessionReplay,
|
|
2831
|
+
],
|
|
2832
|
+
}),
|
|
2833
|
+
];
|
|
2834
|
+
|
|
2835
|
+
/*
|
|
2836
|
+
* Watching a session. Deliberately a strict subset of the list guard's
|
|
2837
|
+
* audience: reading a recording of a real person's screen is a further
|
|
2838
|
+
* disclosure over knowing that their session went wrong. Mirrors the
|
|
2839
|
+
* `payload` column ACL on RumSessionChunk.
|
|
2840
|
+
*/
|
|
2841
|
+
const requireSessionReplayPayloadAccess: Array<RequestHandler> = [
|
|
2842
|
+
UserMiddleware.getUserMiddleware,
|
|
2843
|
+
UserMiddleware.requireUserAuthentication,
|
|
2844
|
+
UserMiddleware.requirePermission({
|
|
2845
|
+
permissions: [
|
|
2846
|
+
Permission.ProjectOwner,
|
|
2847
|
+
Permission.ProjectAdmin,
|
|
2848
|
+
Permission.TelemetryAdmin,
|
|
2849
|
+
Permission.ReadRumSessionReplayPayload,
|
|
2850
|
+
],
|
|
2851
|
+
}),
|
|
2852
|
+
];
|
|
2853
|
+
|
|
2854
|
+
/* The permission list each guard above enforces, reused for label scope. */
|
|
2855
|
+
const SESSION_REPLAY_LIST_PERMISSIONS: Array<Permission> = [
|
|
2856
|
+
Permission.ProjectOwner,
|
|
2857
|
+
Permission.ProjectAdmin,
|
|
2858
|
+
Permission.TelemetryAdmin,
|
|
2859
|
+
Permission.ReadRumSessionReplay,
|
|
2860
|
+
];
|
|
2861
|
+
|
|
2862
|
+
const SESSION_REPLAY_PAYLOAD_PERMISSIONS: Array<Permission> = [
|
|
2863
|
+
Permission.ProjectOwner,
|
|
2864
|
+
Permission.ProjectAdmin,
|
|
2865
|
+
Permission.TelemetryAdmin,
|
|
2866
|
+
Permission.ReadRumSessionReplayPayload,
|
|
2867
|
+
];
|
|
2868
|
+
|
|
2869
|
+
/*
|
|
2870
|
+
* The raw end-user identifier carries its own narrower ACL on the model
|
|
2871
|
+
* (no TelemetryAdmin): knowing that *a* user had a bad session is triage,
|
|
2872
|
+
* knowing *which named person* is a further disclosure again.
|
|
2873
|
+
*/
|
|
2874
|
+
const SESSION_REPLAY_IDENTITY_PERMISSIONS: Array<Permission> = [
|
|
2875
|
+
Permission.ProjectOwner,
|
|
2876
|
+
Permission.ProjectAdmin,
|
|
2877
|
+
Permission.ReadRumSessionReplayPayload,
|
|
2878
|
+
];
|
|
2879
|
+
|
|
2880
|
+
/*
|
|
2881
|
+
* Label scope for a set of permissions, mirroring the private
|
|
2882
|
+
* AccessControlPermission.getAccessControlIdsByPermissions.
|
|
2883
|
+
*
|
|
2884
|
+
* Returned as a discriminated result rather than as an array whose
|
|
2885
|
+
* emptiness has to be interpreted. An empty array previously meant BOTH
|
|
2886
|
+
* "unrestricted" and "we could not work out a restriction", and every
|
|
2887
|
+
* caller read it as the former - which is a fail-OPEN default for the one
|
|
2888
|
+
* kind of answer that must fail closed.
|
|
2889
|
+
*
|
|
2890
|
+
* Reimplemented here rather than routed through RumApplicationService
|
|
2891
|
+
* with the caller's props on purpose: RumApplication's own read ACL
|
|
2892
|
+
* requires ReadRumApplication / ProjectMember / Viewer, none of which a
|
|
2893
|
+
* narrowly-scoped session-replay reviewer necessarily holds, so going
|
|
2894
|
+
* through it would deny legitimate callers.
|
|
2895
|
+
*/
|
|
2896
|
+
type SessionReplayScope =
|
|
2897
|
+
| { isUnrestricted: true }
|
|
2898
|
+
/*
|
|
2899
|
+
* At least one label the RUM application must carry. An EMPTY array
|
|
2900
|
+
* here means the caller reaches nothing at all, which is a real and
|
|
2901
|
+
* different answer from "unrestricted".
|
|
2902
|
+
*/
|
|
2903
|
+
| { isUnrestricted: false; labelIds: Array<ObjectID> };
|
|
2904
|
+
|
|
2905
|
+
type SessionReplayLabelScopeFunction = (
|
|
2906
|
+
databaseProps: DatabaseCommonInteractionProps,
|
|
2907
|
+
permissions: Array<Permission>,
|
|
2908
|
+
) => SessionReplayScope;
|
|
2909
|
+
|
|
2910
|
+
const getSessionReplayLabelScope: SessionReplayLabelScopeFunction = (
|
|
2911
|
+
databaseProps: DatabaseCommonInteractionProps,
|
|
2912
|
+
permissions: Array<Permission>,
|
|
2913
|
+
): SessionReplayScope => {
|
|
2914
|
+
if (databaseProps.isRoot || databaseProps.isMasterAdmin) {
|
|
2915
|
+
return { isUnrestricted: true };
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
const userPermissions: Array<UserPermission> =
|
|
2919
|
+
DatabaseCommonInteractionPropsUtil.getUserPermissions(
|
|
2920
|
+
databaseProps,
|
|
2921
|
+
PermissionType.Allow,
|
|
2922
|
+
);
|
|
2923
|
+
|
|
2924
|
+
const unscopedPermissions: Array<Permission> =
|
|
2925
|
+
PermissionHelper.getNonAccessControlPermissions(userPermissions);
|
|
2926
|
+
|
|
2927
|
+
if (
|
|
2928
|
+
PermissionHelper.doesPermissionsIntersect(permissions, unscopedPermissions)
|
|
2929
|
+
) {
|
|
2930
|
+
return { isUnrestricted: true };
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
const scopedPermissions: Array<UserPermission> =
|
|
2934
|
+
PermissionHelper.getAccessControlPermissions(userPermissions);
|
|
2935
|
+
|
|
2936
|
+
const labelIds: Array<ObjectID> = [];
|
|
2937
|
+
|
|
2938
|
+
for (const permission of permissions) {
|
|
2939
|
+
for (const scopedPermission of scopedPermissions) {
|
|
2940
|
+
if (
|
|
2941
|
+
scopedPermission.permission === permission &&
|
|
2942
|
+
scopedPermission.labelIds.length > 0
|
|
2943
|
+
) {
|
|
2944
|
+
labelIds.push(...scopedPermission.labelIds);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
if (labelIds.length > 0) {
|
|
2950
|
+
return { isUnrestricted: false, labelIds: labelIds };
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
/*
|
|
2954
|
+
* Neither an unscoped grant nor a label-scoped one, yet the route guard
|
|
2955
|
+
* let the caller in - so the grant they hold carries a scope this
|
|
2956
|
+
* bespoke path does not implement. PermissionScope.Owned is the real
|
|
2957
|
+
* case: both PermissionHelper filters exclude Owned rows on purpose
|
|
2958
|
+
* because the ORM enforces them through
|
|
2959
|
+
* OwnedScopePermission.addOwnedScopeToQuery, and there is no such step
|
|
2960
|
+
* here. Refusing is the only safe answer; returning "no labels" would
|
|
2961
|
+
* hand an administrator's deliberately narrowed reviewer the whole
|
|
2962
|
+
* project.
|
|
2963
|
+
*/
|
|
2964
|
+
const hasOwnedScopedGrant: boolean = userPermissions.some(
|
|
2965
|
+
(userPermission: UserPermission): boolean => {
|
|
2966
|
+
return (
|
|
2967
|
+
userPermission.scope === PermissionScope.Owned &&
|
|
2968
|
+
permissions.includes(userPermission.permission)
|
|
2969
|
+
);
|
|
2970
|
+
},
|
|
2971
|
+
);
|
|
2972
|
+
|
|
2973
|
+
if (hasOwnedScopedGrant) {
|
|
2974
|
+
throw new NotAuthorizedException(
|
|
2975
|
+
"Owned-scoped session replay permissions are not supported. Ask an administrator to scope this permission to labels instead.",
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/*
|
|
2980
|
+
* No applicable grant at all. This is reachable even behind the route
|
|
2981
|
+
* guard, because the guard matches on permission NAME across every
|
|
2982
|
+
* tenant permission row while getUserPermissions drops block rows. It
|
|
2983
|
+
* means the caller reaches no application, not every application.
|
|
2984
|
+
*/
|
|
2985
|
+
return { isUnrestricted: false, labelIds: [] };
|
|
2986
|
+
};
|
|
2987
|
+
|
|
2988
|
+
/*
|
|
2989
|
+
* Ceiling on the RUM applications scanned when resolving a label-scoped
|
|
2990
|
+
* caller's reachable set. RUM is keyed by application (service.name), not
|
|
2991
|
+
* by end-user device, so a project has tens of these, not millions.
|
|
2992
|
+
*/
|
|
2993
|
+
const MAX_RUM_APPLICATIONS_SCANNED: number = 1000;
|
|
2994
|
+
|
|
2995
|
+
/*
|
|
2996
|
+
* Plan gate for the replay reads.
|
|
2997
|
+
*
|
|
2998
|
+
* Both replay models declare tableBillingAccessControl.read =
|
|
2999
|
+
* PlanType.Growth, and that declaration is enforced by ModelPermission -
|
|
3000
|
+
* which is never invoked on this path, because neither model has a
|
|
3001
|
+
* crudApiPath. Exactly the same trap as the permission ACLs above, so it
|
|
3002
|
+
* gets the same treatment: the declaration is read off the model and
|
|
3003
|
+
* applied here rather than being left decorative.
|
|
3004
|
+
*/
|
|
3005
|
+
type AssertSessionReplayPlanFunction = (
|
|
3006
|
+
databaseProps: DatabaseCommonInteractionProps,
|
|
3007
|
+
) => void;
|
|
3008
|
+
|
|
3009
|
+
const assertSessionReplayPlan: AssertSessionReplayPlanFunction = (
|
|
3010
|
+
databaseProps: DatabaseCommonInteractionProps,
|
|
3011
|
+
): void => {
|
|
3012
|
+
if (!IsBillingEnabled || !databaseProps.currentPlan) {
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
const requiredPlan: PlanType | null = new RumSession().getReadBillingPlan();
|
|
3017
|
+
|
|
3018
|
+
if (!requiredPlan) {
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
if (
|
|
3023
|
+
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
|
3024
|
+
requiredPlan,
|
|
3025
|
+
databaseProps.currentPlan,
|
|
3026
|
+
getAllEnvVars(),
|
|
3027
|
+
)
|
|
3028
|
+
) {
|
|
3029
|
+
throw new PaymentRequiredException(
|
|
3030
|
+
`Please upgrade your plan to ${requiredPlan} to access session replay.`,
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
};
|
|
3034
|
+
|
|
3035
|
+
/* Label ids carried by a RUM application, read as root. */
|
|
3036
|
+
type GetRumApplicationLabelIdsFunction = (
|
|
3037
|
+
application: RumApplication,
|
|
3038
|
+
) => Array<string>;
|
|
3039
|
+
|
|
3040
|
+
const getRumApplicationLabelIds: GetRumApplicationLabelIdsFunction = (
|
|
3041
|
+
application: RumApplication,
|
|
3042
|
+
): Array<string> => {
|
|
3043
|
+
return (application.labels || [])
|
|
3044
|
+
.filter((label: Label): boolean => {
|
|
3045
|
+
return Boolean(label.id);
|
|
3046
|
+
})
|
|
3047
|
+
.map((label: Label): string => {
|
|
3048
|
+
return label.id!.toString();
|
|
3049
|
+
});
|
|
3050
|
+
};
|
|
3051
|
+
|
|
3052
|
+
/*
|
|
3053
|
+
* Confirm the caller may reach this RUM application.
|
|
3054
|
+
*
|
|
3055
|
+
* `rumApplicationId` must always be the value resolved from the session
|
|
3056
|
+
* header server-side, never one taken from the request body - taking it
|
|
3057
|
+
* from the body would make the check circular and let anyone name an
|
|
3058
|
+
* application they do happen to have access to while reading a session
|
|
3059
|
+
* from one they do not.
|
|
3060
|
+
*
|
|
3061
|
+
* The application is loaded as root and the label intersection is done
|
|
3062
|
+
* here rather than pushed into the query, because the caller may
|
|
3063
|
+
* legitimately hold no RumApplication read permission at all (see the
|
|
3064
|
+
* note on getSessionReplayLabelScope) and because the many-to-many label
|
|
3065
|
+
* predicate has no typed form on the ORM query surface. Project scope is
|
|
3066
|
+
* already guaranteed: the header row was fetched with
|
|
3067
|
+
* `WHERE projectId = <tenantId>`, so the id cannot come from another
|
|
3068
|
+
* project.
|
|
3069
|
+
*/
|
|
3070
|
+
type IsApplicationInSessionReplayScopeFunction = (data: {
|
|
3071
|
+
scope: SessionReplayScope;
|
|
3072
|
+
application: RumApplication;
|
|
3073
|
+
}) => boolean;
|
|
3074
|
+
|
|
3075
|
+
const isApplicationInSessionReplayScope: IsApplicationInSessionReplayScopeFunction =
|
|
3076
|
+
(data: {
|
|
3077
|
+
scope: SessionReplayScope;
|
|
3078
|
+
application: RumApplication;
|
|
3079
|
+
}): boolean => {
|
|
3080
|
+
if (data.scope.isUnrestricted) {
|
|
3081
|
+
return true;
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
const applicationLabelIds: Array<string> = getRumApplicationLabelIds(
|
|
3085
|
+
data.application,
|
|
3086
|
+
);
|
|
3087
|
+
|
|
3088
|
+
return data.scope.labelIds.some((labelId: ObjectID): boolean => {
|
|
3089
|
+
return applicationLabelIds.includes(labelId.toString());
|
|
3090
|
+
});
|
|
3091
|
+
};
|
|
3092
|
+
|
|
3093
|
+
/*
|
|
3094
|
+
* Returns the application so callers that need its labels for a second,
|
|
3095
|
+
* narrower decision (the identity column) do not have to load it twice.
|
|
3096
|
+
*/
|
|
3097
|
+
type AssertSessionReplayApplicationAccessFunction = (data: {
|
|
3098
|
+
projectId: ObjectID;
|
|
3099
|
+
rumApplicationId: ObjectID;
|
|
3100
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3101
|
+
permissions: Array<Permission>;
|
|
3102
|
+
}) => Promise<RumApplication>;
|
|
3103
|
+
|
|
3104
|
+
const assertSessionReplayApplicationAccess: AssertSessionReplayApplicationAccessFunction =
|
|
3105
|
+
async (data: {
|
|
3106
|
+
projectId: ObjectID;
|
|
3107
|
+
rumApplicationId: ObjectID;
|
|
3108
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3109
|
+
permissions: Array<Permission>;
|
|
3110
|
+
}): Promise<RumApplication> => {
|
|
3111
|
+
const scope: SessionReplayScope = getSessionReplayLabelScope(
|
|
3112
|
+
data.databaseProps,
|
|
3113
|
+
data.permissions,
|
|
3114
|
+
);
|
|
3115
|
+
|
|
3116
|
+
const application: RumApplication | null =
|
|
3117
|
+
await RumApplicationService.findOneBy({
|
|
3118
|
+
query: {
|
|
3119
|
+
_id: data.rumApplicationId.toString(),
|
|
3120
|
+
projectId: data.projectId,
|
|
3121
|
+
},
|
|
3122
|
+
select: {
|
|
3123
|
+
_id: true,
|
|
3124
|
+
labels: {
|
|
3125
|
+
_id: true,
|
|
3126
|
+
},
|
|
3127
|
+
},
|
|
3128
|
+
props: {
|
|
3129
|
+
isRoot: true,
|
|
3130
|
+
},
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
/*
|
|
3134
|
+
* An application that does not exist in this project is refused the
|
|
3135
|
+
* same way an out-of-scope one is, so the endpoint cannot be used to
|
|
3136
|
+
* probe which application ids exist elsewhere.
|
|
3137
|
+
*/
|
|
3138
|
+
if (!application) {
|
|
3139
|
+
throw new NotAuthorizedException(
|
|
3140
|
+
"You do not have access to session replays for this application.",
|
|
3141
|
+
);
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
if (
|
|
3145
|
+
!isApplicationInSessionReplayScope({
|
|
3146
|
+
scope: scope,
|
|
3147
|
+
application: application,
|
|
3148
|
+
})
|
|
3149
|
+
) {
|
|
3150
|
+
throw new NotAuthorizedException(
|
|
3151
|
+
"You do not have access to session replays for this application.",
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
return application;
|
|
3156
|
+
};
|
|
3157
|
+
|
|
3158
|
+
/*
|
|
3159
|
+
* May the caller see the RAW end-user identifier for THIS application?
|
|
3160
|
+
*
|
|
3161
|
+
* Holding the identity permission somewhere in the project is not enough.
|
|
3162
|
+
* identifiedUserLabel carries the narrowest ACL in the schema, and a
|
|
3163
|
+
* caller can legitimately hold ReadRumSessionReplay unscoped (so the list
|
|
3164
|
+
* route admits them for every application) while their
|
|
3165
|
+
* ReadRumSessionReplayPayload grant is scoped to a single label. Checking
|
|
3166
|
+
* only the permission name would hand them named end users for every
|
|
3167
|
+
* application in the project.
|
|
3168
|
+
*/
|
|
3169
|
+
type CanReadIdentifiedUserLabelFunction = (data: {
|
|
3170
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3171
|
+
application: RumApplication;
|
|
3172
|
+
}) => boolean;
|
|
3173
|
+
|
|
3174
|
+
const canReadIdentifiedUserLabel: CanReadIdentifiedUserLabelFunction = (data: {
|
|
3175
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3176
|
+
application: RumApplication;
|
|
3177
|
+
}): boolean => {
|
|
3178
|
+
let scope: SessionReplayScope;
|
|
3179
|
+
|
|
3180
|
+
try {
|
|
3181
|
+
scope = getSessionReplayLabelScope(
|
|
3182
|
+
data.databaseProps,
|
|
3183
|
+
SESSION_REPLAY_IDENTITY_PERMISSIONS,
|
|
3184
|
+
);
|
|
3185
|
+
} catch {
|
|
3186
|
+
/*
|
|
3187
|
+
* getSessionReplayLabelScope refuses a scope it cannot enforce. For
|
|
3188
|
+
* an optional column the right answer is to omit the column, not to
|
|
3189
|
+
* fail the whole listing the caller is otherwise entitled to.
|
|
3190
|
+
*/
|
|
3191
|
+
return false;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
return isApplicationInSessionReplayScope({
|
|
3195
|
+
scope: scope,
|
|
3196
|
+
application: data.application,
|
|
3197
|
+
});
|
|
3198
|
+
};
|
|
3199
|
+
|
|
3200
|
+
/*
|
|
3201
|
+
* The set of applications a label-scoped caller may reach, for the
|
|
3202
|
+
* project-wide exception lookup which has no single application to
|
|
3203
|
+
* resolve. null means unrestricted; an empty array means the caller can
|
|
3204
|
+
* reach none, which must return no rows rather than everything.
|
|
3205
|
+
*/
|
|
3206
|
+
interface AccessibleRumApplications {
|
|
3207
|
+
/* null means unrestricted; see getSessionsForException. */
|
|
3208
|
+
applicationIds: Array<ObjectID> | null;
|
|
3209
|
+
/*
|
|
3210
|
+
* True when the project holds more RUM applications than one scan can
|
|
3211
|
+
* cover, so the accessible set may be short. Surfaced rather than
|
|
3212
|
+
* swallowed: a quietly incomplete answer to "which sessions saw this
|
|
3213
|
+
* exception" is the same failure mode as timeout_overflow_mode =
|
|
3214
|
+
* 'break', which this whole read path refuses elsewhere.
|
|
3215
|
+
*/
|
|
3216
|
+
isTruncated: boolean;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
type ResolveAccessibleRumApplicationIdsFunction = (data: {
|
|
3220
|
+
projectId: ObjectID;
|
|
3221
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3222
|
+
permissions: Array<Permission>;
|
|
3223
|
+
}) => Promise<AccessibleRumApplications>;
|
|
3224
|
+
|
|
3225
|
+
const resolveAccessibleRumApplicationIds: ResolveAccessibleRumApplicationIdsFunction =
|
|
3226
|
+
async (data: {
|
|
3227
|
+
projectId: ObjectID;
|
|
3228
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3229
|
+
permissions: Array<Permission>;
|
|
3230
|
+
}): Promise<AccessibleRumApplications> => {
|
|
3231
|
+
const scope: SessionReplayScope = getSessionReplayLabelScope(
|
|
3232
|
+
data.databaseProps,
|
|
3233
|
+
data.permissions,
|
|
3234
|
+
);
|
|
3235
|
+
|
|
3236
|
+
if (scope.isUnrestricted) {
|
|
3237
|
+
return { applicationIds: null, isTruncated: false };
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
if (scope.labelIds.length === 0) {
|
|
3241
|
+
/* Reaches no application at all - not "reaches everything". */
|
|
3242
|
+
return { applicationIds: [], isTruncated: false };
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
const applications: Array<RumApplication> =
|
|
3246
|
+
await RumApplicationService.findBy({
|
|
3247
|
+
query: {
|
|
3248
|
+
projectId: data.projectId,
|
|
3249
|
+
},
|
|
3250
|
+
select: {
|
|
3251
|
+
_id: true,
|
|
3252
|
+
labels: {
|
|
3253
|
+
_id: true,
|
|
3254
|
+
},
|
|
3255
|
+
},
|
|
3256
|
+
/*
|
|
3257
|
+
* A deterministic sort so the page that is scanned is at least
|
|
3258
|
+
* stable between calls, and one row past the ceiling so hitting
|
|
3259
|
+
* it is detectable rather than indistinguishable from a project
|
|
3260
|
+
* that happens to have exactly that many applications.
|
|
3261
|
+
*/
|
|
3262
|
+
sort: {
|
|
3263
|
+
createdAt: SortOrder.Ascending,
|
|
3264
|
+
},
|
|
3265
|
+
skip: 0,
|
|
3266
|
+
limit: MAX_RUM_APPLICATIONS_SCANNED + 1,
|
|
3267
|
+
props: {
|
|
3268
|
+
isRoot: true,
|
|
3269
|
+
},
|
|
3270
|
+
});
|
|
3271
|
+
|
|
3272
|
+
const isTruncated: boolean =
|
|
3273
|
+
applications.length > MAX_RUM_APPLICATIONS_SCANNED;
|
|
3274
|
+
|
|
3275
|
+
const accessibleIds: Array<ObjectID> = [];
|
|
3276
|
+
|
|
3277
|
+
for (const application of applications.slice(
|
|
3278
|
+
0,
|
|
3279
|
+
MAX_RUM_APPLICATIONS_SCANNED,
|
|
3280
|
+
)) {
|
|
3281
|
+
if (
|
|
3282
|
+
isApplicationInSessionReplayScope({
|
|
3283
|
+
scope: scope,
|
|
3284
|
+
application: application,
|
|
3285
|
+
}) &&
|
|
3286
|
+
application.id
|
|
3287
|
+
) {
|
|
3288
|
+
accessibleIds.push(application.id);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
return { applicationIds: accessibleIds, isTruncated: isTruncated };
|
|
3293
|
+
};
|
|
3294
|
+
|
|
3295
|
+
/*
|
|
3296
|
+
* Resolve a caller-supplied sessionId to its header, scoped strictly to
|
|
3297
|
+
* the tenant, and authorize the owning application. Every payload-bearing
|
|
3298
|
+
* route funnels through this so the authorization decision exists in
|
|
3299
|
+
* exactly one place.
|
|
3300
|
+
*/
|
|
3301
|
+
type ResolveAuthorizedSessionFunction = (data: {
|
|
3302
|
+
projectId: ObjectID;
|
|
3303
|
+
sessionId: string;
|
|
3304
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3305
|
+
}) => Promise<SessionReplaySessionHeader>;
|
|
3306
|
+
|
|
3307
|
+
const resolveAuthorizedSession: ResolveAuthorizedSessionFunction =
|
|
3308
|
+
async (data: {
|
|
3309
|
+
projectId: ObjectID;
|
|
3310
|
+
sessionId: string;
|
|
3311
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
3312
|
+
}): Promise<SessionReplaySessionHeader> => {
|
|
3313
|
+
const header: SessionReplaySessionHeader | null =
|
|
3314
|
+
await SessionReplayReadService.getSessionHeader({
|
|
3315
|
+
projectId: data.projectId,
|
|
3316
|
+
sessionId: data.sessionId,
|
|
3317
|
+
});
|
|
3318
|
+
|
|
3319
|
+
if (!header) {
|
|
3320
|
+
throw new BadDataException("Session replay not found.");
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
await assertSessionReplayApplicationAccess({
|
|
3324
|
+
projectId: data.projectId,
|
|
3325
|
+
rumApplicationId: new ObjectID(header.rumApplicationId),
|
|
3326
|
+
databaseProps: data.databaseProps,
|
|
3327
|
+
permissions: SESSION_REPLAY_PAYLOAD_PERMISSIONS,
|
|
3328
|
+
});
|
|
3329
|
+
|
|
3330
|
+
return header;
|
|
3331
|
+
};
|
|
3332
|
+
|
|
3333
|
+
/*
|
|
3334
|
+
* Object ids arrive as untrusted strings. They are always bound as query
|
|
3335
|
+
* parameters rather than interpolated, so a bad value cannot inject SQL -
|
|
3336
|
+
* but an object or a number would silently stringify to something like
|
|
3337
|
+
* "[object Object]" and match nothing, turning a client bug into a
|
|
3338
|
+
* confusing empty result instead of an error.
|
|
3339
|
+
*
|
|
3340
|
+
* The shape is checked as well as the type. ObjectID's constructor
|
|
3341
|
+
* validates nothing, so an arbitrary string reaches Postgres as a uuid
|
|
3342
|
+
* literal and ClickHouse as a String bound against a UUID column - both
|
|
3343
|
+
* of which raise a driver error and surface as a 500. A malformed id is a
|
|
3344
|
+
* bad request, not a server fault.
|
|
3345
|
+
*/
|
|
3346
|
+
type ReadObjectIdFromBodyFunction = (body: JSONObject, key: string) => ObjectID;
|
|
3347
|
+
|
|
3348
|
+
const readObjectIdFromBody: ReadObjectIdFromBodyFunction = (
|
|
3349
|
+
body: JSONObject,
|
|
3350
|
+
key: string,
|
|
3351
|
+
): ObjectID => {
|
|
3352
|
+
const value: unknown = body[key];
|
|
3353
|
+
|
|
3354
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3355
|
+
throw new BadDataException(`${key} is required`);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
if (!ObjectID.isValidUUID(value)) {
|
|
3359
|
+
throw new BadDataException(`${key} is not a valid id`);
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
return new ObjectID(value);
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3365
|
+
type ReadSessionIdFromBodyFunction = (body: JSONObject) => string;
|
|
3366
|
+
|
|
3367
|
+
const readSessionIdFromBody: ReadSessionIdFromBodyFunction = (
|
|
3368
|
+
body: JSONObject,
|
|
3369
|
+
): string => {
|
|
3370
|
+
const sessionId: unknown = body["sessionId"];
|
|
3371
|
+
|
|
3372
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
3373
|
+
throw new BadDataException("sessionId is required");
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
return sessionId;
|
|
3377
|
+
};
|
|
3378
|
+
|
|
3379
|
+
type ReadStringArrayFromBodyFunction = (
|
|
3380
|
+
body: JSONObject,
|
|
3381
|
+
key: string,
|
|
3382
|
+
) => Array<string> | undefined;
|
|
3383
|
+
|
|
3384
|
+
const readStringArrayFromBody: ReadStringArrayFromBodyFunction = (
|
|
3385
|
+
body: JSONObject,
|
|
3386
|
+
key: string,
|
|
3387
|
+
): Array<string> | undefined => {
|
|
3388
|
+
const value: unknown = body[key];
|
|
3389
|
+
|
|
3390
|
+
if (!Array.isArray(value)) {
|
|
3391
|
+
return undefined;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
const strings: Array<string> = value.filter(
|
|
3395
|
+
(item: unknown): item is string => {
|
|
3396
|
+
return typeof item === "string" && item.length > 0;
|
|
3397
|
+
},
|
|
3398
|
+
);
|
|
3399
|
+
|
|
3400
|
+
return strings.length > 0 ? strings : undefined;
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3403
|
+
// --- Session Replay List Endpoint ---
|
|
3404
|
+
|
|
3405
|
+
router.post(
|
|
3406
|
+
"/telemetry/rum/session-replay/list",
|
|
3407
|
+
...requireSessionReplayListAccess,
|
|
3408
|
+
async (
|
|
3409
|
+
req: ExpressRequest,
|
|
3410
|
+
res: ExpressResponse,
|
|
3411
|
+
next: NextFunction,
|
|
3412
|
+
): Promise<void> => {
|
|
3413
|
+
try {
|
|
3414
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
3415
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
3416
|
+
|
|
3417
|
+
if (!databaseProps?.tenantId) {
|
|
3418
|
+
return Response.sendErrorResponse(
|
|
3419
|
+
req,
|
|
3420
|
+
res,
|
|
3421
|
+
new BadDataException("Invalid Project ID"),
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
assertSessionReplayPlan(databaseProps);
|
|
3426
|
+
|
|
3427
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
3428
|
+
const body: JSONObject = req.body as JSONObject;
|
|
3429
|
+
|
|
3430
|
+
const rumApplicationId: ObjectID = readObjectIdFromBody(
|
|
3431
|
+
body,
|
|
3432
|
+
"rumApplicationId",
|
|
3433
|
+
);
|
|
3434
|
+
|
|
3435
|
+
/*
|
|
3436
|
+
* The application id is caller-supplied here, which is safe only
|
|
3437
|
+
* because it is the thing being authorized rather than a lookup key
|
|
3438
|
+
* for something already authorized: a caller outside the label
|
|
3439
|
+
* scope is refused, and the query is tenant-pinned regardless.
|
|
3440
|
+
*/
|
|
3441
|
+
const application: RumApplication =
|
|
3442
|
+
await assertSessionReplayApplicationAccess({
|
|
3443
|
+
projectId: projectId,
|
|
3444
|
+
rumApplicationId: rumApplicationId,
|
|
3445
|
+
databaseProps: databaseProps,
|
|
3446
|
+
permissions: SESSION_REPLAY_LIST_PERMISSIONS,
|
|
3447
|
+
});
|
|
3448
|
+
|
|
3449
|
+
const startTime: Date = body["startTime"]
|
|
3450
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
3451
|
+
: OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -7);
|
|
3452
|
+
|
|
3453
|
+
const endTime: Date = body["endTime"]
|
|
3454
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
3455
|
+
: OneUptimeDate.getCurrentDate();
|
|
3456
|
+
|
|
3457
|
+
const rawFilters: JSONObject = (body["filters"] as JSONObject) || {};
|
|
3458
|
+
|
|
3459
|
+
const filters: SessionReplayListFilters = {
|
|
3460
|
+
...(typeof rawFilters["hasError"] === "boolean" && {
|
|
3461
|
+
hasError: rawFilters["hasError"],
|
|
3462
|
+
}),
|
|
3463
|
+
...(typeof rawFilters["hasFrustration"] === "boolean" && {
|
|
3464
|
+
hasFrustration: rawFilters["hasFrustration"],
|
|
3465
|
+
}),
|
|
3466
|
+
...(typeof rawFilters["isFinalized"] === "boolean" && {
|
|
3467
|
+
isFinalized: rawFilters["isFinalized"],
|
|
3468
|
+
}),
|
|
3469
|
+
...(readStringArrayFromBody(rawFilters, "triggerReasons") && {
|
|
3470
|
+
triggerReasons: readStringArrayFromBody(rawFilters, "triggerReasons"),
|
|
3471
|
+
}),
|
|
3472
|
+
...(readStringArrayFromBody(rawFilters, "browserNames") && {
|
|
3473
|
+
browserNames: readStringArrayFromBody(rawFilters, "browserNames"),
|
|
3474
|
+
}),
|
|
3475
|
+
...(readStringArrayFromBody(rawFilters, "osNames") && {
|
|
3476
|
+
osNames: readStringArrayFromBody(rawFilters, "osNames"),
|
|
3477
|
+
}),
|
|
3478
|
+
...(readStringArrayFromBody(rawFilters, "deviceTypes") && {
|
|
3479
|
+
deviceTypes: readStringArrayFromBody(rawFilters, "deviceTypes"),
|
|
3480
|
+
}),
|
|
3481
|
+
...(readStringArrayFromBody(rawFilters, "countryCodes") && {
|
|
3482
|
+
countryCodes: readStringArrayFromBody(rawFilters, "countryCodes"),
|
|
3483
|
+
}),
|
|
3484
|
+
...(typeof rawFilters["identifiedUserKey"] === "string" && {
|
|
3485
|
+
identifiedUserKey: rawFilters["identifiedUserKey"],
|
|
3486
|
+
}),
|
|
3487
|
+
...(typeof rawFilters["route"] === "string" && {
|
|
3488
|
+
route: rawFilters["route"],
|
|
3489
|
+
}),
|
|
3490
|
+
...(typeof rawFilters["minDurationMs"] === "number" && {
|
|
3491
|
+
minDurationMs: rawFilters["minDurationMs"],
|
|
3492
|
+
}),
|
|
3493
|
+
};
|
|
3494
|
+
|
|
3495
|
+
const rawCursor: JSONObject | undefined = body["cursor"]
|
|
3496
|
+
? (body["cursor"] as JSONObject)
|
|
3497
|
+
: undefined;
|
|
3498
|
+
|
|
3499
|
+
const cursor: SessionReplayListCursor | undefined =
|
|
3500
|
+
rawCursor &&
|
|
3501
|
+
typeof rawCursor["startTimeUnixMs"] === "number" &&
|
|
3502
|
+
typeof rawCursor["sessionId"] === "string"
|
|
3503
|
+
? {
|
|
3504
|
+
startTimeUnixMs: rawCursor["startTimeUnixMs"],
|
|
3505
|
+
sessionId: rawCursor["sessionId"],
|
|
3506
|
+
}
|
|
3507
|
+
: undefined;
|
|
3508
|
+
|
|
3509
|
+
/*
|
|
3510
|
+
* The narrower identity ACL is enforced by simply not naming the
|
|
3511
|
+
* column in the SELECT. There is no ModelPermission on this path to
|
|
3512
|
+
* strip it after the fact. Decided against the application already
|
|
3513
|
+
* loaded by the access check, so a caller whose identity grant is
|
|
3514
|
+
* label-scoped elsewhere does not get named end users here.
|
|
3515
|
+
*/
|
|
3516
|
+
const includeIdentifiedUserLabel: boolean = canReadIdentifiedUserLabel({
|
|
3517
|
+
databaseProps: databaseProps,
|
|
3518
|
+
application: application,
|
|
3519
|
+
});
|
|
3520
|
+
|
|
3521
|
+
const result: SessionReplayListResult =
|
|
3522
|
+
await SessionReplayReadService.listSessions({
|
|
3523
|
+
projectId: projectId,
|
|
3524
|
+
rumApplicationId: rumApplicationId,
|
|
3525
|
+
startTime: startTime,
|
|
3526
|
+
endTime: endTime,
|
|
3527
|
+
filters: filters,
|
|
3528
|
+
limit:
|
|
3529
|
+
typeof body["limit"] === "number"
|
|
3530
|
+
? (body["limit"] as number)
|
|
3531
|
+
: DEFAULT_SESSION_REPLAY_LIST_LIMIT,
|
|
3532
|
+
...(cursor !== undefined && { cursor }),
|
|
3533
|
+
includeIdentifiedUserLabel: includeIdentifiedUserLabel,
|
|
3534
|
+
});
|
|
3535
|
+
|
|
3536
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
3537
|
+
sessions: result.sessions as unknown as JSONObject,
|
|
3538
|
+
nextCursor: result.nextCursor as unknown as JSONObject,
|
|
3539
|
+
});
|
|
3540
|
+
} catch (err: unknown) {
|
|
3541
|
+
next(err);
|
|
3542
|
+
}
|
|
3543
|
+
},
|
|
3544
|
+
);
|
|
3545
|
+
|
|
3546
|
+
// --- Session Replay Manifest Endpoint ---
|
|
3547
|
+
|
|
3548
|
+
router.post(
|
|
3549
|
+
"/telemetry/rum/session-replay/manifest",
|
|
3550
|
+
...requireSessionReplayPayloadAccess,
|
|
3551
|
+
async (
|
|
3552
|
+
req: ExpressRequest,
|
|
3553
|
+
res: ExpressResponse,
|
|
3554
|
+
next: NextFunction,
|
|
3555
|
+
): Promise<void> => {
|
|
3556
|
+
try {
|
|
3557
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
3558
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
3559
|
+
|
|
3560
|
+
if (!databaseProps?.tenantId) {
|
|
3561
|
+
return Response.sendErrorResponse(
|
|
3562
|
+
req,
|
|
3563
|
+
res,
|
|
3564
|
+
new BadDataException("Invalid Project ID"),
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
assertSessionReplayPlan(databaseProps);
|
|
3569
|
+
|
|
3570
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
3571
|
+
const body: JSONObject = req.body as JSONObject;
|
|
3572
|
+
const sessionId: string = readSessionIdFromBody(body);
|
|
3573
|
+
|
|
3574
|
+
const header: SessionReplaySessionHeader = await resolveAuthorizedSession(
|
|
3575
|
+
{
|
|
3576
|
+
projectId: projectId,
|
|
3577
|
+
sessionId: sessionId,
|
|
3578
|
+
databaseProps: databaseProps,
|
|
3579
|
+
},
|
|
3580
|
+
);
|
|
3581
|
+
|
|
3582
|
+
/*
|
|
3583
|
+
* The authorized application, resolved server-side from the header.
|
|
3584
|
+
* Every chunk-table read below is pinned to it: that table's replace
|
|
3585
|
+
* key is (projectId, sessionId, tabId, chunkIndex) with
|
|
3586
|
+
* rumApplicationId a plain column, so (projectId, sessionId) alone
|
|
3587
|
+
* is not enough to keep two applications' recordings apart.
|
|
3588
|
+
*/
|
|
3589
|
+
const authorizedApplicationId: ObjectID = new ObjectID(
|
|
3590
|
+
header.rumApplicationId,
|
|
3591
|
+
);
|
|
3592
|
+
|
|
3593
|
+
/*
|
|
3594
|
+
* The audit row is written BEFORE the manifest is built, not after.
|
|
3595
|
+
* A read that fails halfway through still happened, and an audit
|
|
3596
|
+
* that only records successful reads is an audit an attacker can
|
|
3597
|
+
* evade by aborting the request.
|
|
3598
|
+
*/
|
|
3599
|
+
const view: RumSessionReplayView =
|
|
3600
|
+
await RumSessionReplayViewService.recordView({
|
|
3601
|
+
projectId: projectId,
|
|
3602
|
+
rumApplicationId: authorizedApplicationId,
|
|
3603
|
+
sessionId: sessionId,
|
|
3604
|
+
viewedByUserId: databaseProps.userId,
|
|
3605
|
+
ipAddress: getClientIp(req),
|
|
3606
|
+
userAgent:
|
|
3607
|
+
typeof req.headers["user-agent"] === "string"
|
|
3608
|
+
? req.headers["user-agent"]
|
|
3609
|
+
: undefined,
|
|
3610
|
+
accessReason:
|
|
3611
|
+
typeof body["accessReason"] === "string"
|
|
3612
|
+
? (body["accessReason"] as string)
|
|
3613
|
+
: undefined,
|
|
3614
|
+
linkedIncidentId:
|
|
3615
|
+
typeof body["linkedIncidentId"] === "string" &&
|
|
3616
|
+
body["linkedIncidentId"].length > 0
|
|
3617
|
+
? new ObjectID(body["linkedIncidentId"])
|
|
3618
|
+
: undefined,
|
|
3619
|
+
linkedExceptionFingerprint:
|
|
3620
|
+
typeof body["linkedExceptionFingerprint"] === "string"
|
|
3621
|
+
? (body["linkedExceptionFingerprint"] as string)
|
|
3622
|
+
: undefined,
|
|
3623
|
+
});
|
|
3624
|
+
|
|
3625
|
+
const manifest: SessionReplayManifest =
|
|
3626
|
+
await SessionReplayReadService.getManifest({
|
|
3627
|
+
header: header,
|
|
3628
|
+
projectId: projectId,
|
|
3629
|
+
rumApplicationId: authorizedApplicationId,
|
|
3630
|
+
sessionId: sessionId,
|
|
3631
|
+
});
|
|
3632
|
+
|
|
3633
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
3634
|
+
/*
|
|
3635
|
+
* viewId is echoed back so the player's heartbeat can advance the
|
|
3636
|
+
* very row this read created, instead of guessing at one.
|
|
3637
|
+
*/
|
|
3638
|
+
viewId: view.id ? view.id.toString() : null,
|
|
3639
|
+
header: manifest.header as unknown as JSONObject,
|
|
3640
|
+
tabs: manifest.tabs as unknown as JSONObject,
|
|
3641
|
+
isChunkIndexTruncated: manifest.isChunkIndexTruncated,
|
|
3642
|
+
});
|
|
3643
|
+
} catch (err: unknown) {
|
|
3644
|
+
next(err);
|
|
3645
|
+
}
|
|
3646
|
+
},
|
|
3647
|
+
);
|
|
3648
|
+
|
|
3649
|
+
// --- Session Replay Chunk Endpoint ---
|
|
3650
|
+
|
|
3651
|
+
router.post(
|
|
3652
|
+
"/telemetry/rum/session-replay/chunks",
|
|
3653
|
+
...requireSessionReplayPayloadAccess,
|
|
3654
|
+
async (
|
|
3655
|
+
req: ExpressRequest,
|
|
3656
|
+
res: ExpressResponse,
|
|
3657
|
+
next: NextFunction,
|
|
3658
|
+
): Promise<void> => {
|
|
3659
|
+
try {
|
|
3660
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
3661
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
3662
|
+
|
|
3663
|
+
if (!databaseProps?.tenantId) {
|
|
3664
|
+
return Response.sendErrorResponse(
|
|
3665
|
+
req,
|
|
3666
|
+
res,
|
|
3667
|
+
new BadDataException("Invalid Project ID"),
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
assertSessionReplayPlan(databaseProps);
|
|
3672
|
+
|
|
3673
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
3674
|
+
const body: JSONObject = req.body as JSONObject;
|
|
3675
|
+
const sessionId: string = readSessionIdFromBody(body);
|
|
3676
|
+
|
|
3677
|
+
const tabId: unknown = body["tabId"];
|
|
3678
|
+
|
|
3679
|
+
if (typeof tabId !== "string" || tabId.length === 0) {
|
|
3680
|
+
return Response.sendErrorResponse(
|
|
3681
|
+
req,
|
|
3682
|
+
res,
|
|
3683
|
+
new BadDataException("tabId is required"),
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
const rawChunkIndexes: unknown = body["chunkIndexes"];
|
|
3688
|
+
|
|
3689
|
+
if (!Array.isArray(rawChunkIndexes) || rawChunkIndexes.length === 0) {
|
|
3690
|
+
return Response.sendErrorResponse(
|
|
3691
|
+
req,
|
|
3692
|
+
res,
|
|
3693
|
+
new BadDataException("chunkIndexes is required"),
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
const chunkIndexes: Array<number> = rawChunkIndexes.filter(
|
|
3698
|
+
(index: unknown): index is number => {
|
|
3699
|
+
return (
|
|
3700
|
+
typeof index === "number" && Number.isInteger(index) && index >= 0
|
|
3701
|
+
);
|
|
3702
|
+
},
|
|
3703
|
+
);
|
|
3704
|
+
|
|
3705
|
+
if (chunkIndexes.length !== rawChunkIndexes.length) {
|
|
3706
|
+
return Response.sendErrorResponse(
|
|
3707
|
+
req,
|
|
3708
|
+
res,
|
|
3709
|
+
new BadDataException(
|
|
3710
|
+
"chunkIndexes must contain only non-negative integers",
|
|
3711
|
+
),
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
/*
|
|
3716
|
+
* Checked here as well as inside the read service: the route should
|
|
3717
|
+
* refuse an abusive request before it authorizes and queries
|
|
3718
|
+
* anything, and the service must refuse it for any future caller
|
|
3719
|
+
* that does not come through this route.
|
|
3720
|
+
*/
|
|
3721
|
+
if (chunkIndexes.length > MAX_SESSION_REPLAY_CHUNKS_PER_READ) {
|
|
3722
|
+
return Response.sendErrorResponse(
|
|
3723
|
+
req,
|
|
3724
|
+
res,
|
|
3725
|
+
new BadDataException(
|
|
3726
|
+
`A maximum of ${MAX_SESSION_REPLAY_CHUNKS_PER_READ} chunks may be requested at a time.`,
|
|
3727
|
+
),
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
const header: SessionReplaySessionHeader = await resolveAuthorizedSession(
|
|
3732
|
+
{
|
|
3733
|
+
projectId: projectId,
|
|
3734
|
+
sessionId: sessionId,
|
|
3735
|
+
databaseProps: databaseProps,
|
|
3736
|
+
},
|
|
3737
|
+
);
|
|
3738
|
+
|
|
3739
|
+
const chunks: Array<SessionReplayChunkPayload> =
|
|
3740
|
+
await SessionReplayReadService.getChunks({
|
|
3741
|
+
projectId: projectId,
|
|
3742
|
+
/* Always the application the caller was authorized against. */
|
|
3743
|
+
rumApplicationId: new ObjectID(header.rumApplicationId),
|
|
3744
|
+
sessionId: sessionId,
|
|
3745
|
+
tabId: tabId,
|
|
3746
|
+
chunkIndexes: chunkIndexes,
|
|
3747
|
+
});
|
|
3748
|
+
|
|
3749
|
+
/*
|
|
3750
|
+
* Concatenated binary frames: [u32 chunkIndex][u32 byteLength][bytes]
|
|
3751
|
+
* repeated, little-endian, application/octet-stream.
|
|
3752
|
+
*
|
|
3753
|
+
* Not JSON: the payload is already a JSON document, so a JSON
|
|
3754
|
+
* envelope would force the server to re-escape and the browser to
|
|
3755
|
+
* double-parse several megabytes per page. The length prefix lets
|
|
3756
|
+
* the player slice the response without scanning it.
|
|
3757
|
+
*/
|
|
3758
|
+
const frames: Array<Buffer> = [];
|
|
3759
|
+
|
|
3760
|
+
/*
|
|
3761
|
+
* The byte cap is re-checked here against the bytes actually being
|
|
3762
|
+
* framed, not only against what the pre-check in the read service
|
|
3763
|
+
* believed. This is the last place the size of the response is
|
|
3764
|
+
* knowable, so it is the one place a cap on the response can be
|
|
3765
|
+
* absolute regardless of how stored size was estimated upstream.
|
|
3766
|
+
*/
|
|
3767
|
+
let responseBytes: number = 0;
|
|
3768
|
+
|
|
3769
|
+
for (const chunk of chunks) {
|
|
3770
|
+
const payloadBuffer: Buffer = Buffer.from(chunk.payload, "utf8");
|
|
3771
|
+
|
|
3772
|
+
responseBytes += payloadBuffer.length + 8;
|
|
3773
|
+
|
|
3774
|
+
if (responseBytes > MAX_SESSION_REPLAY_READ_BYTES) {
|
|
3775
|
+
return Response.sendErrorResponse(
|
|
3776
|
+
req,
|
|
3777
|
+
res,
|
|
3778
|
+
new BadDataException(
|
|
3779
|
+
`The requested chunks exceed the ${MAX_SESSION_REPLAY_READ_BYTES} byte limit for a single read. Request fewer chunks.`,
|
|
3780
|
+
),
|
|
3781
|
+
);
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
const headerBuffer: Buffer = Buffer.alloc(8);
|
|
3785
|
+
headerBuffer.writeUInt32LE(chunk.chunkIndex, 0);
|
|
3786
|
+
headerBuffer.writeUInt32LE(payloadBuffer.length, 4);
|
|
3787
|
+
frames.push(headerBuffer, payloadBuffer);
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
const responseBody: Buffer = Buffer.concat(frames);
|
|
3791
|
+
|
|
3792
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
3793
|
+
res.setHeader("Content-Length", responseBody.length.toString());
|
|
3794
|
+
/*
|
|
3795
|
+
* A recording is personal data. Nothing about it may sit in a
|
|
3796
|
+
* shared cache, and the browser should not keep it on disk either.
|
|
3797
|
+
*/
|
|
3798
|
+
res.setHeader("Cache-Control", "no-store");
|
|
3799
|
+
res.send(responseBody);
|
|
3800
|
+
} catch (err: unknown) {
|
|
3801
|
+
next(err);
|
|
3802
|
+
}
|
|
3803
|
+
},
|
|
3804
|
+
);
|
|
3805
|
+
|
|
3806
|
+
// --- Session Replay Heartbeat Endpoint ---
|
|
3807
|
+
|
|
3808
|
+
router.post(
|
|
3809
|
+
"/telemetry/rum/session-replay/heartbeat",
|
|
3810
|
+
...requireSessionReplayPayloadAccess,
|
|
3811
|
+
async (
|
|
3812
|
+
req: ExpressRequest,
|
|
3813
|
+
res: ExpressResponse,
|
|
3814
|
+
next: NextFunction,
|
|
3815
|
+
): Promise<void> => {
|
|
3816
|
+
try {
|
|
3817
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
3818
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
3819
|
+
|
|
3820
|
+
if (!databaseProps?.tenantId) {
|
|
3821
|
+
return Response.sendErrorResponse(
|
|
3822
|
+
req,
|
|
3823
|
+
res,
|
|
3824
|
+
new BadDataException("Invalid Project ID"),
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
assertSessionReplayPlan(databaseProps);
|
|
3829
|
+
|
|
3830
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
3831
|
+
const body: JSONObject = req.body as JSONObject;
|
|
3832
|
+
|
|
3833
|
+
const viewId: ObjectID = readObjectIdFromBody(body, "viewId");
|
|
3834
|
+
|
|
3835
|
+
const secondsWatched: unknown = body["secondsWatched"];
|
|
3836
|
+
|
|
3837
|
+
if (
|
|
3838
|
+
typeof secondsWatched !== "number" ||
|
|
3839
|
+
!Number.isFinite(secondsWatched)
|
|
3840
|
+
) {
|
|
3841
|
+
return Response.sendErrorResponse(
|
|
3842
|
+
req,
|
|
3843
|
+
res,
|
|
3844
|
+
new BadDataException("secondsWatched must be a number"),
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
/*
|
|
3849
|
+
* Floored to the 15s heartbeat cadence. recordSecondsWatched is
|
|
3850
|
+
* monotonic and returns without writing when the value does not
|
|
3851
|
+
* advance, so flooring here turns a chatty client into at most one
|
|
3852
|
+
* UPDATE per 15 seconds watched without any server-side timer or
|
|
3853
|
+
* cross-pod throttle state.
|
|
3854
|
+
*/
|
|
3855
|
+
const throttledSeconds: number =
|
|
3856
|
+
Math.floor(Math.max(0, secondsWatched) / 15) * 15;
|
|
3857
|
+
|
|
3858
|
+
/*
|
|
3859
|
+
* secondsWatched is a privacy control, not telemetry: it is shown on
|
|
3860
|
+
* the player as "who watched how much of this recording". Scoping
|
|
3861
|
+
* the write to the tenant alone would let any principal holding the
|
|
3862
|
+
* payload permission inflate somebody else's audit row for a session
|
|
3863
|
+
* they may not even read, which forges the very record the design
|
|
3864
|
+
* relies on.
|
|
3865
|
+
*
|
|
3866
|
+
* The row is therefore looked up as the CALLER'S own view row - a
|
|
3867
|
+
* viewId belonging to anyone else matches nothing and is refused
|
|
3868
|
+
* indistinguishably from one that does not exist - and the
|
|
3869
|
+
* application it points at is authorized exactly as a payload read
|
|
3870
|
+
* would be.
|
|
3871
|
+
*/
|
|
3872
|
+
if (!databaseProps.userId) {
|
|
3873
|
+
return Response.sendErrorResponse(
|
|
3874
|
+
req,
|
|
3875
|
+
res,
|
|
3876
|
+
new NotAuthorizedException(
|
|
3877
|
+
"You do not have access to this session replay view.",
|
|
3878
|
+
),
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
const view: RumSessionReplayView | null =
|
|
3883
|
+
await RumSessionReplayViewService.findOneBy({
|
|
3884
|
+
query: {
|
|
3885
|
+
_id: viewId.toString(),
|
|
3886
|
+
projectId: projectId,
|
|
3887
|
+
viewedByUserId: databaseProps.userId,
|
|
3888
|
+
},
|
|
3889
|
+
select: {
|
|
3890
|
+
_id: true,
|
|
3891
|
+
rumApplicationId: true,
|
|
3892
|
+
},
|
|
3893
|
+
props: {
|
|
3894
|
+
isRoot: true,
|
|
3895
|
+
},
|
|
3896
|
+
});
|
|
3897
|
+
|
|
3898
|
+
if (!view || !view.rumApplicationId) {
|
|
3899
|
+
return Response.sendErrorResponse(
|
|
3900
|
+
req,
|
|
3901
|
+
res,
|
|
3902
|
+
new NotAuthorizedException(
|
|
3903
|
+
"You do not have access to this session replay view.",
|
|
3904
|
+
),
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
await assertSessionReplayApplicationAccess({
|
|
3909
|
+
projectId: projectId,
|
|
3910
|
+
rumApplicationId: view.rumApplicationId,
|
|
3911
|
+
databaseProps: databaseProps,
|
|
3912
|
+
permissions: SESSION_REPLAY_PAYLOAD_PERMISSIONS,
|
|
3913
|
+
});
|
|
3914
|
+
|
|
3915
|
+
await RumSessionReplayViewService.recordSecondsWatched({
|
|
3916
|
+
viewId: viewId,
|
|
3917
|
+
projectId: projectId,
|
|
3918
|
+
secondsWatched: throttledSeconds,
|
|
3919
|
+
});
|
|
3920
|
+
|
|
3921
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
3922
|
+
secondsWatched: throttledSeconds,
|
|
3923
|
+
});
|
|
3924
|
+
} catch (err: unknown) {
|
|
3925
|
+
next(err);
|
|
3926
|
+
}
|
|
3927
|
+
},
|
|
3928
|
+
);
|
|
3929
|
+
|
|
3930
|
+
// --- Session Replay For Exception Endpoint ---
|
|
3931
|
+
|
|
3932
|
+
router.post(
|
|
3933
|
+
"/telemetry/rum/session-replay/for-exception",
|
|
3934
|
+
...requireSessionReplayListAccess,
|
|
3935
|
+
async (
|
|
3936
|
+
req: ExpressRequest,
|
|
3937
|
+
res: ExpressResponse,
|
|
3938
|
+
next: NextFunction,
|
|
3939
|
+
): Promise<void> => {
|
|
3940
|
+
try {
|
|
3941
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
3942
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
3943
|
+
|
|
3944
|
+
if (!databaseProps?.tenantId) {
|
|
3945
|
+
return Response.sendErrorResponse(
|
|
3946
|
+
req,
|
|
3947
|
+
res,
|
|
3948
|
+
new BadDataException("Invalid Project ID"),
|
|
3949
|
+
);
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
assertSessionReplayPlan(databaseProps);
|
|
3953
|
+
|
|
3954
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
3955
|
+
const body: JSONObject = req.body as JSONObject;
|
|
3956
|
+
|
|
3957
|
+
const fingerprint: unknown = body["fingerprint"];
|
|
3958
|
+
|
|
3959
|
+
if (typeof fingerprint !== "string" || fingerprint.length === 0) {
|
|
3960
|
+
return Response.sendErrorResponse(
|
|
3961
|
+
req,
|
|
3962
|
+
res,
|
|
3963
|
+
new BadDataException("fingerprint is required"),
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
/*
|
|
3968
|
+
* An exception is not scoped to a RUM application, so there is no
|
|
3969
|
+
* single application to authorize against. Restrict the query to
|
|
3970
|
+
* the applications the caller's labels reach instead.
|
|
3971
|
+
*/
|
|
3972
|
+
const accessibleApplications: AccessibleRumApplications =
|
|
3973
|
+
await resolveAccessibleRumApplicationIds({
|
|
3974
|
+
projectId: projectId,
|
|
3975
|
+
databaseProps: databaseProps,
|
|
3976
|
+
permissions: SESSION_REPLAY_LIST_PERMISSIONS,
|
|
3977
|
+
});
|
|
3978
|
+
|
|
3979
|
+
const startTime: Date | undefined = body["startTime"]
|
|
3980
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
3981
|
+
: undefined;
|
|
3982
|
+
|
|
3983
|
+
const endTime: Date | undefined = body["endTime"]
|
|
3984
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
3985
|
+
: undefined;
|
|
3986
|
+
|
|
3987
|
+
const sessions: Array<SessionReplayExceptionSession> =
|
|
3988
|
+
await SessionReplayReadService.getSessionsForException({
|
|
3989
|
+
projectId: projectId,
|
|
3990
|
+
exceptionFingerprint: fingerprint,
|
|
3991
|
+
accessibleRumApplicationIds: accessibleApplications.applicationIds,
|
|
3992
|
+
...(startTime !== undefined && { startTime }),
|
|
3993
|
+
...(endTime !== undefined && { endTime }),
|
|
3994
|
+
limit:
|
|
3995
|
+
typeof body["limit"] === "number"
|
|
3996
|
+
? (body["limit"] as number)
|
|
3997
|
+
: MAX_SESSION_REPLAY_FOR_EXCEPTION_LIMIT,
|
|
3998
|
+
});
|
|
3999
|
+
|
|
4000
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
4001
|
+
sessions: sessions as unknown as JSONObject,
|
|
4002
|
+
/*
|
|
4003
|
+
* Told, not hidden: the accessible-application scan has a ceiling,
|
|
4004
|
+
* and a caller who hits it is looking at a possibly short answer.
|
|
4005
|
+
*/
|
|
4006
|
+
isApplicationScopeTruncated: accessibleApplications.isTruncated,
|
|
4007
|
+
});
|
|
4008
|
+
} catch (err: unknown) {
|
|
4009
|
+
next(err);
|
|
4010
|
+
}
|
|
4011
|
+
},
|
|
4012
|
+
);
|
|
4013
|
+
|
|
4014
|
+
// --- Session Replay Ingest Status Endpoint ---
|
|
4015
|
+
|
|
4016
|
+
/*
|
|
4017
|
+
* Recording health for one application: is replay switched on at every
|
|
4018
|
+
* level, when did the last chunk actually land, and how much of the byte
|
|
4019
|
+
* budgets is spent. This is what lets the Dashboard answer "why are there
|
|
4020
|
+
* no recordings?" and "am I about to hit my budget?" as states rather than
|
|
4021
|
+
* leaving the customer to infer them from silence — the ingest gate's
|
|
4022
|
+
* refusals are deliberately quiet toward end users' browsers, so this
|
|
4023
|
+
* endpoint is the loud side of that trade.
|
|
4024
|
+
*
|
|
4025
|
+
* Reads settings metadata and Redis counters only; never touches recording
|
|
4026
|
+
* payloads, so list-level access is the right bar.
|
|
4027
|
+
*
|
|
4028
|
+
* The application row is loaded with root props even though its columns
|
|
4029
|
+
* carry their own read ACLs. That is deliberate and bounded: every column
|
|
4030
|
+
* returned here (appIdentifier, the replay policy fields, the two health
|
|
4031
|
+
* timestamps) is readable by a plain project Viewer under its declared
|
|
4032
|
+
* ACL — the least-privileged read tier there is — while this route demands
|
|
4033
|
+
* the STRICTER session-replay list permission and pins tenancy and label
|
|
4034
|
+
* scope first. Nothing is disclosed that the caller's project peers cannot
|
|
4035
|
+
* already read through the generic CRUD API. If a column with a narrower
|
|
4036
|
+
* ACL (e.g. anything identity-adjacent) is ever added to this SELECT, it
|
|
4037
|
+
* must get an explicit permission check, not a wider isRoot ride-along.
|
|
4038
|
+
*/
|
|
4039
|
+
router.post(
|
|
4040
|
+
"/telemetry/rum/session-replay/ingest-status",
|
|
4041
|
+
...requireSessionReplayListAccess,
|
|
4042
|
+
async (
|
|
4043
|
+
req: ExpressRequest,
|
|
4044
|
+
res: ExpressResponse,
|
|
4045
|
+
next: NextFunction,
|
|
4046
|
+
): Promise<void> => {
|
|
4047
|
+
try {
|
|
4048
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
4049
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
4050
|
+
|
|
4051
|
+
if (!databaseProps?.tenantId) {
|
|
4052
|
+
return Response.sendErrorResponse(
|
|
4053
|
+
req,
|
|
4054
|
+
res,
|
|
4055
|
+
new BadDataException("Invalid Project ID"),
|
|
4056
|
+
);
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
assertSessionReplayPlan(databaseProps);
|
|
4060
|
+
|
|
4061
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
4062
|
+
const body: JSONObject = req.body as JSONObject;
|
|
4063
|
+
|
|
4064
|
+
const rumApplicationId: ObjectID = readObjectIdFromBody(
|
|
4065
|
+
body,
|
|
4066
|
+
"rumApplicationId",
|
|
4067
|
+
);
|
|
4068
|
+
|
|
4069
|
+
await assertSessionReplayApplicationAccess({
|
|
4070
|
+
projectId: projectId,
|
|
4071
|
+
rumApplicationId: rumApplicationId,
|
|
4072
|
+
databaseProps: databaseProps,
|
|
4073
|
+
permissions: SESSION_REPLAY_LIST_PERMISSIONS,
|
|
4074
|
+
});
|
|
4075
|
+
|
|
4076
|
+
const application: RumApplication | null =
|
|
4077
|
+
await RumApplicationService.findOneBy({
|
|
4078
|
+
query: {
|
|
4079
|
+
_id: rumApplicationId.toString(),
|
|
4080
|
+
projectId: projectId,
|
|
4081
|
+
},
|
|
4082
|
+
select: {
|
|
4083
|
+
_id: true,
|
|
4084
|
+
appIdentifier: true,
|
|
4085
|
+
isSessionReplayEnabled: true,
|
|
4086
|
+
sessionReplayAllowedOrigins: true,
|
|
4087
|
+
sessionReplaySamplePercentage: true,
|
|
4088
|
+
sessionReplayCaptureTrigger: true,
|
|
4089
|
+
sessionReplayMonthlyBudgetInGB: true,
|
|
4090
|
+
sessionReplayLastChunkReceivedAt: true,
|
|
4091
|
+
sessionReplayBudgetExceededAt: true,
|
|
4092
|
+
},
|
|
4093
|
+
props: {
|
|
4094
|
+
isRoot: true,
|
|
4095
|
+
},
|
|
4096
|
+
});
|
|
4097
|
+
|
|
4098
|
+
if (!application) {
|
|
4099
|
+
return Response.sendErrorResponse(
|
|
4100
|
+
req,
|
|
4101
|
+
res,
|
|
4102
|
+
new BadDataException("RUM application not found."),
|
|
4103
|
+
);
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
const project: Project | null = await ProjectService.findOneBy({
|
|
4107
|
+
query: {
|
|
4108
|
+
_id: projectId.toString(),
|
|
4109
|
+
},
|
|
4110
|
+
select: {
|
|
4111
|
+
isSessionReplayAllowed: true,
|
|
4112
|
+
},
|
|
4113
|
+
props: {
|
|
4114
|
+
isRoot: true,
|
|
4115
|
+
},
|
|
4116
|
+
});
|
|
4117
|
+
|
|
4118
|
+
const [projectBytesUsedToday, applicationBytesUsedThisMonth]: [
|
|
4119
|
+
number | null,
|
|
4120
|
+
number | null,
|
|
4121
|
+
] = await Promise.all([
|
|
4122
|
+
SessionReplayUsage.getProjectBytesUsedToday(projectId),
|
|
4123
|
+
SessionReplayUsage.getApplicationBytesUsedThisMonth({
|
|
4124
|
+
projectId: projectId,
|
|
4125
|
+
rumApplicationId: rumApplicationId,
|
|
4126
|
+
}),
|
|
4127
|
+
]);
|
|
4128
|
+
|
|
4129
|
+
/*
|
|
4130
|
+
* Same env var, same default, as the value the ingest gate enforces
|
|
4131
|
+
* (App Telemetry Config). The default is a shared constant so the
|
|
4132
|
+
* number shown here cannot drift from the number enforced there.
|
|
4133
|
+
*/
|
|
4134
|
+
const dailyCapEnv: number = parseInt(
|
|
4135
|
+
process.env["SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY"] || "",
|
|
4136
|
+
10,
|
|
4137
|
+
);
|
|
4138
|
+
|
|
4139
|
+
const dailyByteLimit: number =
|
|
4140
|
+
!isNaN(dailyCapEnv) && dailyCapEnv > 0
|
|
4141
|
+
? dailyCapEnv
|
|
4142
|
+
: DEFAULT_SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY;
|
|
4143
|
+
|
|
4144
|
+
const applicationView: JSONObject = application as unknown as JSONObject;
|
|
4145
|
+
|
|
4146
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
4147
|
+
isProjectAllowed: Boolean(
|
|
4148
|
+
(project as unknown as JSONObject | null)?.["isSessionReplayAllowed"],
|
|
4149
|
+
),
|
|
4150
|
+
isApplicationEnabled: Boolean(
|
|
4151
|
+
applicationView["isSessionReplayEnabled"],
|
|
4152
|
+
),
|
|
4153
|
+
appIdentifier: String(applicationView["appIdentifier"] || ""),
|
|
4154
|
+
allowedOrigins: (applicationView["sessionReplayAllowedOrigins"] ||
|
|
4155
|
+
[]) as JSONArray,
|
|
4156
|
+
samplePercentage: Number(
|
|
4157
|
+
applicationView["sessionReplaySamplePercentage"] || 0,
|
|
4158
|
+
),
|
|
4159
|
+
captureTrigger: String(
|
|
4160
|
+
applicationView["sessionReplayCaptureTrigger"] || "",
|
|
4161
|
+
),
|
|
4162
|
+
lastChunkReceivedAt:
|
|
4163
|
+
(applicationView["sessionReplayLastChunkReceivedAt"] as string) ||
|
|
4164
|
+
null,
|
|
4165
|
+
budgetExceededAt:
|
|
4166
|
+
(applicationView["sessionReplayBudgetExceededAt"] as string) || null,
|
|
4167
|
+
/* null = counter unreachable; render as unknown, never as zero. */
|
|
4168
|
+
projectBytesUsedToday: projectBytesUsedToday,
|
|
4169
|
+
dailyByteLimit: dailyByteLimit,
|
|
4170
|
+
applicationBytesUsedThisMonth: applicationBytesUsedThisMonth,
|
|
4171
|
+
monthlyBudgetInGB:
|
|
4172
|
+
(applicationView["sessionReplayMonthlyBudgetInGB"] as number) ?? null,
|
|
4173
|
+
});
|
|
4174
|
+
} catch (err: unknown) {
|
|
4175
|
+
next(err);
|
|
4176
|
+
}
|
|
4177
|
+
},
|
|
4178
|
+
);
|
|
4179
|
+
|
|
4180
|
+
/*
|
|
4181
|
+
* "Record the next session for this user."
|
|
4182
|
+
*
|
|
4183
|
+
* One route, three actions, because all three are the same tiny
|
|
4184
|
+
* conversation with one Redis key: set arms a 24h one-shot target for a
|
|
4185
|
+
* named end-user reference, clear disarms it, status reads it. The
|
|
4186
|
+
* response is always the resulting { isPending }.
|
|
4187
|
+
*
|
|
4188
|
+
* The guard requires EDIT on the application, not the session-replay READ
|
|
4189
|
+
* permission: arming this causes a recording of a named person to be
|
|
4190
|
+
* made, which is a capture-policy write - the same class of action as
|
|
4191
|
+
* flipping the enable flag - and a reviewer who may only WATCH recordings
|
|
4192
|
+
* must not be able to order new ones.
|
|
4193
|
+
*/
|
|
4194
|
+
const requireSessionReplayTargetAccess: Array<RequestHandler> = [
|
|
4195
|
+
UserMiddleware.getUserMiddleware,
|
|
4196
|
+
UserMiddleware.requireUserAuthentication,
|
|
4197
|
+
UserMiddleware.requirePermission({
|
|
4198
|
+
permissions: [
|
|
4199
|
+
Permission.ProjectOwner,
|
|
4200
|
+
Permission.ProjectAdmin,
|
|
4201
|
+
Permission.TelemetryAdmin,
|
|
4202
|
+
Permission.EditRumApplication,
|
|
4203
|
+
],
|
|
4204
|
+
}),
|
|
4205
|
+
];
|
|
4206
|
+
|
|
4207
|
+
router.post(
|
|
4208
|
+
"/telemetry/rum/session-replay/target",
|
|
4209
|
+
...requireSessionReplayTargetAccess,
|
|
4210
|
+
async (
|
|
4211
|
+
req: ExpressRequest,
|
|
4212
|
+
res: ExpressResponse,
|
|
4213
|
+
next: NextFunction,
|
|
4214
|
+
): Promise<void> => {
|
|
4215
|
+
try {
|
|
4216
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
4217
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
4218
|
+
|
|
4219
|
+
if (!databaseProps?.tenantId) {
|
|
4220
|
+
return Response.sendErrorResponse(
|
|
4221
|
+
req,
|
|
4222
|
+
res,
|
|
4223
|
+
new BadDataException("Invalid Project ID"),
|
|
4224
|
+
);
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
assertSessionReplayPlan(databaseProps);
|
|
4228
|
+
|
|
4229
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
4230
|
+
const body: JSONObject = req.body as JSONObject;
|
|
4231
|
+
|
|
4232
|
+
const rumApplicationId: ObjectID = readObjectIdFromBody(
|
|
4233
|
+
body,
|
|
4234
|
+
"rumApplicationId",
|
|
4235
|
+
);
|
|
4236
|
+
|
|
4237
|
+
const action: unknown = body["action"];
|
|
4238
|
+
|
|
4239
|
+
if (action !== "set" && action !== "clear" && action !== "status") {
|
|
4240
|
+
return Response.sendErrorResponse(
|
|
4241
|
+
req,
|
|
4242
|
+
res,
|
|
4243
|
+
new BadDataException(
|
|
4244
|
+
'action must be one of "set", "clear" or "status".',
|
|
4245
|
+
),
|
|
4246
|
+
);
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
const userRef: unknown = body["userRef"];
|
|
4250
|
+
|
|
4251
|
+
if (!SessionReplayTargeting.isUsableUserRef(userRef)) {
|
|
4252
|
+
return Response.sendErrorResponse(
|
|
4253
|
+
req,
|
|
4254
|
+
res,
|
|
4255
|
+
new BadDataException(
|
|
4256
|
+
"userRef must be a non-empty string of at most 512 characters.",
|
|
4257
|
+
),
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
await assertSessionReplayApplicationAccess({
|
|
4262
|
+
projectId: projectId,
|
|
4263
|
+
rumApplicationId: rumApplicationId,
|
|
4264
|
+
databaseProps: databaseProps,
|
|
4265
|
+
permissions: [
|
|
4266
|
+
Permission.ProjectOwner,
|
|
4267
|
+
Permission.ProjectAdmin,
|
|
4268
|
+
Permission.TelemetryAdmin,
|
|
4269
|
+
Permission.EditRumApplication,
|
|
4270
|
+
],
|
|
4271
|
+
});
|
|
4272
|
+
|
|
4273
|
+
/*
|
|
4274
|
+
* The Redis key is scoped by the application's ingest identifier -
|
|
4275
|
+
* the name the recorder introduces itself with - not by the row id
|
|
4276
|
+
* the dashboard holds, so resolve one from the other here.
|
|
4277
|
+
*/
|
|
4278
|
+
const application: RumApplication | null =
|
|
4279
|
+
await RumApplicationService.findOneBy({
|
|
4280
|
+
query: {
|
|
4281
|
+
_id: rumApplicationId.toString(),
|
|
4282
|
+
projectId: projectId,
|
|
4283
|
+
},
|
|
4284
|
+
select: {
|
|
4285
|
+
_id: true,
|
|
4286
|
+
appIdentifier: true,
|
|
4287
|
+
},
|
|
4288
|
+
props: {
|
|
4289
|
+
isRoot: true,
|
|
4290
|
+
},
|
|
4291
|
+
});
|
|
4292
|
+
|
|
4293
|
+
const appIdentifier: string = String(
|
|
4294
|
+
(application as unknown as JSONObject | null)?.["appIdentifier"] || "",
|
|
4295
|
+
);
|
|
4296
|
+
|
|
4297
|
+
if (!application || !appIdentifier) {
|
|
4298
|
+
return Response.sendErrorResponse(
|
|
4299
|
+
req,
|
|
4300
|
+
res,
|
|
4301
|
+
new BadDataException("RUM application not found."),
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
const target: {
|
|
4306
|
+
projectId: ObjectID;
|
|
4307
|
+
appIdentifier: string;
|
|
4308
|
+
userRef: string;
|
|
4309
|
+
} = {
|
|
4310
|
+
projectId: projectId,
|
|
4311
|
+
appIdentifier: appIdentifier,
|
|
4312
|
+
userRef: userRef,
|
|
4313
|
+
};
|
|
4314
|
+
|
|
4315
|
+
let isPending: boolean = false;
|
|
4316
|
+
|
|
4317
|
+
if (action === "set") {
|
|
4318
|
+
await SessionReplayTargeting.setTarget(target);
|
|
4319
|
+
isPending = true;
|
|
4320
|
+
} else if (action === "clear") {
|
|
4321
|
+
await SessionReplayTargeting.clearTarget(target);
|
|
4322
|
+
isPending = false;
|
|
4323
|
+
} else {
|
|
4324
|
+
isPending = await SessionReplayTargeting.isTargetPending(target);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
4328
|
+
isPending: isPending,
|
|
4329
|
+
});
|
|
4330
|
+
} catch (err: unknown) {
|
|
4331
|
+
next(err);
|
|
4332
|
+
}
|
|
4333
|
+
},
|
|
4334
|
+
);
|
|
4335
|
+
|
|
2717
4336
|
export default router;
|