@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
|
@@ -10,6 +10,7 @@ import PodmanHostService from "../../../Server/Services/PodmanHostService";
|
|
|
10
10
|
import ProxmoxClusterService from "../../../Server/Services/ProxmoxClusterService";
|
|
11
11
|
import RumApplicationService from "../../../Server/Services/RumApplicationService";
|
|
12
12
|
import ServerlessFunctionService from "../../../Server/Services/ServerlessFunctionService";
|
|
13
|
+
import SingleFlight from "../../../Server/Utils/SingleFlight";
|
|
13
14
|
import ObjectID from "../../../Types/ObjectID";
|
|
14
15
|
import {
|
|
15
16
|
afterEach,
|
|
@@ -21,26 +22,42 @@ import {
|
|
|
21
22
|
} from "@jest/globals";
|
|
22
23
|
|
|
23
24
|
/*
|
|
24
|
-
*
|
|
25
|
-
* never the only service shaped that way. Ten siblings write the same two
|
|
26
|
-
* liveness columns (lastSeenAt + otelCollectorStatus) in the SAME hook-free
|
|
27
|
-
* UPDATE as a handful of optional columns harvested from OpenTelemetry
|
|
28
|
-
* resource attributes — and each has its own markDisconnected* job that
|
|
29
|
-
* flips the resource to "disconnected" 15 minutes after lastSeenAt stops
|
|
30
|
-
* advancing.
|
|
25
|
+
* Every resource type's liveness heartbeat, pinned together.
|
|
31
26
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Enrichment is what gets dropped, never the heartbeat.
|
|
27
|
+
* Eleven services write the same two liveness columns (lastSeenAt +
|
|
28
|
+
* otelCollectorStatus) alongside optional columns harvested from
|
|
29
|
+
* OpenTelemetry resource attributes, and each has its own markDisconnected*
|
|
30
|
+
* job that flips the resource to "disconnected" 15 minutes after lastSeenAt
|
|
31
|
+
* stops advancing. They now share one implementation — ResourceHeartbeat —
|
|
32
|
+
* because eleven hand-copied versions meant eleven copies of the same defect.
|
|
39
33
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
34
|
+
* WHAT THE DEFECT WAS. The throttle keyed on the row id but STORED a
|
|
35
|
+
* fingerprint of the incoming payload, skipping only on a match. That gives
|
|
36
|
+
* zero throttling whenever the fingerprinted payload changes as fast as the
|
|
37
|
+
* batches arrive, and on these paths it does:
|
|
38
|
+
*
|
|
39
|
+
* - Cluster and fleet snapshots hash per-scrape GAUGES into the
|
|
40
|
+
* fingerprint — runningTaskCount, osdUpCount, onlineDeviceCount,
|
|
41
|
+
* capacityUsedPercent. A float utilisation percentage changes on
|
|
42
|
+
* literally every scrape, so the fingerprint never repeated and the
|
|
43
|
+
* window never engaged.
|
|
44
|
+
* - Four of these paths (Proxmox, Ceph, IoTFleet and DockerSwarm snapshot
|
|
45
|
+
* writebacks) have NO upstream shouldRunMaintenance fence either, so that
|
|
46
|
+
* broken throttle was the only thing standing between the ingest firehose
|
|
47
|
+
* and a row shared by every node in the cluster.
|
|
48
|
+
*
|
|
49
|
+
* Result: one UPDATE per batch per shared row, which is the row-lock convoy
|
|
50
|
+
* that took production down.
|
|
51
|
+
*
|
|
52
|
+
* WHAT IS PINNED HERE, per service:
|
|
53
|
+
*
|
|
54
|
+
* 1. Liveness is gated on key PRESENCE, so no payload — however fast it
|
|
55
|
+
* churns — can force a second heartbeat inside a window. This is the
|
|
56
|
+
* assertion that would have caught the outage.
|
|
57
|
+
* 2. Enrichment is bounded to one write per window even under continuous
|
|
58
|
+
* change.
|
|
59
|
+
* 3. Liveness survives a failing enriched write (issue #3006), a Redis
|
|
60
|
+
* outage (fail open), and losing the row-lock race.
|
|
44
61
|
*
|
|
45
62
|
* Everything external is mocked — no Postgres, no Redis.
|
|
46
63
|
*/
|
|
@@ -52,16 +69,23 @@ type ServiceCase = {
|
|
|
52
69
|
name: string;
|
|
53
70
|
service: {
|
|
54
71
|
updateLastSeen(id: ObjectID, extra?: never): Promise<void>;
|
|
55
|
-
|
|
72
|
+
updateColumnsByIdIfUnlockedWithoutHooks(input: {
|
|
56
73
|
id: ObjectID;
|
|
57
74
|
data: unknown;
|
|
58
|
-
}): Promise<
|
|
75
|
+
}): Promise<boolean>;
|
|
59
76
|
};
|
|
77
|
+
/** The Redis namespace this model's gates live under. */
|
|
78
|
+
namespace: string;
|
|
60
79
|
/** A metadata payload this service accepts, and a different one. */
|
|
61
80
|
extra: Record<string, unknown>;
|
|
62
81
|
otherExtra: Record<string, unknown>;
|
|
63
82
|
/** A string column whose value the collector supplies. */
|
|
64
83
|
oversizedExtra: Record<string, unknown>;
|
|
84
|
+
/**
|
|
85
|
+
* A payload whose values move on every scrape, if this service has one.
|
|
86
|
+
* These are what defeated the old throttle — see the header.
|
|
87
|
+
*/
|
|
88
|
+
churningExtras?: Array<Record<string, unknown>>;
|
|
65
89
|
};
|
|
66
90
|
|
|
67
91
|
/** Longer than any bounded column on any of these models. */
|
|
@@ -72,6 +96,7 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
72
96
|
{
|
|
73
97
|
name: "DockerHostService",
|
|
74
98
|
service: DockerHostService as any,
|
|
99
|
+
namespace: "docker-host-last-seen",
|
|
75
100
|
extra: { osType: "linux" },
|
|
76
101
|
otherExtra: { osType: "windows" },
|
|
77
102
|
oversizedExtra: { osVersion: OVERSIZED },
|
|
@@ -79,6 +104,7 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
79
104
|
{
|
|
80
105
|
name: "PodmanHostService",
|
|
81
106
|
service: PodmanHostService as any,
|
|
107
|
+
namespace: "podman-host-last-seen",
|
|
82
108
|
extra: { osType: "linux" },
|
|
83
109
|
otherExtra: { osType: "windows" },
|
|
84
110
|
oversizedExtra: { osVersion: OVERSIZED },
|
|
@@ -86,6 +112,7 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
86
112
|
{
|
|
87
113
|
name: "KubernetesClusterService",
|
|
88
114
|
service: KubernetesClusterService as any,
|
|
115
|
+
namespace: "k8s-cluster-last-seen",
|
|
89
116
|
extra: { agentVersion: "1.2.3" },
|
|
90
117
|
otherExtra: { agentVersion: "1.2.4" },
|
|
91
118
|
oversizedExtra: { agentVersion: OVERSIZED },
|
|
@@ -93,34 +120,59 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
93
120
|
{
|
|
94
121
|
name: "ProxmoxClusterService",
|
|
95
122
|
service: ProxmoxClusterService as any,
|
|
123
|
+
namespace: "proxmox-cluster-last-seen",
|
|
96
124
|
extra: { pveVersion: "8.1.4" },
|
|
97
125
|
otherExtra: { pveVersion: "8.2.0" },
|
|
98
126
|
oversizedExtra: { pveVersion: OVERSIZED },
|
|
127
|
+
churningExtras: [
|
|
128
|
+
{ pveVersion: "8.1.4", onlineNodeCount: 11, guestCount: 240 },
|
|
129
|
+
{ pveVersion: "8.1.4", onlineNodeCount: 12, guestCount: 241 },
|
|
130
|
+
],
|
|
99
131
|
},
|
|
100
132
|
{
|
|
101
133
|
name: "IoTFleetService",
|
|
102
134
|
service: IoTFleetService as any,
|
|
135
|
+
namespace: "iot-fleet-last-seen",
|
|
103
136
|
extra: { agentVersion: "1.2.3" },
|
|
104
137
|
otherExtra: { agentVersion: "1.2.4" },
|
|
105
138
|
oversizedExtra: { agentVersion: OVERSIZED },
|
|
139
|
+
churningExtras: [
|
|
140
|
+
{ agentVersion: "1.2.3", onlineDeviceCount: 8014 },
|
|
141
|
+
{ agentVersion: "1.2.3", onlineDeviceCount: 8015 },
|
|
142
|
+
],
|
|
106
143
|
},
|
|
107
144
|
{
|
|
108
145
|
name: "DockerSwarmClusterService",
|
|
109
146
|
service: DockerSwarmClusterService as any,
|
|
147
|
+
namespace: "docker-swarm-cluster-last-seen",
|
|
110
148
|
extra: { dockerVersion: "25.0.3" },
|
|
111
149
|
otherExtra: { dockerVersion: "26.0.0" },
|
|
112
150
|
oversizedExtra: { swarmId: OVERSIZED },
|
|
151
|
+
churningExtras: [
|
|
152
|
+
{ dockerVersion: "25.0.3", runningTaskCount: 512, taskCount: 530 },
|
|
153
|
+
{ dockerVersion: "25.0.3", runningTaskCount: 513, taskCount: 530 },
|
|
154
|
+
],
|
|
113
155
|
},
|
|
114
156
|
{
|
|
115
157
|
name: "CephClusterService",
|
|
116
158
|
service: CephClusterService as any,
|
|
159
|
+
namespace: "ceph-cluster-last-seen",
|
|
117
160
|
extra: { cephVersion: "18.2.2" },
|
|
118
161
|
otherExtra: { cephVersion: "19.0.0" },
|
|
119
162
|
oversizedExtra: { fsid: OVERSIZED },
|
|
163
|
+
/*
|
|
164
|
+
* The worst of them: a float utilisation percentage moves on every single
|
|
165
|
+
* scrape, so the old fingerprint was guaranteed never to repeat.
|
|
166
|
+
*/
|
|
167
|
+
churningExtras: [
|
|
168
|
+
{ cephVersion: "18.2.2", capacityUsedPercent: 61.4, osdUpCount: 48 },
|
|
169
|
+
{ cephVersion: "18.2.2", capacityUsedPercent: 61.41, osdUpCount: 48 },
|
|
170
|
+
],
|
|
120
171
|
},
|
|
121
172
|
{
|
|
122
173
|
name: "ServerlessFunctionService",
|
|
123
174
|
service: ServerlessFunctionService as any,
|
|
175
|
+
namespace: "serverless-function-last-seen",
|
|
124
176
|
extra: { runtimeName: "nodejs" },
|
|
125
177
|
otherExtra: { runtimeName: "python" },
|
|
126
178
|
oversizedExtra: { functionVersion: OVERSIZED },
|
|
@@ -128,6 +180,7 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
128
180
|
{
|
|
129
181
|
name: "CloudResourceService",
|
|
130
182
|
service: CloudResourceService as any,
|
|
183
|
+
namespace: "cloud-resource-last-seen",
|
|
131
184
|
extra: { cloudProvider: "aws" },
|
|
132
185
|
otherExtra: { cloudProvider: "gcp" },
|
|
133
186
|
oversizedExtra: { cloudRegion: OVERSIZED },
|
|
@@ -135,6 +188,7 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
135
188
|
{
|
|
136
189
|
name: "RumApplicationService",
|
|
137
190
|
service: RumApplicationService as any,
|
|
191
|
+
namespace: "rum-application-last-seen",
|
|
138
192
|
extra: { sdkLanguage: "webjs" },
|
|
139
193
|
otherExtra: { sdkLanguage: "swift" },
|
|
140
194
|
oversizedExtra: { clientType: OVERSIZED },
|
|
@@ -147,31 +201,83 @@ const SERVICE_CASES: Array<ServiceCase> = [
|
|
|
147
201
|
*/
|
|
148
202
|
name: "HostService",
|
|
149
203
|
service: HostService as any,
|
|
204
|
+
namespace: "host-last-seen",
|
|
150
205
|
extra: { osType: "linux" },
|
|
151
206
|
otherExtra: { osType: "windows" },
|
|
152
207
|
oversizedExtra: { hostId: OVERSIZED },
|
|
208
|
+
churningExtras: [
|
|
209
|
+
{ osType: "linux", processCount: 412, totalMemoryBytes: 17179869184 },
|
|
210
|
+
{ osType: "linux", processCount: 413, totalMemoryBytes: 17179869184 },
|
|
211
|
+
],
|
|
153
212
|
},
|
|
154
213
|
];
|
|
155
214
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
156
215
|
|
|
157
216
|
describe.each(SERVICE_CASES)(
|
|
158
217
|
"$name.updateLastSeen",
|
|
159
|
-
({
|
|
218
|
+
({
|
|
219
|
+
service,
|
|
220
|
+
namespace,
|
|
221
|
+
extra,
|
|
222
|
+
otherExtra,
|
|
223
|
+
oversizedExtra,
|
|
224
|
+
churningExtras,
|
|
225
|
+
}: ServiceCase) => {
|
|
160
226
|
let writes: Array<WriteCall>;
|
|
161
227
|
let cache: Map<string, string>;
|
|
162
228
|
let deletedCacheKeys: Array<string>;
|
|
229
|
+
/** Set false to simulate the row being held by another writer. */
|
|
230
|
+
let writeLands: boolean;
|
|
163
231
|
|
|
164
232
|
const RESOURCE_ID: ObjectID = ObjectID.generate();
|
|
165
233
|
|
|
234
|
+
/**
|
|
235
|
+
* A faithful in-memory Redis. SET NX succeeds only on an absent key and
|
|
236
|
+
* compare-and-claim only on a differing value — stubbing either to a
|
|
237
|
+
* constant would make this suite agree with a broken implementation.
|
|
238
|
+
*/
|
|
239
|
+
function mockCache(): void {
|
|
240
|
+
jest
|
|
241
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
242
|
+
.mockImplementation(async (ns: string, key: string, value: string) => {
|
|
243
|
+
const full: string = `${ns}:${key}`;
|
|
244
|
+
if (cache.has(full)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
cache.set(full, value);
|
|
248
|
+
return true;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
jest
|
|
252
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
253
|
+
.mockImplementation(async (ns: string, key: string, value: string) => {
|
|
254
|
+
const full: string = `${ns}:${key}`;
|
|
255
|
+
if (cache.get(full) === value) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
cache.set(full, value);
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
jest
|
|
263
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
264
|
+
.mockImplementation(async (ns: string, key: string) => {
|
|
265
|
+
const full: string = `${ns}:${key}`;
|
|
266
|
+
deletedCacheKeys.push(full);
|
|
267
|
+
cache.delete(full);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
166
271
|
/** Records every hook-free UPDATE instead of issuing it. */
|
|
167
272
|
function mockWritesSucceeding(): void {
|
|
168
273
|
jest
|
|
169
|
-
.spyOn(service, "
|
|
274
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
170
275
|
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
171
276
|
writes.push({
|
|
172
277
|
id: input.id,
|
|
173
278
|
data: { ...(input.data as Record<string, unknown>) },
|
|
174
279
|
});
|
|
280
|
+
return writeLands;
|
|
175
281
|
});
|
|
176
282
|
}
|
|
177
283
|
|
|
@@ -181,7 +287,7 @@ describe.each(SERVICE_CASES)(
|
|
|
181
287
|
*/
|
|
182
288
|
function mockEnrichedWriteFailing(): void {
|
|
183
289
|
jest
|
|
184
|
-
.spyOn(service, "
|
|
290
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
185
291
|
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
186
292
|
const data: Record<string, unknown> = {
|
|
187
293
|
...(input.data as Record<string, unknown>),
|
|
@@ -190,6 +296,7 @@ describe.each(SERVICE_CASES)(
|
|
|
190
296
|
if (Object.keys(data).length > 2) {
|
|
191
297
|
throw new Error("value too long for type character varying(100)");
|
|
192
298
|
}
|
|
299
|
+
return true;
|
|
193
300
|
});
|
|
194
301
|
}
|
|
195
302
|
|
|
@@ -197,33 +304,24 @@ describe.each(SERVICE_CASES)(
|
|
|
197
304
|
return writes[writes.length - 1]!.data;
|
|
198
305
|
}
|
|
199
306
|
|
|
307
|
+
/** Re-opens the liveness window without touching the enrichment gates. */
|
|
308
|
+
function expireLivenessWindow(): void {
|
|
309
|
+
cache.delete(`${namespace}:${RESOURCE_ID.toString()}`);
|
|
310
|
+
SingleFlight.clear();
|
|
311
|
+
}
|
|
312
|
+
|
|
200
313
|
beforeEach(() => {
|
|
201
314
|
writes = [];
|
|
202
315
|
cache = new Map<string, string>();
|
|
203
316
|
deletedCacheKeys = [];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.mockImplementation(async (namespace: string, key: string) => {
|
|
208
|
-
return cache.get(`${namespace}:${key}`) ?? null;
|
|
209
|
-
});
|
|
210
|
-
jest
|
|
211
|
-
.spyOn(GlobalCache, "setString")
|
|
212
|
-
.mockImplementation(
|
|
213
|
-
async (namespace: string, key: string, value: string) => {
|
|
214
|
-
cache.set(`${namespace}:${key}`, value);
|
|
215
|
-
},
|
|
216
|
-
);
|
|
217
|
-
jest
|
|
218
|
-
.spyOn(GlobalCache, "deleteKey")
|
|
219
|
-
.mockImplementation(async (namespace: string, key: string) => {
|
|
220
|
-
deletedCacheKeys.push(`${namespace}:${key}`);
|
|
221
|
-
cache.delete(`${namespace}:${key}`);
|
|
222
|
-
});
|
|
317
|
+
writeLands = true;
|
|
318
|
+
SingleFlight.clear();
|
|
319
|
+
mockCache();
|
|
223
320
|
});
|
|
224
321
|
|
|
225
322
|
afterEach(() => {
|
|
226
323
|
jest.restoreAllMocks();
|
|
324
|
+
SingleFlight.clear();
|
|
227
325
|
});
|
|
228
326
|
|
|
229
327
|
describe("liveness columns", () => {
|
|
@@ -247,25 +345,88 @@ describe.each(SERVICE_CASES)(
|
|
|
247
345
|
|
|
248
346
|
test("skips the write when the same metadata was written recently", async () => {
|
|
249
347
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
348
|
+
SingleFlight.clear();
|
|
250
349
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
251
350
|
|
|
252
351
|
expect(writes).toHaveLength(1);
|
|
253
352
|
});
|
|
254
353
|
|
|
255
|
-
test("
|
|
354
|
+
test("passes collector metadata through to the write path", async () => {
|
|
256
355
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
257
|
-
await service.updateLastSeen(RESOURCE_ID, otherExtra as never);
|
|
258
356
|
|
|
259
|
-
|
|
357
|
+
for (const [column, value] of Object.entries(extra)) {
|
|
358
|
+
expect(lastWrite()[column]).toBe(value);
|
|
359
|
+
}
|
|
260
360
|
});
|
|
261
361
|
|
|
262
|
-
|
|
362
|
+
/*
|
|
363
|
+
* A changed attribute still lands — but in the next window, not on the
|
|
364
|
+
* very next batch. That is a deliberate narrowing of the old contract:
|
|
365
|
+
* change detection cannot tell a real update apart from a gauge that
|
|
366
|
+
* moves every scrape, both present as "it changed", and only one of
|
|
367
|
+
* those is bounded. Bounding both is what removes the convoy. The delay
|
|
368
|
+
* is at most one throttle window and `lastSeenAt` is unaffected, since
|
|
369
|
+
* it rides the separate liveness gate.
|
|
370
|
+
*/
|
|
371
|
+
test("a metadata change lands in the next window", async () => {
|
|
263
372
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
373
|
+
expireLivenessWindow();
|
|
374
|
+
cache.delete(`${namespace}-write-window:${RESOURCE_ID.toString()}`);
|
|
264
375
|
|
|
265
|
-
|
|
376
|
+
await service.updateLastSeen(RESOURCE_ID, otherExtra as never);
|
|
377
|
+
|
|
378
|
+
expect(writes).toHaveLength(2);
|
|
379
|
+
for (const [column, value] of Object.entries(otherExtra)) {
|
|
266
380
|
expect(lastWrite()[column]).toBe(value);
|
|
267
381
|
}
|
|
268
382
|
});
|
|
383
|
+
|
|
384
|
+
/*
|
|
385
|
+
* THE OUTAGE, reproduced per service. Continuously-changing metadata
|
|
386
|
+
* must not be able to buy more than one write per window. Under the old
|
|
387
|
+
* throttle each of these calls issued its own UPDATE against a row
|
|
388
|
+
* shared by every node reporting into it.
|
|
389
|
+
*/
|
|
390
|
+
test("continuously changing metadata cannot force a write per batch", async () => {
|
|
391
|
+
for (let batch: number = 0; batch < 20; batch++) {
|
|
392
|
+
SingleFlight.clear();
|
|
393
|
+
await service.updateLastSeen(RESOURCE_ID, {
|
|
394
|
+
...extra,
|
|
395
|
+
...(batch % 2 === 0 ? otherExtra : {}),
|
|
396
|
+
} as never);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
expect(writes.length).toBeLessThanOrEqual(2);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (churningExtras) {
|
|
403
|
+
/*
|
|
404
|
+
* The concrete production shape for this service: a payload whose
|
|
405
|
+
* gauges move on every scrape (task counts, online device counts,
|
|
406
|
+
* capacity percentages). These are hashed into the fingerprint, so
|
|
407
|
+
* the old throttle never engaged for exactly the resources producing
|
|
408
|
+
* the most batches.
|
|
409
|
+
*/
|
|
410
|
+
test("per-scrape gauges cannot defeat the throttle", async () => {
|
|
411
|
+
for (let scrape: number = 0; scrape < 20; scrape++) {
|
|
412
|
+
SingleFlight.clear();
|
|
413
|
+
await service.updateLastSeen(
|
|
414
|
+
RESOURCE_ID,
|
|
415
|
+
churningExtras[scrape % churningExtras.length] as never,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
expect(writes.length).toBeLessThanOrEqual(2);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("the gauges are still written when a write happens", async () => {
|
|
423
|
+
await service.updateLastSeen(RESOURCE_ID, churningExtras[0] as never);
|
|
424
|
+
|
|
425
|
+
for (const [column, value] of Object.entries(churningExtras[0]!)) {
|
|
426
|
+
expect(lastWrite()[column]).toBe(value);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
269
430
|
});
|
|
270
431
|
|
|
271
432
|
describe("fallback when the enriched write fails", () => {
|
|
@@ -297,16 +458,26 @@ describe.each(SERVICE_CASES)(
|
|
|
297
458
|
expect(writes[1]!.id.toString()).toBe(RESOURCE_ID.toString());
|
|
298
459
|
});
|
|
299
460
|
|
|
300
|
-
|
|
461
|
+
/*
|
|
462
|
+
* The enrichment gates were claimed before the write that failed, so
|
|
463
|
+
* without releasing them the next batch would short-circuit and the
|
|
464
|
+
* retry would never happen.
|
|
465
|
+
*/
|
|
466
|
+
test("re-opens the enrichment gates so the next batch retries", async () => {
|
|
301
467
|
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
302
468
|
|
|
303
|
-
expect(deletedCacheKeys).
|
|
304
|
-
|
|
469
|
+
expect(deletedCacheKeys).toContain(
|
|
470
|
+
`${namespace}-fingerprint:${RESOURCE_ID.toString()}`,
|
|
471
|
+
);
|
|
472
|
+
expect(deletedCacheKeys).toContain(
|
|
473
|
+
`${namespace}-write-window:${RESOURCE_ID.toString()}`,
|
|
474
|
+
);
|
|
305
475
|
});
|
|
306
476
|
|
|
307
|
-
test("the next batch retries rather than short-circuiting
|
|
477
|
+
test("the next batch retries rather than short-circuiting", async () => {
|
|
308
478
|
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
309
479
|
writes = [];
|
|
480
|
+
expireLivenessWindow();
|
|
310
481
|
|
|
311
482
|
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
312
483
|
|
|
@@ -330,7 +501,7 @@ describe.each(SERVICE_CASES)(
|
|
|
330
501
|
test("surfaces a genuinely broken database instead of hiding it", async () => {
|
|
331
502
|
// Both attempts fail — the caller must find out.
|
|
332
503
|
jest
|
|
333
|
-
.spyOn(service, "
|
|
504
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
334
505
|
.mockRejectedValue(new Error("connection terminated"));
|
|
335
506
|
|
|
336
507
|
await expect(
|
|
@@ -344,24 +515,90 @@ describe.each(SERVICE_CASES)(
|
|
|
344
515
|
mockWritesSucceeding();
|
|
345
516
|
});
|
|
346
517
|
|
|
347
|
-
|
|
518
|
+
/*
|
|
519
|
+
* Never mark a live resource disconnected because the cache went down.
|
|
520
|
+
* Affordable only because the write cannot queue — see the SKIP LOCKED
|
|
521
|
+
* note on the write primitive.
|
|
522
|
+
*/
|
|
523
|
+
test("a liveness gate failure still writes", async () => {
|
|
348
524
|
jest
|
|
349
|
-
.spyOn(GlobalCache, "
|
|
525
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
350
526
|
.mockRejectedValue(new Error("redis down"));
|
|
351
527
|
|
|
352
528
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
353
529
|
|
|
354
530
|
expect(writes).toHaveLength(1);
|
|
531
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
355
532
|
});
|
|
356
533
|
|
|
357
|
-
test("
|
|
534
|
+
test("keeps writing liveness for as long as the cache is down", async () => {
|
|
358
535
|
jest
|
|
359
|
-
.spyOn(GlobalCache, "
|
|
536
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
537
|
+
.mockRejectedValue(new Error("redis down"));
|
|
538
|
+
|
|
539
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
540
|
+
SingleFlight.clear();
|
|
541
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
542
|
+
|
|
543
|
+
expect(writes).toHaveLength(2);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
/*
|
|
547
|
+
* Enrichment gets the OPPOSITE treatment: opening that gate during an
|
|
548
|
+
* outage would restore the per-batch write storm, and nothing is
|
|
549
|
+
* mis-reported by writing a descriptive column a little later.
|
|
550
|
+
*/
|
|
551
|
+
test("a change-detection failure suppresses enrichment, not liveness", async () => {
|
|
552
|
+
jest
|
|
553
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
360
554
|
.mockRejectedValue(new Error("redis down"));
|
|
361
555
|
|
|
362
556
|
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
363
557
|
|
|
364
558
|
expect(writes).toHaveLength(1);
|
|
559
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
560
|
+
"lastSeenAt",
|
|
561
|
+
"otelCollectorStatus",
|
|
562
|
+
]);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe("losing the row-lock race", () => {
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
mockWritesSucceeding();
|
|
569
|
+
writeLands = false;
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("uses the non-blocking write primitive", async () => {
|
|
573
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
574
|
+
|
|
575
|
+
expect(
|
|
576
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks,
|
|
577
|
+
).toHaveBeenCalled();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/*
|
|
581
|
+
* A skipped liveness bump is a non-event — the writer holding the lock
|
|
582
|
+
* is storing the same timestamp. Retrying it would generate pointless
|
|
583
|
+
* statements against a row that is demonstrably being written.
|
|
584
|
+
*/
|
|
585
|
+
test("a skipped liveness-only write is dropped silently", async () => {
|
|
586
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
587
|
+
|
|
588
|
+
expect(deletedCacheKeys).toHaveLength(0);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
/*
|
|
592
|
+
* Enrichment is different: the lock holder may be writing an OLDER
|
|
593
|
+
* payload, and the gates this call claimed would suppress the newer one
|
|
594
|
+
* for the rest of the window.
|
|
595
|
+
*/
|
|
596
|
+
test("a skipped enrichment write re-opens the gates", async () => {
|
|
597
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
598
|
+
|
|
599
|
+
expect(deletedCacheKeys).toContain(
|
|
600
|
+
`${namespace}-fingerprint:${RESOURCE_ID.toString()}`,
|
|
601
|
+
);
|
|
365
602
|
});
|
|
366
603
|
});
|
|
367
604
|
},
|
|
@@ -384,4 +621,16 @@ describe("liveness fallback coverage", () => {
|
|
|
384
621
|
|
|
385
622
|
expect(new Set(names).size).toBe(names.length);
|
|
386
623
|
});
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
* The namespace in each case must match the one the service actually uses,
|
|
627
|
+
* or the gate assertions above silently check keys nobody writes.
|
|
628
|
+
*/
|
|
629
|
+
test("no namespace is shared between services", () => {
|
|
630
|
+
const namespaces: Array<string> = SERVICE_CASES.map((c: ServiceCase) => {
|
|
631
|
+
return c.namespace;
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
expect(new Set(namespaces).size).toBe(namespaces.length);
|
|
635
|
+
});
|
|
387
636
|
});
|