@oneuptime/common 12.0.2 → 12.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +458 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +38 -4
- package/Server/Infrastructure/GlobalCache.ts +152 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CephClusterService.ts +26 -86
- package/Server/Services/CloudResourceService.ts +23 -83
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +296 -2
- package/Server/Services/DockerHostService.ts +19 -79
- package/Server/Services/DockerSwarmClusterService.ts +27 -88
- package/Server/Services/HostService.ts +46 -99
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetService.ts +19 -79
- package/Server/Services/KubernetesClusterService.ts +17 -78
- package/Server/Services/LabelService.ts +57 -10
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +89 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/PodmanHostService.ts +19 -79
- package/Server/Services/ProxmoxClusterService.ts +23 -83
- package/Server/Services/RumApplicationService.ts +19 -80
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +24 -85
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/Decimal.ts +16 -4
- package/Types/HashedString.ts +139 -4
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +343 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +34 -4
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -76
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -73
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +246 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -69
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -102
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -69
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +42 -7
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +81 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -69
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -70
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { JSONObject, JSONValue } from "../JSON";
|
|
2
|
+
import { TableColumnMetadata } from "./TableColumn";
|
|
3
|
+
import TableColumnType from "./TableColumnType";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Column types whose in-memory representation is a plain JavaScript number.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately excludes the types that merely *look* numeric — `Port`,
|
|
9
|
+
* `Version`, `Date` — because coercing those would destroy them.
|
|
10
|
+
*
|
|
11
|
+
* The declared type is not a perfect proxy for the in-memory one: an SMTP
|
|
12
|
+
* port is declared `Number` but typed `Port`, and a usage figure is declared
|
|
13
|
+
* `Number` but typed `Decimal`. Coercing a string to a number is safe for
|
|
14
|
+
* those only because each of their transformers accepts a raw number —
|
|
15
|
+
* which is a property their tests pin, not an accident.
|
|
16
|
+
*/
|
|
17
|
+
const NUMERIC_TABLE_COLUMN_TYPES: Set<TableColumnType> =
|
|
18
|
+
new Set<TableColumnType>([
|
|
19
|
+
TableColumnType.Number,
|
|
20
|
+
TableColumnType.SmallNumber,
|
|
21
|
+
TableColumnType.BigNumber,
|
|
22
|
+
TableColumnType.PositiveNumber,
|
|
23
|
+
TableColumnType.SmallPositiveNumber,
|
|
24
|
+
TableColumnType.BigPositiveNumber,
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export function isNumericTableColumnType(
|
|
28
|
+
type: TableColumnType | undefined | null,
|
|
29
|
+
): boolean {
|
|
30
|
+
if (!type) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return NUMERIC_TABLE_COLUMN_TYPES.has(type);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* Normalize a JSON value destined for a number column.
|
|
39
|
+
*
|
|
40
|
+
* The dashboard's number form field is an `<input type="number">`, and a DOM
|
|
41
|
+
* input's value is a *string* — there is no numeric input event in HTML. So
|
|
42
|
+
* "10" typed into "Sample Percentage" reached the API as the JSON string
|
|
43
|
+
* "10", and any server-side check spelled `typeof value === "number"`
|
|
44
|
+
* rejected a perfectly valid form submission:
|
|
45
|
+
*
|
|
46
|
+
* BadDataException: Sample percentage is required when the action is
|
|
47
|
+
* "Sample" ... between 1 and 99.
|
|
48
|
+
*
|
|
49
|
+
* Postgres silently coerces '10' to 10 on the way in, which is why this had
|
|
50
|
+
* gone unnoticed on every other number field in the product: the string only
|
|
51
|
+
* becomes visible when something reads the value before it is stored.
|
|
52
|
+
*
|
|
53
|
+
* Normalizing here — where the column's declared type is known, on both the
|
|
54
|
+
* browser side (ModelForm -> BaseModel.fromJSON) and the server side
|
|
55
|
+
* (BaseAPI -> BaseModel.fromJSON) — means a number column holds a number by
|
|
56
|
+
* the time any hook, validator or query sees it.
|
|
57
|
+
*
|
|
58
|
+
* Only strings that parse cleanly to a finite number are converted. A blank
|
|
59
|
+
* string is left alone rather than turned into `0` (`Number("")` is 0, which
|
|
60
|
+
* would silently write a real value for a field the user cleared) or into
|
|
61
|
+
* `null` (which would defeat a column DEFAULT the same way the bigint
|
|
62
|
+
* transformer used to). Garbage like "abc" is left alone too, so validation
|
|
63
|
+
* rejects it with a message about the field instead of this function
|
|
64
|
+
* inventing a value.
|
|
65
|
+
*/
|
|
66
|
+
export function coerceNumericColumnValue(value: unknown): unknown {
|
|
67
|
+
if (typeof value !== "string") {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const trimmed: string = value.trim();
|
|
72
|
+
|
|
73
|
+
if (trimmed.length === 0) {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parsed: number = Number(trimmed);
|
|
78
|
+
|
|
79
|
+
if (!Number.isFinite(parsed)) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* The minimum a model has to offer for its number columns to be found.
|
|
88
|
+
* Every DatabaseBaseModel satisfies it; asking for only this keeps the
|
|
89
|
+
* coercion usable from the API layer without dragging the model class in.
|
|
90
|
+
*/
|
|
91
|
+
export interface ColumnMetadataSource {
|
|
92
|
+
getTableColumnMetadata: (columnName: string) => TableColumnMetadata;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Coerce the number columns of a loose JSON patch, in place.
|
|
97
|
+
*
|
|
98
|
+
* `BaseModel.fromJSON` does this on its way to building a model, but an
|
|
99
|
+
* update never builds one — `BaseAPI.updateItem` goes from the request body
|
|
100
|
+
* straight to a partial entity — so without this a PATCH and a POST
|
|
101
|
+
* disagree about whether "10" is a number. That matters because the
|
|
102
|
+
* save-time hooks read these values before the database gets a chance to
|
|
103
|
+
* coerce them.
|
|
104
|
+
*
|
|
105
|
+
* Values that are not strings are left exactly as they are, including the
|
|
106
|
+
* `() => string` raw SQL expressions a PartialEntity may carry.
|
|
107
|
+
*/
|
|
108
|
+
export function coerceNumericColumnsInJSON(
|
|
109
|
+
json: JSONObject,
|
|
110
|
+
model: ColumnMetadataSource,
|
|
111
|
+
): JSONObject {
|
|
112
|
+
for (const key of Object.keys(json)) {
|
|
113
|
+
const tableColumnMetadata: TableColumnMetadata =
|
|
114
|
+
model.getTableColumnMetadata(key);
|
|
115
|
+
|
|
116
|
+
if (!tableColumnMetadata) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!isNumericTableColumnType(tableColumnMetadata.type)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
json[key] = coerceNumericColumnValue(json[key]) as JSONValue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return json;
|
|
128
|
+
}
|
|
@@ -16,6 +16,17 @@ export interface TableColumnMetadata {
|
|
|
16
16
|
unique?: boolean;
|
|
17
17
|
computed?: boolean;
|
|
18
18
|
hashed?: boolean;
|
|
19
|
+
/*
|
|
20
|
+
* Name of the sibling column that holds this column's per-record salt.
|
|
21
|
+
*
|
|
22
|
+
* When set, the write path generates a fresh random salt on every write of
|
|
23
|
+
* this column, stores it in the named column, and mixes it into the hash.
|
|
24
|
+
* Two records with the same plaintext then hash to different values, so one
|
|
25
|
+
* precomputed table (or one cracked value) buys an attacker nothing
|
|
26
|
+
* anywhere else. The salt column must be nullable — rows written before the
|
|
27
|
+
* salt existed have none and verify against the legacy unsalted scheme.
|
|
28
|
+
*/
|
|
29
|
+
hashSaltColumn?: string;
|
|
19
30
|
encrypted?: boolean;
|
|
20
31
|
manyToOneRelationColumn?: string;
|
|
21
32
|
type: TableColumnType;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { IndexOptions } from "typeorm";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Options for an index whose definition TypeORM cannot express, and which must
|
|
5
|
+
* therefore be owned by a migration rather than by the schema builder.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS EXISTS
|
|
8
|
+
*
|
|
9
|
+
* TypeORM's schema builder matches database indexes to entity metadata BY NAME
|
|
10
|
+
* (RdbmsSchemaBuilder.dropOldIndices: `metadata.indices.find((i) => i.name ===
|
|
11
|
+
* tableIndex.name)`) and DROPS every index it cannot find a match for. An index
|
|
12
|
+
* created only in a migration is invisible to entity metadata, so the next
|
|
13
|
+
* `npm run generate-postgres-migration` emits a DROP for it — silently, mixed
|
|
14
|
+
* in with whatever change was actually being made.
|
|
15
|
+
*
|
|
16
|
+
* That is not hypothetical. An audit of every hand-named index ever created by
|
|
17
|
+
* a migration against a fully-migrated database found ten that had been deleted
|
|
18
|
+
* this way and never restored, including the unique guards on six `slug`
|
|
19
|
+
* columns and the LOWER(name) index serving the telemetry ingest hot path.
|
|
20
|
+
* Migration 1775735059360 is a representative example: two unrelated column
|
|
21
|
+
* default changes, plus a DROP of an expression index, plus a down() that
|
|
22
|
+
* "restores" it without the expression.
|
|
23
|
+
*
|
|
24
|
+
* HOW THIS FIXES IT
|
|
25
|
+
*
|
|
26
|
+
* Declaring the index on the entity BY NAME with these options makes the schema
|
|
27
|
+
* builder skip it in both directions: `dropOldIndices` returns early on
|
|
28
|
+
* `synchronize === false`, and `createNewIndices` only considers indexes with
|
|
29
|
+
* `synchronize === true`. The migration stays the single source of truth for
|
|
30
|
+
* the real definition, and the declaration exists purely so TypeORM leaves it
|
|
31
|
+
* alone.
|
|
32
|
+
*
|
|
33
|
+
* The columns passed alongside these options are documentation — the schema
|
|
34
|
+
* builder never reads them for an unsynchronized index. Where the real
|
|
35
|
+
* definition is partial or an expression, say so in a comment at the call site.
|
|
36
|
+
*
|
|
37
|
+
* THE CAST
|
|
38
|
+
*
|
|
39
|
+
* `synchronize` is a real, supported option: the @Index decorator forwards it
|
|
40
|
+
* into IndexMetadataArgs (typeorm/decorator/Index.js), IndexMetadataArgs
|
|
41
|
+
* declares it, and RdbmsSchemaBuilder reads it. Only the public IndexOptions
|
|
42
|
+
* type omits it — a gap in TypeORM's own typings rather than a misuse.
|
|
43
|
+
*/
|
|
44
|
+
const UNSYNCHRONIZED_INDEX: IndexOptions = {
|
|
45
|
+
synchronize: false,
|
|
46
|
+
} as IndexOptions;
|
|
47
|
+
|
|
48
|
+
export default UNSYNCHRONIZED_INDEX;
|
package/Types/Decimal.ts
CHANGED
|
@@ -35,14 +35,26 @@ export default class Decimal extends DatabaseProperty {
|
|
|
35
35
|
return this.value.toString();
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/*
|
|
39
|
+
* A decimal column is declared `TableColumnType.Number`, so what reaches
|
|
40
|
+
* the transformer may be a Decimal, a string, or a plain number depending
|
|
41
|
+
* on how far the request body got through deserialization.
|
|
42
|
+
*
|
|
43
|
+
* Both raw forms are handled before the truthiness check, because `0` is a
|
|
44
|
+
* perfectly good decimal and the old `if (value)` sent it to `null` — on a
|
|
45
|
+
* NOT NULL column that is a constraint violation for a value the caller
|
|
46
|
+
* supplied.
|
|
47
|
+
*/
|
|
38
48
|
protected static override toDatabase(
|
|
39
49
|
value: Decimal | FindOperator<Decimal>,
|
|
40
50
|
): string | null {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const rawValue: unknown = value;
|
|
52
|
+
|
|
53
|
+
if (typeof rawValue === "string" || typeof rawValue === "number") {
|
|
54
|
+
return new Decimal(rawValue).toString();
|
|
55
|
+
}
|
|
45
56
|
|
|
57
|
+
if (value) {
|
|
46
58
|
return value.toString();
|
|
47
59
|
}
|
|
48
60
|
|
package/Types/HashedString.ts
CHANGED
|
@@ -7,6 +7,29 @@ import ObjectID from "./ObjectID";
|
|
|
7
7
|
import CryptoJS from "crypto-js";
|
|
8
8
|
import { FindOperator } from "typeorm";
|
|
9
9
|
|
|
10
|
+
/*
|
|
11
|
+
* SHA-256 hashing for values that are ALREADY high-entropy: session refresh
|
|
12
|
+
* tokens, email verification tokens, reset tokens, permission fingerprints.
|
|
13
|
+
* Those are random values the server minted, so there is nothing to guess and
|
|
14
|
+
* a fast hash is the right tool — it also keeps them searchable by hash,
|
|
15
|
+
* which is how they are looked up.
|
|
16
|
+
*
|
|
17
|
+
* User passwords are NOT hashed here any more. A password is low-entropy and
|
|
18
|
+
* guessable, so it goes through scrypt in Common/Server/Utils/PasswordHash,
|
|
19
|
+
* which is deliberately slow and memory-hard. What remains in this class is
|
|
20
|
+
* the two SHA-256 schemes passwords used to be stored under, kept so those
|
|
21
|
+
* rows still verify (and are then upgraded) at their owner's next login:
|
|
22
|
+
*
|
|
23
|
+
* no salt `SHA256(secret + value)`, from before per-user salts existed.
|
|
24
|
+
* salted `SHA256("v2:" + salt + ":" + secret + ":" + value)`.
|
|
25
|
+
*
|
|
26
|
+
* Neither records its scheme in the digest — it is a bare hex SHA-256 either
|
|
27
|
+
* way — so they are told apart by whether the row carries a salt. The "v2"
|
|
28
|
+
* prefix and the separators exist for domain separation: without them a
|
|
29
|
+
* crafted salt could make a salted input collide with an unsalted one.
|
|
30
|
+
*/
|
|
31
|
+
const SALTED_HASH_SCHEME_VERSION: string = "v2";
|
|
32
|
+
|
|
10
33
|
export default class HashedString extends DatabaseProperty {
|
|
11
34
|
private isHashed: boolean = false;
|
|
12
35
|
|
|
@@ -65,7 +88,62 @@ export default class HashedString extends DatabaseProperty {
|
|
|
65
88
|
return this.isHashed;
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Adopt an already-computed digest.
|
|
93
|
+
*
|
|
94
|
+
* For hashes this class does not compute itself — user passwords go
|
|
95
|
+
* through scrypt in Common/Server/Utils/PasswordHash, which is server-only
|
|
96
|
+
* because this type is also bundled into the browser. The guard is the
|
|
97
|
+
* same one hashValue() carries: a value is hashed once and never re-hashed
|
|
98
|
+
* over the top of itself.
|
|
99
|
+
*/
|
|
100
|
+
public setHashedValue(hashedValue: string): void {
|
|
101
|
+
if (this.isHashed) {
|
|
102
|
+
throw new BadOperationException("Value is already hashed");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.isHashed = true;
|
|
106
|
+
this.value = hashedValue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The exact byte string fed to SHA-256.
|
|
111
|
+
*
|
|
112
|
+
* The unsalted branch MUST stay byte-for-byte identical forever: every
|
|
113
|
+
* password hashed before per-user salts existed is verified through it.
|
|
114
|
+
*/
|
|
115
|
+
private static buildValueToHash(
|
|
116
|
+
value: string,
|
|
117
|
+
encryptionSecret: ObjectID | null,
|
|
118
|
+
salt: string | null,
|
|
119
|
+
): string {
|
|
120
|
+
if (!salt) {
|
|
121
|
+
return (encryptionSecret || "") + value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return `${SALTED_HASH_SCHEME_VERSION}:${salt}:${
|
|
125
|
+
encryptionSecret || ""
|
|
126
|
+
}:${value}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public static computeHash(
|
|
130
|
+
value: string,
|
|
131
|
+
encryptionSecret: ObjectID | null,
|
|
132
|
+
salt?: string | null,
|
|
133
|
+
): string {
|
|
134
|
+
if (!value) {
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return CryptoJS.SHA256(
|
|
139
|
+
HashedString.buildValueToHash(value, encryptionSecret, salt || null),
|
|
140
|
+
).toString();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public async hashValue(
|
|
144
|
+
encryptionSecret: ObjectID | null,
|
|
145
|
+
salt?: string | null,
|
|
146
|
+
): Promise<string> {
|
|
69
147
|
if (!this.value) {
|
|
70
148
|
return "";
|
|
71
149
|
}
|
|
@@ -74,19 +152,76 @@ export default class HashedString extends DatabaseProperty {
|
|
|
74
152
|
throw new BadOperationException("Value is already hashed");
|
|
75
153
|
}
|
|
76
154
|
|
|
77
|
-
const valueToHash: string = (encryptionSecret || "") + this.value;
|
|
78
155
|
this.isHashed = true;
|
|
79
156
|
|
|
80
|
-
this.value =
|
|
157
|
+
this.value = HashedString.computeHash(
|
|
158
|
+
this.value,
|
|
159
|
+
encryptionSecret,
|
|
160
|
+
salt || null,
|
|
161
|
+
);
|
|
81
162
|
return this.value;
|
|
82
163
|
}
|
|
83
164
|
|
|
84
165
|
public static async hashValue(
|
|
85
166
|
value: string,
|
|
86
167
|
encryptionSecret: ObjectID | null,
|
|
168
|
+
salt?: string | null,
|
|
87
169
|
): Promise<string> {
|
|
88
170
|
const hashstring: HashedString = new HashedString(value, false);
|
|
89
|
-
return await hashstring.hashValue(encryptionSecret);
|
|
171
|
+
return await hashstring.hashValue(encryptionSecret, salt || null);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Compare two already-hashed values without leaking, through timing, how
|
|
176
|
+
* many leading characters matched. Comparing digests with `===` lets an
|
|
177
|
+
* attacker who can measure response time recover a stored hash one
|
|
178
|
+
* character at a time; recovering the hash is what makes offline cracking
|
|
179
|
+
* possible in the first place.
|
|
180
|
+
*
|
|
181
|
+
* Lengths are compared up front. Digests here are always the same length,
|
|
182
|
+
* so an early exit only happens on malformed input, where the length is
|
|
183
|
+
* not the secret.
|
|
184
|
+
*/
|
|
185
|
+
public static isEqual(a: string, b: string): boolean {
|
|
186
|
+
if (a.length !== b.length) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let difference: number = 0;
|
|
191
|
+
|
|
192
|
+
for (let i: number = 0; i < a.length; i++) {
|
|
193
|
+
difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return difference === 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Verify a plaintext value against a stored hash.
|
|
201
|
+
*
|
|
202
|
+
* Pass the salt stored alongside the hash. Pass null/undefined for records
|
|
203
|
+
* written before per-user salts existed — those verify against the legacy
|
|
204
|
+
* unsalted scheme, and the caller should re-hash them with a fresh salt
|
|
205
|
+
* once the value is known to be correct.
|
|
206
|
+
*/
|
|
207
|
+
public static async verifyValue(data: {
|
|
208
|
+
plainValue: string;
|
|
209
|
+
hashedValue: string;
|
|
210
|
+
encryptionSecret: ObjectID | null;
|
|
211
|
+
salt?: string | null | undefined;
|
|
212
|
+
}): Promise<boolean> {
|
|
213
|
+
if (!data.plainValue || !data.hashedValue) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return HashedString.isEqual(
|
|
218
|
+
HashedString.computeHash(
|
|
219
|
+
data.plainValue,
|
|
220
|
+
data.encryptionSecret,
|
|
221
|
+
data.salt || null,
|
|
222
|
+
),
|
|
223
|
+
data.hashedValue,
|
|
224
|
+
);
|
|
90
225
|
}
|
|
91
226
|
|
|
92
227
|
protected static override fromDatabase(_value: string): HashedString | null {
|
package/Types/Permission.ts
CHANGED
|
@@ -484,6 +484,12 @@ enum Permission {
|
|
|
484
484
|
EditStatusPageLabelRule = "EditStatusPageLabelRule",
|
|
485
485
|
ReadStatusPageLabelRule = "ReadStatusPageLabelRule",
|
|
486
486
|
|
|
487
|
+
// Status Page Monitor Rule Permissions
|
|
488
|
+
CreateStatusPageMonitorRule = "CreateStatusPageMonitorRule",
|
|
489
|
+
DeleteStatusPageMonitorRule = "DeleteStatusPageMonitorRule",
|
|
490
|
+
EditStatusPageMonitorRule = "EditStatusPageMonitorRule",
|
|
491
|
+
ReadStatusPageMonitorRule = "ReadStatusPageMonitorRule",
|
|
492
|
+
|
|
487
493
|
// Host Owner Rule Permissions
|
|
488
494
|
CreateHostOwnerRule = "CreateHostOwnerRule",
|
|
489
495
|
DeleteHostOwnerRule = "DeleteHostOwnerRule",
|
|
@@ -11104,6 +11110,48 @@ export class PermissionHelper {
|
|
|
11104
11110
|
group: PermissionGroup.StatusPage,
|
|
11105
11111
|
},
|
|
11106
11112
|
|
|
11113
|
+
// Status Page Monitor Rule Permissions
|
|
11114
|
+
{
|
|
11115
|
+
permission: Permission.CreateStatusPageMonitorRule,
|
|
11116
|
+
title: "Create Status Page Monitor Rule",
|
|
11117
|
+
description:
|
|
11118
|
+
"This permission can create Status Page Monitor Rules in this project.",
|
|
11119
|
+
isAssignableToTenant: true,
|
|
11120
|
+
isAccessControlPermission: false,
|
|
11121
|
+
isRolePermission: false,
|
|
11122
|
+
group: PermissionGroup.StatusPage,
|
|
11123
|
+
},
|
|
11124
|
+
{
|
|
11125
|
+
permission: Permission.DeleteStatusPageMonitorRule,
|
|
11126
|
+
title: "Delete Status Page Monitor Rule",
|
|
11127
|
+
description:
|
|
11128
|
+
"This permission can delete Status Page Monitor Rules of this project.",
|
|
11129
|
+
isAssignableToTenant: true,
|
|
11130
|
+
isAccessControlPermission: false,
|
|
11131
|
+
isRolePermission: false,
|
|
11132
|
+
group: PermissionGroup.StatusPage,
|
|
11133
|
+
},
|
|
11134
|
+
{
|
|
11135
|
+
permission: Permission.EditStatusPageMonitorRule,
|
|
11136
|
+
title: "Edit Status Page Monitor Rule",
|
|
11137
|
+
description:
|
|
11138
|
+
"This permission can edit Status Page Monitor Rules of this project.",
|
|
11139
|
+
isAssignableToTenant: true,
|
|
11140
|
+
isAccessControlPermission: false,
|
|
11141
|
+
isRolePermission: false,
|
|
11142
|
+
group: PermissionGroup.StatusPage,
|
|
11143
|
+
},
|
|
11144
|
+
{
|
|
11145
|
+
permission: Permission.ReadStatusPageMonitorRule,
|
|
11146
|
+
title: "Read Status Page Monitor Rule",
|
|
11147
|
+
description:
|
|
11148
|
+
"This permission can read Status Page Monitor Rules of this project.",
|
|
11149
|
+
isAssignableToTenant: true,
|
|
11150
|
+
isAccessControlPermission: false,
|
|
11151
|
+
isRolePermission: false,
|
|
11152
|
+
group: PermissionGroup.StatusPage,
|
|
11153
|
+
},
|
|
11154
|
+
|
|
11107
11155
|
// Host Owner Rule Permissions
|
|
11108
11156
|
{
|
|
11109
11157
|
permission: Permission.CreateHostOwnerRule,
|
package/Types/Port.ts
CHANGED
|
@@ -59,17 +59,29 @@ export default class Port extends DatabaseProperty {
|
|
|
59
59
|
this.port = new PositiveNumber(port);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/*
|
|
63
|
+
* A port column is declared `TableColumnType.Number`, so a raw number is
|
|
64
|
+
* as legitimate an incoming value as a raw string — the SMTP forms send
|
|
65
|
+
* one or the other depending on how far the value got through
|
|
66
|
+
* deserialization. Dropping a number on the floor here wrote NULL over a
|
|
67
|
+
* port the user had just typed, with no error anywhere.
|
|
68
|
+
*/
|
|
62
69
|
public static override toDatabase(
|
|
63
70
|
value: Port | FindOperator<Port>,
|
|
64
71
|
): number | null {
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
/*
|
|
73
|
+
* The declared parameter type stays Port-shaped so this still overrides
|
|
74
|
+
* DatabaseProperty.toDatabase, but a raw string or number genuinely
|
|
75
|
+
* arrives here — see the note above — so widen at the check instead.
|
|
76
|
+
*/
|
|
77
|
+
const rawValue: unknown = value;
|
|
78
|
+
|
|
79
|
+
if (typeof rawValue === "string" || typeof rawValue === "number") {
|
|
80
|
+
value = new Port(rawValue);
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
if (value instanceof Port) {
|
|
70
84
|
return value.toNumber();
|
|
71
|
-
} else if (typeof value === "string") {
|
|
72
|
-
return parseInt(value);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
return null;
|
|
@@ -51,14 +51,41 @@ export interface ComponentProps {
|
|
|
51
51
|
customFieldType: DatabaseBaseModelType;
|
|
52
52
|
projectId: ObjectID;
|
|
53
53
|
name: string;
|
|
54
|
-
isEditable?: boolean;
|
|
54
|
+
isEditable?: boolean | undefined;
|
|
55
|
+
/*
|
|
56
|
+
* For call sites that mount this card unconditionally - the resource
|
|
57
|
+
* overview pages - where most projects have never defined a custom field.
|
|
58
|
+
*
|
|
59
|
+
* With this set the card renders NOTHING until it knows the project has at
|
|
60
|
+
* least one field of this type: no loader flash and no "no custom fields
|
|
61
|
+
* have been added" placeholder occupying a slot on a page the operator
|
|
62
|
+
* opened to look at something else.
|
|
63
|
+
*
|
|
64
|
+
* It also hides on a load failure, because reading the schema is gated on
|
|
65
|
+
* both a permission and a billing plan. Without that, every incident a
|
|
66
|
+
* project below the custom-fields plan opens would carry an error card it
|
|
67
|
+
* can do nothing about. The dedicated Custom Fields page mounts this same
|
|
68
|
+
* component WITHOUT the flag, so anyone who goes looking still sees the
|
|
69
|
+
* real reason.
|
|
70
|
+
*/
|
|
71
|
+
hideIfEmpty?: boolean | undefined;
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
58
75
|
props: ComponentProps,
|
|
59
76
|
): ReactElement => {
|
|
60
77
|
const [schemaList, setSchemaList] = useState<Array<BaseModel>>([]);
|
|
61
|
-
|
|
78
|
+
/*
|
|
79
|
+
* Read failures and write failures are kept apart because hideIfEmpty
|
|
80
|
+
* treats them oppositely. Failing to READ the schema is something an
|
|
81
|
+
* overview page swallows - the project may simply not be on a plan that
|
|
82
|
+
* includes custom fields, and there is nothing the operator can do about it
|
|
83
|
+
* on an incident page. Failing to WRITE is the operator's own edit coming
|
|
84
|
+
* back rejected, and must be reported wherever it happens. Sharing one slot
|
|
85
|
+
* meant a rejected save made the whole card disappear.
|
|
86
|
+
*/
|
|
87
|
+
const [loadError, setLoadError] = useState<string>("");
|
|
88
|
+
const [saveError, setSaveError] = useState<string>("");
|
|
62
89
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
63
90
|
const [model, setModel] = useState<BaseModel | null>(null);
|
|
64
91
|
const [showModelForm, setShowModelForm] = useState<boolean>(false);
|
|
@@ -67,6 +94,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
67
94
|
try {
|
|
68
95
|
// load schema.
|
|
69
96
|
setIsLoading(true);
|
|
97
|
+
setLoadError("");
|
|
70
98
|
|
|
71
99
|
const schemaList: ListResult<BaseModel> =
|
|
72
100
|
await ModelAPI.getList<BaseModel>({
|
|
@@ -99,7 +127,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
99
127
|
setIsLoading(false);
|
|
100
128
|
} catch (err) {
|
|
101
129
|
setIsLoading(false);
|
|
102
|
-
|
|
130
|
+
setLoadError(API.getFriendlyMessage(err));
|
|
103
131
|
}
|
|
104
132
|
};
|
|
105
133
|
|
|
@@ -109,6 +137,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
109
137
|
try {
|
|
110
138
|
// load schema.
|
|
111
139
|
setIsLoading(true);
|
|
140
|
+
setSaveError("");
|
|
112
141
|
setShowModelForm(false);
|
|
113
142
|
|
|
114
143
|
await ModelAPI.updateById({
|
|
@@ -122,7 +151,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
122
151
|
await onLoad();
|
|
123
152
|
} catch (err) {
|
|
124
153
|
setIsLoading(false);
|
|
125
|
-
|
|
154
|
+
setSaveError(API.getFriendlyMessage(err));
|
|
126
155
|
}
|
|
127
156
|
};
|
|
128
157
|
|
|
@@ -132,12 +161,45 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
132
161
|
|
|
133
162
|
const isEditable: boolean = props.isEditable !== false;
|
|
134
163
|
|
|
164
|
+
/*
|
|
165
|
+
* schemaList is empty both before the first load finishes and when the
|
|
166
|
+
* project genuinely has no fields of this type, which is exactly when an
|
|
167
|
+
* overview page should show nothing - so one check covers both.
|
|
168
|
+
*
|
|
169
|
+
* `!model` covers the record itself coming back empty. On a page that is
|
|
170
|
+
* plainly displaying that record, a card announcing "Item not found" is a
|
|
171
|
+
* contradiction; showing nothing is the honest version.
|
|
172
|
+
*
|
|
173
|
+
* Deliberately NOT saveError: a rejected save has to stay on screen, and it
|
|
174
|
+
* leaves schemaList and model intact, so the card keeps rendering.
|
|
175
|
+
*/
|
|
176
|
+
if (
|
|
177
|
+
props.hideIfEmpty &&
|
|
178
|
+
(Boolean(loadError) || schemaList.length === 0 || !model)
|
|
179
|
+
) {
|
|
180
|
+
return <></>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* There has to be something to edit before the button is worth offering:
|
|
185
|
+
* the modal reads its initial values off the record and its inputs off the
|
|
186
|
+
* schema, so opening it mid-load threw on a null record, and opening it
|
|
187
|
+
* with no fields defined produced a form with nothing in it.
|
|
188
|
+
*
|
|
189
|
+
* A failed LOAD needs no term of its own - it leaves schemaList empty and
|
|
190
|
+
* model null, which the checks below already cover. A failed SAVE must not
|
|
191
|
+
* take the button away, or the error message is telling the operator to
|
|
192
|
+
* retry something they can no longer reach.
|
|
193
|
+
*/
|
|
194
|
+
const canEdit: boolean =
|
|
195
|
+
isEditable && !isLoading && schemaList.length > 0 && Boolean(model);
|
|
196
|
+
|
|
135
197
|
return (
|
|
136
198
|
<Card
|
|
137
199
|
title={props.title}
|
|
138
200
|
description={props.description}
|
|
139
201
|
buttons={
|
|
140
|
-
|
|
202
|
+
canEdit
|
|
141
203
|
? [
|
|
142
204
|
{
|
|
143
205
|
title: "Edit Fields",
|
|
@@ -152,14 +214,26 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
152
214
|
}
|
|
153
215
|
>
|
|
154
216
|
<div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2">
|
|
155
|
-
{isLoading && !
|
|
156
|
-
{!isLoading && !
|
|
217
|
+
{isLoading && !loadError && <ComponentLoader />}
|
|
218
|
+
{!isLoading && !loadError && schemaList.length === 0 && (
|
|
157
219
|
<ErrorMessage message="No custom fields have been added for this resource. You may add custom fields in Project Settings." />
|
|
158
220
|
)}
|
|
159
|
-
{
|
|
160
|
-
{
|
|
221
|
+
{loadError && <ErrorMessage message={loadError} />}
|
|
222
|
+
{/*
|
|
223
|
+
* Above the values, which are still the ones on the record: the save
|
|
224
|
+
* did not happen, so what is on screen below is what is stored.
|
|
225
|
+
*/}
|
|
226
|
+
{saveError && <ErrorMessage message={saveError} />}
|
|
227
|
+
{/*
|
|
228
|
+
* Only once the fetch has settled: model is null on the very first
|
|
229
|
+
* render too, and announcing "Item not found" while the request is
|
|
230
|
+
* still in flight reads as a broken record rather than a pending one.
|
|
231
|
+
*/}
|
|
232
|
+
{!isLoading && !loadError && schemaList.length > 0 && !model && (
|
|
233
|
+
<ErrorMessage message={"Item not found"} />
|
|
234
|
+
)}
|
|
161
235
|
|
|
162
|
-
{!isLoading && !
|
|
236
|
+
{!isLoading && !loadError && schemaList.length > 0 && model && (
|
|
163
237
|
<Detail
|
|
164
238
|
id={props.name}
|
|
165
239
|
item={(model as any)["customFields"] || {}}
|
|
@@ -194,7 +268,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
194
268
|
await onSave(data).catch();
|
|
195
269
|
}}
|
|
196
270
|
formProps={{
|
|
197
|
-
initialValues: (model as any)["customFields"] || {},
|
|
271
|
+
initialValues: (model as any)?.["customFields"] || {},
|
|
198
272
|
fields: schemaList.map((schemaItem: BaseModel) => {
|
|
199
273
|
const isDropdown: boolean =
|
|
200
274
|
(schemaItem as any).customFieldType ===
|