@oneuptime/common 12.0.4 → 12.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AIRun.ts +38 -1
- package/Models/DatabaseModels/AlertFeed.ts +30 -0
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentFeed.ts +30 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/Project.ts +231 -9
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
- package/Models/DatabaseModels/StatusPage.ts +28 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Server/API/AIAgentDataAPI.ts +44 -55
- package/Server/API/AIInvestigationAPI.ts +175 -17
- package/Server/API/DashboardAPI.ts +10 -10
- package/Server/API/StatusPageAPI.ts +38 -19
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
- package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AIRunService.ts +78 -10
- package/Server/Services/AIService.ts +94 -22
- package/Server/Services/AlertFeedService.ts +5 -0
- package/Server/Services/AnalyticsDatabaseService.ts +29 -0
- package/Server/Services/DatabaseService.ts +43 -2
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
- package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
- package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
- package/Server/Utils/AI/SRE/README.md +1 -1
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/PasswordHash.ts +28 -4
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
- package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
- package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
- package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
- package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
- package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
- package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
- package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
- package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
- package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
- package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
- package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
- package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
- package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
- package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
- package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
- package/Tests/Types/Permission.test.ts +137 -0
- package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
- package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
- package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
- package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
- package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
- package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
- package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
- package/Tests/UI/Utils/Project.test.ts +31 -0
- package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
- package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
- package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
- package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
- package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
- package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
- package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
- package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
- package/Types/AI/CodeFixTaskContext.ts +63 -3
- package/Types/AI/CodeFixTaskType.ts +3 -3
- package/Types/HashedString.ts +4 -3
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
- package/Types/Monitor/MonitorMetricType.ts +8 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
- package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
- package/Types/Permission.ts +23 -175
- package/Types/Probe/ProbeMonitorResponse.ts +6 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
- package/UI/Components/ErrorBoundary.tsx +138 -11
- package/UI/Components/Feed/FeedItem.tsx +9 -2
- package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
- package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
- package/UI/Utils/DownloadFile.ts +16 -8
- package/UI/Utils/ErrorSupportBundle.ts +805 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
- package/UI/Utils/Project.ts +4 -0
- package/UI/Utils/StatusPage.ts +8 -2
- package/UI/Utils/UseDashboardGridDnd.ts +917 -0
- package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
- package/Utils/Dashboard/GridLayout.ts +523 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
- package/Utils/Monitor/MonitorMetricType.ts +44 -2
- package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
- package/Utils/StatusPage/GroupTree.ts +157 -66
- package/Utils/StatusPage/ResourceUptime.ts +37 -2
- package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
- package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
- package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +241 -11
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +8 -5
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +29 -11
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +58 -12
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +74 -21
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertFeedService.js +3 -0
- package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +21 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFeedService.js +3 -0
- package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +22 -1
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/PasswordHash.js +25 -4
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
- package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
- package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
- package/build/dist/Types/HashedString.js +4 -3
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
- package/build/dist/Types/Permission.js +23 -155
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +67 -8
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Utils/DownloadFile.js +15 -8
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
- package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
- package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +3 -0
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/StatusPage.js +4 -4
- package/build/dist/UI/Utils/StatusPage.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
- package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
- package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { AddMasterPasswordSalt1786400000000 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt";
|
|
2
|
+
import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
|
|
3
|
+
import { describe, expect, test } from "@jest/globals";
|
|
4
|
+
import { QueryRunner } from "typeorm";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Dashboard and status-page master passwords are upgraded from a legacy
|
|
10
|
+
* unsalted digest to salted scrypt after this migration runs. The salt column
|
|
11
|
+
* therefore has two unusual requirements:
|
|
12
|
+
*
|
|
13
|
+
* - it must start nullable, because existing hashes have no salt until their
|
|
14
|
+
* next successful verification; and
|
|
15
|
+
* - it must not be removed while any scrypt hash remains, because a scrypt
|
|
16
|
+
* hash cannot be verified without its per-row salt.
|
|
17
|
+
*
|
|
18
|
+
* These tests pin that SQL contract without requiring Postgres. In particular,
|
|
19
|
+
* the rollback tests model each table independently so a future edit cannot
|
|
20
|
+
* check one table, drop a column, and only then discover that the other table
|
|
21
|
+
* still contains scrypt credentials.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const MIGRATION_TIMESTAMP: number = 1786400000000;
|
|
25
|
+
const MIGRATION_FILENAME: string = "1786400000000-AddMasterPasswordSalt.ts";
|
|
26
|
+
const DROP_COLUMN_PATTERN: RegExp = /\bDROP COLUMN\b/i;
|
|
27
|
+
const SELECT_EXISTS_PATTERN: RegExp = /^SELECT EXISTS/i;
|
|
28
|
+
const MIGRATION_PATH: string = path.resolve(
|
|
29
|
+
__dirname,
|
|
30
|
+
"..",
|
|
31
|
+
"..",
|
|
32
|
+
"..",
|
|
33
|
+
"Server",
|
|
34
|
+
"Infrastructure",
|
|
35
|
+
"Postgres",
|
|
36
|
+
"SchemaMigrations",
|
|
37
|
+
MIGRATION_FILENAME,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
type TableWithMasterPassword = "Dashboard" | "StatusPage";
|
|
41
|
+
|
|
42
|
+
type ExistingScryptRows = Record<TableWithMasterPassword, boolean>;
|
|
43
|
+
|
|
44
|
+
const UNSAFE_ROLLBACK_CASES: Array<[string, ExistingScryptRows]> = [
|
|
45
|
+
["Dashboard", { Dashboard: true, StatusPage: false }],
|
|
46
|
+
["StatusPage", { Dashboard: false, StatusPage: true }],
|
|
47
|
+
["both tables", { Dashboard: true, StatusPage: true }],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
type MakeQueryRunnerResult = {
|
|
51
|
+
runner: QueryRunner;
|
|
52
|
+
statements: Array<string>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const makeQueryRunner: (
|
|
56
|
+
existingScryptRows?: ExistingScryptRows,
|
|
57
|
+
) => MakeQueryRunnerResult = (
|
|
58
|
+
existingScryptRows: ExistingScryptRows = {
|
|
59
|
+
Dashboard: false,
|
|
60
|
+
StatusPage: false,
|
|
61
|
+
},
|
|
62
|
+
): MakeQueryRunnerResult => {
|
|
63
|
+
const statements: Array<string> = [];
|
|
64
|
+
|
|
65
|
+
const query: (...args: Array<unknown>) => Promise<unknown> = (
|
|
66
|
+
...args: Array<unknown>
|
|
67
|
+
): Promise<unknown> => {
|
|
68
|
+
const statement: string = String(args[0]);
|
|
69
|
+
statements.push(statement);
|
|
70
|
+
|
|
71
|
+
for (const table of ["Dashboard", "StatusPage"] as const) {
|
|
72
|
+
if (statement.includes(`FROM "${table}"`)) {
|
|
73
|
+
return Promise.resolve([{ exists: existingScryptRows[table] }]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return Promise.resolve(undefined);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
runner: { query } as unknown as QueryRunner,
|
|
82
|
+
statements,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const dropsFrom: (statements: Array<string>) => Array<string> = (
|
|
87
|
+
statements: Array<string>,
|
|
88
|
+
): Array<string> => {
|
|
89
|
+
return statements.filter((statement: string) => {
|
|
90
|
+
return DROP_COLUMN_PATTERN.test(statement);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const optionalTrailingTimestampFrom: (value: string) => number | null = (
|
|
95
|
+
value: string,
|
|
96
|
+
): number | null => {
|
|
97
|
+
const timestamp: string | undefined = value.match(/(\d+)(?:\.ts)?$/)?.[1];
|
|
98
|
+
|
|
99
|
+
return timestamp ? Number(timestamp) : null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const timestampFrom: (value: string) => number = (value: string): number => {
|
|
103
|
+
const timestamp: number | null = optionalTrailingTimestampFrom(value);
|
|
104
|
+
|
|
105
|
+
if (timestamp === null) {
|
|
106
|
+
throw new Error(`Expected a trailing migration timestamp in: ${value}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return timestamp;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const timestampFromFilename: (value: string) => number = (
|
|
113
|
+
value: string,
|
|
114
|
+
): number => {
|
|
115
|
+
const match: RegExpMatchArray | null = value.match(/^(\d+)-/);
|
|
116
|
+
|
|
117
|
+
if (!match?.[1]) {
|
|
118
|
+
throw new Error(`Expected a leading migration timestamp in: ${value}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return Number(match[1]);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
describe("AddMasterPasswordSalt1786400000000 - up SQL contract", () => {
|
|
125
|
+
test("adds only the dashboard and status-page salt columns", async () => {
|
|
126
|
+
const { runner, statements } = makeQueryRunner();
|
|
127
|
+
|
|
128
|
+
await new AddMasterPasswordSalt1786400000000().up(runner);
|
|
129
|
+
|
|
130
|
+
expect(statements).toEqual([
|
|
131
|
+
`ALTER TABLE "StatusPage" ADD "masterPasswordSalt" character varying(100)`,
|
|
132
|
+
`ALTER TABLE "Dashboard" ADD "masterPasswordSalt" character varying(100)`,
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("keeps both columns nullable and does not backfill a fake salt", async () => {
|
|
137
|
+
const { runner, statements } = makeQueryRunner();
|
|
138
|
+
|
|
139
|
+
await new AddMasterPasswordSalt1786400000000().up(runner);
|
|
140
|
+
|
|
141
|
+
expect(statements).toHaveLength(2);
|
|
142
|
+
|
|
143
|
+
for (const statement of statements) {
|
|
144
|
+
expect(statement).toMatch(
|
|
145
|
+
/^ALTER TABLE "(?:Dashboard|StatusPage)" ADD "masterPasswordSalt" character varying\(\d+\)$/,
|
|
146
|
+
);
|
|
147
|
+
expect(statement).not.toMatch(/NOT NULL/i);
|
|
148
|
+
expect(statement).not.toMatch(/DEFAULT/i);
|
|
149
|
+
expect(statement).not.toMatch(/\bUPDATE\b/i);
|
|
150
|
+
expect(statement).not.toMatch(/\bINSERT\b/i);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("allocates enough space for the generated 64-character salts", async () => {
|
|
155
|
+
const { runner, statements } = makeQueryRunner();
|
|
156
|
+
|
|
157
|
+
await new AddMasterPasswordSalt1786400000000().up(runner);
|
|
158
|
+
|
|
159
|
+
for (const statement of statements) {
|
|
160
|
+
const width: RegExpMatchArray | null = statement.match(
|
|
161
|
+
/character varying\((\d+)\)/,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(width).not.toBeNull();
|
|
165
|
+
expect(Number(width?.[1])).toBeGreaterThanOrEqual(64);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("AddMasterPasswordSalt1786400000000 - safe rollback", () => {
|
|
171
|
+
test("checks both master-password tables before the first DROP", async () => {
|
|
172
|
+
const { runner, statements } = makeQueryRunner();
|
|
173
|
+
|
|
174
|
+
await new AddMasterPasswordSalt1786400000000().down(runner);
|
|
175
|
+
|
|
176
|
+
const firstDropIndex: number = statements.findIndex((statement: string) => {
|
|
177
|
+
return DROP_COLUMN_PATTERN.test(statement);
|
|
178
|
+
});
|
|
179
|
+
const dashboardCheckIndex: number = statements.findIndex(
|
|
180
|
+
(statement: string) => {
|
|
181
|
+
return (
|
|
182
|
+
statement.includes(`FROM "Dashboard"`) &&
|
|
183
|
+
statement.includes(`"masterPassword" LIKE 'scrypt$%'`)
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
const statusPageCheckIndex: number = statements.findIndex(
|
|
188
|
+
(statement: string) => {
|
|
189
|
+
return (
|
|
190
|
+
statement.includes(`FROM "StatusPage"`) &&
|
|
191
|
+
statement.includes(`"masterPassword" LIKE 'scrypt$%'`)
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(dashboardCheckIndex).toBeGreaterThanOrEqual(0);
|
|
197
|
+
expect(statusPageCheckIndex).toBeGreaterThanOrEqual(0);
|
|
198
|
+
expect(firstDropIndex).toBeGreaterThan(dashboardCheckIndex);
|
|
199
|
+
expect(firstDropIndex).toBeGreaterThan(statusPageCheckIndex);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test.each(UNSAFE_ROLLBACK_CASES)(
|
|
203
|
+
"refuses to drop either salt when %s still has a scrypt hash",
|
|
204
|
+
async (_scenario: string, existingScryptRows: ExistingScryptRows) => {
|
|
205
|
+
const { runner, statements } = makeQueryRunner(existingScryptRows);
|
|
206
|
+
|
|
207
|
+
await expect(
|
|
208
|
+
new AddMasterPasswordSalt1786400000000().down(runner),
|
|
209
|
+
).rejects.toThrow(
|
|
210
|
+
"Cannot remove master-password salt columns while scrypt master-password hashes exist.",
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(dropsFrom(statements)).toEqual([]);
|
|
214
|
+
expect(
|
|
215
|
+
statements.filter((statement: string) => {
|
|
216
|
+
return SELECT_EXISTS_PATTERN.test(statement);
|
|
217
|
+
}),
|
|
218
|
+
).toHaveLength(2);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
test("drops both exact salt columns when neither table has scrypt hashes", async () => {
|
|
223
|
+
const { runner, statements } = makeQueryRunner();
|
|
224
|
+
|
|
225
|
+
await new AddMasterPasswordSalt1786400000000().down(runner);
|
|
226
|
+
|
|
227
|
+
expect(dropsFrom(statements)).toEqual([
|
|
228
|
+
`ALTER TABLE "Dashboard" DROP COLUMN "masterPasswordSalt"`,
|
|
229
|
+
`ALTER TABLE "StatusPage" DROP COLUMN "masterPasswordSalt"`,
|
|
230
|
+
]);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("AddMasterPasswordSalt1786400000000 - identity and registration", () => {
|
|
235
|
+
test("filename, exported class, and TypeORM name carry one timestamp", () => {
|
|
236
|
+
const migration: AddMasterPasswordSalt1786400000000 =
|
|
237
|
+
new AddMasterPasswordSalt1786400000000();
|
|
238
|
+
|
|
239
|
+
expect(fs.existsSync(MIGRATION_PATH)).toBe(true);
|
|
240
|
+
expect(path.basename(MIGRATION_PATH)).toBe(MIGRATION_FILENAME);
|
|
241
|
+
expect(timestampFromFilename(MIGRATION_FILENAME)).toBe(MIGRATION_TIMESTAMP);
|
|
242
|
+
expect(timestampFrom(AddMasterPasswordSalt1786400000000.name)).toBe(
|
|
243
|
+
MIGRATION_TIMESTAMP,
|
|
244
|
+
);
|
|
245
|
+
expect(timestampFrom(migration.name)).toBe(MIGRATION_TIMESTAMP);
|
|
246
|
+
expect(migration.name).toBe("AddMasterPasswordSalt1786400000000");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* Registration is the invariant that matters — an unregistered migration
|
|
251
|
+
* never runs. Being LAST is not: it was true the day this landed and every
|
|
252
|
+
* migration added since has appended after it, so pinning it here would
|
|
253
|
+
* fail the next person's unrelated PR rather than catch a real defect.
|
|
254
|
+
*/
|
|
255
|
+
test("is registered exactly once", () => {
|
|
256
|
+
const occurrences: number = SchemaMigrations.filter(
|
|
257
|
+
(migration: unknown) => {
|
|
258
|
+
return migration === AddMasterPasswordSalt1786400000000;
|
|
259
|
+
},
|
|
260
|
+
).length;
|
|
261
|
+
|
|
262
|
+
expect(occurrences).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("has a timestamp strictly after every previously registered migration", () => {
|
|
266
|
+
const migrationIndex: number = SchemaMigrations.indexOf(
|
|
267
|
+
AddMasterPasswordSalt1786400000000,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(migrationIndex).toBeGreaterThan(-1);
|
|
271
|
+
|
|
272
|
+
for (const earlierMigration of SchemaMigrations.slice(0, migrationIndex)) {
|
|
273
|
+
const earlierTimestamp: number | null = optionalTrailingTimestampFrom(
|
|
274
|
+
earlierMigration.name,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// The original `InitialMigration` is the sole pre-timestamp convention.
|
|
278
|
+
if (earlierTimestamp !== null) {
|
|
279
|
+
expect(earlierTimestamp).toBeLessThan(MIGRATION_TIMESTAMP);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import AnalyticsDatabaseService
|
|
1
|
+
import AnalyticsDatabaseService, {
|
|
2
|
+
MigrationExecuteOptions,
|
|
3
|
+
} from "../../../Server/Services/AnalyticsDatabaseService";
|
|
2
4
|
import {
|
|
3
5
|
SQL,
|
|
4
6
|
Statement,
|
|
@@ -930,4 +932,26 @@ describe("AnalyticsDatabaseService", () => {
|
|
|
930
932
|
});
|
|
931
933
|
});
|
|
932
934
|
});
|
|
935
|
+
|
|
936
|
+
describe("MigrationExecuteOptions", () => {
|
|
937
|
+
/*
|
|
938
|
+
* Guards the exact regression a customer hit: ON CLUSTER DDL running with
|
|
939
|
+
* the server-default `throw` output mode aborted the whole migrate Job
|
|
940
|
+
* with TIMEOUT_EXCEEDED (code 159) when 4 of 5 hosts had a backlogged
|
|
941
|
+
* DDL queue — even though the queued task completes in the background.
|
|
942
|
+
*/
|
|
943
|
+
test("migration DDL never throws on distributed DDL confirmation timeout", () => {
|
|
944
|
+
expect(MigrationExecuteOptions.useMigrationConnection).toBe(true);
|
|
945
|
+
expect(
|
|
946
|
+
MigrationExecuteOptions.clickhouseSettings?.distributed_ddl_output_mode,
|
|
947
|
+
).toBe("null_status_on_timeout");
|
|
948
|
+
// Int64 settings travel as strings; default mirrors the server's 180s.
|
|
949
|
+
expect(
|
|
950
|
+
String(
|
|
951
|
+
MigrationExecuteOptions.clickhouseSettings
|
|
952
|
+
?.distributed_ddl_task_timeout,
|
|
953
|
+
),
|
|
954
|
+
).toMatch(/^-?\d+$/);
|
|
955
|
+
});
|
|
956
|
+
});
|
|
933
957
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import UserService from "../../../Server/Services/UserService";
|
|
2
2
|
import StatusPagePrivateUserService from "../../../Server/Services/StatusPagePrivateUserService";
|
|
3
|
-
import
|
|
3
|
+
import UserSessionService from "../../../Server/Services/UserSessionService";
|
|
4
4
|
import ColumnPermissions from "../../../Server/Types/Database/Permissions/ColumnPermission";
|
|
5
5
|
import DatabaseRequestType from "../../../Server/Types/BaseDatabase/DatabaseRequestType";
|
|
6
6
|
import PasswordHash from "../../../Server/Utils/PasswordHash";
|
|
7
7
|
import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
|
|
8
8
|
import User from "../../../Models/DatabaseModels/User";
|
|
9
9
|
import StatusPagePrivateUser from "../../../Models/DatabaseModels/StatusPagePrivateUser";
|
|
10
|
-
import
|
|
10
|
+
import UserSession from "../../../Models/DatabaseModels/UserSession";
|
|
11
11
|
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
12
12
|
import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
|
|
13
13
|
import TableColumnType from "../../../Types/Database/TableColumnType";
|
|
@@ -33,8 +33,8 @@ import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
|
33
33
|
* - a salt column that forgets `computed: true` breaks every non-root
|
|
34
34
|
* create of a row carrying a password, because the create permission
|
|
35
35
|
* check runs AFTER hashing and would reject a column nobody may write;
|
|
36
|
-
* - a hashed column with no declared salt column (session refresh tokens
|
|
37
|
-
*
|
|
36
|
+
* - a hashed column with no declared salt column (session refresh tokens)
|
|
37
|
+
* must keep hashing exactly as it always did;
|
|
38
38
|
* - an update that does not touch the password must not touch the salt,
|
|
39
39
|
* or every password in the affected rows stops verifying.
|
|
40
40
|
*
|
|
@@ -355,56 +355,57 @@ describe("Update path — sanitizeCreateOrUpdate mints a per-record salt", () =>
|
|
|
355
355
|
});
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
-
describe("
|
|
359
|
-
test("a
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
).hash(dashboard);
|
|
358
|
+
describe("High-entropy hashed columns remain searchable", () => {
|
|
359
|
+
test("a session refresh token keeps the fast deterministic SHA-256 digest", async () => {
|
|
360
|
+
const data: JSONObject = (await (
|
|
361
|
+
UserSessionService as unknown as {
|
|
362
|
+
sanitizeCreateOrUpdate: SanitizeFunction;
|
|
363
|
+
}
|
|
364
|
+
).sanitizeCreateOrUpdate(
|
|
365
|
+
{
|
|
366
|
+
refreshToken: new HashedString("server-generated-refresh-token"),
|
|
367
|
+
},
|
|
368
|
+
{ isRoot: true },
|
|
369
|
+
)) as JSONObject;
|
|
371
370
|
|
|
372
|
-
expect(
|
|
371
|
+
expect(data["refreshToken"]).toBe(
|
|
373
372
|
CryptoJS.SHA256(
|
|
374
|
-
EncryptionSecret.toString() + "
|
|
373
|
+
EncryptionSecret.toString() + "server-generated-refresh-token",
|
|
375
374
|
).toString(),
|
|
376
375
|
);
|
|
377
376
|
});
|
|
378
377
|
|
|
379
|
-
test("
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
378
|
+
test("refresh tokens do not declare or invent a password salt", async () => {
|
|
379
|
+
const data: JSONObject = (await (
|
|
380
|
+
UserSessionService as unknown as {
|
|
381
|
+
sanitizeCreateOrUpdate: SanitizeFunction;
|
|
382
|
+
}
|
|
383
|
+
).sanitizeCreateOrUpdate(
|
|
384
|
+
{
|
|
385
|
+
refreshToken: new HashedString("server-generated-refresh-token"),
|
|
386
|
+
},
|
|
387
|
+
{ isRoot: true },
|
|
388
|
+
)) as JSONObject;
|
|
386
389
|
|
|
387
390
|
expect(
|
|
388
|
-
new
|
|
389
|
-
).toBeUndefined();
|
|
390
|
-
expect(
|
|
391
|
-
(dashboard as unknown as JSONObject)["passwordSalt"],
|
|
391
|
+
new UserSession().getTableColumnMetadata("refreshToken").hashSaltColumn,
|
|
392
392
|
).toBeUndefined();
|
|
393
|
+
expect(data["refreshTokenSalt"]).toBeUndefined();
|
|
393
394
|
});
|
|
394
395
|
|
|
395
|
-
test("a
|
|
396
|
-
// The desync guard is scoped to salted columns only.
|
|
396
|
+
test("a pre-hashed refresh-token digest remains accepted on update", async () => {
|
|
397
397
|
const data: JSONObject = (await (
|
|
398
|
-
|
|
398
|
+
UserSessionService as unknown as {
|
|
399
399
|
sanitizeCreateOrUpdate: SanitizeFunction;
|
|
400
400
|
}
|
|
401
401
|
).sanitizeCreateOrUpdate(
|
|
402
|
-
{
|
|
402
|
+
{ refreshToken: "a-raw-digest" },
|
|
403
403
|
{ isRoot: true },
|
|
404
404
|
true,
|
|
405
405
|
)) as JSONObject;
|
|
406
406
|
|
|
407
|
-
expect(data["
|
|
407
|
+
expect(data["refreshToken"]).toBe("a-raw-digest");
|
|
408
|
+
expect(data["refreshTokenSalt"]).toBeUndefined();
|
|
408
409
|
});
|
|
409
410
|
});
|
|
410
411
|
|
|
@@ -415,6 +416,7 @@ describe("Hashed columns that declare no salt column are untouched", () => {
|
|
|
415
416
|
type UpdateColumnsInput = {
|
|
416
417
|
id: ObjectID;
|
|
417
418
|
data: JSONObject;
|
|
419
|
+
expectedData?: JSONObject;
|
|
418
420
|
skipUpdateDateColumn?: boolean;
|
|
419
421
|
};
|
|
420
422
|
|
|
@@ -597,6 +599,10 @@ describe("verifyHashedColumnValue — transparent upgrade of legacy hashes", ()
|
|
|
597
599
|
|
|
598
600
|
expect(write.id.toString()).toBe(user._id);
|
|
599
601
|
expect(write.data["passwordSalt"]).toMatch(/^[0-9a-f]{64}$/);
|
|
602
|
+
expect(write.expectedData).toEqual({
|
|
603
|
+
password: user.password!.toString(),
|
|
604
|
+
passwordSalt: null,
|
|
605
|
+
});
|
|
600
606
|
|
|
601
607
|
// The written hash verifies under the written salt.
|
|
602
608
|
await expect(
|
|
@@ -726,6 +732,10 @@ describe("verifyHashedColumnValue — transparent upgrade of legacy hashes", ()
|
|
|
726
732
|
|
|
727
733
|
expect(captured).toHaveLength(1);
|
|
728
734
|
expect(captured[0]!.data["password"]).toMatch(/^scrypt\$/);
|
|
735
|
+
expect(captured[0]!.expectedData).toEqual({
|
|
736
|
+
password: user.password!.toString(),
|
|
737
|
+
passwordSalt: salt,
|
|
738
|
+
});
|
|
729
739
|
// A brand new salt, not the interim one it came in with.
|
|
730
740
|
expect(captured[0]!.data["passwordSalt"]).not.toBe(salt);
|
|
731
741
|
});
|
|
@@ -125,4 +125,58 @@ describe("DatabaseService.updateColumnsByIdWithoutHooks", () => {
|
|
|
125
125
|
APPLICATION_ID.toString(),
|
|
126
126
|
);
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
it("adds null-safe compare-and-set guards for expected values", async () => {
|
|
130
|
+
const newValue: Date = new Date("2026-08-07T10:00:00.000Z");
|
|
131
|
+
const oldValue: Date = new Date("2026-08-07T09:00:00.000Z");
|
|
132
|
+
|
|
133
|
+
await service.updateColumnsByIdWithoutHooks({
|
|
134
|
+
id: APPLICATION_ID,
|
|
135
|
+
data: { lastSeenAt: newValue },
|
|
136
|
+
expectedData: {
|
|
137
|
+
sessionReplayLastChunkReceivedAt: oldValue,
|
|
138
|
+
sessionReplayBudgetExceededAt: null,
|
|
139
|
+
},
|
|
140
|
+
skipUpdateDateColumn: true,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(captured).toEqual([
|
|
144
|
+
{
|
|
145
|
+
sql:
|
|
146
|
+
`UPDATE "RumApplication" SET "lastSeenAt" = $1 ` +
|
|
147
|
+
`WHERE "_id" = $2 ` +
|
|
148
|
+
`AND "sessionReplayLastChunkReceivedAt" IS NOT DISTINCT FROM $3 ` +
|
|
149
|
+
`AND "sessionReplayBudgetExceededAt" IS NOT DISTINCT FROM $4`,
|
|
150
|
+
params: [newValue, APPLICATION_ID.toString(), oldValue, null],
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("rejects unknown compare-and-set columns before issuing SQL", async () => {
|
|
156
|
+
await expect(
|
|
157
|
+
service.updateColumnsByIdWithoutHooks({
|
|
158
|
+
id: APPLICATION_ID,
|
|
159
|
+
data: { lastSeenAt: new Date() },
|
|
160
|
+
expectedData: { notAColumn: "value" } as never,
|
|
161
|
+
}),
|
|
162
|
+
).rejects.toThrow('unknown expected column "notAColumn"');
|
|
163
|
+
|
|
164
|
+
expect(captured).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("rejects SQL expressions in compare-and-set values", async () => {
|
|
168
|
+
await expect(
|
|
169
|
+
service.updateColumnsByIdWithoutHooks({
|
|
170
|
+
id: APPLICATION_ID,
|
|
171
|
+
data: { lastSeenAt: new Date() },
|
|
172
|
+
expectedData: {
|
|
173
|
+
sessionReplayLastChunkReceivedAt: (() => {
|
|
174
|
+
return "NOW()";
|
|
175
|
+
}) as never,
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
).rejects.toThrow("SQL-expression expected values are not supported");
|
|
179
|
+
|
|
180
|
+
expect(captured).toHaveLength(0);
|
|
181
|
+
});
|
|
128
182
|
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import AlertFeed, {
|
|
2
|
+
AlertFeedEventType,
|
|
3
|
+
} from "../../../Models/DatabaseModels/AlertFeed";
|
|
4
|
+
import IncidentFeed, {
|
|
5
|
+
IncidentFeedEventType,
|
|
6
|
+
} from "../../../Models/DatabaseModels/IncidentFeed";
|
|
7
|
+
import AlertFeedService from "../../../Server/Services/AlertFeedService";
|
|
8
|
+
import IncidentFeedService from "../../../Server/Services/IncidentFeedService";
|
|
9
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
10
|
+
import type { TableColumnMetadata } from "../../../Types/Database/TableColumn";
|
|
11
|
+
import TableColumnType from "../../../Types/Database/TableColumnType";
|
|
12
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
13
|
+
import { afterEach, describe, expect, it, jest } from "@jest/globals";
|
|
14
|
+
import { getMetadataArgsStorage } from "typeorm";
|
|
15
|
+
import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
|
|
16
|
+
import type { IndexMetadataArgs } from "typeorm/metadata-args/IndexMetadataArgs";
|
|
17
|
+
|
|
18
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
19
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
20
|
+
);
|
|
21
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
22
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
23
|
+
);
|
|
24
|
+
const ALERT_ID: ObjectID = new ObjectID("cccccccc-cccc-4ccc-8ccc-cccccccccccc");
|
|
25
|
+
const AI_RUN_ID: ObjectID = new ObjectID(
|
|
26
|
+
"dddddddd-dddd-4ddd-8ddd-dddddddddddd",
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function expectRunAssociationMetadata(
|
|
30
|
+
model: IncidentFeed | AlertFeed,
|
|
31
|
+
target: typeof IncidentFeed | typeof AlertFeed,
|
|
32
|
+
subjectIdColumn: "incidentId" | "alertId",
|
|
33
|
+
): void {
|
|
34
|
+
expect(model.getTableColumns().columns).toContain("aiRunId");
|
|
35
|
+
|
|
36
|
+
const tableColumn: TableColumnMetadata =
|
|
37
|
+
model.getTableColumnMetadata("aiRunId");
|
|
38
|
+
expect(tableColumn.type).toBe(TableColumnType.ObjectID);
|
|
39
|
+
expect(tableColumn.required).toBe(false);
|
|
40
|
+
expect(tableColumn.canReadOnRelationQuery).toBe(true);
|
|
41
|
+
|
|
42
|
+
const databaseColumn: ColumnMetadataArgs | undefined =
|
|
43
|
+
getMetadataArgsStorage().columns.find((column: ColumnMetadataArgs) => {
|
|
44
|
+
return column.target === target && column.propertyName === "aiRunId";
|
|
45
|
+
});
|
|
46
|
+
expect(databaseColumn?.options.nullable).toBe(true);
|
|
47
|
+
|
|
48
|
+
const hasExactLookupIndex: boolean = getMetadataArgsStorage().indices.some(
|
|
49
|
+
(index: IndexMetadataArgs): boolean => {
|
|
50
|
+
return (
|
|
51
|
+
index.target === target &&
|
|
52
|
+
Array.isArray(index.columns) &&
|
|
53
|
+
index.columns.length === 2 &&
|
|
54
|
+
index.columns[0] === subjectIdColumn &&
|
|
55
|
+
index.columns[1] === "aiRunId"
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
expect(hasExactLookupIndex).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("feed AI run association metadata", () => {
|
|
63
|
+
it("defines a nullable, exact-lookup-indexed incident feed association", () => {
|
|
64
|
+
expectRunAssociationMetadata(
|
|
65
|
+
new IncidentFeed(),
|
|
66
|
+
IncidentFeed,
|
|
67
|
+
"incidentId",
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("defines a nullable, exact-lookup-indexed alert feed association", () => {
|
|
72
|
+
expectRunAssociationMetadata(new AlertFeed(), AlertFeed, "alertId");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("feed service AI run persistence", () => {
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
jest.restoreAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("copies aiRunId into the incident RootCause row", async () => {
|
|
82
|
+
jest
|
|
83
|
+
.spyOn(IncidentFeedService, "create")
|
|
84
|
+
.mockResolvedValue(new IncidentFeed());
|
|
85
|
+
|
|
86
|
+
await IncidentFeedService.createIncidentFeedItem({
|
|
87
|
+
incidentId: INCIDENT_ID,
|
|
88
|
+
projectId: PROJECT_ID,
|
|
89
|
+
aiRunId: AI_RUN_ID,
|
|
90
|
+
incidentFeedEventType: IncidentFeedEventType.RootCause,
|
|
91
|
+
feedInfoInMarkdown: "Incident analysis",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const createCall: CreateBy<IncidentFeed> = jest.mocked(
|
|
95
|
+
IncidentFeedService.create,
|
|
96
|
+
).mock.calls[0]![0];
|
|
97
|
+
const createData: IncidentFeed = createCall.data;
|
|
98
|
+
expect(createData.aiRunId).toEqual(AI_RUN_ID);
|
|
99
|
+
expect(createData.incidentId).toEqual(INCIDENT_ID);
|
|
100
|
+
expect(createData.incidentFeedEventType).toBe(
|
|
101
|
+
IncidentFeedEventType.RootCause,
|
|
102
|
+
);
|
|
103
|
+
expect(createCall.props).toEqual({ isRoot: true });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("copies aiRunId into the alert RootCause row", async () => {
|
|
107
|
+
jest.spyOn(AlertFeedService, "create").mockResolvedValue(new AlertFeed());
|
|
108
|
+
|
|
109
|
+
await AlertFeedService.createAlertFeedItem({
|
|
110
|
+
alertId: ALERT_ID,
|
|
111
|
+
projectId: PROJECT_ID,
|
|
112
|
+
aiRunId: AI_RUN_ID,
|
|
113
|
+
alertFeedEventType: AlertFeedEventType.RootCause,
|
|
114
|
+
feedInfoInMarkdown: "Alert analysis",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const createCall: CreateBy<AlertFeed> = jest.mocked(AlertFeedService.create)
|
|
118
|
+
.mock.calls[0]![0];
|
|
119
|
+
const createData: AlertFeed = createCall.data;
|
|
120
|
+
expect(createData.aiRunId).toEqual(AI_RUN_ID);
|
|
121
|
+
expect(createData.alertId).toEqual(ALERT_ID);
|
|
122
|
+
expect(createData.alertFeedEventType).toBe(AlertFeedEventType.RootCause);
|
|
123
|
+
expect(createCall.props).toEqual({ isRoot: true });
|
|
124
|
+
});
|
|
125
|
+
});
|