@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
package/Types/API/URL.ts
CHANGED
|
@@ -9,6 +9,33 @@ import Protocol from "./Protocol";
|
|
|
9
9
|
import Route from "./Route";
|
|
10
10
|
import { FindOperator } from "typeorm";
|
|
11
11
|
|
|
12
|
+
/*
|
|
13
|
+
* Schemes whose remainder is NOT an authority: there is no host, no port and
|
|
14
|
+
* no path to parse, so the value is carried verbatim instead of being routed
|
|
15
|
+
* through Hostname. "mailto:" is handled separately because its payload is an
|
|
16
|
+
* Email, which already has its own type.
|
|
17
|
+
*/
|
|
18
|
+
const OPAQUE_PROTOCOLS: Array<Protocol> = [Protocol.TEL, Protocol.SMS];
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* A tel/sms subscriber per RFC 3966: an optional "+" for a global number, then
|
|
22
|
+
* digits and the visual separators the RFC permits ("-", ".", "(", ")"). At
|
|
23
|
+
* least one digit is required, but it need not come first — "(313)636-1710"
|
|
24
|
+
* opens on a separator and is a number people really write.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately narrow beyond that: the point is to reject anything that could
|
|
27
|
+
* smuggle a path, query, fragment or authority ("/", "?", "#", "@",
|
|
28
|
+
* whitespace) into a value that later gets interpolated into an href.
|
|
29
|
+
*/
|
|
30
|
+
const TEL_SUBSCRIBER_REGEX: RegExp = /^\+?[0-9\-.()]*[0-9][0-9\-.()]*$/;
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* ";key=value" parameters, e.g. ";phone-context=+1". Values are restricted to
|
|
34
|
+
* RFC 3986 unreserved characters plus the few sub-delims real numbers use.
|
|
35
|
+
*/
|
|
36
|
+
const TEL_PARAM_REGEX: RegExp =
|
|
37
|
+
/^[A-Za-z0-9\-._~%]+(?:=[A-Za-z0-9\-._~%+:]*)?$/;
|
|
38
|
+
|
|
12
39
|
export default class URL extends DatabaseProperty {
|
|
13
40
|
private _route: Route = new Route();
|
|
14
41
|
public get route(): Route {
|
|
@@ -50,6 +77,75 @@ export default class URL extends DatabaseProperty {
|
|
|
50
77
|
this._protocol = v;
|
|
51
78
|
}
|
|
52
79
|
|
|
80
|
+
/*
|
|
81
|
+
* The payload of an opaque scheme — the phone number of a tel:/sms: URL.
|
|
82
|
+
* Empty for every other scheme.
|
|
83
|
+
*/
|
|
84
|
+
private _opaqueValue: string = "";
|
|
85
|
+
public get opaqueValue(): string {
|
|
86
|
+
return this._opaqueValue;
|
|
87
|
+
}
|
|
88
|
+
public set opaqueValue(v: string) {
|
|
89
|
+
const value: string = v.trim();
|
|
90
|
+
|
|
91
|
+
if (!URL.isValidOpaqueValue(value)) {
|
|
92
|
+
throw new BadDataException(
|
|
93
|
+
"Phone number " + v + " is not in valid format.",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this._opaqueValue = value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* Set only by the lenient read paths (fromDatabase / fromJSON) when a stored
|
|
102
|
+
* value cannot be parsed. Such a URL keeps the raw string so it still
|
|
103
|
+
* round-trips and still renders, but exposes no hostname and is refused by
|
|
104
|
+
* toDatabase so the bad value can never be written back or newly created.
|
|
105
|
+
*/
|
|
106
|
+
private _isMalformed: boolean = false;
|
|
107
|
+
public isMalformed(): boolean {
|
|
108
|
+
return this._isMalformed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private _rawValue: string = "";
|
|
112
|
+
|
|
113
|
+
public static isOpaqueProtocol(protocol: Protocol): boolean {
|
|
114
|
+
return OPAQUE_PROTOCOLS.includes(protocol);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* A tel:/sms: payload: one or more comma-separated numbers (sms: allows
|
|
119
|
+
* several recipients) followed by optional ";key=value" parameters.
|
|
120
|
+
*/
|
|
121
|
+
public static isValidOpaqueValue(value: string): boolean {
|
|
122
|
+
const trimmed: string = value.trim();
|
|
123
|
+
|
|
124
|
+
if (!trimmed || trimmed.length > 256) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [subscribers, ...params] = trimmed.split(";");
|
|
129
|
+
|
|
130
|
+
if (!subscribers) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const numbers: Array<string> = subscribers.split(",");
|
|
135
|
+
|
|
136
|
+
const everyNumberIsValid: boolean = numbers.every((number: string) => {
|
|
137
|
+
return TEL_SUBSCRIBER_REGEX.test(number);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!everyNumberIsValid) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return params.every((param: string) => {
|
|
145
|
+
return TEL_PARAM_REGEX.test(param);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
53
149
|
public constructor(
|
|
54
150
|
protocol: Protocol,
|
|
55
151
|
hostname: Hostname | string | Email,
|
|
@@ -58,6 +154,21 @@ export default class URL extends DatabaseProperty {
|
|
|
58
154
|
) {
|
|
59
155
|
super();
|
|
60
156
|
|
|
157
|
+
/*
|
|
158
|
+
* Opaque schemes first: their payload is a phone number, and asking
|
|
159
|
+
* Hostname to validate one would either reject it or — as it did before
|
|
160
|
+
* tel: was a known scheme — read "tel:1234567890" as host + port.
|
|
161
|
+
*/
|
|
162
|
+
if (URL.isOpaqueProtocol(protocol)) {
|
|
163
|
+
this.protocol = protocol;
|
|
164
|
+
this.opaqueValue =
|
|
165
|
+
hostname instanceof Hostname || hostname instanceof Email
|
|
166
|
+
? hostname.toString()
|
|
167
|
+
: (hostname as string);
|
|
168
|
+
this.setParamsFromQueryString(queryString);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
61
172
|
if (
|
|
62
173
|
typeof hostname === Typeof.String &&
|
|
63
174
|
Email.isValid(hostname as string)
|
|
@@ -77,15 +188,21 @@ export default class URL extends DatabaseProperty {
|
|
|
77
188
|
this.route = route;
|
|
78
189
|
}
|
|
79
190
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
191
|
+
this.setParamsFromQueryString(queryString);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private setParamsFromQueryString(queryString?: string): void {
|
|
195
|
+
if (!queryString) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const keyValues: Array<string> = queryString.split("&");
|
|
200
|
+
for (const keyValue of keyValues) {
|
|
201
|
+
if (keyValue.split("=")[0] && keyValue.split("=")[1]) {
|
|
202
|
+
const key: string | undefined = keyValue.split("=")[0];
|
|
203
|
+
const value: string | undefined = keyValue.split("=")[1];
|
|
204
|
+
if (key && value) {
|
|
205
|
+
this._params[key] = value;
|
|
89
206
|
}
|
|
90
207
|
}
|
|
91
208
|
}
|
|
@@ -95,7 +212,34 @@ export default class URL extends DatabaseProperty {
|
|
|
95
212
|
return this.protocol === Protocol.HTTPS;
|
|
96
213
|
}
|
|
97
214
|
|
|
215
|
+
private queryStringSuffix(): string {
|
|
216
|
+
if (Object.keys(this.params).length === 0) {
|
|
217
|
+
return "";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
"?" +
|
|
222
|
+
Object.keys(this.params)
|
|
223
|
+
.map((key: string) => {
|
|
224
|
+
return key + "=" + this.params[key];
|
|
225
|
+
})
|
|
226
|
+
.join("&")
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
98
230
|
public override toString(): string {
|
|
231
|
+
/*
|
|
232
|
+
* A value that failed to parse on read is echoed back exactly as stored,
|
|
233
|
+
* so a legacy row still renders as the link it always was.
|
|
234
|
+
*/
|
|
235
|
+
if (this.isMalformed()) {
|
|
236
|
+
return this._rawValue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (URL.isOpaqueProtocol(this.protocol)) {
|
|
240
|
+
return `${this.protocol}${this.opaqueValue}${this.queryStringSuffix()}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
99
243
|
let urlString: string = `${this.protocol}${this.hostname || this.email}`;
|
|
100
244
|
if (!this.email && !urlString.startsWith("mailto:")) {
|
|
101
245
|
if (this.route && this.route.toString().startsWith("/")) {
|
|
@@ -110,15 +254,7 @@ export default class URL extends DatabaseProperty {
|
|
|
110
254
|
urlString += "/" + this.route.toString();
|
|
111
255
|
}
|
|
112
256
|
|
|
113
|
-
|
|
114
|
-
urlString += "?";
|
|
115
|
-
|
|
116
|
-
for (const key of Object.keys(this.params)) {
|
|
117
|
-
urlString += key + "=" + this.params[key] + "&";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
urlString = urlString.substring(0, urlString.length - 1); // remove last &
|
|
121
|
-
}
|
|
257
|
+
urlString += this.queryStringSuffix();
|
|
122
258
|
}
|
|
123
259
|
|
|
124
260
|
return urlString;
|
|
@@ -128,6 +264,50 @@ export default class URL extends DatabaseProperty {
|
|
|
128
264
|
return URL.fromString(url.toString());
|
|
129
265
|
}
|
|
130
266
|
|
|
267
|
+
/*
|
|
268
|
+
* An unrecognised scheme used to be swallowed: the prefix loop simply did
|
|
269
|
+
* not match, the protocol stayed at its https default, and the rest was read
|
|
270
|
+
* as an authority. "tel:3136361710" was stored as "https://tel:3136361710/"
|
|
271
|
+
* that way — a value nobody typed, with a host of "tel", which a later,
|
|
272
|
+
* stricter Hostname then refused to read back.
|
|
273
|
+
*
|
|
274
|
+
* Anything that clearly announces a scheme this type cannot represent is now
|
|
275
|
+
* refused with a message that says so, rather than silently rewritten:
|
|
276
|
+
*
|
|
277
|
+
* - "scheme://..." for any scheme not in the supported list, and
|
|
278
|
+
* - the schemes that are dangerous in an href even without "//".
|
|
279
|
+
*
|
|
280
|
+
* A scheme-less "example.com:8080/hook" is deliberately still allowed: its
|
|
281
|
+
* "example.com:" looks like a scheme to a regex but is a host and port, and
|
|
282
|
+
* callers really do pass it.
|
|
283
|
+
*/
|
|
284
|
+
private static rejectUnsupportedScheme(url: string): void {
|
|
285
|
+
const schemeMatch: RegExpMatchArray | null = url
|
|
286
|
+
.trim()
|
|
287
|
+
.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(\/\/)?/);
|
|
288
|
+
|
|
289
|
+
if (!schemeMatch || !schemeMatch[1]) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const scheme: string = schemeMatch[1].toLowerCase();
|
|
294
|
+
const hasAuthorityMarker: boolean = Boolean(schemeMatch[2]);
|
|
295
|
+
|
|
296
|
+
const isDangerousInAnHref: boolean = [
|
|
297
|
+
"javascript",
|
|
298
|
+
"data",
|
|
299
|
+
"vbscript",
|
|
300
|
+
"file",
|
|
301
|
+
"blob",
|
|
302
|
+
].includes(scheme);
|
|
303
|
+
|
|
304
|
+
if (hasAuthorityMarker || isDangerousInAnHref) {
|
|
305
|
+
throw new BadDataException(
|
|
306
|
+
"URL scheme " + scheme + ": is not supported.",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
131
311
|
public static fromString(url: string): URL {
|
|
132
312
|
let protocol: Protocol = Protocol.HTTPS;
|
|
133
313
|
|
|
@@ -146,18 +326,27 @@ export default class URL extends DatabaseProperty {
|
|
|
146
326
|
["ws://", Protocol.WS],
|
|
147
327
|
["mongodb://", Protocol.MONGO_DB],
|
|
148
328
|
["mailto:", Protocol.MAIL],
|
|
329
|
+
["tel:", Protocol.TEL],
|
|
330
|
+
["sms:", Protocol.SMS],
|
|
149
331
|
];
|
|
150
332
|
|
|
151
333
|
const lowerCasedUrl: string = url.toLowerCase();
|
|
152
334
|
|
|
335
|
+
let matchedKnownScheme: boolean = false;
|
|
336
|
+
|
|
153
337
|
for (const [prefix, prefixProtocol] of schemePrefixes) {
|
|
154
338
|
if (lowerCasedUrl.startsWith(prefix)) {
|
|
155
339
|
protocol = prefixProtocol;
|
|
156
340
|
url = url.substring(prefix.length);
|
|
341
|
+
matchedKnownScheme = true;
|
|
157
342
|
break;
|
|
158
343
|
}
|
|
159
344
|
}
|
|
160
345
|
|
|
346
|
+
if (!matchedKnownScheme) {
|
|
347
|
+
URL.rejectUnsupportedScheme(url);
|
|
348
|
+
}
|
|
349
|
+
|
|
161
350
|
if (protocol === Protocol.MAIL) {
|
|
162
351
|
/*
|
|
163
352
|
* Hand the bare address to the constructor so it is recognised as an
|
|
@@ -170,6 +359,18 @@ export default class URL extends DatabaseProperty {
|
|
|
170
359
|
return new URL(protocol, address, undefined, mailQueryString);
|
|
171
360
|
}
|
|
172
361
|
|
|
362
|
+
if (URL.isOpaqueProtocol(protocol)) {
|
|
363
|
+
/*
|
|
364
|
+
* "sms:+15555550123?body=hi" — the number is everything before the
|
|
365
|
+
* query. There is no authority and no path, so nothing here may reach
|
|
366
|
+
* Hostname.
|
|
367
|
+
*/
|
|
368
|
+
const number: string = url.split("?")[0] || "";
|
|
369
|
+
const opaqueQueryString: string = url.split("?")[1] || "";
|
|
370
|
+
|
|
371
|
+
return new URL(protocol, number, undefined, opaqueQueryString);
|
|
372
|
+
}
|
|
373
|
+
|
|
173
374
|
/*
|
|
174
375
|
* The authority ends at the first "/", "?" or "#". Splitting on "/" alone
|
|
175
376
|
* left the query and fragment glued to the host for URLs with no path
|
|
@@ -194,7 +395,50 @@ export default class URL extends DatabaseProperty {
|
|
|
194
395
|
return new URL(protocol, hostname, route, queryString);
|
|
195
396
|
}
|
|
196
397
|
|
|
398
|
+
/*
|
|
399
|
+
* Parses like fromString, but never throws: a value it cannot understand
|
|
400
|
+
* comes back as a malformed URL that preserves the original string.
|
|
401
|
+
*
|
|
402
|
+
* This exists for READ paths only. A single unparseable row must not be able
|
|
403
|
+
* to fail the request that reads it — a status page footer link of
|
|
404
|
+
* "https://tel:1234567890/", stored years earlier by a looser validator,
|
|
405
|
+
* used to 400 the whole status page config endpoint and left the page stuck
|
|
406
|
+
* on "Loading...". Writes stay strict: see toDatabase.
|
|
407
|
+
*
|
|
408
|
+
* A malformed URL exposes NO hostname, so nothing can read a host off it and
|
|
409
|
+
* build a request. The outbound-request guards (SSRFProtection) re-parse the
|
|
410
|
+
* raw string with the WHATWG parser and never trusted this type's parse
|
|
411
|
+
* anyway, so leniency here does not widen them.
|
|
412
|
+
*/
|
|
413
|
+
public static fromStringLenient(url: string): URL {
|
|
414
|
+
try {
|
|
415
|
+
return URL.fromString(url);
|
|
416
|
+
} catch {
|
|
417
|
+
return URL.createMalformed(url);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private static createMalformed(rawValue: string): URL {
|
|
422
|
+
/*
|
|
423
|
+
* Built through the opaque branch so the constructor never asks Hostname
|
|
424
|
+
* to validate the very value that just failed to parse; the placeholder is
|
|
425
|
+
* then cleared, leaving a URL with no hostname and no opaque value. The
|
|
426
|
+
* protocol reports the same https default fromString falls back to for an
|
|
427
|
+
* unrecognised scheme.
|
|
428
|
+
*/
|
|
429
|
+
const url: URL = new URL(Protocol.TEL, "0");
|
|
430
|
+
url._protocol = Protocol.HTTPS;
|
|
431
|
+
url._opaqueValue = "";
|
|
432
|
+
url._isMalformed = true;
|
|
433
|
+
url._rawValue = rawValue;
|
|
434
|
+
return url;
|
|
435
|
+
}
|
|
436
|
+
|
|
197
437
|
public removeQueryString(): URL {
|
|
438
|
+
if (this.isMalformed()) {
|
|
439
|
+
return this;
|
|
440
|
+
}
|
|
441
|
+
|
|
198
442
|
return URL.fromString(this.toString().split("?")[0] || "");
|
|
199
443
|
}
|
|
200
444
|
|
|
@@ -205,9 +449,14 @@ export default class URL extends DatabaseProperty {
|
|
|
205
449
|
};
|
|
206
450
|
}
|
|
207
451
|
|
|
452
|
+
/*
|
|
453
|
+
* Lenient for the same reason fromDatabase is: this is how a URL crosses the
|
|
454
|
+
* wire into the browser. If it threw, moving the server-side failure out of
|
|
455
|
+
* the API would only move the crash into the client that renders the link.
|
|
456
|
+
*/
|
|
208
457
|
public static override fromJSON(json: JSONObject): URL {
|
|
209
458
|
if (json["_type"] === ObjectType.URL) {
|
|
210
|
-
return URL.
|
|
459
|
+
return URL.fromStringLenient((json["value"] as string) || "");
|
|
211
460
|
}
|
|
212
461
|
|
|
213
462
|
throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
|
|
@@ -263,6 +512,12 @@ export default class URL extends DatabaseProperty {
|
|
|
263
512
|
return null;
|
|
264
513
|
}
|
|
265
514
|
|
|
515
|
+
/*
|
|
516
|
+
* The write side stays strict. Reads tolerate a legacy value that no longer
|
|
517
|
+
* validates, but nothing may persist one: a malformed URL only ever comes
|
|
518
|
+
* from fromDatabase/fromJSON, so refusing it here stops a bad value being
|
|
519
|
+
* written back on an unrelated update, and stops a client inventing one.
|
|
520
|
+
*/
|
|
266
521
|
protected static override toDatabase(
|
|
267
522
|
value: URL | FindOperator<URL>,
|
|
268
523
|
): string | null {
|
|
@@ -271,15 +526,26 @@ export default class URL extends DatabaseProperty {
|
|
|
271
526
|
value = URL.fromString(value);
|
|
272
527
|
}
|
|
273
528
|
|
|
529
|
+
if (value instanceof URL && value.isMalformed()) {
|
|
530
|
+
throw new BadDataException(
|
|
531
|
+
"URL " + value.toString() + " is not in valid format.",
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
274
535
|
return value.toString();
|
|
275
536
|
}
|
|
276
537
|
|
|
277
538
|
return null;
|
|
278
539
|
}
|
|
279
540
|
|
|
541
|
+
/*
|
|
542
|
+
* Lenient by design — see fromStringLenient. A row that cannot be parsed is
|
|
543
|
+
* returned as a malformed URL rather than throwing, because throwing here
|
|
544
|
+
* fails the entire query that touched the row, not just the one column.
|
|
545
|
+
*/
|
|
280
546
|
protected static override fromDatabase(_value: string): URL | null {
|
|
281
547
|
if (_value) {
|
|
282
|
-
return URL.
|
|
548
|
+
return URL.fromStringLenient(_value);
|
|
283
549
|
}
|
|
284
550
|
|
|
285
551
|
return null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The public pay-as-you-go rates, as advertised on
|
|
3
|
+
* https://oneuptime.com/pricing.
|
|
4
|
+
*
|
|
5
|
+
* These live in Types (and not next to the metered plans, which are
|
|
6
|
+
* server-only) because two very different places need the same numbers: the
|
|
7
|
+
* server meters and bills against them, and the dashboard has to tell a Free
|
|
8
|
+
* plan user what they are about to be charged before they commit to it. A
|
|
9
|
+
* price quoted in the product that disagrees with the price on the invoice is
|
|
10
|
+
* worse than quoting no price at all, so there is exactly one copy.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Telemetry ingest - logs, traces, metrics and profiles.
|
|
14
|
+
export const TELEMETRY_PRICE_IN_USD_PER_GB: number = 0.1;
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Session replay recordings travel on the same ingestion key as the rest of
|
|
18
|
+
* telemetry but are priced well above it, on purpose - see
|
|
19
|
+
* SessionReplayDataIngestMeteredPlan. Anything that quotes "what this key
|
|
20
|
+
* costs" has to say both numbers, or it is quoting a rate 20x below what a
|
|
21
|
+
* replay-heavy project will actually be invoiced.
|
|
22
|
+
*/
|
|
23
|
+
export const SESSION_REPLAY_PRICE_IN_USD_PER_GB: number = 2;
|
|
24
|
+
|
|
25
|
+
// The retention the per-GB telemetry prices above are quoted for.
|
|
26
|
+
export const TELEMETRY_PRICE_RETENTION_IN_DAYS: number = 15;
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Active monitoring. Every monitor except a Manual one is an active monitor -
|
|
30
|
+
* see MonitorTypeHelper.isBilledAsActiveMonitor.
|
|
31
|
+
*/
|
|
32
|
+
export const ACTIVE_MONITOR_PRICE_IN_USD_PER_MONTH: number = 1;
|
|
33
|
+
|
|
34
|
+
export const PRICING_PAGE_URL: string = "https://oneuptime.com/pricing";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Money as the pricing page writes it: `$1` rather than `$1.00`, but `$0.10`
|
|
38
|
+
* rather than `$0.1`. In-app copy that quotes a rate should read exactly like
|
|
39
|
+
* the page the user will check it against.
|
|
40
|
+
*/
|
|
41
|
+
export function formatPriceInUSD(amountInUSD: number): string {
|
|
42
|
+
if (Number.isInteger(amountInUSD)) {
|
|
43
|
+
return `$${amountInUSD}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return `$${amountInUSD.toFixed(2)}`;
|
|
47
|
+
}
|
|
@@ -1,9 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The member VALUES are load-bearing beyond this enum.
|
|
3
|
+
*
|
|
4
|
+
* CustomFieldsDetail passes a definition's `customFieldType` straight through
|
|
5
|
+
* as the `fieldType` of a Detail field and of a form field, so every member
|
|
6
|
+
* here has to name a member of BOTH `FieldType` (Detail) and
|
|
7
|
+
* `FormFieldSchemaType` (Forms) by value. `Boolean` is the standing example:
|
|
8
|
+
* it reaches the form as FormFieldSchemaType.Toggle, which is spelled
|
|
9
|
+
* `Toggle = "Boolean"` for exactly this reason.
|
|
10
|
+
*
|
|
11
|
+
* Adding a member without that correspondence compiles fine and then renders
|
|
12
|
+
* an empty cell and an input the user cannot type into.
|
|
13
|
+
*/
|
|
1
14
|
enum CustomFieldType {
|
|
2
15
|
Text = "Text",
|
|
3
16
|
Number = "Number",
|
|
4
17
|
Boolean = "Boolean",
|
|
5
18
|
Dropdown = "Dropdown",
|
|
6
19
|
MultiSelectDropdown = "MultiSelectDropdown",
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A calendar date with no time of day — purchase date, warranty expiry,
|
|
23
|
+
* end-of-life. Stored in the `customFields` jsonb bag as an ISO-8601 string,
|
|
24
|
+
* which is what makes it comparable: ISO-8601 sorts identically as text and
|
|
25
|
+
* as an instant, so the range filters work over a jsonb key without a cast
|
|
26
|
+
* (see JSONColumnQuery.compareText).
|
|
27
|
+
*/
|
|
28
|
+
Date = "Date",
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* As `Date`, but the time of day is part of the answer — last audited at,
|
|
32
|
+
* decommissioned at.
|
|
33
|
+
*/
|
|
34
|
+
DateTime = "DateTime",
|
|
7
35
|
}
|
|
8
36
|
|
|
9
37
|
export default CustomFieldType;
|
|
@@ -170,6 +170,17 @@ export class MonitorTypeHelper {
|
|
|
170
170
|
return monitorType === MonitorType.Manual;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/*
|
|
174
|
+
* Whether a monitor of this type adds to the project's Active Monitoring
|
|
175
|
+
* bill. Mirrors what the server actually meters -
|
|
176
|
+
* ActiveMonitoringMeteredPlan counts every monitor whose type is not
|
|
177
|
+
* Manual - so the dashboard can warn a Free plan user about a charge using
|
|
178
|
+
* the same rule that produces it. Manual monitors are free and unlimited.
|
|
179
|
+
*/
|
|
180
|
+
public static isBilledAsActiveMonitor(monitorType: MonitorType): boolean {
|
|
181
|
+
return !this.isManualMonitor(monitorType);
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
public static getAllMonitorTypeProps(): Array<MonitorTypeProps> {
|
|
174
185
|
const monitorTypeProps: Array<MonitorTypeProps> = [
|
|
175
186
|
{
|
|
@@ -156,6 +156,19 @@ export interface NetworkTopologyEdge {
|
|
|
156
156
|
monitorState?: "up" | "down" | undefined;
|
|
157
157
|
// Operator-supplied label, set only on manual links.
|
|
158
158
|
name?: string | undefined;
|
|
159
|
+
/*
|
|
160
|
+
* Which end of this edge is the parent, when somebody has SAID so —
|
|
161
|
+
* either on the link itself or through a link rule, which is stated in
|
|
162
|
+
* child/parent labels and so knows the answer by construction.
|
|
163
|
+
*
|
|
164
|
+
* Always one of `fromNodeId`/`toNodeId`. Absent on every discovered
|
|
165
|
+
* edge: LLDP and CDP report a cable, not a hierarchy, and the direction
|
|
166
|
+
* a neighbour entry happens to be read from says nothing about which
|
|
167
|
+
* box is upstream. Layouts that draw a tree must therefore treat this as
|
|
168
|
+
* a constraint where it is present and keep inferring where it is not —
|
|
169
|
+
* absent is "not stated", never "these are peers".
|
|
170
|
+
*/
|
|
171
|
+
parentNodeId?: string | undefined;
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
export default interface NetworkTopology {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { JSONObject } from "../JSON";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* What a manual "Run now" of a Network Automation rule did.
|
|
5
|
+
*
|
|
6
|
+
* Both rule kinds fire automatically only when a device is created (and, for
|
|
7
|
+
* site assignment, when its identity changes or on the next poll of a device
|
|
8
|
+
* with no site), so a rule written after an estate was imported never reaches
|
|
9
|
+
* it. "Run now" closes that gap — OneUptime/oneuptime#3191 — and these are the
|
|
10
|
+
* shapes it answers with.
|
|
11
|
+
*
|
|
12
|
+
* They live in Types, not next to the services that produce them, because the
|
|
13
|
+
* dashboard renders the same counters the server computed and neither side
|
|
14
|
+
* should be restating the other's field names.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* One run of a site assignment rule. Every device the run looked at lands in
|
|
19
|
+
* exactly one bucket, which is what lets the UI explain an assignment count of
|
|
20
|
+
* zero: "nothing matched" and "everything that matched is already there" are
|
|
21
|
+
* very different answers, and a single number cannot tell them apart.
|
|
22
|
+
*/
|
|
23
|
+
export interface SiteAssignmentRuleRunResult {
|
|
24
|
+
// Devices in the project the run evaluated.
|
|
25
|
+
devicesEvaluated: number;
|
|
26
|
+
// Of those, the ones this rule's criteria matched.
|
|
27
|
+
devicesMatched: number;
|
|
28
|
+
// Matched devices whose site this run actually changed.
|
|
29
|
+
devicesAssigned: number;
|
|
30
|
+
// Matched devices already sitting in this rule's site.
|
|
31
|
+
devicesAlreadyInRuleSite: number;
|
|
32
|
+
/*
|
|
33
|
+
* Matched devices left alone because they are already in some OTHER site
|
|
34
|
+
* and the run was not asked to overwrite existing assignments.
|
|
35
|
+
*/
|
|
36
|
+
devicesSkippedAlreadyInAnotherSite: number;
|
|
37
|
+
/*
|
|
38
|
+
* Matched devices a higher-priority rule also matches. Running one rule
|
|
39
|
+
* never does another rule's work, so these are reported rather than moved.
|
|
40
|
+
*/
|
|
41
|
+
devicesClaimedByHigherPriorityRule: number;
|
|
42
|
+
// Matched devices whose update threw. Logged server-side, never fatal.
|
|
43
|
+
devicesFailed: number;
|
|
44
|
+
// True when the run stopped at its device cap with devices left over.
|
|
45
|
+
isTruncated: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// One run of a network device label rule.
|
|
49
|
+
export interface LabelRuleRunResult {
|
|
50
|
+
// Devices in the project the run evaluated.
|
|
51
|
+
devicesEvaluated: number;
|
|
52
|
+
// Of those, the ones this rule's criteria matched.
|
|
53
|
+
devicesMatched: number;
|
|
54
|
+
/*
|
|
55
|
+
* Matched devices that gained at least one label. Lower than devicesMatched
|
|
56
|
+
* whenever a device already carries everything the rule attaches —
|
|
57
|
+
* re-running a rule is idempotent, not additive.
|
|
58
|
+
*/
|
|
59
|
+
devicesLabeled: number;
|
|
60
|
+
// (device, label) pairs actually inserted.
|
|
61
|
+
labelsAttached: number;
|
|
62
|
+
// Pairs whose insert threw. Logged server-side, never fatal.
|
|
63
|
+
labelsFailed: number;
|
|
64
|
+
// True when the run stopped at its device cap with devices left over.
|
|
65
|
+
isTruncated: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* A count off the wire. An absent or non-numeric field reads as zero rather
|
|
70
|
+
* than as NaN: a summary line that says "NaN devices" is worse than one that
|
|
71
|
+
* under-reports a counter the server never sent.
|
|
72
|
+
*/
|
|
73
|
+
function readCount(json: JSONObject, key: string): number {
|
|
74
|
+
const value: unknown = json[key];
|
|
75
|
+
|
|
76
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class RuleRunResultUtil {
|
|
84
|
+
public static parseSiteAssignmentRuleRunResult(
|
|
85
|
+
json: JSONObject | undefined | null,
|
|
86
|
+
): SiteAssignmentRuleRunResult {
|
|
87
|
+
const source: JSONObject = json || {};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
devicesEvaluated: readCount(source, "devicesEvaluated"),
|
|
91
|
+
devicesMatched: readCount(source, "devicesMatched"),
|
|
92
|
+
devicesAssigned: readCount(source, "devicesAssigned"),
|
|
93
|
+
devicesAlreadyInRuleSite: readCount(source, "devicesAlreadyInRuleSite"),
|
|
94
|
+
devicesSkippedAlreadyInAnotherSite: readCount(
|
|
95
|
+
source,
|
|
96
|
+
"devicesSkippedAlreadyInAnotherSite",
|
|
97
|
+
),
|
|
98
|
+
devicesClaimedByHigherPriorityRule: readCount(
|
|
99
|
+
source,
|
|
100
|
+
"devicesClaimedByHigherPriorityRule",
|
|
101
|
+
),
|
|
102
|
+
devicesFailed: readCount(source, "devicesFailed"),
|
|
103
|
+
isTruncated: source["isTruncated"] === true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public static parseLabelRuleRunResult(
|
|
108
|
+
json: JSONObject | undefined | null,
|
|
109
|
+
): LabelRuleRunResult {
|
|
110
|
+
const source: JSONObject = json || {};
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
devicesEvaluated: readCount(source, "devicesEvaluated"),
|
|
114
|
+
devicesMatched: readCount(source, "devicesMatched"),
|
|
115
|
+
devicesLabeled: readCount(source, "devicesLabeled"),
|
|
116
|
+
labelsAttached: readCount(source, "labelsAttached"),
|
|
117
|
+
labelsFailed: readCount(source, "labelsFailed"),
|
|
118
|
+
isTruncated: source["isTruncated"] === true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -33,3 +33,26 @@ export const RUNNER_EXECUTED_STEP_TYPES: Array<RunbookStepType> = [
|
|
|
33
33
|
export function isRunnerExecutedStepType(type: RunbookStepType): boolean {
|
|
34
34
|
return RUNNER_EXECUTED_STEP_TYPES.includes(type);
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* Runner-executed step types that act on a system OTHER than the Runner's own
|
|
39
|
+
* host. They are dispatched as structured instructions (RunnerJob.payload)
|
|
40
|
+
* plus a credential the server resolves at claim time — never as a script, so
|
|
41
|
+
* their job rows carry an empty script by design.
|
|
42
|
+
*
|
|
43
|
+
* That is why RunnerJob.script is not a required column: an empty string is
|
|
44
|
+
* falsy, so a required-column check would reject exactly these jobs. The real
|
|
45
|
+
* rule — script types need a script, these need a payload — is per-type and
|
|
46
|
+
* therefore lives in RunnerJobService.enqueue rather than in column metadata.
|
|
47
|
+
*
|
|
48
|
+
* Every runner-executed type not listed here carries a script instead; the
|
|
49
|
+
* two sets are complementary and a test asserts they stay that way.
|
|
50
|
+
*/
|
|
51
|
+
export const PAYLOAD_CARRYING_STEP_TYPES: Array<RunbookStepType> = [
|
|
52
|
+
RunbookStepType.SSH,
|
|
53
|
+
RunbookStepType.Kubernetes,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
export function isPayloadCarryingStepType(type: RunbookStepType): boolean {
|
|
57
|
+
return PAYLOAD_CARRYING_STEP_TYPES.includes(type);
|
|
58
|
+
}
|