@oneuptime/common 12.0.9 → 12.0.11
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/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.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/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import URL from "../../../Types/API/URL";
|
|
2
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
3
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
4
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
5
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
6
|
+
import { RuleRunResultUtil } from "../../../Types/NetworkAutomation/RuleRunResult";
|
|
7
|
+
import RuleRunSummary from "../../../Utils/NetworkAutomation/RuleRunSummary";
|
|
8
|
+
import { APP_API_URL } from "../../Config";
|
|
9
|
+
import API from "../API/API";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Client for the two Network Automation "Run now" endpoints.
|
|
13
|
+
*
|
|
14
|
+
* Both rule kinds fire automatically only when a device is created (and, for
|
|
15
|
+
* site assignment, when its identity changes or on the next poll of a device
|
|
16
|
+
* with no site), so a rule written after an estate was imported never reaches
|
|
17
|
+
* it. These endpoints apply one rule to the devices that already exist —
|
|
18
|
+
* OneUptime/oneuptime#3191.
|
|
19
|
+
*
|
|
20
|
+
* The two differ only in the route they post to and in whether the run can
|
|
21
|
+
* overwrite something a person chose, so they share one client rather than
|
|
22
|
+
* two that drift.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export enum NetworkRuleKind {
|
|
26
|
+
SiteAssignment = "SiteAssignment",
|
|
27
|
+
DeviceLabel = "DeviceLabel",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const RULE_RUN_ROUTE: Record<NetworkRuleKind, string> = {
|
|
31
|
+
[NetworkRuleKind.SiteAssignment]: "/network-site-assignment-rule",
|
|
32
|
+
[NetworkRuleKind.DeviceLabel]: "/network-device-label-rule",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* What the caller shows. A failed run is not an exception here: the modal
|
|
37
|
+
* renders the same sentence either way, only in the error slot.
|
|
38
|
+
*/
|
|
39
|
+
export interface NetworkRuleRunOutcome {
|
|
40
|
+
isSuccess: boolean;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default class RunNetworkRule {
|
|
45
|
+
public static getRunRoute(data: {
|
|
46
|
+
ruleKind: NetworkRuleKind;
|
|
47
|
+
ruleId: string;
|
|
48
|
+
}): string {
|
|
49
|
+
return `${RULE_RUN_ROUTE[data.ruleKind]}/${data.ruleId}/run`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static async run(data: {
|
|
53
|
+
ruleKind: NetworkRuleKind;
|
|
54
|
+
ruleId: string;
|
|
55
|
+
/*
|
|
56
|
+
* Site assignment only, and only ever sent as a real boolean — the
|
|
57
|
+
* endpoint accepts nothing else, so a run cannot move devices somebody
|
|
58
|
+
* placed by hand through a stray truthy value.
|
|
59
|
+
*/
|
|
60
|
+
reassignDevicesAlreadyInASite?: boolean | undefined;
|
|
61
|
+
headers?: Dictionary<string> | undefined;
|
|
62
|
+
}): Promise<NetworkRuleRunOutcome> {
|
|
63
|
+
/*
|
|
64
|
+
* Without this the request would go to `/…//run`, which matches no route
|
|
65
|
+
* and comes back as an unhelpful 404 rather than as the real problem.
|
|
66
|
+
*/
|
|
67
|
+
if (!data.ruleId) {
|
|
68
|
+
return {
|
|
69
|
+
isSuccess: false,
|
|
70
|
+
message: "This rule has no id, so it cannot be run.",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isSiteAssignment: boolean =
|
|
75
|
+
data.ruleKind === NetworkRuleKind.SiteAssignment;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
|
79
|
+
await API.post<JSONObject>({
|
|
80
|
+
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
|
81
|
+
RunNetworkRule.getRunRoute({
|
|
82
|
+
ruleKind: data.ruleKind,
|
|
83
|
+
ruleId: data.ruleId,
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
data: isSiteAssignment
|
|
87
|
+
? {
|
|
88
|
+
reassignDevicesAlreadyInASite: Boolean(
|
|
89
|
+
data.reassignDevicesAlreadyInASite,
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
: {},
|
|
93
|
+
...(data.headers ? { headers: data.headers } : {}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (response.isFailure()) {
|
|
97
|
+
return {
|
|
98
|
+
isSuccess: false,
|
|
99
|
+
message: API.getFriendlyMessage(response),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const body: JSONObject = (response.data || {}) as JSONObject;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
isSuccess: true,
|
|
107
|
+
message: isSiteAssignment
|
|
108
|
+
? RuleRunSummary.describeSiteAssignmentRun(
|
|
109
|
+
RuleRunResultUtil.parseSiteAssignmentRuleRunResult(body),
|
|
110
|
+
)
|
|
111
|
+
: RuleRunSummary.describeLabelRun(
|
|
112
|
+
RuleRunResultUtil.parseLabelRuleRunResult(body),
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return {
|
|
117
|
+
isSuccess: false,
|
|
118
|
+
message: API.getFriendlyMessage(err),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
package/Utils/API.ts
CHANGED
|
@@ -51,6 +51,28 @@ export interface RequestOutcome {
|
|
|
51
51
|
export interface RequestOptions {
|
|
52
52
|
retries?: number | undefined;
|
|
53
53
|
exponentialBackoff?: boolean | undefined;
|
|
54
|
+
/*
|
|
55
|
+
* Ceiling on any single backoff sleep. Doubling is unbounded by
|
|
56
|
+
* definition, so without a cap a double-digit retry budget spends nearly
|
|
57
|
+
* all of its wall clock asleep — the tenth wait alone is over seventeen
|
|
58
|
+
* minutes. Defaults to DEFAULT_MAX_BACKOFF_IN_MS, which is far above what
|
|
59
|
+
* a handful of retries can reach, so short retry budgets are unaffected.
|
|
60
|
+
*/
|
|
61
|
+
maxBackoffInMs?: number | undefined;
|
|
62
|
+
/*
|
|
63
|
+
* Spend retries only on failures that repeating could plausibly fix —
|
|
64
|
+
* see isRetryableError. Off by default: callers that have always retried
|
|
65
|
+
* every error keep doing so until they opt in.
|
|
66
|
+
*/
|
|
67
|
+
retryOnlyOnRetryableErrors?: boolean | undefined;
|
|
68
|
+
/*
|
|
69
|
+
* Wall-clock budget for the whole call, every attempt and backoff
|
|
70
|
+
* included. No attempt is started that the budget cannot pay for, so N
|
|
71
|
+
* retries of a request that times out can no longer take N x timeout and
|
|
72
|
+
* outlive whatever is waiting on the answer. It does not abort an attempt
|
|
73
|
+
* already in flight — the per-attempt `timeout` is what bounds that.
|
|
74
|
+
*/
|
|
75
|
+
totalTimeoutInMs?: number | undefined;
|
|
54
76
|
timeout?: number | undefined;
|
|
55
77
|
doNotFollowRedirects?: boolean | undefined;
|
|
56
78
|
// Per-request proxy agent support (Probe supplies these instead of mutating global axios defaults)
|
|
@@ -97,6 +119,25 @@ export interface AuthRetryContext {
|
|
|
97
119
|
}
|
|
98
120
|
|
|
99
121
|
export default class API {
|
|
122
|
+
/*
|
|
123
|
+
* Backstop ceiling for a single backoff sleep when the caller names none.
|
|
124
|
+
* Set well above what a small retry budget can reach (three retries top out
|
|
125
|
+
* at eight seconds), so it only ever bites on the long budgets that need it.
|
|
126
|
+
*/
|
|
127
|
+
public static readonly DEFAULT_MAX_BACKOFF_IN_MS: number = 60 * 1000;
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* 4xx responses are the server saying the request itself is wrong, and
|
|
131
|
+
* resending it byte-for-byte gets the same answer — except for these three,
|
|
132
|
+
* which say "not now" rather than "not ever".
|
|
133
|
+
*/
|
|
134
|
+
private static readonly RETRYABLE_CLIENT_ERROR_STATUS_CODES: Set<number> =
|
|
135
|
+
new Set([
|
|
136
|
+
408, // Request Timeout
|
|
137
|
+
425, // Too Early
|
|
138
|
+
429, // Too Many Requests
|
|
139
|
+
]);
|
|
140
|
+
|
|
100
141
|
private _protocol: Protocol = Protocol.HTTPS;
|
|
101
142
|
public get protocol(): Protocol {
|
|
102
143
|
return this._protocol;
|
|
@@ -417,57 +458,88 @@ export default class API {
|
|
|
417
458
|
finalBody = new URLSearchParams(data as Dictionary<string>);
|
|
418
459
|
}
|
|
419
460
|
|
|
420
|
-
|
|
421
|
-
|
|
461
|
+
/*
|
|
462
|
+
* Negative budgets are clamped rather than trusted: `while (0 <= -1)`
|
|
463
|
+
* used to skip the request entirely and report the misleading "No
|
|
464
|
+
* response received from server" for a request never sent.
|
|
465
|
+
*/
|
|
466
|
+
const maxRetries: number = Math.max(0, options?.retries || 0);
|
|
422
467
|
const exponentialBackoff: boolean = options?.exponentialBackoff || false;
|
|
423
468
|
|
|
424
|
-
|
|
469
|
+
// Identical on every attempt, so build it once.
|
|
470
|
+
const axiosOptions: AxiosRequestConfig = {
|
|
471
|
+
method: method,
|
|
472
|
+
url: url.toString(),
|
|
473
|
+
headers: finalHeaders,
|
|
474
|
+
data: finalBody,
|
|
475
|
+
};
|
|
425
476
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
try {
|
|
430
|
-
const axiosOptions: AxiosRequestConfig = {
|
|
431
|
-
method: method,
|
|
432
|
-
url: url.toString(),
|
|
433
|
-
headers: finalHeaders,
|
|
434
|
-
data: finalBody,
|
|
435
|
-
};
|
|
477
|
+
if (options?.timeout) {
|
|
478
|
+
axiosOptions.timeout = options.timeout;
|
|
479
|
+
}
|
|
436
480
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
481
|
+
if (options?.doNotFollowRedirects) {
|
|
482
|
+
axiosOptions.maxRedirects = 0;
|
|
483
|
+
}
|
|
440
484
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
485
|
+
// Attach proxy agents per request if provided (avoids global side-effects)
|
|
486
|
+
if (options?.httpAgent) {
|
|
487
|
+
axiosOptions.httpAgent = options.httpAgent;
|
|
488
|
+
}
|
|
489
|
+
if (options?.httpsAgent) {
|
|
490
|
+
axiosOptions.httpsAgent = options.httpsAgent;
|
|
491
|
+
}
|
|
444
492
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
if (options?.httpsAgent) {
|
|
450
|
-
(axiosOptions as AxiosRequestConfig).httpsAgent =
|
|
451
|
-
options.httpsAgent;
|
|
452
|
-
}
|
|
493
|
+
if (options?.onUploadProgress) {
|
|
494
|
+
axiosOptions.onUploadProgress = options.onUploadProgress;
|
|
495
|
+
}
|
|
453
496
|
|
|
454
|
-
|
|
455
|
-
axiosOptions.onUploadProgress = options.onUploadProgress;
|
|
456
|
-
}
|
|
497
|
+
let result: AxiosResponse | null = null;
|
|
457
498
|
|
|
499
|
+
for (let attempt: number = 0; attempt <= maxRetries; attempt++) {
|
|
500
|
+
attempts++;
|
|
501
|
+
|
|
502
|
+
try {
|
|
458
503
|
result = await axios(axiosOptions);
|
|
459
504
|
|
|
460
505
|
break;
|
|
461
506
|
} catch (e) {
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
507
|
+
if (attempt >= maxRetries) {
|
|
508
|
+
throw e;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/*
|
|
512
|
+
* A rejected request stays rejected. Failing now surfaces the
|
|
513
|
+
* provider's own explanation immediately instead of after a full
|
|
514
|
+
* ladder of backoffs that cannot change the answer.
|
|
515
|
+
*/
|
|
516
|
+
if (options?.retryOnlyOnRetryableErrors && !API.isRetryableError(e)) {
|
|
517
|
+
throw e;
|
|
518
|
+
}
|
|
466
519
|
|
|
467
|
-
|
|
468
|
-
|
|
520
|
+
const delayInMs: number = API.getRetryDelayInMs({
|
|
521
|
+
attempt: attempt,
|
|
522
|
+
error: e,
|
|
523
|
+
exponentialBackoff: exponentialBackoff,
|
|
524
|
+
maxBackoffInMs: options?.maxBackoffInMs,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
/*
|
|
528
|
+
* Check the budget against the point the next attempt would START,
|
|
529
|
+
* not the point we are at now: sleeping out the remainder and then
|
|
530
|
+
* giving up would waste the wait for nothing.
|
|
531
|
+
*/
|
|
532
|
+
if (
|
|
533
|
+
options?.totalTimeoutInMs !== undefined &&
|
|
534
|
+
Date.now() - requestStartedAtInMs + delayInMs >=
|
|
535
|
+
options.totalTimeoutInMs
|
|
536
|
+
) {
|
|
469
537
|
throw e;
|
|
470
538
|
}
|
|
539
|
+
|
|
540
|
+
if (delayInMs > 0) {
|
|
541
|
+
await Sleep.sleep(delayInMs);
|
|
542
|
+
}
|
|
471
543
|
}
|
|
472
544
|
}
|
|
473
545
|
|
|
@@ -620,6 +692,141 @@ export default class API {
|
|
|
620
692
|
}
|
|
621
693
|
}
|
|
622
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Is repeating this request worth an attempt?
|
|
697
|
+
*
|
|
698
|
+
* Retries exist for failures the network or the server may not repeat: a
|
|
699
|
+
* dropped connection, a timeout, an overloaded backend, a rate limit that
|
|
700
|
+
* lapses. Everything else — a malformed body, a bad key, an unknown route —
|
|
701
|
+
* fails identically every time, and retrying it only delays the error the
|
|
702
|
+
* operator needs to read.
|
|
703
|
+
*/
|
|
704
|
+
public static isRetryableError(error: unknown): boolean {
|
|
705
|
+
if (!axios.isAxiosError(error)) {
|
|
706
|
+
/*
|
|
707
|
+
* Not a transport failure at all: the request could not even be built,
|
|
708
|
+
* or an interceptor threw. Nothing about resending changes that.
|
|
709
|
+
*/
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const axiosError: AxiosError = error;
|
|
714
|
+
|
|
715
|
+
// The caller pulled the plug on purpose.
|
|
716
|
+
if (axiosError.code === "ERR_CANCELED") {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const statusCode: number | undefined = axiosError.response?.status;
|
|
721
|
+
|
|
722
|
+
/*
|
|
723
|
+
* No response at all — timeout, DNS failure, refused or reset connection.
|
|
724
|
+
* These are exactly what a retry is for.
|
|
725
|
+
*/
|
|
726
|
+
if (statusCode === undefined) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// The server broke, not the request.
|
|
731
|
+
if (statusCode >= 500) {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return API.RETRYABLE_CLIENT_ERROR_STATUS_CODES.has(statusCode);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* How long to wait before the attempt that follows `attempt` (0-based).
|
|
740
|
+
*/
|
|
741
|
+
private static getRetryDelayInMs(data: {
|
|
742
|
+
attempt: number;
|
|
743
|
+
error: unknown;
|
|
744
|
+
exponentialBackoff: boolean;
|
|
745
|
+
maxBackoffInMs?: number | undefined;
|
|
746
|
+
}): number {
|
|
747
|
+
const ceilingInMs: number =
|
|
748
|
+
data.maxBackoffInMs ?? API.DEFAULT_MAX_BACKOFF_IN_MS;
|
|
749
|
+
|
|
750
|
+
/*
|
|
751
|
+
* A server that says when to come back knows better than any formula we
|
|
752
|
+
* could pick — coming back early just spends another attempt on the same
|
|
753
|
+
* 429. Still capped: the caller's budget is what bounds the total wait,
|
|
754
|
+
* and an unbounded Retry-After would hand that control to the server.
|
|
755
|
+
*/
|
|
756
|
+
const retryAfterInMs: number | undefined = API.getRetryAfterInMs(
|
|
757
|
+
data.error,
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
if (retryAfterInMs !== undefined) {
|
|
761
|
+
return Math.min(retryAfterInMs, ceilingInMs);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (!data.exponentialBackoff) {
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const backoffInMs: number = Math.min(
|
|
769
|
+
2 ** (data.attempt + 1) * 1000,
|
|
770
|
+
ceilingInMs,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
/*
|
|
774
|
+
* Equal jitter. Everything that failed together — every chat turn behind
|
|
775
|
+
* one provider outage, every probe behind one flapping host — otherwise
|
|
776
|
+
* comes back at the very same millisecond and knocks the recovering
|
|
777
|
+
* server straight back over. Half the delay stays fixed so a retry is
|
|
778
|
+
* never effectively immediate, and half is spread at random.
|
|
779
|
+
*/
|
|
780
|
+
return Math.round(backoffInMs / 2 + Math.random() * (backoffInMs / 2));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Read a Retry-After header off a failed response. Returns undefined unless
|
|
785
|
+
* the header is present and names a sane, non-negative wait.
|
|
786
|
+
*/
|
|
787
|
+
private static getRetryAfterInMs(error: unknown): number | undefined {
|
|
788
|
+
if (!axios.isAxiosError(error)) {
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const headers: unknown = error.response?.headers;
|
|
793
|
+
|
|
794
|
+
if (!headers || typeof headers !== "object") {
|
|
795
|
+
return undefined;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const rawHeader: unknown =
|
|
799
|
+
(headers as Dictionary<unknown>)["retry-after"] ??
|
|
800
|
+
(headers as Dictionary<unknown>)["Retry-After"];
|
|
801
|
+
|
|
802
|
+
if (rawHeader === undefined || rawHeader === null) {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const headerValue: string = String(rawHeader).trim();
|
|
807
|
+
|
|
808
|
+
if (!headerValue) {
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Form 1: delta-seconds.
|
|
813
|
+
const deltaSecondsRegex: RegExp = /^\d+$/;
|
|
814
|
+
|
|
815
|
+
if (deltaSecondsRegex.test(headerValue)) {
|
|
816
|
+
return Number(headerValue) * 1000;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Form 2: an HTTP-date to wait until.
|
|
820
|
+
const retryAtInMs: number = Date.parse(headerValue);
|
|
821
|
+
|
|
822
|
+
if (Number.isNaN(retryAtInMs)) {
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// A date already in the past means "retry now", not "retry in the past".
|
|
827
|
+
return Math.max(0, retryAtInMs - Date.now());
|
|
828
|
+
}
|
|
829
|
+
|
|
623
830
|
private static getErrorResponse(error: AxiosError): HTTPErrorResponse {
|
|
624
831
|
if (error.response) {
|
|
625
832
|
return new HTTPErrorResponse(
|
|
@@ -531,3 +531,48 @@ export function labelForDeviceRole(
|
|
|
531
531
|
}
|
|
532
532
|
return DEVICE_ROLE_LABELS[role] || DEVICE_ROLE_LABELS.unknown;
|
|
533
533
|
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Read an operator's stored role override off a NetworkDevice row.
|
|
537
|
+
*
|
|
538
|
+
* The classifier above is evidence-driven, and its evidence is SNMP: a
|
|
539
|
+
* device nothing walks — a ping-only device imported by discovery, say —
|
|
540
|
+
* offers it a hostname and nothing else, so it comes back "unknown" and
|
|
541
|
+
* stays there forever. `NetworkDevice.deviceRole` is the operator saying
|
|
542
|
+
* what the box actually is, and it is the only statement about a role that
|
|
543
|
+
* is not an inference, so it outranks everything the classifier can find.
|
|
544
|
+
*
|
|
545
|
+
* Undefined means "no override" — NOT "unknown". The two are different
|
|
546
|
+
* answers and the difference decides whether the classifier gets to run at
|
|
547
|
+
* all, which is why an unrecognised or empty column reads as undefined
|
|
548
|
+
* rather than falling back to a role. "unknown" is itself refused as an
|
|
549
|
+
* override for the same reason: storing it would silently disable the
|
|
550
|
+
* classifier on a device the operator was only declining to classify.
|
|
551
|
+
*/
|
|
552
|
+
export function parseDeviceRoleOverride(
|
|
553
|
+
value: string | undefined | null,
|
|
554
|
+
): NetworkTopologyDeviceRole | undefined {
|
|
555
|
+
const normalized: string = (value || "").trim().toLowerCase();
|
|
556
|
+
if (!normalized) {
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
return DEVICE_ROLES_IN_LEGEND_ORDER.find(
|
|
560
|
+
(role: NetworkTopologyDeviceRole) => {
|
|
561
|
+
return role.toLowerCase() === normalized;
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* The role to draw a managed device with: the operator's override when
|
|
568
|
+
* there is one, otherwise whatever the evidence supports.
|
|
569
|
+
*
|
|
570
|
+
* One function so the topology builder, the forms and the tests cannot
|
|
571
|
+
* drift about which of the two wins.
|
|
572
|
+
*/
|
|
573
|
+
export function resolveDeviceRole(
|
|
574
|
+
roleOverride: string | undefined | null,
|
|
575
|
+
signals: DeviceRoleSignals,
|
|
576
|
+
): NetworkTopologyDeviceRole {
|
|
577
|
+
return parseDeviceRoleOverride(roleOverride) ?? classifyDeviceRole(signals);
|
|
578
|
+
}
|
|
@@ -11,6 +11,7 @@ import { normalizeMac } from "./EndpointAttachmentUtil";
|
|
|
11
11
|
import {
|
|
12
12
|
classifyDeviceRole,
|
|
13
13
|
classifyEndpointRole,
|
|
14
|
+
resolveDeviceRole,
|
|
14
15
|
} from "./NetworkDeviceRoleUtil";
|
|
15
16
|
|
|
16
17
|
export interface TopologyDeviceInput {
|
|
@@ -29,6 +30,14 @@ export interface TopologyDeviceInput {
|
|
|
29
30
|
*/
|
|
30
31
|
sysDescr?: string | undefined;
|
|
31
32
|
sysObjectId?: string | undefined;
|
|
33
|
+
/*
|
|
34
|
+
* The operator's own answer to what this device is, when they gave one.
|
|
35
|
+
* Outranks the classifier below, because it is the only statement about
|
|
36
|
+
* the role that is not an inference — and on a device nothing walks it
|
|
37
|
+
* is the ONLY statement available at all, the classifier having nothing
|
|
38
|
+
* but a hostname to go on.
|
|
39
|
+
*/
|
|
40
|
+
deviceRole?: string | undefined;
|
|
32
41
|
/*
|
|
33
42
|
* ENTITY-MIB chassis serial. Not rendered — it is here because LLDP
|
|
34
43
|
* chassis-id subtype 7 ("locally assigned") is very often exactly this
|
|
@@ -109,6 +118,13 @@ export interface TopologyManualLinkInput {
|
|
|
109
118
|
toPortName?: string | undefined;
|
|
110
119
|
// Health from a bound Monitor, already reduced by the caller.
|
|
111
120
|
monitorStatus?: "up" | "down" | undefined;
|
|
121
|
+
/*
|
|
122
|
+
* Whichever end the operator declared the parent, when they declared
|
|
123
|
+
* one. Must be `fromDeviceId` or `toDeviceId`; anything else is dropped
|
|
124
|
+
* rather than drawn, because a parent that is not on the link cannot be
|
|
125
|
+
* rendered as one and a wrong hierarchy is worse than an inferred one.
|
|
126
|
+
*/
|
|
127
|
+
parentDeviceId?: string | undefined;
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
/*
|
|
@@ -232,7 +248,10 @@ export default class NetworkTopologyUtil {
|
|
|
232
248
|
* alias are one unmanaged node, however many switches saw it. Edges are
|
|
233
249
|
* undirected and deduplicated — a link reported from both ends, or by
|
|
234
250
|
* both protocols, appears once with the union of what each report knew
|
|
235
|
-
* (ports, protocols, per-end interface state).
|
|
251
|
+
* (ports, protocols, per-end interface state). Undirected in the sense
|
|
252
|
+
* that neither end is privileged by how the edge was discovered: an edge
|
|
253
|
+
* carries a direction only where an operator declared one, as
|
|
254
|
+
* `parentNodeId`. When `interfaces` rows are
|
|
236
255
|
* provided, each edge end whose interface is identifiable carries its
|
|
237
256
|
* operational state (up/down, utilization, rates). When `endpoints` rows
|
|
238
257
|
* are provided, each one that attaches to a device in the graph becomes an
|
|
@@ -306,7 +325,7 @@ export default class NetworkTopologyUtil {
|
|
|
306
325
|
name: device.name,
|
|
307
326
|
isManaged: true,
|
|
308
327
|
kind: "device",
|
|
309
|
-
role:
|
|
328
|
+
role: resolveDeviceRole(device.deviceRole, {
|
|
310
329
|
sysDescr: device.sysDescr,
|
|
311
330
|
sysObjectId: device.sysObjectId,
|
|
312
331
|
vendor: device.vendor,
|
|
@@ -520,6 +539,18 @@ export default class NetworkTopologyUtil {
|
|
|
520
539
|
continue;
|
|
521
540
|
}
|
|
522
541
|
|
|
542
|
+
/*
|
|
543
|
+
* A declared parent is only meaningful if it is one of the two ends.
|
|
544
|
+
* The service refuses anything else at write time, so this is a
|
|
545
|
+
* second line rather than the only one — it also covers rule-derived
|
|
546
|
+
* links, which never pass through that service.
|
|
547
|
+
*/
|
|
548
|
+
const declaredParentId: string | undefined =
|
|
549
|
+
link.parentDeviceId === link.fromDeviceId ||
|
|
550
|
+
link.parentDeviceId === link.toDeviceId
|
|
551
|
+
? link.parentDeviceId
|
|
552
|
+
: undefined;
|
|
553
|
+
|
|
523
554
|
const edgeKey: string = [link.fromDeviceId, link.toDeviceId]
|
|
524
555
|
.sort()
|
|
525
556
|
.join("::");
|
|
@@ -534,6 +565,7 @@ export default class NetworkTopologyUtil {
|
|
|
534
565
|
protocols: ["manual"],
|
|
535
566
|
monitorState: link.monitorStatus,
|
|
536
567
|
name: link.name,
|
|
568
|
+
parentNodeId: declaredParentId,
|
|
537
569
|
});
|
|
538
570
|
continue;
|
|
539
571
|
}
|
|
@@ -557,6 +589,14 @@ export default class NetworkTopologyUtil {
|
|
|
557
589
|
}
|
|
558
590
|
existing.name = existing.name || link.name;
|
|
559
591
|
existing.monitorState = existing.monitorState ?? link.monitorStatus;
|
|
592
|
+
/*
|
|
593
|
+
* First declaration wins, matching the ordering the caller already
|
|
594
|
+
* relies on: explicit links are merged before rule-derived ones, so
|
|
595
|
+
* a hierarchy somebody set on the link itself beats a rule that
|
|
596
|
+
* happens to cover the same pair. The specific statement beats the
|
|
597
|
+
* general one — the same precedence the ports and the name use.
|
|
598
|
+
*/
|
|
599
|
+
existing.parentNodeId = existing.parentNodeId || declaredParentId;
|
|
560
600
|
}
|
|
561
601
|
|
|
562
602
|
const edges: Array<NetworkTopologyEdge> = Array.from(edgeByKey.values());
|