@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- 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/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -1153,9 +1153,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1153
1153
|
})
|
|
1154
1154
|
@TableColumn({
|
|
1155
1155
|
type: TableColumnType.Boolean,
|
|
1156
|
-
title: "
|
|
1156
|
+
title: "Valkey Memory Notifications",
|
|
1157
1157
|
description:
|
|
1158
|
-
"Notify master administrators when
|
|
1158
|
+
"Notify master administrators when Valkey memory usage rises above the configured threshold.",
|
|
1159
1159
|
defaultValue: false,
|
|
1160
1160
|
isDefaultValueColumn: true,
|
|
1161
1161
|
required: true,
|
|
@@ -1174,9 +1174,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1174
1174
|
})
|
|
1175
1175
|
@TableColumn({
|
|
1176
1176
|
type: TableColumnType.Number,
|
|
1177
|
-
title: "
|
|
1177
|
+
title: "Valkey Memory Notification Threshold (%)",
|
|
1178
1178
|
description:
|
|
1179
|
-
"Notify master administrators when
|
|
1179
|
+
"Notify master administrators when Valkey memory usage rises above this percentage of maxmemory.",
|
|
1180
1180
|
defaultValue: 80,
|
|
1181
1181
|
isDefaultValueColumn: true,
|
|
1182
1182
|
required: true,
|
|
@@ -1195,9 +1195,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1195
1195
|
})
|
|
1196
1196
|
@TableColumn({
|
|
1197
1197
|
type: TableColumnType.Boolean,
|
|
1198
|
-
title: "
|
|
1198
|
+
title: "Valkey Connection Saturation Notifications",
|
|
1199
1199
|
description:
|
|
1200
|
-
"Notify master administrators when
|
|
1200
|
+
"Notify master administrators when Valkey client connections approach the maxclients limit or connections are being rejected.",
|
|
1201
1201
|
defaultValue: false,
|
|
1202
1202
|
isDefaultValueColumn: true,
|
|
1203
1203
|
required: true,
|
|
@@ -1216,9 +1216,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1216
1216
|
})
|
|
1217
1217
|
@TableColumn({
|
|
1218
1218
|
type: TableColumnType.Number,
|
|
1219
|
-
title: "
|
|
1219
|
+
title: "Valkey Connection Notification Threshold (%)",
|
|
1220
1220
|
description:
|
|
1221
|
-
"Notify master administrators when connected
|
|
1221
|
+
"Notify master administrators when connected Valkey clients rise above this percentage of maxclients.",
|
|
1222
1222
|
defaultValue: 80,
|
|
1223
1223
|
isDefaultValueColumn: true,
|
|
1224
1224
|
required: true,
|
|
@@ -1237,9 +1237,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1237
1237
|
})
|
|
1238
1238
|
@TableColumn({
|
|
1239
1239
|
type: TableColumnType.Boolean,
|
|
1240
|
-
title: "
|
|
1240
|
+
title: "Valkey Key Eviction Notifications",
|
|
1241
1241
|
description:
|
|
1242
|
-
"Notify master administrators when
|
|
1242
|
+
"Notify master administrators when Valkey evicts keys because it reached its memory limit.",
|
|
1243
1243
|
defaultValue: false,
|
|
1244
1244
|
isDefaultValueColumn: true,
|
|
1245
1245
|
required: true,
|
|
@@ -1258,9 +1258,9 @@ export default class GlobalConfig extends GlobalConfigModel {
|
|
|
1258
1258
|
})
|
|
1259
1259
|
@TableColumn({
|
|
1260
1260
|
type: TableColumnType.Boolean,
|
|
1261
|
-
title: "
|
|
1261
|
+
title: "Valkey Persistence Failure Notifications",
|
|
1262
1262
|
description:
|
|
1263
|
-
"Notify master administrators when a
|
|
1263
|
+
"Notify master administrators when a Valkey RDB snapshot or AOF write fails.",
|
|
1264
1264
|
defaultValue: false,
|
|
1265
1265
|
isDefaultValueColumn: true,
|
|
1266
1266
|
required: true,
|
|
@@ -18,7 +18,19 @@ import ObjectID from "../../Types/ObjectID";
|
|
|
18
18
|
import Permission from "../../Types/Permission";
|
|
19
19
|
import EntitySource from "../../Types/Telemetry/EntitySource";
|
|
20
20
|
import EntityType from "../../Types/Telemetry/EntityType";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
InventoryLiveness,
|
|
23
|
+
INVENTORY_LIVE_WINDOW_MINUTES,
|
|
24
|
+
INVENTORY_STALE_AFTER_MINUTES,
|
|
25
|
+
} from "../../Types/Telemetry/InventoryLiveness";
|
|
26
|
+
import {
|
|
27
|
+
Column,
|
|
28
|
+
Entity,
|
|
29
|
+
Index,
|
|
30
|
+
JoinColumn,
|
|
31
|
+
ManyToOne,
|
|
32
|
+
VirtualColumn,
|
|
33
|
+
} from "typeorm";
|
|
22
34
|
import DatabaseBaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
|
23
35
|
|
|
24
36
|
/*
|
|
@@ -368,6 +380,37 @@ export default class InventoryItem extends DatabaseBaseModel {
|
|
|
368
380
|
@Column({ type: ColumnType.Date, nullable: true })
|
|
369
381
|
public lastSeenAt?: Date = undefined;
|
|
370
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Computed at read time so filters, counts and pagination use the same
|
|
385
|
+
* status without persisting a value that becomes stale as time passes.
|
|
386
|
+
* A virtual column needs no schema migration and is never written.
|
|
387
|
+
*
|
|
388
|
+
* The badge floors elapsed minutes before applying inclusive thresholds:
|
|
389
|
+
* 30m59s is still Live, and 24h00m59s is still Recent. Strict comparisons
|
|
390
|
+
* against the following minute preserve those exact boundaries.
|
|
391
|
+
*/
|
|
392
|
+
@ColumnAccessControl({ create: [], read: READ_PERMS, update: [] })
|
|
393
|
+
@TableColumn({
|
|
394
|
+
type: TableColumnType.ShortText,
|
|
395
|
+
required: false,
|
|
396
|
+
title: "Status",
|
|
397
|
+
description:
|
|
398
|
+
"Current heartbeat status: live, recent, stale, never seen, or not tracked.",
|
|
399
|
+
})
|
|
400
|
+
@VirtualColumn({
|
|
401
|
+
type: ColumnType.ShortText,
|
|
402
|
+
query: (alias: string): string => {
|
|
403
|
+
return `CASE
|
|
404
|
+
WHEN ${alias}."source" IS DISTINCT FROM '${EntitySource.Discovered}' THEN '${InventoryLiveness.NotTracked}'
|
|
405
|
+
WHEN ${alias}."lastSeenAt" IS NULL THEN '${InventoryLiveness.Never}'
|
|
406
|
+
WHEN ${alias}."lastSeenAt" > CURRENT_TIMESTAMP - INTERVAL '${INVENTORY_LIVE_WINDOW_MINUTES + 1} minutes' THEN '${InventoryLiveness.Live}'
|
|
407
|
+
WHEN ${alias}."lastSeenAt" > CURRENT_TIMESTAMP - INTERVAL '${INVENTORY_STALE_AFTER_MINUTES + 1} minutes' THEN '${InventoryLiveness.Recent}'
|
|
408
|
+
ELSE '${InventoryLiveness.Stale}'
|
|
409
|
+
END`;
|
|
410
|
+
},
|
|
411
|
+
})
|
|
412
|
+
public inventoryStatus?: InventoryLiveness = undefined;
|
|
413
|
+
|
|
371
414
|
/*
|
|
372
415
|
* Archiving is the disposal route that actually works for this table.
|
|
373
416
|
*
|
|
@@ -434,6 +434,132 @@ export default class NetworkDevice extends BaseModel {
|
|
|
434
434
|
})
|
|
435
435
|
public hostname?: string = undefined;
|
|
436
436
|
|
|
437
|
+
/*
|
|
438
|
+
* --- Which switch port is this device on? ---
|
|
439
|
+
*
|
|
440
|
+
* LLDP and CDP answer that for a device that speaks them. A device that
|
|
441
|
+
* does not - a POS register, a handset, a kiosk, anything monitored by
|
|
442
|
+
* ping alone - is invisible to both, and until now the only way to draw
|
|
443
|
+
* its cable on the map was a NetworkDeviceLink somebody typed in and then
|
|
444
|
+
* had to keep true by hand (issue #3489).
|
|
445
|
+
*
|
|
446
|
+
* The switches already know. Every walked switch reports its forwarding
|
|
447
|
+
* table, and the server keeps one NetworkEndpoint row per MAC it learned
|
|
448
|
+
* with the switch and port that learned it. This column is what lets a
|
|
449
|
+
* device be recognised in that inventory: a device whose MAC matches an
|
|
450
|
+
* endpoint's is drawn on the port that endpoint was learned on, and the
|
|
451
|
+
* link moves with the device when it is re-cabled, because the next walk
|
|
452
|
+
* learns the MAC somewhere else.
|
|
453
|
+
*
|
|
454
|
+
* Optional twice over. A device whose hostname is an IP address that a
|
|
455
|
+
* walked device's ARP table resolves is matched by address without it,
|
|
456
|
+
* and the MAC learned that way is written here so the match survives the
|
|
457
|
+
* ARP entry ageing out. The server fills an EMPTY value, and corrects a
|
|
458
|
+
* value it learned itself when the table later binds the address to a
|
|
459
|
+
* different MAC (isMacAddressLearned says which); anything an operator
|
|
460
|
+
* typed is left alone. Normalised to lowercase colon form on write; any
|
|
461
|
+
* of the usual spellings is accepted.
|
|
462
|
+
*/
|
|
463
|
+
@ColumnAccessControl({
|
|
464
|
+
create: [
|
|
465
|
+
Permission.ProjectOwner,
|
|
466
|
+
Permission.ProjectAdmin,
|
|
467
|
+
Permission.ProjectMember,
|
|
468
|
+
Permission.SettingsAdmin,
|
|
469
|
+
Permission.SettingsMember,
|
|
470
|
+
Permission.CreateNetworkDevice,
|
|
471
|
+
],
|
|
472
|
+
read: [
|
|
473
|
+
Permission.ProjectOwner,
|
|
474
|
+
Permission.ProjectAdmin,
|
|
475
|
+
Permission.ProjectMember,
|
|
476
|
+
Permission.Viewer,
|
|
477
|
+
Permission.SettingsAdmin,
|
|
478
|
+
Permission.SettingsMember,
|
|
479
|
+
Permission.SettingsViewer,
|
|
480
|
+
Permission.ReadNetworkDevice,
|
|
481
|
+
],
|
|
482
|
+
update: [
|
|
483
|
+
Permission.ProjectOwner,
|
|
484
|
+
Permission.ProjectAdmin,
|
|
485
|
+
Permission.ProjectMember,
|
|
486
|
+
Permission.SettingsAdmin,
|
|
487
|
+
Permission.SettingsMember,
|
|
488
|
+
Permission.EditNetworkDevice,
|
|
489
|
+
],
|
|
490
|
+
})
|
|
491
|
+
@TableColumn({
|
|
492
|
+
required: false,
|
|
493
|
+
type: TableColumnType.ShortText,
|
|
494
|
+
canReadOnRelationQuery: true,
|
|
495
|
+
title: "MAC Address",
|
|
496
|
+
description:
|
|
497
|
+
"MAC address of this device. Lets the topology map find the switch port it is plugged into from the forwarding tables of walked switches, for a device that speaks neither LLDP nor CDP (one monitored by ping alone). Optional: a device whose hostname is an IP address that a walked router's ARP table resolves is matched by address, and the MAC learned that way is stored here.",
|
|
498
|
+
example: "aa:bb:cc:dd:ee:ff",
|
|
499
|
+
})
|
|
500
|
+
@Column({
|
|
501
|
+
nullable: true,
|
|
502
|
+
type: ColumnType.ShortText,
|
|
503
|
+
length: ColumnLength.ShortText,
|
|
504
|
+
})
|
|
505
|
+
public macAddress?: string = undefined;
|
|
506
|
+
|
|
507
|
+
/*
|
|
508
|
+
* Where the MAC above came from, when the server wrote it.
|
|
509
|
+
*
|
|
510
|
+
* A MAC an operator typed is theirs and is never touched. A MAC the ARP
|
|
511
|
+
* pass learned is only as good as the router's table was on the walk
|
|
512
|
+
* that learned it - an address re-leased to a laptop the day before the
|
|
513
|
+
* register was plugged in stamps the laptop's MAC on the register - so
|
|
514
|
+
* the pass may correct its OWN answer when a later walk binds the
|
|
515
|
+
* address to a different MAC, and this flag is what tells the two apart.
|
|
516
|
+
* Set by the learner's write, cleared by any write of macAddress that
|
|
517
|
+
* comes through the API (see NetworkDeviceService).
|
|
518
|
+
*/
|
|
519
|
+
@ColumnAccessControl({
|
|
520
|
+
create: [
|
|
521
|
+
Permission.ProjectOwner,
|
|
522
|
+
Permission.ProjectAdmin,
|
|
523
|
+
Permission.ProjectMember,
|
|
524
|
+
Permission.SettingsAdmin,
|
|
525
|
+
Permission.SettingsMember,
|
|
526
|
+
Permission.CreateNetworkDevice,
|
|
527
|
+
],
|
|
528
|
+
read: [
|
|
529
|
+
Permission.ProjectOwner,
|
|
530
|
+
Permission.ProjectAdmin,
|
|
531
|
+
Permission.ProjectMember,
|
|
532
|
+
Permission.Viewer,
|
|
533
|
+
Permission.SettingsAdmin,
|
|
534
|
+
Permission.SettingsMember,
|
|
535
|
+
Permission.SettingsViewer,
|
|
536
|
+
Permission.ReadNetworkDevice,
|
|
537
|
+
],
|
|
538
|
+
update: [
|
|
539
|
+
Permission.ProjectOwner,
|
|
540
|
+
Permission.ProjectAdmin,
|
|
541
|
+
Permission.ProjectMember,
|
|
542
|
+
Permission.SettingsAdmin,
|
|
543
|
+
Permission.SettingsMember,
|
|
544
|
+
Permission.EditNetworkDevice,
|
|
545
|
+
],
|
|
546
|
+
})
|
|
547
|
+
@TableColumn({
|
|
548
|
+
required: false,
|
|
549
|
+
type: TableColumnType.Boolean,
|
|
550
|
+
canReadOnRelationQuery: true,
|
|
551
|
+
title: "MAC Address Learned",
|
|
552
|
+
description:
|
|
553
|
+
"True when the MAC Address was filled in from a walked device's ARP table rather than typed. A learned MAC is corrected when a later walk binds the device's address to a different MAC; a typed one is never touched.",
|
|
554
|
+
defaultValue: false,
|
|
555
|
+
})
|
|
556
|
+
@Column({
|
|
557
|
+
nullable: true,
|
|
558
|
+
type: ColumnType.Boolean,
|
|
559
|
+
default: false,
|
|
560
|
+
})
|
|
561
|
+
public isMacAddressLearned?: boolean = undefined;
|
|
562
|
+
|
|
437
563
|
@ColumnAccessControl({
|
|
438
564
|
create: [
|
|
439
565
|
Permission.ProjectOwner,
|
|
@@ -472,32 +472,63 @@ export const HomeHostname: Hostname = Hostname.fromString(
|
|
|
472
472
|
|
|
473
473
|
export const Env: string = process.env["NODE_ENV"] || "production";
|
|
474
474
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
475
|
+
/*
|
|
476
|
+
* The cache and queue tier runs Valkey -- the BSD-licensed fork of Redis 7.2 --
|
|
477
|
+
* so its settings are named VALKEY_*. Any Redis-protocol server works here,
|
|
478
|
+
* including real Redis, which is why the in-code names below stay `Redis*`:
|
|
479
|
+
* they describe the protocol, not the vendor.
|
|
480
|
+
*
|
|
481
|
+
* The REDIS_* names these shipped under are still read, so an existing
|
|
482
|
+
* config.env or values.yaml keeps working untouched. They are DEPRECATED, not
|
|
483
|
+
* removed; VALKEY_* wins when both are set. Nothing here may start requiring
|
|
484
|
+
* the new name -- a self-hoster who never edits config.env must keep running.
|
|
485
|
+
*
|
|
486
|
+
* `||` here is load-bearing and must not be "modernised" to `??`. Compose
|
|
487
|
+
* materialises an unset variable as an EMPTY STRING, and docker-compose.base.yml
|
|
488
|
+
* lists every VALKEY_* name, so the new name is always *defined* inside our
|
|
489
|
+
* containers. Under `??` an empty VALKEY_PASSWORD would beat the operator's real
|
|
490
|
+
* REDIS_PASSWORD and the app would silently fall through to the literal default
|
|
491
|
+
* below.
|
|
492
|
+
*/
|
|
493
|
+
export function getCacheEnvVar(suffix: string): string | undefined {
|
|
494
|
+
return (
|
|
495
|
+
process.env[`VALKEY_${suffix}`] ||
|
|
496
|
+
process.env[`REDIS_${suffix}`] ||
|
|
497
|
+
undefined
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/*
|
|
502
|
+
* The cache does not require a password.
|
|
503
|
+
*
|
|
504
|
+
* The default hostname stays "redis" even though the compose service is now
|
|
505
|
+
* `valkey`. This default is only reached when NEITHER variable is set, which
|
|
506
|
+
* never happens in our own compose or Helm -- both always set them. What it does
|
|
507
|
+
* cover is hand-written Kubernetes manifests, a bare `docker run`, and third
|
|
508
|
+
* party compose files, where the Service has been called `redis` for years.
|
|
509
|
+
* Compose is unaffected either way: its valkey service answers to `redis` too.
|
|
510
|
+
*/
|
|
511
|
+
export const RedisHostname: string = getCacheEnvVar("HOST") || "redis";
|
|
512
|
+
export const RedisPort: Port = new Port(getCacheEnvVar("PORT") || "6379");
|
|
513
|
+
export const RedisDb: number = Number(getCacheEnvVar("DB")) || 0;
|
|
514
|
+
export const RedisUsername: string = getCacheEnvVar("USERNAME") || "default";
|
|
515
|
+
export const RedisPassword: string = getCacheEnvVar("PASSWORD") || "password";
|
|
482
516
|
|
|
483
|
-
export const RedisTlsCa: string | undefined =
|
|
484
|
-
process.env["REDIS_TLS_CA"] || undefined;
|
|
517
|
+
export const RedisTlsCa: string | undefined = getCacheEnvVar("TLS_CA");
|
|
485
518
|
|
|
486
|
-
export const RedisTlsCert: string | undefined =
|
|
487
|
-
process.env["REDIS_TLS_CERT"] || undefined;
|
|
519
|
+
export const RedisTlsCert: string | undefined = getCacheEnvVar("TLS_CERT");
|
|
488
520
|
|
|
489
|
-
export const RedisTlsKey: string | undefined =
|
|
490
|
-
process.env["REDIS_TLS_KEY"] || undefined;
|
|
521
|
+
export const RedisTlsKey: string | undefined = getCacheEnvVar("TLS_KEY");
|
|
491
522
|
|
|
492
523
|
export const RedisTlsSentinelMode: boolean =
|
|
493
|
-
|
|
524
|
+
getCacheEnvVar("TLS_SENTINEL_MODE") === "true";
|
|
494
525
|
|
|
495
526
|
export const ShouldRedisTlsEnable: boolean = Boolean(
|
|
496
527
|
RedisTlsCa || (RedisTlsCert && RedisTlsKey),
|
|
497
528
|
);
|
|
498
529
|
|
|
499
|
-
export const RedisIPFamily: number =
|
|
500
|
-
? Number(
|
|
530
|
+
export const RedisIPFamily: number = getCacheEnvVar("IP_FAMILY")
|
|
531
|
+
? Number(getCacheEnvVar("IP_FAMILY"))
|
|
501
532
|
: 4;
|
|
502
533
|
|
|
503
534
|
export const IsProduction: boolean =
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class AddMacAddressToNetworkDevice1791700000000
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
public name: string = "AddMacAddressToNetworkDevice1791700000000";
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
await queryRunner.query(
|
|
10
|
+
`ALTER TABLE "NetworkDevice" ADD "macAddress" character varying(100)`,
|
|
11
|
+
);
|
|
12
|
+
await queryRunner.query(
|
|
13
|
+
`ALTER TABLE "NetworkDevice" ADD "isMacAddressLearned" boolean DEFAULT false`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
18
|
+
await queryRunner.query(
|
|
19
|
+
`ALTER TABLE "NetworkDevice" DROP COLUMN "isMacAddressLearned"`,
|
|
20
|
+
);
|
|
21
|
+
await queryRunner.query(
|
|
22
|
+
`ALTER TABLE "NetworkDevice" DROP COLUMN "macAddress"`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -577,6 +577,7 @@ import { AddTelemetryIngestionKeyType1791300000000 } from "./1791300000000-AddTe
|
|
|
577
577
|
import { SessionReplayRecordEverySessionByDefault1791400000000 } from "./1791400000000-SessionReplayRecordEverySessionByDefault";
|
|
578
578
|
import { WidenCustomFieldDropdownOptions1791500000000 } from "./1791500000000-WidenCustomFieldDropdownOptions";
|
|
579
579
|
import { AddCustomFieldValueMapping1791600000000 } from "./1791600000000-AddCustomFieldValueMapping";
|
|
580
|
+
import { AddMacAddressToNetworkDevice1791700000000 } from "./1791700000000-AddMacAddressToNetworkDevice";
|
|
580
581
|
|
|
581
582
|
export default [
|
|
582
583
|
InitialMigration,
|
|
@@ -1158,4 +1159,5 @@ export default [
|
|
|
1158
1159
|
SessionReplayRecordEverySessionByDefault1791400000000,
|
|
1159
1160
|
WidenCustomFieldDropdownOptions1791500000000,
|
|
1160
1161
|
AddCustomFieldValueMapping1791600000000,
|
|
1162
|
+
AddMacAddressToNetworkDevice1791700000000,
|
|
1161
1163
|
];
|
|
@@ -71,9 +71,9 @@ export default abstract class Redis {
|
|
|
71
71
|
// Listen to 'error' events to the Redis connection
|
|
72
72
|
this.client.on("error", (error: Error) => {
|
|
73
73
|
if ((error as any).code === "ECONNRESET") {
|
|
74
|
-
logger.error("Connection to
|
|
74
|
+
logger.error("Connection to Valkey Session Store timed out.");
|
|
75
75
|
} else if ((error as any).code === "ECONNREFUSED") {
|
|
76
|
-
logger.error("Connection to
|
|
76
|
+
logger.error("Connection to Valkey Session Store refused!");
|
|
77
77
|
} else {
|
|
78
78
|
logger.error(error);
|
|
79
79
|
}
|
|
@@ -82,16 +82,16 @@ export default abstract class Redis {
|
|
|
82
82
|
// Listen to 'reconnecting' event to Redis
|
|
83
83
|
this.client.on("reconnecting", () => {
|
|
84
84
|
if (this.client?.status === "reconnecting") {
|
|
85
|
-
logger.error("Reconnecting to
|
|
85
|
+
logger.error("Reconnecting to Valkey Session Store...");
|
|
86
86
|
} else {
|
|
87
|
-
logger.error("Error reconnecting to
|
|
87
|
+
logger.error("Error reconnecting to Valkey Session Store.");
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
// Listen to the 'connect' event to Redis
|
|
92
92
|
this.client.on("connect", (err: Error) => {
|
|
93
93
|
if (!err) {
|
|
94
|
-
logger.debug("Connected to
|
|
94
|
+
logger.debug("Connected to Valkey Session Store!");
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -137,7 +137,7 @@ export default abstract class Redis {
|
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
logger.debug("Cannot connect to
|
|
140
|
+
logger.debug("Cannot connect to Valkey. Retrying again in 5 seconds");
|
|
141
141
|
// sleep for 5 seconds.
|
|
142
142
|
|
|
143
143
|
await Sleep.sleep(5000);
|
|
@@ -163,7 +163,7 @@ export default abstract class Redis {
|
|
|
163
163
|
|
|
164
164
|
return this.client;
|
|
165
165
|
} catch (err) {
|
|
166
|
-
logger.error("
|
|
166
|
+
logger.error("Valkey Connection Failed");
|
|
167
167
|
logger.error(err);
|
|
168
168
|
throw err;
|
|
169
169
|
}
|
|
@@ -191,7 +191,7 @@ export default abstract class Redis {
|
|
|
191
191
|
|
|
192
192
|
return true;
|
|
193
193
|
} catch (err) {
|
|
194
|
-
logger.error("
|
|
194
|
+
logger.error("Valkey Connection Lost");
|
|
195
195
|
logger.error(err);
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
@@ -17,12 +17,18 @@ export default class InfrastructureStatus {
|
|
|
17
17
|
logger.info("Checking infrastructure status");
|
|
18
18
|
|
|
19
19
|
if (data.checkRedisStatus) {
|
|
20
|
-
logger.info("Checking
|
|
20
|
+
logger.info("Checking Valkey status");
|
|
21
21
|
if (!(await Redis.checkConnnectionStatus())) {
|
|
22
|
-
logger.info("
|
|
23
|
-
|
|
22
|
+
logger.info("Valkey is not connected");
|
|
23
|
+
/*
|
|
24
|
+
* This message is not log-only: StatusAPI passes it straight to
|
|
25
|
+
* sendErrorResponse, so it is the body of GET /api/status/global-cache,
|
|
26
|
+
* /ready and /live. It is the first thing an operator curls when the
|
|
27
|
+
* cache is down, which is why it says what the container actually is.
|
|
28
|
+
*/
|
|
29
|
+
throw new DatabaseNotConnectedException("Valkey is not connected");
|
|
24
30
|
}
|
|
25
|
-
logger.info("
|
|
31
|
+
logger.info("Valkey is connected");
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
if (data.checkPostgresStatus) {
|
|
@@ -46,6 +46,7 @@ import Protocol from "../../Types/API/Protocol";
|
|
|
46
46
|
import Route from "../../Types/API/Route";
|
|
47
47
|
import URL from "../../Types/API/URL";
|
|
48
48
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
49
|
+
import Sort from "../../Types/BaseDatabase/Sort";
|
|
49
50
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
50
51
|
import { getMaxLengthFromTableColumnType } from "../../Types/Database/ColumnLength";
|
|
51
52
|
import Columns from "../../Types/Database/Columns";
|
|
@@ -1636,12 +1637,28 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
1636
1637
|
* A query builder scoped to exactly the rows this read may see, and — this
|
|
1637
1638
|
* is the whole point — scoped so that each of them appears ONCE.
|
|
1638
1639
|
*
|
|
1639
|
-
* The permission pipeline expresses a label-scoped
|
|
1640
|
-
* the access-control RELATION
|
|
1641
|
-
*
|
|
1642
|
-
* TypeORM's FindOptions machinery
|
|
1643
|
-
* many-to-many junction table — and a join
|
|
1644
|
-
* carrying two permitted labels comes back twice.
|
|
1640
|
+
* The permission pipeline expresses a label-scoped ALLOW as a condition on
|
|
1641
|
+
* the access-control RELATION — `{ labels: [permittedIds] }`, which
|
|
1642
|
+
* QueryUtil.serializeQuery then nests into `{ labels: { _id: <a set
|
|
1643
|
+
* membership operator> } }`. Handed to TypeORM's FindOptions machinery that
|
|
1644
|
+
* becomes a join through the many-to-many junction table — and a join
|
|
1645
|
+
* multiplies rows. A device carrying two permitted labels comes back twice.
|
|
1646
|
+
*
|
|
1647
|
+
* A label BLOCK no longer takes that route, and it is worth being precise
|
|
1648
|
+
* about why, because the shape reads like the allow half's mirror image and
|
|
1649
|
+
* is not. A relation condition cannot express "has NONE of these labels" at
|
|
1650
|
+
* all: a device labelled {blocked, other} still matches the join through its
|
|
1651
|
+
* "other" row. So ReadPermission writes the block as a flat predicate on the
|
|
1652
|
+
* owner's own id instead — `_id NOT IN (SELECT ownerId FROM the junction
|
|
1653
|
+
* table WHERE labelId IN (...))`. Flat means no join, which means no
|
|
1654
|
+
* duplicate rows, which means the block half needs nothing from this method
|
|
1655
|
+
* beyond the cheap path below.
|
|
1656
|
+
*
|
|
1657
|
+
* The allow half is not the only producer of relation-keyed conditions
|
|
1658
|
+
* either. `@CanAccessIfCanReadOn` makes BasePermission write
|
|
1659
|
+
* `query[relation] = { <the related model's access-control column>: ids }`
|
|
1660
|
+
* for StatusPageResource, IncidentInternalNote, AlertEpisodeMember and
|
|
1661
|
+
* others, and that arrives here indistinguishable from a label allow.
|
|
1645
1662
|
*
|
|
1646
1663
|
* `findBy` never notices, because entity hydration de-duplicates by primary
|
|
1647
1664
|
* key on the way out. An aggregate has no such step: COUNT(*) would report
|
|
@@ -1656,9 +1673,10 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
1656
1673
|
* by a DISTINCT subquery of ids. The joins live inside that subquery, where
|
|
1657
1674
|
* duplicate rows collapse before anything is added up.
|
|
1658
1675
|
*
|
|
1659
|
-
* The subquery is skipped when the query is flat —
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1676
|
+
* The subquery is skipped when the query is flat — every read by a user with
|
|
1677
|
+
* no label-scoped allow grant and no `@CanAccessIfCanReadOn` relation, which
|
|
1678
|
+
* is the hot path, and the block half above with it — so the ordinary
|
|
1679
|
+
* full-fleet aggregate stays a single sequential scan.
|
|
1662
1680
|
*/
|
|
1663
1681
|
private buildAggregateScope(
|
|
1664
1682
|
query: Query<TBaseModel>,
|
|
@@ -1698,10 +1716,14 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
1698
1716
|
`"${this.modelName}"."_id" IN (${scopeBuilder.getQuery()})`,
|
|
1699
1717
|
);
|
|
1700
1718
|
/*
|
|
1701
|
-
* The subquery's own bound values
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1704
|
-
*
|
|
1719
|
+
* The subquery's own bound values, copied across under whatever names they
|
|
1720
|
+
* already carry. Two naming schemes reach this line: TypeORM auto-names
|
|
1721
|
+
* the parameters it creates itself (`orm_param_N`), but a label predicate
|
|
1722
|
+
* arrives as a `Raw` operator carrying its own object-literal parameters,
|
|
1723
|
+
* and TypeORM registers those verbatim — QueryHelper names them with ten
|
|
1724
|
+
* random characters apiece. Neither can clash with the outer builder,
|
|
1725
|
+
* which generates no parameters of its own here: its only WHERE is the raw
|
|
1726
|
+
* string above. Caller-supplied parameters are named and applied later.
|
|
1705
1727
|
*/
|
|
1706
1728
|
queryBuilder.setParameters(scopeBuilder.getParameters());
|
|
1707
1729
|
|
|
@@ -2149,6 +2171,36 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
2149
2171
|
throw new BadDataException("GroupBy is currently not supported");
|
|
2150
2172
|
}
|
|
2151
2173
|
|
|
2174
|
+
/*
|
|
2175
|
+
* Complete the caller's sort into a TOTAL order.
|
|
2176
|
+
*
|
|
2177
|
+
* Everything here pages with LIMIT/OFFSET, and OFFSET only means
|
|
2178
|
+
* anything against an ordering that has no ties: rows that compare
|
|
2179
|
+
* equal may come back in any order, and Postgres is free to return them
|
|
2180
|
+
* differently for the two queries that fetch page 1 and page 2. A tied
|
|
2181
|
+
* row can therefore be served on both pages while another is served on
|
|
2182
|
+
* neither — the same row apparently listed twice.
|
|
2183
|
+
*
|
|
2184
|
+
* Ties are not exotic. The Inventory list sorts on `lastSeenAt`, which
|
|
2185
|
+
* the entity reconciler rewrites for every live entity every few
|
|
2186
|
+
* minutes, and the inventory mirror stamps one identical timestamp
|
|
2187
|
+
* across a whole page of rows; "created at" sorts tie for anything
|
|
2188
|
+
* bulk-inserted in one statement. Appending the primary key breaks
|
|
2189
|
+
* every tie deterministically and costs nothing — `_id` is the PK, and
|
|
2190
|
+
* it is already in the select (see above), so this adds no column to
|
|
2191
|
+
* the projection and nothing to strip afterwards.
|
|
2192
|
+
*/
|
|
2193
|
+
const sortSoFar: Dictionary<SortOrder> =
|
|
2194
|
+
(onBeforeFind.sort as Dictionary<SortOrder> | undefined) || {};
|
|
2195
|
+
|
|
2196
|
+
if (!sortSoFar["_id"]) {
|
|
2197
|
+
// Copied, never mutated: callers reuse their sort objects across queries.
|
|
2198
|
+
onBeforeFind.sort = {
|
|
2199
|
+
...sortSoFar,
|
|
2200
|
+
_id: SortOrder.Ascending,
|
|
2201
|
+
} as Sort<TBaseModel>;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2152
2204
|
const items: Array<TBaseModel> = await this.getRepository().find({
|
|
2153
2205
|
skip: onBeforeFind.skip.toNumber(),
|
|
2154
2206
|
take: onBeforeFind.limit.toNumber(),
|
|
@@ -57,6 +57,27 @@ export class InventoryItemService extends DatabaseService<Model> {
|
|
|
57
57
|
): Promise<OnCreate<Model>> {
|
|
58
58
|
const data: Model = createBy.data;
|
|
59
59
|
|
|
60
|
+
/*
|
|
61
|
+
* Who is calling decides this, not what they sent.
|
|
62
|
+
*
|
|
63
|
+
* The two machine writers — the ingest reconciler and the inventory
|
|
64
|
+
* mirror — construct the model themselves and create it with
|
|
65
|
+
* `props: { isRoot: true }`. Everything else is a person or an API
|
|
66
|
+
* client. Keying the branch on "did the payload include an entityKey"
|
|
67
|
+
* instead let any caller opt out of every check below simply by sending
|
|
68
|
+
* one: the manual-type restriction, the required display name, the
|
|
69
|
+
* canonicalize-then-hash identity derivation and the firstSeenAt /
|
|
70
|
+
* lastSeenAt stamp were all skipped, and a hand-supplied
|
|
71
|
+
* `source: "discovered"` was taken at face value — putting a row in
|
|
72
|
+
* Inventory that claims to have been discovered from telemetry, of a type
|
|
73
|
+
* only ingest is supposed to create, which the prune sweep then treats as
|
|
74
|
+
* TTL-reapable.
|
|
75
|
+
*/
|
|
76
|
+
if (createBy.props.isRoot !== true) {
|
|
77
|
+
delete data.entityKey;
|
|
78
|
+
data.source = EntitySource.Manual;
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
if (!data.entityKey) {
|
|
61
82
|
data.source = EntitySource.Manual;
|
|
62
83
|
}
|
|
@@ -150,7 +171,16 @@ export class InventoryItemService extends DatabaseService<Model> {
|
|
|
150
171
|
const countByType: Map<EntityType, number> = new Map<EntityType, number>();
|
|
151
172
|
|
|
152
173
|
for (const entity of data.entities) {
|
|
153
|
-
|
|
174
|
+
/*
|
|
175
|
+
* Both promotion gates, repeated at the write boundary. The caller
|
|
176
|
+
* applies them too, but this method is public and the cost of a missed
|
|
177
|
+
* gate here is a registry row that should never have existed — see
|
|
178
|
+
* `ExtractedEntity.membershipOnly` for what the per-entity flag means.
|
|
179
|
+
*/
|
|
180
|
+
if (
|
|
181
|
+
entity.membershipOnly ||
|
|
182
|
+
!REGISTRY_PROMOTED_TYPES.has(entity.entityType)
|
|
183
|
+
) {
|
|
154
184
|
continue;
|
|
155
185
|
}
|
|
156
186
|
try {
|