@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
|
@@ -1,10 +1,94 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import Model from "../../Models/DatabaseModels/MetricType";
|
|
3
|
+
import ObjectID from "../../Types/ObjectID";
|
|
4
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
5
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
6
|
+
import { RelationMetadata } from "typeorm/metadata/RelationMetadata";
|
|
3
7
|
|
|
4
8
|
export class Service extends DatabaseService<Model> {
|
|
5
9
|
public constructor() {
|
|
6
10
|
super(Model);
|
|
7
11
|
}
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Additively associate services with a metric type, in ONE statement.
|
|
15
|
+
*
|
|
16
|
+
* This replaces routing the association through `updateOneById` with the
|
|
17
|
+
* whole `services` array in the payload. That looked innocuous and was not:
|
|
18
|
+
* `services` is a `TableColumnType.EntityArray`, and its mere PRESENCE as a
|
|
19
|
+
* key flips `hasRelationUpdates` in DatabaseService, which routes the write
|
|
20
|
+
* to `getRepository().save()` — a real BEGIN/COMMIT that reloads the entity,
|
|
21
|
+
* loads the relation ids, bumps `version`, and DELETEs then re-INSERTs
|
|
22
|
+
* junction rows, holding the MetricType row's write lock across every one of
|
|
23
|
+
* those round trips. On the ingest path, where this runs per metric name per
|
|
24
|
+
* batch with no backpressure, it was the only multi-round-trip lock hold in
|
|
25
|
+
* the pipeline.
|
|
26
|
+
*
|
|
27
|
+
* The whole-array write was also silently LOSING data. `save()` diffs the
|
|
28
|
+
* array it is given against what is currently in the database and deletes
|
|
29
|
+
* anything missing — but each ingest worker only knows the services in ITS
|
|
30
|
+
* batch. Two workers with different batches therefore deleted each other's
|
|
31
|
+
* associations and re-inserted them on the next batch: permanent junction
|
|
32
|
+
* churn, and real service-to-metric links disappearing from the UI in
|
|
33
|
+
* between. Additive insert makes that impossible to express.
|
|
34
|
+
*
|
|
35
|
+
* ON CONFLICT DO NOTHING is well-defined here: the junction's primary key is
|
|
36
|
+
* exactly (metricTypeId, serviceId), so a concurrent writer adding the same
|
|
37
|
+
* association is a no-op rather than a unique violation.
|
|
38
|
+
*/
|
|
39
|
+
@CaptureSpan()
|
|
40
|
+
public async attachServices(data: {
|
|
41
|
+
metricTypeId: ObjectID;
|
|
42
|
+
serviceIds: Array<ObjectID>;
|
|
43
|
+
}): Promise<void> {
|
|
44
|
+
if (!data.metricTypeId) {
|
|
45
|
+
throw new BadDataException("metricTypeId is required");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!data.serviceIds || data.serviceIds.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* Identifiers come from entity metadata, never from the caller, and every
|
|
54
|
+
* value is bound as a parameter — the same rule the other raw-SQL paths in
|
|
55
|
+
* DatabaseService follow.
|
|
56
|
+
*/
|
|
57
|
+
const relation: RelationMetadata | undefined =
|
|
58
|
+
this.getRepository().metadata.findRelationWithPropertyPath("services");
|
|
59
|
+
|
|
60
|
+
const junction: string | undefined =
|
|
61
|
+
relation?.junctionEntityMetadata?.tableName;
|
|
62
|
+
const metricTypeColumn: string | undefined =
|
|
63
|
+
relation?.junctionEntityMetadata?.columns[0]?.databaseName;
|
|
64
|
+
const serviceColumn: string | undefined =
|
|
65
|
+
relation?.junctionEntityMetadata?.columns[1]?.databaseName;
|
|
66
|
+
|
|
67
|
+
if (!junction || !metricTypeColumn || !serviceColumn) {
|
|
68
|
+
throw new BadDataException(
|
|
69
|
+
"MetricTypeService.attachServices: the services relation has no junction metadata",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Deduplicate and SORT. Concurrent statements that insert overlapping sets
|
|
75
|
+
* acquire their row locks in the same order this way, which is what keeps
|
|
76
|
+
* two workers associating the same services from deadlocking each other.
|
|
77
|
+
*/
|
|
78
|
+
const serviceIds: Array<string> = Array.from(
|
|
79
|
+
new Set(
|
|
80
|
+
data.serviceIds.map((id: ObjectID) => {
|
|
81
|
+
return id.toString();
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
).sort();
|
|
85
|
+
|
|
86
|
+
await this.getRepository().manager.query(
|
|
87
|
+
`INSERT INTO "${junction}" ("${metricTypeColumn}", "${serviceColumn}") ` +
|
|
88
|
+
`SELECT $1, unnest($2::uuid[]) ON CONFLICT DO NOTHING`,
|
|
89
|
+
[data.metricTypeId.toString(), serviceIds],
|
|
90
|
+
);
|
|
91
|
+
}
|
|
8
92
|
}
|
|
9
93
|
|
|
10
94
|
export default new Service();
|
|
@@ -15,6 +15,7 @@ import MonitorOwnerUserService from "./MonitorOwnerUserService";
|
|
|
15
15
|
import MonitorProbeService from "./MonitorProbeService";
|
|
16
16
|
import MonitorStatusService from "./MonitorStatusService";
|
|
17
17
|
import ServiceLevelObjectiveMonitorRuleEngineService from "./ServiceLevelObjectiveMonitorRuleEngineService";
|
|
18
|
+
import StatusPageMonitorRuleEngineService from "./StatusPageMonitorRuleEngineService";
|
|
18
19
|
import NetworkSiteService from "./NetworkSiteService";
|
|
19
20
|
import MonitorStatusTimelineService, {
|
|
20
21
|
MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
|
|
@@ -404,27 +405,47 @@ export class Service extends DatabaseService<Model> {
|
|
|
404
405
|
resolveReferenceId(updateBy.data.currentMonitorStatusId) ||
|
|
405
406
|
resolveReferenceId(updateBy.data.currentMonitorStatus);
|
|
406
407
|
|
|
407
|
-
if (updateBy.data.monitorSteps
|
|
408
|
+
if (updateBy.data.monitorSteps) {
|
|
408
409
|
/*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
410
|
+
* Validated per matched monitor rather than per distinct project, because
|
|
411
|
+
* the check needs that monitor's CURRENT monitorSteps: a reference id it
|
|
412
|
+
* already holds is exempt from the existence check, so an update never
|
|
413
|
+
* refuses a monitor that was stored broken before the guard existed. See
|
|
414
|
+
* MonitorStepsProjectValidator.
|
|
411
415
|
*/
|
|
416
|
+
const monitors: Array<Model> = await this.findBy({
|
|
417
|
+
query: updateBy.query,
|
|
418
|
+
select: {
|
|
419
|
+
projectId: true,
|
|
420
|
+
monitorSteps: true,
|
|
421
|
+
},
|
|
422
|
+
limit: LIMIT_MAX,
|
|
423
|
+
skip: 0,
|
|
424
|
+
props: {
|
|
425
|
+
isRoot: true,
|
|
426
|
+
ignoreHooks: true,
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
for (const monitor of monitors) {
|
|
431
|
+
await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
|
|
432
|
+
monitorSteps: updateBy.data.monitorSteps as MonitorSteps | JSONObject,
|
|
433
|
+
/*
|
|
434
|
+
* Root/API updates do not always carry a tenantId, so fall back to
|
|
435
|
+
* the project of the monitor being updated.
|
|
436
|
+
*/
|
|
437
|
+
projectId: updateBy.props.tenantId || monitor.projectId,
|
|
438
|
+
alreadyStoredMonitorSteps: monitor.monitorSteps,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (currentMonitorStatusId) {
|
|
412
444
|
const projectIds: Array<ObjectID> = updateBy.props.tenantId
|
|
413
445
|
? [updateBy.props.tenantId]
|
|
414
446
|
: await this.getProjectIdsForUpdateQuery(updateBy);
|
|
415
447
|
|
|
416
448
|
for (const projectId of projectIds) {
|
|
417
|
-
if (updateBy.data.monitorSteps) {
|
|
418
|
-
await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject(
|
|
419
|
-
{
|
|
420
|
-
monitorSteps: updateBy.data.monitorSteps as
|
|
421
|
-
| MonitorSteps
|
|
422
|
-
| JSONObject,
|
|
423
|
-
projectId: projectId,
|
|
424
|
-
},
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
449
|
await ProjectScopedReferenceValidator.validateReferencesBelongToProject(
|
|
429
450
|
{
|
|
430
451
|
projectId: projectId,
|
|
@@ -644,6 +665,37 @@ export class Service extends DatabaseService<Model> {
|
|
|
644
665
|
}
|
|
645
666
|
}
|
|
646
667
|
|
|
668
|
+
/*
|
|
669
|
+
* Status page monitor rules match on labels, name and description, so an
|
|
670
|
+
* edit to any of the three can pull this monitor onto a status page or
|
|
671
|
+
* push it off one. Keyed on `!== undefined` for the same reason as the SLO
|
|
672
|
+
* block above: clearing every label arrives as `[]`, and that is exactly
|
|
673
|
+
* the edit that should detach the monitor from every label-driven rule.
|
|
674
|
+
*/
|
|
675
|
+
if (
|
|
676
|
+
(onUpdate.updateBy.data.labels !== undefined ||
|
|
677
|
+
onUpdate.updateBy.data.name !== undefined ||
|
|
678
|
+
onUpdate.updateBy.data.description !== undefined) &&
|
|
679
|
+
updatedItemIds.length > 0
|
|
680
|
+
) {
|
|
681
|
+
for (const monitorId of updatedItemIds) {
|
|
682
|
+
try {
|
|
683
|
+
await StatusPageMonitorRuleEngineService.syncRulesForMonitor({
|
|
684
|
+
monitorId: monitorId,
|
|
685
|
+
projectId: onUpdate.updateBy.props.tenantId as ObjectID,
|
|
686
|
+
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
logger.error(
|
|
689
|
+
"Syncing status page monitor rules failed in MonitorService.onUpdateSuccess",
|
|
690
|
+
{
|
|
691
|
+
monitorId: monitorId?.toString(),
|
|
692
|
+
} as LogAttributes,
|
|
693
|
+
);
|
|
694
|
+
logger.error(error as Error);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
647
699
|
return onUpdate;
|
|
648
700
|
}
|
|
649
701
|
|
|
@@ -1040,6 +1092,29 @@ ${createdItem.description?.trim() || "No description provided."}
|
|
|
1040
1092
|
}
|
|
1041
1093
|
return Promise.resolve();
|
|
1042
1094
|
})
|
|
1095
|
+
.then(async () => {
|
|
1096
|
+
/*
|
|
1097
|
+
* Also runs after the label rules above, so a monitor that only earns
|
|
1098
|
+
* its labels from a MonitorLabelRule still lands on the status pages
|
|
1099
|
+
* those labels imply.
|
|
1100
|
+
*/
|
|
1101
|
+
try {
|
|
1102
|
+
await StatusPageMonitorRuleEngineService.syncRulesForMonitor({
|
|
1103
|
+
monitorId: createdItem.id!,
|
|
1104
|
+
projectId: createdItem.projectId!,
|
|
1105
|
+
});
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
logger.error(
|
|
1108
|
+
"Syncing status page monitor rules failed in MonitorService.onCreateSuccess",
|
|
1109
|
+
{
|
|
1110
|
+
projectId: createdItem.projectId?.toString(),
|
|
1111
|
+
monitorId: createdItem.id?.toString(),
|
|
1112
|
+
} as LogAttributes,
|
|
1113
|
+
);
|
|
1114
|
+
logger.error(error as Error);
|
|
1115
|
+
}
|
|
1116
|
+
return Promise.resolve();
|
|
1117
|
+
})
|
|
1043
1118
|
.then(async () => {
|
|
1044
1119
|
try {
|
|
1045
1120
|
return await this.refreshMonitorProbeStatus(createdItem.id!);
|
|
@@ -4,6 +4,7 @@ import DeleteBy from "../Types/Database/DeleteBy";
|
|
|
4
4
|
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
5
5
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
6
6
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
7
|
+
import ProjectScopedReferenceValidator from "../Utils/Database/ProjectScopedReferenceValidator";
|
|
7
8
|
import DatabaseService from "./DatabaseService";
|
|
8
9
|
import MonitorService from "./MonitorService";
|
|
9
10
|
import UserService from "./UserService";
|
|
@@ -191,6 +192,34 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
|
|
191
192
|
throw new BadDataException("monitorStatusId is null");
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
/*
|
|
196
|
+
* Every writer of this table funnels through here, so this is the one place
|
|
197
|
+
* that can turn issue #3039's failure mode into an answer. A monitorStatusId
|
|
198
|
+
* that does not exist (or belongs to another project) used to travel all the
|
|
199
|
+
* way to Postgres and come back as
|
|
200
|
+
* insert or update on table "MonitorStatusTimeline" violates foreign key
|
|
201
|
+
* constraint "FK_574feb4161c5216c2c7ee0faaf8"
|
|
202
|
+
* — an opaque 500 over the API, and a job that fails and retries forever in
|
|
203
|
+
* the probe worker. Reject it here instead, naming the id.
|
|
204
|
+
*
|
|
205
|
+
* The probe/telemetry ingest callers screen the id themselves before they
|
|
206
|
+
* get here (Utils/Monitor/MonitorStatusTimeline and Utils/Monitor/MonitorResource)
|
|
207
|
+
* so a monitor whose stored criteria already point at a deleted status skip
|
|
208
|
+
* the status change rather than failing their whole run. This check is the
|
|
209
|
+
* backstop for everything else, chiefly direct API writes.
|
|
210
|
+
*/
|
|
211
|
+
await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
|
|
212
|
+
projectId: createBy.props.tenantId || createBy.data.projectId,
|
|
213
|
+
subject: "monitor status timeline",
|
|
214
|
+
references: [
|
|
215
|
+
{
|
|
216
|
+
modelName: "Monitor Status",
|
|
217
|
+
id: monitorStatusId,
|
|
218
|
+
service: MonitorStatusService,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
|
|
194
223
|
const stateBeforeThis: MonitorStatusTimeline | null = await this.findOneBy({
|
|
195
224
|
query: {
|
|
196
225
|
monitorId: monitorId,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import MonitorService from "./MonitorService";
|
|
3
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
4
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
5
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
6
|
+
import MonitorStepsProjectValidator from "../Utils/Monitor/MonitorStepsProjectValidator";
|
|
3
7
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
4
8
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
5
9
|
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
10
|
+
import { JSONObject } from "../../Types/JSON";
|
|
11
|
+
import MonitorSteps from "../../Types/Monitor/MonitorSteps";
|
|
6
12
|
import ObjectID from "../../Types/ObjectID";
|
|
7
13
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
8
14
|
import Model from "../../Models/DatabaseModels/MonitorTemplate";
|
|
@@ -37,6 +43,65 @@ export class Service extends DatabaseService<Model> {
|
|
|
37
43
|
super(Model);
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
/*
|
|
47
|
+
* A template's monitorSteps embeds the same reference ids a monitor's does,
|
|
48
|
+
* and every one of them reaches a real monitor eventually — through "create
|
|
49
|
+
* monitor from template" and through syncLinkedMonitors, which pushes the
|
|
50
|
+
* blob onto every linked monitor. Validating it here is what makes the error
|
|
51
|
+
* land on the template the bad id was typed into, rather than on a monitor
|
|
52
|
+
* sync days later. Without this the template is the one place a dangling id
|
|
53
|
+
* can still enter the system.
|
|
54
|
+
*/
|
|
55
|
+
@CaptureSpan()
|
|
56
|
+
protected override async onBeforeCreate(
|
|
57
|
+
createBy: CreateBy<Model>,
|
|
58
|
+
): Promise<OnCreate<Model>> {
|
|
59
|
+
await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
|
|
60
|
+
monitorSteps: createBy.data.monitorSteps,
|
|
61
|
+
projectId: createBy.props.tenantId || createBy.data.projectId,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return { createBy, carryForward: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@CaptureSpan()
|
|
68
|
+
protected override async onBeforeUpdate(
|
|
69
|
+
updateBy: UpdateBy<Model>,
|
|
70
|
+
): Promise<OnUpdate<Model>> {
|
|
71
|
+
if (!updateBy.data.monitorSteps) {
|
|
72
|
+
return { updateBy, carryForward: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Per matched template, so each is checked against its own project and its
|
|
77
|
+
* own currently-stored ids — see MonitorService.onBeforeUpdate for why the
|
|
78
|
+
* stored ids matter.
|
|
79
|
+
*/
|
|
80
|
+
const templates: Array<Model> = await this.findBy({
|
|
81
|
+
query: updateBy.query,
|
|
82
|
+
select: {
|
|
83
|
+
projectId: true,
|
|
84
|
+
monitorSteps: true,
|
|
85
|
+
},
|
|
86
|
+
limit: LIMIT_MAX,
|
|
87
|
+
skip: 0,
|
|
88
|
+
props: {
|
|
89
|
+
isRoot: true,
|
|
90
|
+
ignoreHooks: true,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (const template of templates) {
|
|
95
|
+
await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
|
|
96
|
+
monitorSteps: updateBy.data.monitorSteps as MonitorSteps | JSONObject,
|
|
97
|
+
projectId: updateBy.props.tenantId || template.projectId,
|
|
98
|
+
alreadyStoredMonitorSteps: template.monitorSteps,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { updateBy, carryForward: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
40
105
|
/**
|
|
41
106
|
* Count monitors created from this template.
|
|
42
107
|
* Caller must already have read access on the template via the API layer.
|
|
@@ -168,6 +168,66 @@ const PROJECT_RETENTION_CACHE_TTL_SECONDS: number = 5 * 60;
|
|
|
168
168
|
const projectRetentionInProcessCache: Map<string, CachedRetentionContext> =
|
|
169
169
|
new Map();
|
|
170
170
|
|
|
171
|
+
/*
|
|
172
|
+
* Everything `findOrCreateTelemetryService` needs from the Service row. Cached
|
|
173
|
+
* instead of the model itself so the payload is a flat, JSON-round-trippable
|
|
174
|
+
* shape with no TypeORM entity semantics riding along.
|
|
175
|
+
*/
|
|
176
|
+
interface ServiceResolution {
|
|
177
|
+
serviceId: string;
|
|
178
|
+
retainTelemetryDataForDays: number | null;
|
|
179
|
+
telemetryRetentionConfig: TelemetryRetentionConfig | null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface CachedServiceResolution {
|
|
183
|
+
resolution: ServiceResolution;
|
|
184
|
+
expiresAtMs: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* Per-process memo for `service.name` -> Service resolution, backed by the same
|
|
189
|
+
* L1-Map + L2-Redis pair as the project retention context above.
|
|
190
|
+
*
|
|
191
|
+
* This is the hot read on the whole ingest path and it was the one ungated
|
|
192
|
+
* operation in the function. `resolveTelemetryResource` calls it once per OTLP
|
|
193
|
+
* *resource*, not per request — the shipped host-metrics collector config
|
|
194
|
+
* produces hundreds per batch — and again for every signal type a collector
|
|
195
|
+
* exports, from every ingest pod. The per-batch `serviceDictionary` in the four
|
|
196
|
+
* Otel*IngestService files cannot help: it is written AFTER this resolves and
|
|
197
|
+
* never read before it.
|
|
198
|
+
*
|
|
199
|
+
* The uncached read was also the most expensive shape it could have been.
|
|
200
|
+
* `QueryHelper.findWithSameText` emits `LOWER("Service"."name") = $2`, and the
|
|
201
|
+
* live indexes on Service are all over the RAW name column
|
|
202
|
+
* (IDX_80cd6d06b0ab8fbce9fea6d3bc on ("projectId","name")), so Postgres can use
|
|
203
|
+
* only the `projectId` prefix as an index qual and must then heap-fetch and
|
|
204
|
+
* filter every service in the project. Cost per call is O(services in project),
|
|
205
|
+
* not O(log n). The matching expression index existed once —
|
|
206
|
+
* `("projectId", LOWER("name")) WHERE "deletedAt" IS NULL` in migration
|
|
207
|
+
* 1774559064921 — and was dropped as collateral damage by autogenerated
|
|
208
|
+
* migration 1775735059360. Restoring it is a separate change; this cache is
|
|
209
|
+
* what removes the calls in the first place.
|
|
210
|
+
*/
|
|
211
|
+
const SERVICE_RESOLUTION_CACHE_NAMESPACE: string = "service-resolution";
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* Matches the project-retention TTL deliberately: the two are read together on
|
|
215
|
+
* every resolution and there is no value in them expiring at different times.
|
|
216
|
+
*
|
|
217
|
+
* Staleness is bounded rather than invalidated, exactly as it is for project
|
|
218
|
+
* retention. A retention change made in the UI takes up to this long to affect
|
|
219
|
+
* newly ingested rows, and a service deleted in the UI keeps resolving for up
|
|
220
|
+
* to this long — which stamps `primaryEntityId` values into ClickHouse that no
|
|
221
|
+
* longer have a Postgres parent. Those rows are orphaned, not corrupt (the
|
|
222
|
+
* analytics side has no FK to Service) and they age out via retention; this is
|
|
223
|
+
* the same trade-off the dedupe migration already documents for its own
|
|
224
|
+
* ClickHouse rows. Cross-process invalidation would need pub/sub, which
|
|
225
|
+
* GlobalCache has no primitive for.
|
|
226
|
+
*/
|
|
227
|
+
const SERVICE_RESOLUTION_CACHE_TTL_SECONDS: number = 5 * 60;
|
|
228
|
+
const serviceResolutionInProcessCache: Map<string, CachedServiceResolution> =
|
|
229
|
+
new Map();
|
|
230
|
+
|
|
171
231
|
export default class OTelIngestService {
|
|
172
232
|
/*
|
|
173
233
|
* Read a single string-valued OTel resource attribute out of the raw
|
|
@@ -459,6 +519,114 @@ export default class OTelIngestService {
|
|
|
459
519
|
return result;
|
|
460
520
|
}
|
|
461
521
|
|
|
522
|
+
/*
|
|
523
|
+
* Normalises exactly as `QueryHelper.findWithSameText` does
|
|
524
|
+
* (`toLowerCase().trim()`). This has to stay in lockstep with that helper:
|
|
525
|
+
* if the key kept casing or surrounding whitespace that the SQL predicate
|
|
526
|
+
* folds away, two spellings of one `service.name` would occupy two cache
|
|
527
|
+
* entries pointing at the same row, and a batch could miss the entry the
|
|
528
|
+
* previous batch just wrote.
|
|
529
|
+
*/
|
|
530
|
+
private static getServiceResolutionCacheKey(
|
|
531
|
+
projectId: ObjectID,
|
|
532
|
+
serviceName: string,
|
|
533
|
+
): string {
|
|
534
|
+
return `${projectId.toString()}:${serviceName.toLowerCase().trim()}`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private static async getCachedServiceResolution(
|
|
538
|
+
cacheKey: string,
|
|
539
|
+
): Promise<ServiceResolution | null> {
|
|
540
|
+
const now: number = Date.now();
|
|
541
|
+
|
|
542
|
+
// L1: in-process memo. Zero network cost.
|
|
543
|
+
const memoed: CachedServiceResolution | undefined =
|
|
544
|
+
serviceResolutionInProcessCache.get(cacheKey);
|
|
545
|
+
if (memoed && memoed.expiresAtMs > now) {
|
|
546
|
+
return memoed.resolution;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// L2: Redis. Single round-trip; shared across workers.
|
|
550
|
+
try {
|
|
551
|
+
const cached: JSONObject | null = await GlobalCache.getJSONObject(
|
|
552
|
+
SERVICE_RESOLUTION_CACHE_NAMESPACE,
|
|
553
|
+
cacheKey,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
/*
|
|
557
|
+
* `serviceId` is the load-bearing field — a cached entry without one
|
|
558
|
+
* cannot produce a `primaryEntityId`, so treat it as a miss and let the
|
|
559
|
+
* caller re-resolve rather than stamping rows with a broken id.
|
|
560
|
+
*/
|
|
561
|
+
if (cached && cached["serviceId"]) {
|
|
562
|
+
const resolution: ServiceResolution = {
|
|
563
|
+
serviceId: String(cached["serviceId"]),
|
|
564
|
+
retainTelemetryDataForDays:
|
|
565
|
+
(cached["retainTelemetryDataForDays"] as number | null) ?? null,
|
|
566
|
+
telemetryRetentionConfig:
|
|
567
|
+
(cached[
|
|
568
|
+
"telemetryRetentionConfig"
|
|
569
|
+
] as TelemetryRetentionConfig | null) ?? null,
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
serviceResolutionInProcessCache.set(cacheKey, {
|
|
573
|
+
resolution,
|
|
574
|
+
expiresAtMs: now + SERVICE_RESOLUTION_CACHE_TTL_SECONDS * 1000,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
return resolution;
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
// Cache outage must never fail ingest. Fall through to Postgres.
|
|
581
|
+
logger.warn(
|
|
582
|
+
`Service resolution cache read failed for "${cacheKey}"; falling back to Postgres: ${
|
|
583
|
+
err instanceof Error ? err.message : String(err)
|
|
584
|
+
}`,
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private static async cacheServiceResolution(
|
|
592
|
+
cacheKey: string,
|
|
593
|
+
resolution: ServiceResolution,
|
|
594
|
+
): Promise<void> {
|
|
595
|
+
serviceResolutionInProcessCache.set(cacheKey, {
|
|
596
|
+
resolution,
|
|
597
|
+
expiresAtMs: Date.now() + SERVICE_RESOLUTION_CACHE_TTL_SECONDS * 1000,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
await GlobalCache.setJSON(
|
|
602
|
+
SERVICE_RESOLUTION_CACHE_NAMESPACE,
|
|
603
|
+
cacheKey,
|
|
604
|
+
{
|
|
605
|
+
serviceId: resolution.serviceId,
|
|
606
|
+
retainTelemetryDataForDays: resolution.retainTelemetryDataForDays,
|
|
607
|
+
telemetryRetentionConfig: (resolution.telemetryRetentionConfig ??
|
|
608
|
+
null) as unknown as JSONObject,
|
|
609
|
+
},
|
|
610
|
+
{ expiresInSeconds: SERVICE_RESOLUTION_CACHE_TTL_SECONDS },
|
|
611
|
+
);
|
|
612
|
+
} catch (err) {
|
|
613
|
+
// Best-effort warm. Don't fail the request.
|
|
614
|
+
logger.warn(
|
|
615
|
+
`Service resolution cache write failed for "${cacheKey}": ${
|
|
616
|
+
err instanceof Error ? err.message : String(err)
|
|
617
|
+
}`,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private static toServiceResolution(service: Service): ServiceResolution {
|
|
623
|
+
return {
|
|
624
|
+
serviceId: service.id!.toString(),
|
|
625
|
+
retainTelemetryDataForDays: service.retainTelemetryDataForDays ?? null,
|
|
626
|
+
telemetryRetentionConfig: service.telemetryRetentionConfig ?? null,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
462
630
|
@CaptureSpan()
|
|
463
631
|
private static async findOrCreateTelemetryService(data: {
|
|
464
632
|
serviceName: string;
|
|
@@ -481,23 +649,38 @@ export default class OTelIngestService {
|
|
|
481
649
|
* Stored casing is preserved: service-detail pages key telemetry by the
|
|
482
650
|
* stable primaryEntityId, and the name is user-facing.
|
|
483
651
|
*/
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
652
|
+
const resolutionCacheKey: string = this.getServiceResolutionCacheKey(
|
|
653
|
+
data.projectId,
|
|
654
|
+
data.serviceName,
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
let resolution: ServiceResolution | null =
|
|
658
|
+
await this.getCachedServiceResolution(resolutionCacheKey);
|
|
659
|
+
|
|
660
|
+
if (!resolution) {
|
|
661
|
+
const service: Service | null = await ServiceService.findOneBy({
|
|
662
|
+
query: {
|
|
663
|
+
projectId: data.projectId,
|
|
664
|
+
name: QueryHelper.findWithSameText(data.serviceName),
|
|
665
|
+
},
|
|
666
|
+
select: {
|
|
667
|
+
_id: true,
|
|
668
|
+
retainTelemetryDataForDays: true,
|
|
669
|
+
telemetryRetentionConfig: true,
|
|
670
|
+
},
|
|
671
|
+
sort: {
|
|
672
|
+
createdAt: SortOrder.Ascending,
|
|
673
|
+
},
|
|
674
|
+
props: {
|
|
675
|
+
isRoot: true,
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (service) {
|
|
680
|
+
resolution = this.toServiceResolution(service);
|
|
681
|
+
await this.cacheServiceResolution(resolutionCacheKey, resolution);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
501
684
|
|
|
502
685
|
const projectContext: ProjectRetentionContext =
|
|
503
686
|
await this.getProjectRetentionContext(data.projectId);
|
|
@@ -513,14 +696,16 @@ export default class OTelIngestService {
|
|
|
513
696
|
data.serviceName,
|
|
514
697
|
);
|
|
515
698
|
|
|
516
|
-
const buildMetadata: (
|
|
517
|
-
|
|
699
|
+
const buildMetadata: (
|
|
700
|
+
resolved: ServiceResolution,
|
|
701
|
+
) => TelemetryServiceMetadata = (
|
|
702
|
+
resolved: ServiceResolution,
|
|
518
703
|
): TelemetryServiceMetadata => {
|
|
519
704
|
const serviceLevelRetention: number | null =
|
|
520
|
-
|
|
705
|
+
resolved.retainTelemetryDataForDays ?? null;
|
|
521
706
|
return {
|
|
522
707
|
serviceName: data.serviceName,
|
|
523
|
-
primaryEntityId:
|
|
708
|
+
primaryEntityId: new ObjectID(resolved.serviceId),
|
|
524
709
|
primaryEntityType: ServiceType.OpenTelemetry,
|
|
525
710
|
entityKeys: [serviceEntityKey],
|
|
526
711
|
/*
|
|
@@ -534,14 +719,14 @@ export default class OTelIngestService {
|
|
|
534
719
|
},
|
|
535
720
|
dataRententionInDays:
|
|
536
721
|
serviceLevelRetention || projectContext.projectRetentionInDays,
|
|
537
|
-
serviceRetentionConfig:
|
|
722
|
+
serviceRetentionConfig: resolved.telemetryRetentionConfig ?? null,
|
|
538
723
|
serviceRetentionInDays: serviceLevelRetention,
|
|
539
724
|
projectRetentionConfig: projectContext.projectRetentionConfig,
|
|
540
725
|
projectRetentionInDays: projectContext.projectRetentionInDays,
|
|
541
726
|
};
|
|
542
727
|
};
|
|
543
728
|
|
|
544
|
-
if (!
|
|
729
|
+
if (!resolution) {
|
|
545
730
|
try {
|
|
546
731
|
const newService: Service = new Service();
|
|
547
732
|
newService.projectId = data.projectId;
|
|
@@ -555,11 +740,23 @@ export default class OTelIngestService {
|
|
|
555
740
|
},
|
|
556
741
|
});
|
|
557
742
|
|
|
558
|
-
|
|
743
|
+
const createdResolution: ServiceResolution =
|
|
744
|
+
this.toServiceResolution(createdService);
|
|
745
|
+
await this.cacheServiceResolution(
|
|
746
|
+
resolutionCacheKey,
|
|
747
|
+
createdResolution,
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
return buildMetadata(createdResolution);
|
|
559
751
|
} catch {
|
|
560
752
|
/*
|
|
561
753
|
* Race condition: another request created the service concurrently.
|
|
562
754
|
* Re-fetch the existing service (oldest wins, see sort above).
|
|
755
|
+
*
|
|
756
|
+
* This refetch emits SQL byte-identical to the lookup above, so it is
|
|
757
|
+
* indistinguishable from it in pg_stat_activity — caching the result
|
|
758
|
+
* here matters as much as caching it there. A first-contact stampede
|
|
759
|
+
* on a brand-new service.name is exactly when this branch runs hot.
|
|
563
760
|
*/
|
|
564
761
|
const existingService: Service | null = await ServiceService.findOneBy({
|
|
565
762
|
query: {
|
|
@@ -580,7 +777,14 @@ export default class OTelIngestService {
|
|
|
580
777
|
});
|
|
581
778
|
|
|
582
779
|
if (existingService) {
|
|
583
|
-
|
|
780
|
+
const existingResolution: ServiceResolution =
|
|
781
|
+
this.toServiceResolution(existingService);
|
|
782
|
+
await this.cacheServiceResolution(
|
|
783
|
+
resolutionCacheKey,
|
|
784
|
+
existingResolution,
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
return buildMetadata(existingResolution);
|
|
584
788
|
}
|
|
585
789
|
|
|
586
790
|
throw new Error(
|
|
@@ -589,7 +793,7 @@ export default class OTelIngestService {
|
|
|
589
793
|
}
|
|
590
794
|
}
|
|
591
795
|
|
|
592
|
-
return buildMetadata(
|
|
796
|
+
return buildMetadata(resolution);
|
|
593
797
|
}
|
|
594
798
|
|
|
595
799
|
/*
|