@oneuptime/common 12.0.2 → 12.0.4
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/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +458 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +38 -4
- package/Server/Infrastructure/GlobalCache.ts +152 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CephClusterService.ts +26 -86
- package/Server/Services/CloudResourceService.ts +23 -83
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +296 -2
- package/Server/Services/DockerHostService.ts +19 -79
- package/Server/Services/DockerSwarmClusterService.ts +27 -88
- package/Server/Services/HostService.ts +46 -99
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetService.ts +19 -79
- package/Server/Services/KubernetesClusterService.ts +17 -78
- package/Server/Services/LabelService.ts +57 -10
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +89 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/PodmanHostService.ts +19 -79
- package/Server/Services/ProxmoxClusterService.ts +23 -83
- package/Server/Services/RumApplicationService.ts +19 -80
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +24 -85
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/Decimal.ts +16 -4
- package/Types/HashedString.ts +139 -4
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +343 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +34 -4
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -76
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -73
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +246 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -69
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -102
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -69
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +42 -7
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +81 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -69
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -70
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import StatusPageMonitorRuleEngineService from "./StatusPageMonitorRuleEngineService";
|
|
3
|
+
import StatusPageGroupService from "./StatusPageGroupService";
|
|
4
|
+
import StatusPageResourceService from "./StatusPageResourceService";
|
|
5
|
+
import StatusPageService from "./StatusPageService";
|
|
6
|
+
import Model from "../../Models/DatabaseModels/StatusPageMonitorRule";
|
|
7
|
+
import StatusPageGroup from "../../Models/DatabaseModels/StatusPageGroup";
|
|
8
|
+
import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
|
|
9
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
10
|
+
import DeleteBy from "../Types/Database/DeleteBy";
|
|
11
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
12
|
+
import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
|
|
13
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
14
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
15
|
+
import ObjectID from "../../Types/ObjectID";
|
|
16
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
17
|
+
import ProjectScopedReferenceValidator, {
|
|
18
|
+
resolveReferenceId,
|
|
19
|
+
} from "../Utils/Database/ProjectScopedReferenceValidator";
|
|
20
|
+
import StatusPageMonitorRulePatternValidator from "../Utils/StatusPage/MonitorRulePatternValidator";
|
|
21
|
+
import logger, { LogAttributes } from "../Utils/Logger";
|
|
22
|
+
|
|
23
|
+
export class Service extends DatabaseService<Model> {
|
|
24
|
+
public constructor() {
|
|
25
|
+
super(Model);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@CaptureSpan()
|
|
29
|
+
protected override async onBeforeCreate(
|
|
30
|
+
createBy: CreateBy<Model>,
|
|
31
|
+
): Promise<OnCreate<Model>> {
|
|
32
|
+
if (!createBy.data.statusPageId) {
|
|
33
|
+
throw new BadDataException(
|
|
34
|
+
"Status Page ID is required to create a status page monitor rule.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.assertHasMatchCriteria({
|
|
39
|
+
monitorLabelCount: (createBy.data.monitorLabels || []).length,
|
|
40
|
+
monitorNamePattern: createBy.data.monitorNamePattern,
|
|
41
|
+
monitorDescriptionPattern: createBy.data.monitorDescriptionPattern,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
StatusPageMonitorRulePatternValidator.validate({
|
|
45
|
+
namePattern: createBy.data.monitorNamePattern,
|
|
46
|
+
descriptionPattern: createBy.data.monitorDescriptionPattern,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await this.assertReferencesAreInScope({
|
|
50
|
+
projectId: createBy.props.tenantId || createBy.data.projectId,
|
|
51
|
+
statusPageId: resolveReferenceId(
|
|
52
|
+
createBy.data.statusPageId || createBy.data.statusPage,
|
|
53
|
+
),
|
|
54
|
+
statusPageGroupId: resolveReferenceId(
|
|
55
|
+
createBy.data.statusPageGroupId || createBy.data.statusPageGroup,
|
|
56
|
+
),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
createBy: createBy,
|
|
61
|
+
carryForward: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@CaptureSpan()
|
|
66
|
+
protected override async onCreateSuccess(
|
|
67
|
+
_onCreate: OnCreate<Model>,
|
|
68
|
+
createdItem: Model,
|
|
69
|
+
): Promise<Model> {
|
|
70
|
+
/*
|
|
71
|
+
* Backfill: a rule the user just wrote should populate the page with the
|
|
72
|
+
* monitors that already match, not only with ones created after it.
|
|
73
|
+
*/
|
|
74
|
+
if (createdItem.id) {
|
|
75
|
+
await this.syncQuietly(createdItem.id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return createdItem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@CaptureSpan()
|
|
82
|
+
protected override async onBeforeUpdate(
|
|
83
|
+
updateBy: UpdateBy<Model>,
|
|
84
|
+
): Promise<OnUpdate<Model>> {
|
|
85
|
+
StatusPageMonitorRulePatternValidator.validate({
|
|
86
|
+
namePattern: updateBy.data.monitorNamePattern as string | undefined,
|
|
87
|
+
descriptionPattern: updateBy.data.monitorDescriptionPattern as
|
|
88
|
+
| string
|
|
89
|
+
| undefined,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* An edit can empty a rule out just as easily as a create can, and the
|
|
94
|
+
* result is the same useless rule. The merged view is what matters, so
|
|
95
|
+
* fields the edit does not mention are read back off the stored row -
|
|
96
|
+
* clearing only the labels on a rule that also has a name pattern is
|
|
97
|
+
* perfectly fine, and must not be refused.
|
|
98
|
+
*/
|
|
99
|
+
await this.assertUpdateKeepsMatchCriteria(updateBy);
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Re-pointing a rule at a different group is an ordinary edit, so the same
|
|
103
|
+
* scope checks the create path makes apply here. statusPageId is
|
|
104
|
+
* create-only in the column ACL, so only the group can move.
|
|
105
|
+
*/
|
|
106
|
+
const nextGroupId: ObjectID | string | undefined = resolveReferenceId(
|
|
107
|
+
(updateBy.data as Record<string, unknown>)["statusPageGroupId"] ||
|
|
108
|
+
(updateBy.data as Record<string, unknown>)["statusPageGroup"],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (nextGroupId) {
|
|
112
|
+
for (const rule of await this.findRulesForQuery(updateBy)) {
|
|
113
|
+
await this.assertReferencesAreInScope({
|
|
114
|
+
projectId: updateBy.props.tenantId || rule.projectId,
|
|
115
|
+
statusPageId: rule.statusPageId,
|
|
116
|
+
statusPageGroupId: nextGroupId,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { updateBy, carryForward: null };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@CaptureSpan()
|
|
125
|
+
protected override async onUpdateSuccess(
|
|
126
|
+
onUpdate: OnUpdate<Model>,
|
|
127
|
+
updatedItemIds: Array<ObjectID>,
|
|
128
|
+
): Promise<OnUpdate<Model>> {
|
|
129
|
+
/*
|
|
130
|
+
* Every editable field on this model changes what the rule matches or how
|
|
131
|
+
* the resources it owns are rendered, so any edit re-runs the rule. The
|
|
132
|
+
* sync is idempotent, so re-running it after a no-op edit costs a read.
|
|
133
|
+
*/
|
|
134
|
+
for (const id of updatedItemIds) {
|
|
135
|
+
await this.syncQuietly(id);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return onUpdate;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deleting a rule undoes it: the resources it added go too, and the monitors
|
|
143
|
+
* it was publishing are offered to the rules that survive.
|
|
144
|
+
*
|
|
145
|
+
* Every destructive step lives here rather than in onBeforeDelete, because
|
|
146
|
+
* DatabaseService applies checkDeleteQueryPermission *after* onBeforeDelete.
|
|
147
|
+
* A hook there sees the caller's raw, un-tenant-scoped query, so a
|
|
148
|
+
* root-privileged write from it would let a caller destroy another project's
|
|
149
|
+
* status page resources by naming that project's rule id, with the
|
|
150
|
+
* permission check only rejecting the (already moot) rule delete afterwards.
|
|
151
|
+
* `itemIdsBeforeDelete` is the permission-checked set, so acting on it is
|
|
152
|
+
* safe.
|
|
153
|
+
*
|
|
154
|
+
* By this point the StatusPageResource -> StatusPageMonitorRule foreign key
|
|
155
|
+
* has usually already cascaded the resources away, which makes the removal
|
|
156
|
+
* pass belt-and-braces rather than the primary mechanism. It is kept so the
|
|
157
|
+
* behaviour does not depend on the cascade staying as it is — and it is also
|
|
158
|
+
* why the monitor ids come from carryForward: after the cascade there is
|
|
159
|
+
* nothing left to read them off.
|
|
160
|
+
*/
|
|
161
|
+
@CaptureSpan()
|
|
162
|
+
protected override async onDeleteSuccess(
|
|
163
|
+
onDelete: OnDelete<Model>,
|
|
164
|
+
itemIdsBeforeDelete: Array<ObjectID>,
|
|
165
|
+
): Promise<OnDelete<Model>> {
|
|
166
|
+
const monitorIdsByRuleId: Record<string, Array<string>> = (
|
|
167
|
+
onDelete.carryForward as {
|
|
168
|
+
monitorIdsByRuleId?: Record<string, Array<string>>;
|
|
169
|
+
} | null
|
|
170
|
+
)?.monitorIdsByRuleId || {};
|
|
171
|
+
|
|
172
|
+
for (const statusPageMonitorRuleId of itemIdsBeforeDelete) {
|
|
173
|
+
try {
|
|
174
|
+
await StatusPageMonitorRuleEngineService.removeResourcesAddedByRule({
|
|
175
|
+
statusPageMonitorRuleId: statusPageMonitorRuleId,
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger.error(
|
|
179
|
+
`Error removing resources added by status page monitor rule ${statusPageMonitorRuleId.toString()}: ${error}`,
|
|
180
|
+
{
|
|
181
|
+
statusPageMonitorRuleId: statusPageMonitorRuleId.toString(),
|
|
182
|
+
} as LogAttributes,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* Deleting a rule must behave like disabling one. Disabling routes
|
|
188
|
+
* through syncResourcesForRule, which hands each released monitor to the
|
|
189
|
+
* other rules on the page; without this, deleting would instead drop a
|
|
190
|
+
* monitor that a sibling rule still claims — the same rule, switched off
|
|
191
|
+
* two different ways, giving two different pages.
|
|
192
|
+
*/
|
|
193
|
+
for (const monitorId of monitorIdsByRuleId[
|
|
194
|
+
statusPageMonitorRuleId.toString()
|
|
195
|
+
] || []) {
|
|
196
|
+
try {
|
|
197
|
+
await StatusPageMonitorRuleEngineService.syncRulesForMonitor({
|
|
198
|
+
monitorId: new ObjectID(monitorId),
|
|
199
|
+
projectId: onDelete.deleteBy.props.tenantId as ObjectID | undefined,
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
logger.error(
|
|
203
|
+
`Error re-homing monitor ${monitorId} after status page monitor rule ${statusPageMonitorRuleId.toString()} was deleted: ${error}`,
|
|
204
|
+
{ monitorId: monitorId } as LogAttributes,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return onDelete;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Notes down which monitors each doomed rule is publishing, so
|
|
215
|
+
* onDeleteSuccess can offer them to the rules that survive.
|
|
216
|
+
*
|
|
217
|
+
* This has to happen before the delete because the StatusPageResource ->
|
|
218
|
+
* StatusPageMonitorRule foreign key is ON DELETE CASCADE: by the time the
|
|
219
|
+
* rule row is gone, so are the resources that named the monitors.
|
|
220
|
+
*
|
|
221
|
+
* It is READ ONLY, and reads through the caller's own props rather than as
|
|
222
|
+
* root. onBeforeDelete runs before checkDeleteQueryPermission, so anything
|
|
223
|
+
* here sees the caller's raw query — a root-privileged write from this hook
|
|
224
|
+
* would let a caller destroy another project's status page resources by
|
|
225
|
+
* naming that project's rule id.
|
|
226
|
+
*/
|
|
227
|
+
@CaptureSpan()
|
|
228
|
+
protected override async onBeforeDelete(
|
|
229
|
+
deleteBy: DeleteBy<Model>,
|
|
230
|
+
): Promise<OnDelete<Model>> {
|
|
231
|
+
const monitorIdsByRuleId: Record<string, Array<string>> = {};
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const rules: Array<Model> = await this.findBy({
|
|
235
|
+
query: deleteBy.query,
|
|
236
|
+
select: {
|
|
237
|
+
_id: true,
|
|
238
|
+
},
|
|
239
|
+
limit: LIMIT_PER_PROJECT,
|
|
240
|
+
skip: 0,
|
|
241
|
+
props: deleteBy.props,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
for (const rule of rules) {
|
|
245
|
+
if (!rule.id) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const resources: Array<StatusPageResource> =
|
|
250
|
+
await StatusPageResourceService.findBy({
|
|
251
|
+
query: {
|
|
252
|
+
statusPageMonitorRuleId: rule.id,
|
|
253
|
+
},
|
|
254
|
+
select: {
|
|
255
|
+
monitorId: true,
|
|
256
|
+
},
|
|
257
|
+
limit: LIMIT_PER_PROJECT,
|
|
258
|
+
skip: 0,
|
|
259
|
+
props: {
|
|
260
|
+
isRoot: true,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
monitorIdsByRuleId[rule.id.toString()] = resources
|
|
265
|
+
.map((resource: StatusPageResource) => {
|
|
266
|
+
return resource.monitorId?.toString() || "";
|
|
267
|
+
})
|
|
268
|
+
.filter((monitorId: string) => {
|
|
269
|
+
return monitorId !== "";
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
/*
|
|
274
|
+
* Best effort. Failing to note the monitors down must not block the
|
|
275
|
+
* delete; the worst case is that a sibling rule re-adopts them on its
|
|
276
|
+
* next run instead of immediately.
|
|
277
|
+
*/
|
|
278
|
+
logger.error(
|
|
279
|
+
`Error collecting monitors published by status page monitor rules before delete: ${error}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { deleteBy, carryForward: { monitorIdsByRuleId } };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A rule points at a status page, and optionally at a group on that page.
|
|
288
|
+
* Neither is checked by the framework, and both decide what ends up on a
|
|
289
|
+
* PUBLIC page, so both are checked here:
|
|
290
|
+
*
|
|
291
|
+
* - a status page from another project would let one tenant publish its
|
|
292
|
+
* monitors on another tenant's status page;
|
|
293
|
+
* - a group belonging to a different status page would produce resources
|
|
294
|
+
* that render nowhere, because the page only draws groups that are its
|
|
295
|
+
* own — a rule that silently adds invisible monitors.
|
|
296
|
+
*/
|
|
297
|
+
private async assertReferencesAreInScope(data: {
|
|
298
|
+
projectId: ObjectID | undefined;
|
|
299
|
+
statusPageId: ObjectID | string | undefined;
|
|
300
|
+
statusPageGroupId: ObjectID | string | undefined;
|
|
301
|
+
}): Promise<void> {
|
|
302
|
+
await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
|
|
303
|
+
projectId: data.projectId,
|
|
304
|
+
subject: "status page monitor rule",
|
|
305
|
+
references: [
|
|
306
|
+
{
|
|
307
|
+
modelName: "Status Page",
|
|
308
|
+
id: data.statusPageId,
|
|
309
|
+
service: StatusPageService,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
modelName: "Status Page Group",
|
|
313
|
+
id: data.statusPageGroupId,
|
|
314
|
+
service: StatusPageGroupService,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (!data.statusPageGroupId || !data.statusPageId) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const group: StatusPageGroup | null =
|
|
324
|
+
await StatusPageGroupService.findOneById({
|
|
325
|
+
id: new ObjectID(data.statusPageGroupId.toString()),
|
|
326
|
+
select: {
|
|
327
|
+
_id: true,
|
|
328
|
+
statusPageId: true,
|
|
329
|
+
},
|
|
330
|
+
props: {
|
|
331
|
+
isRoot: true,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
/*
|
|
336
|
+
* A group id that matches no row at all is left to the foreign key to
|
|
337
|
+
* report, the same call the project-scope validator above makes.
|
|
338
|
+
*/
|
|
339
|
+
if (!group) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (group.statusPageId?.toString() !== data.statusPageId.toString()) {
|
|
344
|
+
throw new BadDataException(
|
|
345
|
+
"The group this rule adds monitors to belongs to a different status page. Pick a group on the status page the rule is for.",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* A rule with no criteria would claim every monitor in the project. An empty
|
|
352
|
+
* form is much more likely to be an unfinished one, and the cost of guessing
|
|
353
|
+
* wrong is a private monitor on a public page — so say so instead.
|
|
354
|
+
*/
|
|
355
|
+
private assertHasMatchCriteria(data: {
|
|
356
|
+
monitorLabelCount: number;
|
|
357
|
+
monitorNamePattern?: string | undefined;
|
|
358
|
+
monitorDescriptionPattern?: string | undefined;
|
|
359
|
+
}): void {
|
|
360
|
+
if (
|
|
361
|
+
data.monitorLabelCount === 0 &&
|
|
362
|
+
!data.monitorNamePattern &&
|
|
363
|
+
!data.monitorDescriptionPattern
|
|
364
|
+
) {
|
|
365
|
+
throw new BadDataException(
|
|
366
|
+
"A status page monitor rule needs at least one match criterion: monitor labels, a monitor name pattern, or a monitor description pattern. Use .* as the name pattern to match every monitor.",
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The rules an update actually touches, read for validation.
|
|
373
|
+
*
|
|
374
|
+
* onBeforeUpdate runs before DatabaseService applies the update query's
|
|
375
|
+
* permission check, so the query here is still the caller's raw one. The
|
|
376
|
+
* tenant is pinned onto it when the caller has one, so a validation read can
|
|
377
|
+
* never reach across projects and answer questions about another tenant's
|
|
378
|
+
* rules. Genuinely root callers (the engines, migrations) have no tenantId
|
|
379
|
+
* and are trusted.
|
|
380
|
+
*/
|
|
381
|
+
private async findRulesForQuery(
|
|
382
|
+
updateBy: UpdateBy<Model>,
|
|
383
|
+
): Promise<Array<Model>> {
|
|
384
|
+
const query: Record<string, unknown> = {
|
|
385
|
+
...(updateBy.query as Record<string, unknown>),
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (updateBy.props.tenantId) {
|
|
389
|
+
query["projectId"] = updateBy.props.tenantId;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return await this.findBy({
|
|
393
|
+
query: query as UpdateBy<Model>["query"],
|
|
394
|
+
select: {
|
|
395
|
+
_id: true,
|
|
396
|
+
projectId: true,
|
|
397
|
+
statusPageId: true,
|
|
398
|
+
monitorLabels: {
|
|
399
|
+
_id: true,
|
|
400
|
+
},
|
|
401
|
+
monitorNamePattern: true,
|
|
402
|
+
monitorDescriptionPattern: true,
|
|
403
|
+
},
|
|
404
|
+
limit: LIMIT_PER_PROJECT,
|
|
405
|
+
skip: 0,
|
|
406
|
+
props: {
|
|
407
|
+
isRoot: true,
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The create-time criteria check, applied to what the rule will look like
|
|
414
|
+
* after this edit. Every rule the edit touches has to survive it.
|
|
415
|
+
*/
|
|
416
|
+
private async assertUpdateKeepsMatchCriteria(
|
|
417
|
+
updateBy: UpdateBy<Model>,
|
|
418
|
+
): Promise<void> {
|
|
419
|
+
const touchesCriteria: boolean =
|
|
420
|
+
updateBy.data.monitorLabels !== undefined ||
|
|
421
|
+
updateBy.data.monitorNamePattern !== undefined ||
|
|
422
|
+
updateBy.data.monitorDescriptionPattern !== undefined;
|
|
423
|
+
|
|
424
|
+
if (!touchesCriteria) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const rules: Array<Model> = await this.findRulesForQuery(updateBy);
|
|
429
|
+
|
|
430
|
+
const nextLabels: Array<unknown> | undefined = updateBy.data
|
|
431
|
+
.monitorLabels as Array<unknown> | undefined;
|
|
432
|
+
|
|
433
|
+
for (const rule of rules) {
|
|
434
|
+
this.assertHasMatchCriteria({
|
|
435
|
+
monitorLabelCount: (nextLabels === undefined
|
|
436
|
+
? rule.monitorLabels || []
|
|
437
|
+
: nextLabels
|
|
438
|
+
).length,
|
|
439
|
+
monitorNamePattern:
|
|
440
|
+
updateBy.data.monitorNamePattern === undefined
|
|
441
|
+
? rule.monitorNamePattern
|
|
442
|
+
: (updateBy.data.monitorNamePattern as string | undefined),
|
|
443
|
+
monitorDescriptionPattern:
|
|
444
|
+
updateBy.data.monitorDescriptionPattern === undefined
|
|
445
|
+
? rule.monitorDescriptionPattern
|
|
446
|
+
: (updateBy.data.monitorDescriptionPattern as string | undefined),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Rule CRUD must not fail because the sync did. The rule is already saved
|
|
453
|
+
* and the next monitor change (or the next edit) re-runs it.
|
|
454
|
+
*/
|
|
455
|
+
private async syncQuietly(statusPageMonitorRuleId: ObjectID): Promise<void> {
|
|
456
|
+
try {
|
|
457
|
+
await StatusPageMonitorRuleEngineService.syncResourcesForRule({
|
|
458
|
+
statusPageMonitorRuleId: statusPageMonitorRuleId,
|
|
459
|
+
});
|
|
460
|
+
} catch (error) {
|
|
461
|
+
logger.error(
|
|
462
|
+
`Error syncing status page monitor rule ${statusPageMonitorRuleId.toString()}: ${error}`,
|
|
463
|
+
{
|
|
464
|
+
statusPageMonitorRuleId: statusPageMonitorRuleId.toString(),
|
|
465
|
+
} as LogAttributes,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export default new Service();
|
|
@@ -55,6 +55,7 @@ export class TelemetryEntityRelationshipService extends DatabaseService<Model> {
|
|
|
55
55
|
},
|
|
56
56
|
lastSeenAt: now,
|
|
57
57
|
describe: `edge ${edge.fromEntityKey}-[${edge.relationshipType}]->${edge.toEntityKey}`,
|
|
58
|
+
rowFenceId: `${projectId.toString()}:${edge.fromEntityKey}:${edge.toEntityKey}:${edge.relationshipType}`,
|
|
58
59
|
buildModel: () => {
|
|
59
60
|
const model: Model = new Model();
|
|
60
61
|
model.projectId = projectId;
|
|
@@ -137,6 +137,7 @@ export class TelemetryEntityService extends DatabaseService<Model> {
|
|
|
137
137
|
},
|
|
138
138
|
lastSeenAt: now,
|
|
139
139
|
describe: `entity ${entity.entityType}/${entity.entityKey}`,
|
|
140
|
+
rowFenceId: `${projectId.toString()}:${entity.entityType}:${entity.entityKey}`,
|
|
140
141
|
select: { descriptiveAttributes: true, labels: true, resourceId: true },
|
|
141
142
|
buildUpdate: (existing: Model): QueryDeepPartialEntity<Model> => {
|
|
142
143
|
const update: QueryDeepPartialEntity<Model> =
|
|
@@ -24,6 +24,9 @@ import URL from "../../Types/API/URL";
|
|
|
24
24
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
25
25
|
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
26
26
|
import OneUptimeDate from "../../Types/Date";
|
|
27
|
+
import PositiveNumber from "../../Types/PositiveNumber";
|
|
28
|
+
import ProjectService from "./ProjectService";
|
|
29
|
+
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
27
30
|
import Email from "../../Types/Email";
|
|
28
31
|
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
|
|
29
32
|
import HashedString from "../../Types/HashedString";
|
|
@@ -45,6 +48,37 @@ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
|
45
48
|
import Timezone from "../../Types/Timezone";
|
|
46
49
|
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
47
50
|
|
|
51
|
+
/*
|
|
52
|
+
* Names the Redis mutex that serializes the first-Master-Admin election across
|
|
53
|
+
* every process on this instance. Namespaced the same way as the other
|
|
54
|
+
* cross-process mutexes in this codebase (see ProjectService's counter locks)
|
|
55
|
+
* so the key is self-documenting in Redis and cannot collide with another lock.
|
|
56
|
+
*/
|
|
57
|
+
const FIRST_MASTER_ADMIN_ELECTION_LOCK_NAMESPACE: string =
|
|
58
|
+
"UserService.firstMasterAdminElection";
|
|
59
|
+
const FIRST_MASTER_ADMIN_ELECTION_LOCK_KEY: string = "instance";
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* How long the mutex survives without being refreshed. This is a crash bound,
|
|
63
|
+
* NOT a work budget: redis-semaphore refreshes an held lock every
|
|
64
|
+
* 0.8 * lockTimeout for as long as the holder is alive, so a slow COUNT+INSERT
|
|
65
|
+
* does not expire the lock out from under itself. It only matters when a holder
|
|
66
|
+
* dies mid-election (SIGKILL, eviction) — the lock then frees itself after this
|
|
67
|
+
* long instead of wedging every subsequent signup on the instance forever.
|
|
68
|
+
*/
|
|
69
|
+
const FIRST_MASTER_ADMIN_ELECTION_LOCK_TIMEOUT_MS: number = 10_000;
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* How long a signup waits for the lock before giving up. Signup is a
|
|
73
|
+
* user-facing request, so the wait has to be bounded rather than indefinite —
|
|
74
|
+
* this replaces the bound that Postgres `lock_timeout` used to give us.
|
|
75
|
+
*
|
|
76
|
+
* Failing the signup is the correct outcome when it fires: refusing to serve
|
|
77
|
+
* one request is safe, serving it unserialized is exactly the bug this lock
|
|
78
|
+
* exists to prevent (GHSA-3qqq-hprx-g2jw).
|
|
79
|
+
*/
|
|
80
|
+
const FIRST_MASTER_ADMIN_ELECTION_ACQUIRE_TIMEOUT_MS: number = 5_000;
|
|
81
|
+
|
|
48
82
|
export class Service extends DatabaseService<Model> {
|
|
49
83
|
/*
|
|
50
84
|
* Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
|
|
@@ -499,6 +533,156 @@ export class Service extends DatabaseService<Model> {
|
|
|
499
533
|
return onUpdate;
|
|
500
534
|
}
|
|
501
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Creates a user who is signing themselves up, and decides — atomically —
|
|
538
|
+
* whether they are the instance's very first Master Admin.
|
|
539
|
+
*
|
|
540
|
+
* Every signup MUST come through here rather than calling `create` directly,
|
|
541
|
+
* because this method is the only place that is allowed to set
|
|
542
|
+
* `isMasterAdmin` on a self-service signup. It always assigns the column, so
|
|
543
|
+
* an `isMasterAdmin: true` smuggled into the request body is overwritten
|
|
544
|
+
* rather than honoured (signup creates with `isRoot: true`, which bypasses
|
|
545
|
+
* the column's empty `create: []` access control).
|
|
546
|
+
*/
|
|
547
|
+
@CaptureSpan()
|
|
548
|
+
public async createUserOnSignup(data: {
|
|
549
|
+
user: Model;
|
|
550
|
+
props: DatabaseCommonInteractionProps;
|
|
551
|
+
}): Promise<Model> {
|
|
552
|
+
// Never inherited from the caller. Decided below, or not at all.
|
|
553
|
+
data.user.isMasterAdmin = false;
|
|
554
|
+
|
|
555
|
+
if (IsBillingEnabled) {
|
|
556
|
+
/*
|
|
557
|
+
* On the hosted service there is no first-user bootstrap: master admins
|
|
558
|
+
* are provisioned out of band, so there is nothing to elect and no reason
|
|
559
|
+
* to take a lock.
|
|
560
|
+
*/
|
|
561
|
+
return await this.create({
|
|
562
|
+
data: data.user,
|
|
563
|
+
props: data.props,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/*
|
|
568
|
+
* Self-hosted: the first user to sign up bootstraps the instance as Master
|
|
569
|
+
* Admin. The eligibility check reads the User table and the create writes to
|
|
570
|
+
* it, which is a read-then-write and therefore not atomic on its own — two
|
|
571
|
+
* signups landing together both counted zero users and both became Master
|
|
572
|
+
* Admin (GHSA-3qqq-hprx-g2jw). Serialize the check and the insert
|
|
573
|
+
* instance-wide so the second request observes the first one's row.
|
|
574
|
+
*
|
|
575
|
+
* The mutex is the Redis one every other cross-process critical section in
|
|
576
|
+
* this codebase uses (Semaphore), not a Postgres advisory lock. It holds no
|
|
577
|
+
* database connection and no open transaction, so serializing signups
|
|
578
|
+
* cannot pin a pooled backend `idle in transaction`, and it is unaffected
|
|
579
|
+
* by how PgBouncer is pooling.
|
|
580
|
+
*/
|
|
581
|
+
let mutex: SemaphoreMutex;
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
mutex = await Semaphore.lock({
|
|
585
|
+
key: FIRST_MASTER_ADMIN_ELECTION_LOCK_KEY,
|
|
586
|
+
namespace: FIRST_MASTER_ADMIN_ELECTION_LOCK_NAMESPACE,
|
|
587
|
+
lockTimeout: FIRST_MASTER_ADMIN_ELECTION_LOCK_TIMEOUT_MS,
|
|
588
|
+
acquireTimeout: FIRST_MASTER_ADMIN_ELECTION_ACQUIRE_TIMEOUT_MS,
|
|
589
|
+
});
|
|
590
|
+
} catch (err) {
|
|
591
|
+
/*
|
|
592
|
+
* Redis unreachable, or the lock stayed contended past the acquire
|
|
593
|
+
* timeout. Fail the signup rather than fall back to an unserialized
|
|
594
|
+
* election — the fallback is the vulnerability.
|
|
595
|
+
*/
|
|
596
|
+
logger.error(
|
|
597
|
+
"Refusing to create a signup: could not acquire the first-Master-Admin election lock.",
|
|
598
|
+
);
|
|
599
|
+
logger.error(err);
|
|
600
|
+
|
|
601
|
+
throw err;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/*
|
|
605
|
+
* The critical section deliberately covers the INSERT, not just the count:
|
|
606
|
+
* releasing after the count would let the next holder count a table the
|
|
607
|
+
* winner had not written to yet, which is the same race with extra steps.
|
|
608
|
+
*
|
|
609
|
+
* `finally` so a failed create still frees the lock immediately instead of
|
|
610
|
+
* blocking every other signup until lockTimeout elapses.
|
|
611
|
+
*/
|
|
612
|
+
try {
|
|
613
|
+
data.user.isMasterAdmin = await this.isInstanceAwaitingFirstUser();
|
|
614
|
+
|
|
615
|
+
return await this.create({
|
|
616
|
+
data: data.user,
|
|
617
|
+
props: data.props,
|
|
618
|
+
});
|
|
619
|
+
} finally {
|
|
620
|
+
try {
|
|
621
|
+
await Semaphore.release(mutex);
|
|
622
|
+
} catch (err) {
|
|
623
|
+
/*
|
|
624
|
+
* Best-effort and non-masking: an error here must not replace the
|
|
625
|
+
* outcome of the signup, and an unreleased lock still expires on its
|
|
626
|
+
* own after lockTimeout.
|
|
627
|
+
*/
|
|
628
|
+
logger.error(
|
|
629
|
+
"Failed to release the first-Master-Admin election lock; it will expire on its own.",
|
|
630
|
+
);
|
|
631
|
+
logger.error(err);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Is this a brand-new instance that nobody has ever signed up to — i.e. may
|
|
638
|
+
* the next user through the door take Master Admin?
|
|
639
|
+
*
|
|
640
|
+
* Only ever called while holding the first-Master-Admin election mutex.
|
|
641
|
+
*/
|
|
642
|
+
private async isInstanceAwaitingFirstUser(): Promise<boolean> {
|
|
643
|
+
const userCount: PositiveNumber = await this.countBy({
|
|
644
|
+
props: {
|
|
645
|
+
isRoot: true,
|
|
646
|
+
},
|
|
647
|
+
query: {},
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
if (!userCount.isZero()) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/*
|
|
655
|
+
* Zero users, but projects exist: this is not a fresh install, it is an
|
|
656
|
+
* instance whose User table was emptied out from under it — a bad restore, a
|
|
657
|
+
* cleanup script, a cascade. Handing Master Admin to whoever signs up next
|
|
658
|
+
* would be a free promotion for a stranger, so refuse and make the operator
|
|
659
|
+
* grant it deliberately.
|
|
660
|
+
*
|
|
661
|
+
* This cannot fire on any supported path: a user who belongs to a project
|
|
662
|
+
* cannot be deleted (see onBeforeDelete), so "projects with no users at all"
|
|
663
|
+
* is only reachable by writing to the database directly. It does not get in
|
|
664
|
+
* the way of the ordinary recovery either — someone who signs up, has no
|
|
665
|
+
* projects yet, deletes their account and signs up again still bootstraps
|
|
666
|
+
* normally.
|
|
667
|
+
*/
|
|
668
|
+
const projectCount: PositiveNumber = await ProjectService.countBy({
|
|
669
|
+
props: {
|
|
670
|
+
isRoot: true,
|
|
671
|
+
},
|
|
672
|
+
query: {},
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
if (!projectCount.isZero()) {
|
|
676
|
+
logger.warn(
|
|
677
|
+
"Refusing to grant Master Admin to a new signup: the User table is empty but this instance already has projects. Grant Master Admin explicitly instead.",
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
|
|
502
686
|
@CaptureSpan()
|
|
503
687
|
public async createByEmail(data: {
|
|
504
688
|
email: Email;
|