@oneuptime/common 12.0.1 → 12.0.3
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/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +27 -45
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +24 -42
- package/Server/Services/DatabaseService.ts +198 -6
- package/Server/Services/DockerHostService.ts +20 -38
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +28 -46
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +47 -57
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +20 -38
- package/Server/Services/KubernetesClusterService.ts +18 -36
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +153 -1
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +146 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +20 -38
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +24 -42
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +20 -38
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +25 -43
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- 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/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/UsersTableGroupsByPerson.test.tsx +448 -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/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- 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/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -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/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -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/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/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/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -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/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Date.ts +112 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Events/Recurring.ts +99 -1
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- 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/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.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/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.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/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.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/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/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -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/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- 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/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.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/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -39
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -36
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +155 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -32
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -64
- 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/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -32
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +127 -0
- 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 +128 -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/PodmanHostService.js +19 -32
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -32
- 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 -37
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.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/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- 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/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- 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/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- 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/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/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- 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/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/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.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/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- 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/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.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/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
|
+
import HostService from "../../../Server/Services/HostService";
|
|
3
|
+
import SingleFlight from "../../../Server/Utils/SingleFlight";
|
|
4
|
+
import Host from "../../../Models/DatabaseModels/Host";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Regression suite for issue #3006.
|
|
10
|
+
*
|
|
11
|
+
* HostService.updateLastSeen writes liveness (lastSeenAt +
|
|
12
|
+
* otelCollectorStatus) in the SAME hook-free UPDATE as a dozen optional
|
|
13
|
+
* columns harvested from OpenTelemetry resource attributes. Because that
|
|
14
|
+
* raw path bypasses DatabaseService's length validation, an oversized
|
|
15
|
+
* attribute reached Postgres unchecked and failed the whole statement —
|
|
16
|
+
* so a host with telemetry still arriving stopped advancing lastSeenAt and
|
|
17
|
+
* MarkDisconnectedHosts flipped it to "disconnected" 15 minutes later.
|
|
18
|
+
*
|
|
19
|
+
* Two independent guards are pinned here:
|
|
20
|
+
* 1. collector-supplied strings are clamped to their column widths, so
|
|
21
|
+
* the statement does not fail in the first place, and
|
|
22
|
+
* 2. if the enriched write fails ANYWAY (for any reason at all), the
|
|
23
|
+
* liveness columns are retried on their own. Enrichment is what gets
|
|
24
|
+
* dropped, never the heartbeat.
|
|
25
|
+
*
|
|
26
|
+
* Guard 1 has since moved DOWN a layer: updateColumnsByIdWithoutHooks
|
|
27
|
+
* clamps for every caller, so no service can forget it (see
|
|
28
|
+
* HookFreeWriteLengthClamp.test.ts, which pins that for all eleven
|
|
29
|
+
* telemetry models). The clamp assertions below therefore run the real
|
|
30
|
+
* write path against a faked repository and inspect the values actually
|
|
31
|
+
* BOUND to the SQL — the same end-to-end claim, checked where it is now
|
|
32
|
+
* true rather than where it used to be.
|
|
33
|
+
*
|
|
34
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const HOST_ID: ObjectID = ObjectID.generate();
|
|
38
|
+
|
|
39
|
+
/** Longer than every ShortText column (100) and the old LongText bound (500). */
|
|
40
|
+
const HUGE_IP_LIST: string = Array.from(
|
|
41
|
+
{ length: 55 },
|
|
42
|
+
(_unused: unknown, i: number) => {
|
|
43
|
+
return `fe80::42:acff:fe11:${(0x1000 + i).toString(16)}`;
|
|
44
|
+
},
|
|
45
|
+
).join(", ");
|
|
46
|
+
|
|
47
|
+
type WriteCall = { id: ObjectID; data: Record<string, unknown> };
|
|
48
|
+
|
|
49
|
+
/** The (inline) shape of updateLastSeen's optional metadata argument. */
|
|
50
|
+
type UpdateLastSeenExtra = Parameters<typeof HostService.updateLastSeen>[1];
|
|
51
|
+
|
|
52
|
+
/** The argument to the hook-free UPDATE the service ultimately issues. */
|
|
53
|
+
type ColumnWrite = Parameters<
|
|
54
|
+
typeof HostService.updateColumnsByIdIfUnlockedWithoutHooks
|
|
55
|
+
>[0];
|
|
56
|
+
|
|
57
|
+
let writes: Array<WriteCall> = [];
|
|
58
|
+
let cache: Map<string, string> = new Map<string, string>();
|
|
59
|
+
let deletedCacheKeys: Array<string> = [];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A faithful in-memory Redis for the two atomic gate primitives: SET NX
|
|
63
|
+
* succeeds only on an absent key, compare-and-claim only on a differing
|
|
64
|
+
* value. Stubbing either to a constant would let this suite agree with a
|
|
65
|
+
* throttle that does not actually throttle — which is the bug that took
|
|
66
|
+
* production down.
|
|
67
|
+
*/
|
|
68
|
+
function mockCache(): void {
|
|
69
|
+
jest
|
|
70
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
71
|
+
.mockImplementation(
|
|
72
|
+
async (namespace: string, key: string, value: string) => {
|
|
73
|
+
const full: string = `${namespace}:${key}`;
|
|
74
|
+
if (cache.has(full)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
cache.set(full, value);
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
jest
|
|
82
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
83
|
+
.mockImplementation(
|
|
84
|
+
async (namespace: string, key: string, value: string) => {
|
|
85
|
+
const full: string = `${namespace}:${key}`;
|
|
86
|
+
if (cache.get(full) === value) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
cache.set(full, value);
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
jest
|
|
94
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
95
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
96
|
+
deletedCacheKeys.push(`${namespace}:${key}`);
|
|
97
|
+
cache.delete(`${namespace}:${key}`);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Re-opens the liveness window, as its TTL would. */
|
|
102
|
+
function expireLivenessWindow(): void {
|
|
103
|
+
cache.delete(`host-last-seen:${HOST_ID.toString()}`);
|
|
104
|
+
SingleFlight.clear();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Re-opens the enrichment rate-limit window, as its TTL would. */
|
|
108
|
+
function expireEnrichmentWindow(): void {
|
|
109
|
+
cache.delete(`host-last-seen-write-window:${HOST_ID.toString()}`);
|
|
110
|
+
SingleFlight.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function recordWrite(input: {
|
|
114
|
+
id: ObjectID;
|
|
115
|
+
data: unknown;
|
|
116
|
+
}): Record<string, unknown> {
|
|
117
|
+
const data: Record<string, unknown> = {
|
|
118
|
+
...(input.data as Record<string, unknown>),
|
|
119
|
+
};
|
|
120
|
+
writes.push({ id: input.id, data: data });
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Records every hook-free UPDATE instead of issuing it. */
|
|
125
|
+
function mockWritesSucceeding(): void {
|
|
126
|
+
jest
|
|
127
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
128
|
+
.mockImplementation(async (input: ColumnWrite) => {
|
|
129
|
+
recordWrite(input);
|
|
130
|
+
// The SKIP LOCKED primitive resolves true when the row was updated.
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Fails any write that carries more than the two liveness columns — the
|
|
137
|
+
* shape of a Postgres rejection caused by one bad optional column.
|
|
138
|
+
*/
|
|
139
|
+
function mockEnrichedWriteFailing(
|
|
140
|
+
message: string = `value too long for type character varying(100)`,
|
|
141
|
+
): void {
|
|
142
|
+
jest
|
|
143
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
144
|
+
.mockImplementation(async (input: ColumnWrite) => {
|
|
145
|
+
const data: Record<string, unknown> = recordWrite(input);
|
|
146
|
+
if (Object.keys(data).length > 2) {
|
|
147
|
+
throw new Error(message);
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Runs the REAL updateColumnsByIdWithoutHooks against a faked repository,
|
|
155
|
+
* recording the values actually BOUND to the generated SQL. The length
|
|
156
|
+
* clamp lives inside that method now, so this is the only way to observe
|
|
157
|
+
* it end-to-end from the service without a database — mocking the method
|
|
158
|
+
* out (as the other blocks do) would mock the clamp out with it.
|
|
159
|
+
*/
|
|
160
|
+
function mockRealWritePath(): void {
|
|
161
|
+
const host: Host = new Host();
|
|
162
|
+
let bound: Record<string, unknown> = {};
|
|
163
|
+
|
|
164
|
+
jest.spyOn(HostService, "getRepository").mockReturnValue({
|
|
165
|
+
metadata: {
|
|
166
|
+
tableName: "Host",
|
|
167
|
+
findColumnWithPropertyName: (
|
|
168
|
+
propertyName: string,
|
|
169
|
+
): { databaseName: string } | undefined => {
|
|
170
|
+
return host.hasColumn(propertyName)
|
|
171
|
+
? { databaseName: propertyName }
|
|
172
|
+
: undefined;
|
|
173
|
+
},
|
|
174
|
+
updateDateColumn: { databaseName: "updatedAt" },
|
|
175
|
+
primaryColumns: [{ databaseName: "_id" }],
|
|
176
|
+
},
|
|
177
|
+
manager: {
|
|
178
|
+
connection: {
|
|
179
|
+
driver: {
|
|
180
|
+
preparePersistentValue: (
|
|
181
|
+
value: unknown,
|
|
182
|
+
column: { databaseName: string },
|
|
183
|
+
): unknown => {
|
|
184
|
+
bound[column.databaseName] = value;
|
|
185
|
+
return value;
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
query: async (
|
|
190
|
+
_sql: string,
|
|
191
|
+
params: Array<unknown>,
|
|
192
|
+
): Promise<Array<unknown>> => {
|
|
193
|
+
writes.push({
|
|
194
|
+
id: new ObjectID(String(params[params.length - 1])),
|
|
195
|
+
data: bound,
|
|
196
|
+
});
|
|
197
|
+
bound = {};
|
|
198
|
+
// One affected row: the SKIP LOCKED write reports success via RETURNING.
|
|
199
|
+
return [{ _id: String(params[params.length - 1]) }];
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
} as any);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function lastWrite(): Record<string, unknown> {
|
|
207
|
+
return writes[writes.length - 1]?.data as Record<string, unknown>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
describe("HostService.updateLastSeen", () => {
|
|
211
|
+
beforeEach(() => {
|
|
212
|
+
writes = [];
|
|
213
|
+
cache = new Map<string, string>();
|
|
214
|
+
deletedCacheKeys = [];
|
|
215
|
+
SingleFlight.clear();
|
|
216
|
+
mockCache();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
afterEach(() => {
|
|
220
|
+
jest.restoreAllMocks();
|
|
221
|
+
SingleFlight.clear();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("liveness columns", () => {
|
|
225
|
+
beforeEach(() => {
|
|
226
|
+
mockWritesSucceeding();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("always writes lastSeenAt and marks the collector connected", async () => {
|
|
230
|
+
await HostService.updateLastSeen(HOST_ID);
|
|
231
|
+
|
|
232
|
+
expect(writes).toHaveLength(1);
|
|
233
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
234
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("writes to the host it was given", async () => {
|
|
238
|
+
await HostService.updateLastSeen(HOST_ID);
|
|
239
|
+
expect(writes[0]?.id.toString()).toBe(HOST_ID.toString());
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("skips the write when the same metadata was written recently", async () => {
|
|
243
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
244
|
+
SingleFlight.clear();
|
|
245
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
246
|
+
|
|
247
|
+
expect(writes).toHaveLength(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/*
|
|
251
|
+
* A changed attribute lands in the NEXT window, not on the very next
|
|
252
|
+
* batch. That narrows the old "writes again immediately" contract on
|
|
253
|
+
* purpose: change detection cannot tell a real update apart from a gauge
|
|
254
|
+
* that moves every scrape — both present as "it changed" — and only one
|
|
255
|
+
* of the two is bounded. Bounding both is what removes the convoy, and
|
|
256
|
+
* the cost is at most one window of staleness on a descriptive column.
|
|
257
|
+
* `lastSeenAt` is unaffected; it rides the separate liveness gate.
|
|
258
|
+
*/
|
|
259
|
+
test("a metadata change lands in the next window", async () => {
|
|
260
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
261
|
+
expireLivenessWindow();
|
|
262
|
+
expireEnrichmentWindow();
|
|
263
|
+
|
|
264
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
|
|
265
|
+
|
|
266
|
+
expect(writes).toHaveLength(2);
|
|
267
|
+
expect(lastWrite()["osType"]).toBe("windows");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("a metadata change cannot write twice inside one window", async () => {
|
|
271
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
272
|
+
SingleFlight.clear();
|
|
273
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
|
|
274
|
+
|
|
275
|
+
expect(writes).toHaveLength(1);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* Per-scrape gauges must NOT participate in the throttle fingerprint.
|
|
280
|
+
*
|
|
281
|
+
* cpuCores, totalMemoryBytes and processCount used to be hashed into it,
|
|
282
|
+
* and `system.processes.count` is partitioned by process.status and
|
|
283
|
+
* summed — so it changes on essentially every scrape. The fingerprint
|
|
284
|
+
* differed every time and the 60s window never engaged, for exactly the
|
|
285
|
+
* hosts producing the most batches. OneUptime's own install guide enables
|
|
286
|
+
* the processes scraper at a 30s interval, so every host following the
|
|
287
|
+
* documentation hit this.
|
|
288
|
+
*
|
|
289
|
+
* They are still WRITTEN (see below) — they just ride along on whatever
|
|
290
|
+
* write the throttle admits instead of forcing one.
|
|
291
|
+
*/
|
|
292
|
+
test("a gauge-only change does not defeat the throttle", async () => {
|
|
293
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
294
|
+
osType: "linux",
|
|
295
|
+
cpuCores: 8,
|
|
296
|
+
totalMemoryBytes: 17179869184,
|
|
297
|
+
processCount: 412,
|
|
298
|
+
});
|
|
299
|
+
expireEnrichmentWindow();
|
|
300
|
+
|
|
301
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
302
|
+
osType: "linux",
|
|
303
|
+
cpuCores: 8,
|
|
304
|
+
totalMemoryBytes: 17179869184,
|
|
305
|
+
processCount: 413,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
* Both gates were re-opened, so the only thing that can suppress the
|
|
310
|
+
* second write is the fingerprint agreeing — which is the point: the
|
|
311
|
+
* gauges must not be part of it.
|
|
312
|
+
*/
|
|
313
|
+
expect(writes).toHaveLength(1);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("a churning process count over many scrapes still writes once", async () => {
|
|
317
|
+
for (let scrape: number = 0; scrape < 20; scrape++) {
|
|
318
|
+
expireEnrichmentWindow();
|
|
319
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
320
|
+
osType: "linux",
|
|
321
|
+
processCount: 400 + scrape,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
expect(writes).toHaveLength(1);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("a real metadata change alongside a churning gauge still writes", async () => {
|
|
329
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
330
|
+
osType: "linux",
|
|
331
|
+
processCount: 412,
|
|
332
|
+
});
|
|
333
|
+
expireLivenessWindow();
|
|
334
|
+
expireEnrichmentWindow();
|
|
335
|
+
|
|
336
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
337
|
+
osType: "windows",
|
|
338
|
+
processCount: 413,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
expect(writes).toHaveLength(2);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("the gauges are still written when a write happens", async () => {
|
|
345
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
346
|
+
osType: "linux",
|
|
347
|
+
cpuCores: 8,
|
|
348
|
+
totalMemoryBytes: 17179869184,
|
|
349
|
+
processCount: 412,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(lastWrite()["cpuCores"]).toBe(8);
|
|
353
|
+
expect(lastWrite()["totalMemoryBytes"]).toBe(17179869184);
|
|
354
|
+
expect(lastWrite()["processCount"]).toBe(412);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("oversized collector metadata", () => {
|
|
359
|
+
beforeEach(() => {
|
|
360
|
+
// The clamp lives in the write path, so run the real one.
|
|
361
|
+
mockRealWritePath();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("stores a 55-address host.ip list untouched — the column is text now", async () => {
|
|
365
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
366
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
expect(HUGE_IP_LIST.length).toBeGreaterThan(500);
|
|
370
|
+
expect(lastWrite()["hostIpAddresses"]).toBe(HUGE_IP_LIST);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("clamps an oversized ShortText attribute instead of failing the write", async () => {
|
|
374
|
+
const longHostId: string = "i".repeat(400);
|
|
375
|
+
|
|
376
|
+
await HostService.updateLastSeen(HOST_ID, { hostId: longHostId });
|
|
377
|
+
|
|
378
|
+
expect(lastWrite()["hostId"]).toHaveLength(100);
|
|
379
|
+
expect(lastWrite()["hostId"]).toBe("i".repeat(100));
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("clamps an oversized LongText attribute to 500", async () => {
|
|
383
|
+
const longOsVersion: string = "v".repeat(900);
|
|
384
|
+
|
|
385
|
+
await HostService.updateLastSeen(HOST_ID, { osVersion: longOsVersion });
|
|
386
|
+
|
|
387
|
+
expect(lastWrite()["osVersion"]).toHaveLength(500);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("clamping never costs the liveness columns", async () => {
|
|
391
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
392
|
+
hostId: "i".repeat(400),
|
|
393
|
+
hostArch: "a".repeat(400),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
397
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const CLAMPED_COLUMNS: Array<[string, number]> = [
|
|
401
|
+
["osType", 100],
|
|
402
|
+
["hostId", 100],
|
|
403
|
+
["hostArch", 100],
|
|
404
|
+
["hostType", 100],
|
|
405
|
+
["containerRuntime", 100],
|
|
406
|
+
["agentVersion", 100],
|
|
407
|
+
["deploymentEnvironment", 100],
|
|
408
|
+
["runtimeName", 100],
|
|
409
|
+
["runtimeVersion", 100],
|
|
410
|
+
["cloudProvider", 100],
|
|
411
|
+
["cloudPlatform", 100],
|
|
412
|
+
["cloudRegion", 100],
|
|
413
|
+
["cloudAccountId", 100],
|
|
414
|
+
["osVersion", 500],
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
test.each(CLAMPED_COLUMNS)(
|
|
418
|
+
"%s is clamped to %i characters",
|
|
419
|
+
async (column: string, maxLength: number) => {
|
|
420
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
421
|
+
[column]: "x".repeat(maxLength + 250),
|
|
422
|
+
} as UpdateLastSeenExtra);
|
|
423
|
+
|
|
424
|
+
expect(lastWrite()[column]).toHaveLength(maxLength);
|
|
425
|
+
},
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
test("values that already fit are passed through byte-for-byte", async () => {
|
|
429
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
430
|
+
osType: "linux",
|
|
431
|
+
osVersion: "Debian GNU/Linux 12 (bookworm)",
|
|
432
|
+
hostArch: "amd64",
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
expect(lastWrite()["osType"]).toBe("linux");
|
|
436
|
+
expect(lastWrite()["osVersion"]).toBe("Debian GNU/Linux 12 (bookworm)");
|
|
437
|
+
expect(lastWrite()["hostArch"]).toBe("amd64");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("numeric and relation columns are left alone by the clamp", async () => {
|
|
441
|
+
const dockerHostId: ObjectID = ObjectID.generate();
|
|
442
|
+
|
|
443
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
444
|
+
cpuCores: 64,
|
|
445
|
+
totalMemoryBytes: 274877906944,
|
|
446
|
+
processCount: 1337,
|
|
447
|
+
dockerHostId: dockerHostId,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(lastWrite()["cpuCores"]).toBe(64);
|
|
451
|
+
expect(lastWrite()["totalMemoryBytes"]).toBe(274877906944);
|
|
452
|
+
expect(lastWrite()["processCount"]).toBe(1337);
|
|
453
|
+
expect(lastWrite()["dockerHostId"]).toBe(dockerHostId);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe("fallback when the enriched write fails anyway", () => {
|
|
458
|
+
beforeEach(() => {
|
|
459
|
+
mockEnrichedWriteFailing();
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("does not throw — ingest keeps going", async () => {
|
|
463
|
+
await expect(
|
|
464
|
+
HostService.updateLastSeen(HOST_ID, {
|
|
465
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
466
|
+
}),
|
|
467
|
+
).resolves.toBeUndefined();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("retries with liveness only, so lastSeenAt still advances", async () => {
|
|
471
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
472
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
expect(writes).toHaveLength(2);
|
|
476
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
477
|
+
"lastSeenAt",
|
|
478
|
+
"otelCollectorStatus",
|
|
479
|
+
]);
|
|
480
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
481
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
/*
|
|
485
|
+
* The enrichment gates were claimed before the write that failed, so
|
|
486
|
+
* without releasing them the next batch would short-circuit and the retry
|
|
487
|
+
* would never happen. The liveness gate is deliberately NOT released: the
|
|
488
|
+
* fallback write below already refreshed lastSeenAt, so re-opening it
|
|
489
|
+
* would buy a second heartbeat inside one window for nothing.
|
|
490
|
+
*/
|
|
491
|
+
test("busts the enrichment gates so the next batch is not skipped", async () => {
|
|
492
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
493
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
expect(deletedCacheKeys).toEqual([
|
|
497
|
+
`host-last-seen-fingerprint:${HOST_ID.toString()}`,
|
|
498
|
+
`host-last-seen-write-window:${HOST_ID.toString()}`,
|
|
499
|
+
]);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("the next batch retries rather than short-circuiting on a cache hit", async () => {
|
|
503
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
504
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
505
|
+
});
|
|
506
|
+
writes = [];
|
|
507
|
+
|
|
508
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
509
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Enriched attempt + liveness fallback, not a silent no-op.
|
|
513
|
+
expect(writes).toHaveLength(2);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("a failing cache delete does not stop the liveness retry", async () => {
|
|
517
|
+
jest
|
|
518
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
519
|
+
.mockRejectedValue(new Error("redis down"));
|
|
520
|
+
|
|
521
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
522
|
+
hostIpAddresses: HUGE_IP_LIST,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
526
|
+
"lastSeenAt",
|
|
527
|
+
"otelCollectorStatus",
|
|
528
|
+
]);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("surfaces a genuinely broken database instead of hiding it", async () => {
|
|
532
|
+
// Both attempts fail — the caller must find out.
|
|
533
|
+
jest
|
|
534
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
535
|
+
.mockRejectedValue(new Error("connection terminated"));
|
|
536
|
+
|
|
537
|
+
await expect(
|
|
538
|
+
HostService.updateLastSeen(HOST_ID, { osType: "linux" }),
|
|
539
|
+
).rejects.toThrow("connection terminated");
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
describe("cache outages", () => {
|
|
544
|
+
beforeEach(() => {
|
|
545
|
+
mockWritesSucceeding();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("a read failure still writes — never mark a live host disconnected", async () => {
|
|
549
|
+
jest
|
|
550
|
+
.spyOn(GlobalCache, "getString")
|
|
551
|
+
.mockRejectedValue(new Error("redis down"));
|
|
552
|
+
|
|
553
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
554
|
+
|
|
555
|
+
expect(writes).toHaveLength(1);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("a write failure still writes", async () => {
|
|
559
|
+
jest
|
|
560
|
+
.spyOn(GlobalCache, "setString")
|
|
561
|
+
.mockRejectedValue(new Error("redis down"));
|
|
562
|
+
|
|
563
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
564
|
+
|
|
565
|
+
expect(writes).toHaveLength(1);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
describe("DatabaseService.truncateStringColumnsToMaxLength", () => {
|
|
571
|
+
test("clamps only what exceeds its column width", () => {
|
|
572
|
+
const data: Record<string, unknown> = {
|
|
573
|
+
osType: "l".repeat(101),
|
|
574
|
+
hostArch: "amd64",
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
578
|
+
|
|
579
|
+
expect(data["osType"]).toHaveLength(100);
|
|
580
|
+
expect(data["hostArch"]).toBe("amd64");
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("leaves text columns untouched at any length", () => {
|
|
584
|
+
const huge: string = "1.2.3.4, ".repeat(5000);
|
|
585
|
+
const data: Record<string, unknown> = { hostIpAddresses: huge };
|
|
586
|
+
|
|
587
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
588
|
+
|
|
589
|
+
expect(data["hostIpAddresses"]).toBe(huge);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("leaves non-string values untouched", () => {
|
|
593
|
+
const date: Date = new Date();
|
|
594
|
+
const id: ObjectID = ObjectID.generate();
|
|
595
|
+
const data: Record<string, unknown> = {
|
|
596
|
+
lastSeenAt: date,
|
|
597
|
+
cpuCores: 8,
|
|
598
|
+
isArchived: false,
|
|
599
|
+
dockerHostId: id,
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
603
|
+
|
|
604
|
+
expect(data).toEqual({
|
|
605
|
+
lastSeenAt: date,
|
|
606
|
+
cpuCores: 8,
|
|
607
|
+
isArchived: false,
|
|
608
|
+
dockerHostId: id,
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test("ignores keys that are not columns on the model", () => {
|
|
613
|
+
const data: Record<string, unknown> = { notAColumn: "z".repeat(5000) };
|
|
614
|
+
|
|
615
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
616
|
+
|
|
617
|
+
expect(data["notAColumn"]).toHaveLength(5000);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("ignores empty strings", () => {
|
|
621
|
+
const data: Record<string, unknown> = { osType: "" };
|
|
622
|
+
|
|
623
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
624
|
+
|
|
625
|
+
expect(data["osType"]).toBe("");
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test("mutates in place and returns the same object", () => {
|
|
629
|
+
const data: Record<string, unknown> = { osType: "l".repeat(200) };
|
|
630
|
+
|
|
631
|
+
expect(HostService.truncateStringColumnsToMaxLength(data)).toBe(data);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("a value exactly at the limit is not clipped", () => {
|
|
635
|
+
const exact: string = "l".repeat(100);
|
|
636
|
+
const data: Record<string, unknown> = { osType: exact };
|
|
637
|
+
|
|
638
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
639
|
+
|
|
640
|
+
expect(data["osType"]).toBe(exact);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test("every clamped value stays a prefix of the original", () => {
|
|
644
|
+
const original: string = "abcdefghij".repeat(40);
|
|
645
|
+
const data: Record<string, unknown> = { osType: original };
|
|
646
|
+
|
|
647
|
+
HostService.truncateStringColumnsToMaxLength(data);
|
|
648
|
+
|
|
649
|
+
expect(original.startsWith(data["osType"] as string)).toBe(true);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("Host really does have the columns this suite assumes", () => {
|
|
653
|
+
const host: Host = new Host();
|
|
654
|
+
for (const column of ["osType", "osVersion", "hostIpAddresses"]) {
|
|
655
|
+
expect(host.hasColumn(column)).toBe(true);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
});
|