@oneuptime/common 12.0.27 → 12.0.29
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/AnalyticsModels/RumSessionChunk.ts +52 -0
- package/Models/DatabaseModels/CloudResource.ts +17 -1
- package/Models/DatabaseModels/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/RumApplication.ts +35 -2
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/API/TelemetryAPI.ts +104 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +12 -5
- package/Server/Utils/SessionReplay/SessionReplayIdentity.ts +102 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +11 -2
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +81 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +232 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +76 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +48 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +19 -2
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +37 -3
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +67 -14
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js +59 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +9 -2
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
|
@@ -9,7 +9,9 @@ import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
|
9
9
|
import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
|
|
10
10
|
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
11
11
|
import ObjectID from "../../../Types/ObjectID";
|
|
12
|
-
import NetworkDeviceMonitorTemplateUtil
|
|
12
|
+
import NetworkDeviceMonitorTemplateUtil, {
|
|
13
|
+
MAX_PROVISIONED_MONITOR_NAME_LENGTH,
|
|
14
|
+
} from "../../../Utils/Monitor/NetworkDeviceMonitorTemplateUtil";
|
|
13
15
|
import { describe, expect, it } from "@jest/globals";
|
|
14
16
|
|
|
15
17
|
const PROJECT_ID: ObjectID = new ObjectID(
|
|
@@ -290,36 +292,202 @@ describe("NetworkDeviceMonitorTemplateUtil.buildMonitor", () => {
|
|
|
290
292
|
networkDevice: buildDevice({ name: " ", hostname: " " }),
|
|
291
293
|
});
|
|
292
294
|
|
|
293
|
-
expect(monitor.name).toBe(
|
|
294
|
-
`Network Device ${DEVICE_ID.toString()} - Monitor`,
|
|
295
|
-
);
|
|
295
|
+
expect(monitor.name).toBe(`Network Device ${DEVICE_ID.toString()}`);
|
|
296
296
|
expect(monitor.name?.trim()).not.toBe("");
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
/*
|
|
300
|
+
* ISSUE #3486, and the reason the column became optional.
|
|
301
|
+
*
|
|
302
|
+
* An auto-import rule names every monitor it provisions after the device.
|
|
303
|
+
* While the template's default monitor name was required there was nothing
|
|
304
|
+
* an operator could type that did not become a suffix on the whole imported
|
|
305
|
+
* estate - "UN0660WANRTR01 - Unit Router" for a router already called
|
|
306
|
+
* UN0660WANRTR01. The three tests below pin the three ways a template can
|
|
307
|
+
* say "no suffix", because all three are reachable: the column is nullable,
|
|
308
|
+
* the dashboard's edit form PUTs an empty string rather than null when the
|
|
309
|
+
* box is cleared, and the API accepts whitespace.
|
|
310
|
+
*/
|
|
311
|
+
it("names the monitor after the device alone when the template has no default name", () => {
|
|
312
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
313
|
+
template: buildTemplate({ monitorName: undefined }),
|
|
314
|
+
networkDevice: buildDevice({ name: "UN0660WANRTR01" }),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(monitor.name).toBe("UN0660WANRTR01");
|
|
318
|
+
expect(monitor.name).not.toContain(" - ");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("treats an empty default monitor name as no default name", () => {
|
|
322
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
323
|
+
template: buildTemplate({ monitorName: "" }),
|
|
324
|
+
networkDevice: buildDevice({ name: "UN0660WANRTR01" }),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(monitor.name).toBe("UN0660WANRTR01");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("treats a whitespace-only default monitor name as no default name", () => {
|
|
300
331
|
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
301
|
-
template: buildTemplate({ monitorName: "
|
|
332
|
+
template: buildTemplate({ monitorName: " " }),
|
|
333
|
+
networkDevice: buildDevice({ name: "UN0660WANRTR01" }),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(monitor.name).toBe("UN0660WANRTR01");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("falls back to the hostname, not to a suffix, for an unnamed device on an unnamed template", () => {
|
|
340
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
341
|
+
template: buildTemplate({ monitorName: undefined }),
|
|
342
|
+
networkDevice: buildDevice({ name: " ", hostname: "10.20.30.40" }),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(monitor.name).toBe("10.20.30.40");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* The other half of the contract. Dropping the suffix when it IS asked for
|
|
350
|
+
* would make two templates provisioning the same device produce two
|
|
351
|
+
* identically named monitors, which is the thing the suffix is for.
|
|
352
|
+
*/
|
|
353
|
+
it("keeps the suffix when the template does carry a default name", () => {
|
|
354
|
+
const device: NetworkDevice = buildDevice({ name: "UN0660WANRTR01" });
|
|
355
|
+
|
|
356
|
+
const named: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
357
|
+
template: buildTemplate({ monitorName: "Unit Router" }),
|
|
358
|
+
networkDevice: device,
|
|
359
|
+
});
|
|
360
|
+
const otherNamed: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
361
|
+
template: buildTemplate({ monitorName: "Interface Health" }),
|
|
362
|
+
networkDevice: device,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(named.name).toBe("UN0660WANRTR01 - Unit Router");
|
|
366
|
+
expect(otherNamed.name).toBe("UN0660WANRTR01 - Interface Health");
|
|
367
|
+
expect(named.name).not.toBe(otherNamed.name);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("trims the stored default monitor name before composing", () => {
|
|
371
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
372
|
+
template: buildTemplate({ monitorName: " Unit Router " }),
|
|
373
|
+
networkDevice: buildDevice({ name: "UN0660WANRTR01" }),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(monitor.name).toBe("UN0660WANRTR01 - Unit Router");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
/*
|
|
380
|
+
* THE CAP IS THE SLUG'S, NOT THE NAME COLUMN'S.
|
|
381
|
+
*
|
|
382
|
+
* Monitor.name is varchar(100), but Monitor is @SlugifyColumn("name",
|
|
383
|
+
* "slug") and Slug.getSlug appends a dash plus ten random digits into its
|
|
384
|
+
* own varchar(100). A 100-character name therefore produces a
|
|
385
|
+
* 111-character slug and the INSERT throws - the monitor lands in
|
|
386
|
+
* `monitorsFailed` and the device silently never gets one. 88 leaves
|
|
387
|
+
* exactly that 11-character tail.
|
|
388
|
+
*/
|
|
389
|
+
it("caps a provisioned name low enough that its generated slug still fits", () => {
|
|
390
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
391
|
+
template: buildTemplate({ monitorName: "t".repeat(60) }),
|
|
302
392
|
networkDevice: buildDevice({ name: "d".repeat(96) }),
|
|
303
393
|
});
|
|
394
|
+
const slugTailLength: number = "-1234567890".length;
|
|
395
|
+
|
|
396
|
+
expect(monitor.name).toHaveLength(MAX_PROVISIONED_MONITOR_NAME_LENGTH);
|
|
397
|
+
expect(monitor.name!.length + slugTailLength).toBeLessThanOrEqual(
|
|
398
|
+
ColumnLength.Slug,
|
|
399
|
+
);
|
|
400
|
+
expect(monitor.name!.length).toBeLessThanOrEqual(ColumnLength.ShortText);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("caps an unsuffixed device name at the same ceiling", () => {
|
|
404
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
405
|
+
template: buildTemplate({ monitorName: undefined }),
|
|
406
|
+
networkDevice: buildDevice({ name: "d".repeat(120) }),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(monitor.name).toBe("d".repeat(MAX_PROVISIONED_MONITOR_NAME_LENGTH));
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("caps names without leaving half a surrogate pair", () => {
|
|
413
|
+
/*
|
|
414
|
+
* 80 + " - " + 4 characters puts the cap exactly on the leading half of
|
|
415
|
+
* the astral character, the only cut that can produce an unpaired code
|
|
416
|
+
* unit.
|
|
417
|
+
*/
|
|
418
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
419
|
+
template: buildTemplate({ monitorName: "tttt🔥 alerting" }),
|
|
420
|
+
networkDevice: buildDevice({ name: "d".repeat(80) }),
|
|
421
|
+
});
|
|
304
422
|
const loneSurrogate: RegExp =
|
|
305
423
|
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
306
424
|
|
|
307
|
-
expect(monitor.name!.length).toBeLessThanOrEqual(
|
|
308
|
-
|
|
425
|
+
expect(monitor.name!.length).toBeLessThanOrEqual(
|
|
426
|
+
MAX_PROVISIONED_MONITOR_NAME_LENGTH,
|
|
427
|
+
);
|
|
428
|
+
expect(monitor.name).toBe(`${"d".repeat(80)} - tttt`);
|
|
309
429
|
expect(loneSurrogate.test(monitor.name!)).toBe(false);
|
|
310
|
-
expect(monitor.name!.startsWith("d".repeat(96))).toBe(true);
|
|
311
430
|
});
|
|
312
431
|
|
|
313
|
-
it("keeps
|
|
314
|
-
const
|
|
315
|
-
|
|
432
|
+
it("keeps an astral character in the device identity whole", () => {
|
|
433
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
434
|
+
template: buildTemplate(),
|
|
435
|
+
networkDevice: buildDevice({ name: `${"d".repeat(87)}🔥` }),
|
|
436
|
+
});
|
|
437
|
+
const loneSurrogate: RegExp =
|
|
438
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
439
|
+
|
|
440
|
+
expect(monitor.name).toBe("d".repeat(87));
|
|
441
|
+
expect(loneSurrogate.test(monitor.name!)).toBe(false);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("keeps a composed name exactly at the cap", () => {
|
|
445
|
+
const deviceName: string = "d".repeat(70);
|
|
446
|
+
const templateName: string = "t".repeat(15);
|
|
316
447
|
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
317
448
|
template: buildTemplate({ monitorName: templateName }),
|
|
318
449
|
networkDevice: buildDevice({ name: deviceName }),
|
|
319
450
|
});
|
|
320
451
|
|
|
321
452
|
expect(monitor.name).toBe(`${deviceName} - ${templateName}`);
|
|
322
|
-
expect(monitor.name).toHaveLength(
|
|
453
|
+
expect(monitor.name).toHaveLength(MAX_PROVISIONED_MONITOR_NAME_LENGTH);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
/*
|
|
457
|
+
* A cap landing inside " - " would otherwise ship a name trailing a
|
|
458
|
+
* half-written separator - "core switch -" - which reads as a truncation
|
|
459
|
+
* bug to whoever opens the monitor.
|
|
460
|
+
*/
|
|
461
|
+
it("drops a separator the cap cut through", () => {
|
|
462
|
+
// One character into " - ", two characters in, and exactly at its end.
|
|
463
|
+
for (const deviceNameLength of [87, 86, 85]) {
|
|
464
|
+
const deviceName: string = "d".repeat(deviceNameLength);
|
|
465
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
466
|
+
template: buildTemplate({ monitorName: "Unit Router" }),
|
|
467
|
+
networkDevice: buildDevice({ name: deviceName }),
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect({
|
|
471
|
+
deviceNameLength: deviceNameLength,
|
|
472
|
+
name: monitor.name,
|
|
473
|
+
}).toEqual({ deviceNameLength: deviceNameLength, name: deviceName });
|
|
474
|
+
expect(monitor.name!.endsWith(" ")).toBe(false);
|
|
475
|
+
expect(monitor.name!.endsWith("-")).toBe(false);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
/*
|
|
480
|
+
* The mirror of the test above: the remnant rule is a LENGTH check, not a
|
|
481
|
+
* "does it end with a dash" check, so a device whose own name ends in a
|
|
482
|
+
* dash keeps it when nothing was cut.
|
|
483
|
+
*/
|
|
484
|
+
it("keeps a device's own trailing dash when nothing was truncated", () => {
|
|
485
|
+
const monitor: Monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
486
|
+
template: buildTemplate({ monitorName: undefined }),
|
|
487
|
+
networkDevice: buildDevice({ name: "Router A -" }),
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
expect(monitor.name).toBe("Router A -");
|
|
323
491
|
});
|
|
324
492
|
|
|
325
493
|
it("rejects a template without an id", () => {
|
package/Types/API/Route.ts
CHANGED
|
@@ -5,16 +5,27 @@ import { JSONObject, ObjectType } from "../JSON";
|
|
|
5
5
|
import { FindOperator } from "typeorm";
|
|
6
6
|
|
|
7
7
|
export default class Route extends DatabaseProperty {
|
|
8
|
+
private static readonly SCHEME_PREFIX: RegExp = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
|
|
9
|
+
|
|
10
|
+
private static validateRoute(route: string): void {
|
|
11
|
+
if (Route.SCHEME_PREFIX.test(route)) {
|
|
12
|
+
throw new BadDataException(`Invalid route: ${route}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const matchRouteCharacters: RegExp =
|
|
16
|
+
/^[a-zA-Z_\d\-!#$%&'()*+,./:;=?@[\]]*$/;
|
|
17
|
+
|
|
18
|
+
if (route && !matchRouteCharacters.test(route)) {
|
|
19
|
+
throw new BadDataException(`Invalid route: ${route}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
private _route: string = "";
|
|
9
24
|
public get route(): string {
|
|
10
25
|
return this._route;
|
|
11
26
|
}
|
|
12
27
|
public set route(v: string) {
|
|
13
|
-
|
|
14
|
-
/^[a-zA-Z_\d\-!#$%&'()*+,./:;=?@[\]]*$/;
|
|
15
|
-
if (v && !matchRouteCharacters.test(v)) {
|
|
16
|
-
throw new BadDataException(`Invalid route: ${v}`);
|
|
17
|
-
}
|
|
28
|
+
Route.validateRoute(v);
|
|
18
29
|
this._route = v;
|
|
19
30
|
}
|
|
20
31
|
|
|
@@ -24,6 +35,10 @@ export default class Route extends DatabaseProperty {
|
|
|
24
35
|
route = route.toString();
|
|
25
36
|
}
|
|
26
37
|
|
|
38
|
+
if (route) {
|
|
39
|
+
Route.validateRoute(route);
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
route = route?.replace(/\/+/g, "/"); // remove multiple slashes from route and replace with single slash
|
|
28
43
|
|
|
29
44
|
if (route) {
|
package/Types/Date.ts
CHANGED
|
@@ -194,32 +194,48 @@ export default class OneUptimeDate {
|
|
|
194
194
|
return this.getLocalShortMonthName(date);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/**
|
|
198
|
+
* The wall clock `date` reads at in the current timezone - "14:30", or
|
|
199
|
+
* "2:30 PM" when the caller asks for a 12-hour clock.
|
|
200
|
+
*
|
|
201
|
+
* `use12HourFormat` is opt-in rather than defaulted to
|
|
202
|
+
* `getUserPrefers12HourFormat()`: the chart x-axes in
|
|
203
|
+
* UI/Components/Charts/Utils/XAxis.ts label ticks with this and are laid out
|
|
204
|
+
* for the fixed width of a 24-hour reading, so they must keep the clock they
|
|
205
|
+
* were built against. Surfaces that show a person a timestamp - histogram
|
|
206
|
+
* ticks and tooltips, span rows - pass
|
|
207
|
+
* `use12HourFormat: OneUptimeDate.getUserPrefers12HourFormat()` explicitly.
|
|
208
|
+
*/
|
|
197
209
|
public static getLocalTimeString(
|
|
198
210
|
date: Date | string,
|
|
199
211
|
options?: {
|
|
200
212
|
includeMinutes?: boolean;
|
|
201
213
|
includeSeconds?: boolean;
|
|
214
|
+
use12HourFormat?: boolean | undefined;
|
|
202
215
|
},
|
|
203
216
|
): string {
|
|
204
217
|
date = this.fromString(date);
|
|
205
218
|
|
|
206
219
|
const includeMinutes: boolean = options?.includeMinutes ?? true;
|
|
207
220
|
const includeSeconds: boolean = options?.includeSeconds ?? false;
|
|
208
|
-
const
|
|
209
|
-
const hours: string = this.padDatePart(localDate.hours());
|
|
221
|
+
const use12HourFormat: boolean = options?.use12HourFormat ?? false;
|
|
210
222
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
223
|
+
// "HH" and "h" are the zero-padded 24-hour and the bare 12-hour readings.
|
|
224
|
+
let timeFormat: string = use12HourFormat ? "h" : "HH";
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
if (includeMinutes) {
|
|
227
|
+
timeFormat += ":mm";
|
|
228
|
+
|
|
229
|
+
if (includeSeconds) {
|
|
230
|
+
timeFormat += ":ss";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
216
233
|
|
|
217
|
-
if (
|
|
218
|
-
|
|
234
|
+
if (use12HourFormat) {
|
|
235
|
+
timeFormat += " A";
|
|
219
236
|
}
|
|
220
237
|
|
|
221
|
-
|
|
222
|
-
return `${hours}:${minutes}:${seconds}`;
|
|
238
|
+
return this.inCurrentTimezone(date).format(timeFormat);
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
public static getDateAsLocalDayMonthString(date: Date | string): string {
|
|
@@ -237,6 +253,41 @@ export default class OneUptimeDate {
|
|
|
237
253
|
return `${this.getDateAsLocalDayMonthString(date)}, ${this.getLocalTimeString(date, { includeMinutes: false })}:00`;
|
|
238
254
|
}
|
|
239
255
|
|
|
256
|
+
/**
|
|
257
|
+
* A compact "Mar 1, 14:30" (or "Mar 1, 2:30 PM") label for one instant, used
|
|
258
|
+
* where a whole window has to fit on a button - the time range picker above
|
|
259
|
+
* the metrics, traces and logs explorers, chiefly.
|
|
260
|
+
*
|
|
261
|
+
* Both halves follow the user's machine: the wall clock is resolved in the
|
|
262
|
+
* configured timezone (falling back to the one the browser reports), and the
|
|
263
|
+
* 12- vs 24-hour choice follows the operating system's clock preference
|
|
264
|
+
* unless a caller overrides it.
|
|
265
|
+
*/
|
|
266
|
+
public static getDateAsLocalShortDateTimeString(
|
|
267
|
+
date: Date | string,
|
|
268
|
+
options?: {
|
|
269
|
+
use12HourFormat?: boolean | undefined;
|
|
270
|
+
includeSeconds?: boolean | undefined;
|
|
271
|
+
},
|
|
272
|
+
): string {
|
|
273
|
+
date = this.fromString(date);
|
|
274
|
+
|
|
275
|
+
const use12HourFormat: boolean =
|
|
276
|
+
options?.use12HourFormat ?? this.getUserPrefers12HourFormat();
|
|
277
|
+
|
|
278
|
+
let timeFormat: string = use12HourFormat ? "h:mm" : "HH:mm";
|
|
279
|
+
|
|
280
|
+
if (options?.includeSeconds) {
|
|
281
|
+
timeFormat += ":ss";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (use12HourFormat) {
|
|
285
|
+
timeFormat += " A";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return this.inCurrentTimezone(date).format(`MMM D, ${timeFormat}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
240
291
|
public static getDateAsLocalMonthYearString(date: Date | string): string {
|
|
241
292
|
date = this.fromString(date);
|
|
242
293
|
|
|
@@ -857,6 +908,25 @@ export default class OneUptimeDate {
|
|
|
857
908
|
return moment(date1).isSame(date2, "day");
|
|
858
909
|
}
|
|
859
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Whether the two instants fall on the same calendar day *in the current
|
|
913
|
+
* timezone*, which is the question a UI is asking when it decides whether a
|
|
914
|
+
* timestamp needs its date spelled out or a bare clock reading will do.
|
|
915
|
+
*
|
|
916
|
+
* `areOnTheSameDay` above answers it in the zone the process happens to run
|
|
917
|
+
* in. Near midnight those two zones disagree, so asking the wrong one drops
|
|
918
|
+
* the date from yesterday's buckets and adds it to today's.
|
|
919
|
+
*/
|
|
920
|
+
public static areOnTheSameLocalDay(
|
|
921
|
+
date1: Date | string,
|
|
922
|
+
date2: Date | string,
|
|
923
|
+
): boolean {
|
|
924
|
+
return this.inCurrentTimezone(date1).isSame(
|
|
925
|
+
this.inCurrentTimezone(date2),
|
|
926
|
+
"day",
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
860
930
|
public static areOnTheSameMonth(date1: Date, date2: Date): boolean {
|
|
861
931
|
date1 = this.fromString(date1);
|
|
862
932
|
date2 = this.fromString(date2);
|
|
@@ -1465,15 +1535,75 @@ export default class OneUptimeDate {
|
|
|
1465
1535
|
return this.getDateAsFormattedString(new Date(), options);
|
|
1466
1536
|
}
|
|
1467
1537
|
|
|
1538
|
+
/**
|
|
1539
|
+
* Whether the machine this is running on writes the time of day on a 12-hour
|
|
1540
|
+
* clock, read from the browser's resolved default locale - which browsers
|
|
1541
|
+
* derive from the operating system's language and region, so changing those
|
|
1542
|
+
* changes this. An explicit -u-hc- override on the locale is honoured too.
|
|
1543
|
+
*
|
|
1544
|
+
* Note what this cannot see: the standalone "24-Hour Time" switch macOS and
|
|
1545
|
+
* Windows offer separately from the region. Safari and recent Firefox fold
|
|
1546
|
+
* that into the locale they resolve; Chromium does not. A user who wants a
|
|
1547
|
+
* clock that ignores their region would need an explicit preference stored
|
|
1548
|
+
* alongside the timezone in User Settings - there is none today.
|
|
1549
|
+
*
|
|
1550
|
+
* Asked of Intl rather than inferred from a formatted string. The string
|
|
1551
|
+
* probe below only recognises the Latin "AM"/"PM", so it misread every
|
|
1552
|
+
* 12-hour locale that marks the day period some other way - ko-KR writes
|
|
1553
|
+
* the equivalent of "PM" in Hangul, ar-EG in Arabic script - and served
|
|
1554
|
+
* those users a 24-hour clock against their own convention.
|
|
1555
|
+
*/
|
|
1468
1556
|
public static getUserPrefers12HourFormat(): boolean {
|
|
1469
1557
|
if (typeof window === "undefined") {
|
|
1470
1558
|
// Server-side: default to 12-hour format for user-friendly display
|
|
1471
1559
|
return true;
|
|
1472
1560
|
}
|
|
1473
1561
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1562
|
+
/*
|
|
1563
|
+
* `hour12` is only reported when the format actually asks for an hour, so
|
|
1564
|
+
* the probe has to request one.
|
|
1565
|
+
*/
|
|
1566
|
+
try {
|
|
1567
|
+
/*
|
|
1568
|
+
* Typed structurally rather than as Intl.ResolvedDateTimeFormatOptions:
|
|
1569
|
+
* `hourCycle` only appears on that interface from the ES2021 lib, and
|
|
1570
|
+
* this file has to compile against the older one too.
|
|
1571
|
+
*/
|
|
1572
|
+
const resolvedOptions: {
|
|
1573
|
+
hour12?: boolean | undefined;
|
|
1574
|
+
hourCycle?: string | undefined;
|
|
1575
|
+
} = new Intl.DateTimeFormat(undefined, {
|
|
1576
|
+
hour: "numeric",
|
|
1577
|
+
}).resolvedOptions();
|
|
1578
|
+
|
|
1579
|
+
if (typeof resolvedOptions.hour12 === "boolean") {
|
|
1580
|
+
return resolvedOptions.hour12;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/*
|
|
1584
|
+
* Older engines report the cycle but not the boolean. h11/h12 are the
|
|
1585
|
+
* two 12-hour cycles; h23/h24 are the 24-hour ones.
|
|
1586
|
+
*/
|
|
1587
|
+
if (resolvedOptions.hourCycle) {
|
|
1588
|
+
return (
|
|
1589
|
+
resolvedOptions.hourCycle === "h11" ||
|
|
1590
|
+
resolvedOptions.hourCycle === "h12"
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
} catch {
|
|
1594
|
+
/*
|
|
1595
|
+
* A missing or broken Intl must not take every timestamp in the UI down
|
|
1596
|
+
* with it - fall through to the string probe below.
|
|
1597
|
+
*/
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/*
|
|
1601
|
+
* Last resort: read a formatted time and look for a day-period marker.
|
|
1602
|
+
* This only recognises the Latin "AM"/"PM" - which is exactly why it is the
|
|
1603
|
+
* fallback and not the primary check, since locales like ko-KR and ar-EG
|
|
1604
|
+
* are 12-hour but mark the day period with characters this cannot see.
|
|
1605
|
+
*/
|
|
1606
|
+
const timeString: string = new Date().toLocaleTimeString();
|
|
1477
1607
|
return (
|
|
1478
1608
|
timeString.toLowerCase().includes("am") ||
|
|
1479
1609
|
timeString.toLowerCase().includes("pm")
|
|
@@ -296,6 +296,20 @@ export interface SessionReplayChunkEnvelope {
|
|
|
296
296
|
/* Already scrubbed client-side: origin + path, no query, no fragment. */
|
|
297
297
|
url: string;
|
|
298
298
|
|
|
299
|
+
/*
|
|
300
|
+
* Distinct scrubbed URLs the page was on while this chunk was open, in
|
|
301
|
+
* chronological order. Optional so an older recorder posting to a newer
|
|
302
|
+
* server is still accepted - the server then falls back to `url` alone,
|
|
303
|
+
* which is what it did before this field existed.
|
|
304
|
+
*
|
|
305
|
+
* `url` says where the chunk was FLUSHED from; this says where the user
|
|
306
|
+
* went while it was filling. The session header's routes[] column is the
|
|
307
|
+
* union of these, and the "sessions that hit /checkout" filter reads it,
|
|
308
|
+
* so without this a navigation that both starts and ends inside one flush
|
|
309
|
+
* window is invisible.
|
|
310
|
+
*/
|
|
311
|
+
routes?: Array<string>;
|
|
312
|
+
|
|
299
313
|
signals: SessionReplaySignalCounts;
|
|
300
314
|
|
|
301
315
|
fidelityNotices: Array<string>;
|
|
@@ -313,10 +327,52 @@ export interface SessionReplayChunkEnvelope {
|
|
|
313
327
|
traceIds?: Array<string>;
|
|
314
328
|
}
|
|
315
329
|
|
|
330
|
+
/*
|
|
331
|
+
* WHY a config response says enabled:false.
|
|
332
|
+
*
|
|
333
|
+
* The disabled response is deliberately a complete, well-formed config
|
|
334
|
+
* rather than an error, so a recorder that cannot parse a config still
|
|
335
|
+
* refuses to record. The cost of that was one answer for five very
|
|
336
|
+
* different causes: from a browser, an instance-wide kill switch, a
|
|
337
|
+
* deployment whose recorder bundle was never built, an application
|
|
338
|
+
* somebody switched off and a Redis outage were all "enabled: false" and
|
|
339
|
+
* nothing else, and no amount of dashboard configuration fixes three of
|
|
340
|
+
* them.
|
|
341
|
+
*
|
|
342
|
+
* This is a closed vocabulary carrying no project data - it is answered
|
|
343
|
+
* only to a request already bearing a valid ingestion key for that
|
|
344
|
+
* project, and it says strictly less than the Dashboard's own ingest
|
|
345
|
+
* status endpoint already shows the same customer.
|
|
346
|
+
*/
|
|
347
|
+
export enum SessionReplayDisabledReason {
|
|
348
|
+
/* SESSION_REPLAY_INGEST_ENABLED=false on this deployment. */
|
|
349
|
+
IngestDisabled = "ingest-disabled",
|
|
350
|
+
|
|
351
|
+
/* SESSION_REPLAY_ENABLED_BY_DEFAULT=false on this deployment. */
|
|
352
|
+
DisabledByDefault = "disabled-by-default",
|
|
353
|
+
|
|
354
|
+
/*
|
|
355
|
+
* No published recorder artifact. The build never ran, or its output is
|
|
356
|
+
* not where the server looks for it - overwhelmingly the answer on a
|
|
357
|
+
* self-hosted install where replay has never worked at all.
|
|
358
|
+
*/
|
|
359
|
+
RecorderNotBuilt = "recorder-not-built",
|
|
360
|
+
|
|
361
|
+
/* The policy lookup threw. Fail closed, and say that it failed. */
|
|
362
|
+
PolicyUnavailable = "policy-unavailable",
|
|
363
|
+
|
|
364
|
+
/*
|
|
365
|
+
* No policy for this project/application pair: the application is
|
|
366
|
+
* switched off, the project is not allowed replay, the identifier does
|
|
367
|
+
* not match one, or a kill key is set.
|
|
368
|
+
*/
|
|
369
|
+
NotEnabledForApplication = "not-enabled-for-application",
|
|
370
|
+
}
|
|
371
|
+
|
|
316
372
|
/*
|
|
317
373
|
* Policy snapshot handed to the recorder at startup. This endpoint is
|
|
318
374
|
* what makes every server-side privacy control reachable by a live
|
|
319
|
-
* recorder
|
|
375
|
+
* recorder - without it, flipping a masking setting would never take
|
|
320
376
|
* effect in a browser that already loaded.
|
|
321
377
|
*/
|
|
322
378
|
export interface SessionReplayConfigResponse {
|
|
@@ -401,6 +457,25 @@ export interface SessionReplayConfigResponse {
|
|
|
401
457
|
configEpoch: number;
|
|
402
458
|
|
|
403
459
|
directive: SessionReplayDirective;
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
* Set only alongside enabled:false. Optional on the wire like every
|
|
463
|
+
* other field added after v1: an older recorder ignores it, a newer one
|
|
464
|
+
* logs it, and neither breaks.
|
|
465
|
+
*/
|
|
466
|
+
disabledReason?: SessionReplayDisabledReason;
|
|
467
|
+
|
|
468
|
+
/*
|
|
469
|
+
* Ask every recorder that reads this policy to print its decisions to
|
|
470
|
+
* the browser console (SESSION_REPLAY_DEBUG on the deployment).
|
|
471
|
+
*
|
|
472
|
+
* The switch of last resort, and the only one an operator can reach
|
|
473
|
+
* without editing a page they do not own: the per-browser localStorage
|
|
474
|
+
* and query-string switches need somebody at that browser. It is off by
|
|
475
|
+
* default and must stay that way - a RUM script logging on every visitor
|
|
476
|
+
* of every customer is noise on somebody else's site.
|
|
477
|
+
*/
|
|
478
|
+
debug?: boolean;
|
|
404
479
|
}
|
|
405
480
|
|
|
406
481
|
/* Response to a chunk POST. Deliberately tiny. */
|
|
@@ -7,6 +7,7 @@ import React, {
|
|
|
7
7
|
useState,
|
|
8
8
|
} from "react";
|
|
9
9
|
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
10
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
10
11
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
11
12
|
import RangeStartAndEndDateTime, {
|
|
12
13
|
RangeStartAndEndDateTimeUtil,
|
|
@@ -54,12 +55,15 @@ export const TIME_RANGE_PRESET_OPTIONS: Array<TimeRangePickerPresetOption> = [
|
|
|
54
55
|
{ range: TimeRange.PAST_THREE_MONTHS, label: "Past 3 Months" },
|
|
55
56
|
];
|
|
56
57
|
|
|
58
|
+
/*
|
|
59
|
+
* Both ends of a custom window are written the way the user's machine writes
|
|
60
|
+
* times: on its 12- or 24-hour clock, in the timezone they configured. Reading
|
|
61
|
+
* the digits off the Date itself - which is what this used to do - pinned the
|
|
62
|
+
* label to a 24-hour clock in whatever zone the browser process happened to be
|
|
63
|
+
* in, so a machine set to AM/PM still got "Mar 1, 14:30" back.
|
|
64
|
+
*/
|
|
57
65
|
function formatDateShort(date: Date): string {
|
|
58
|
-
|
|
59
|
-
const day: number = date.getDate();
|
|
60
|
-
const hours: string = date.getHours().toString().padStart(2, "0");
|
|
61
|
-
const minutes: string = date.getMinutes().toString().padStart(2, "0");
|
|
62
|
-
return `${month} ${day}, ${hours}:${minutes}`;
|
|
66
|
+
return OneUptimeDate.getDateAsLocalShortDateTimeString(date);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
export function getTimeRangeButtonLabel(
|
|
@@ -175,7 +175,11 @@ const severityWeight: Record<string, number> = {
|
|
|
175
175
|
trace: 1,
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
/*
|
|
179
|
+
* Exported so the window it reports can be unit-tested directly - rendering the
|
|
180
|
+
* whole viewer to read one sentence is not worth the setup.
|
|
181
|
+
*/
|
|
182
|
+
export function getEmptyMessageWithTimeRange(
|
|
179
183
|
timeRange: RangeStartAndEndDateTime | undefined,
|
|
180
184
|
): string {
|
|
181
185
|
if (!timeRange) {
|
|
@@ -185,13 +189,14 @@ function getEmptyMessageWithTimeRange(
|
|
|
185
189
|
if (timeRange.range === TimeRange.CUSTOM && timeRange.startAndEndDate) {
|
|
186
190
|
const startDate: Date = timeRange.startAndEndDate.startValue;
|
|
187
191
|
const endDate: Date = timeRange.startAndEndDate.endValue;
|
|
192
|
+
/*
|
|
193
|
+
* Same shape, clock and timezone as the label on the picker button that
|
|
194
|
+
* set this window - pinning en-US here handed a US viewer AM/PM even on a
|
|
195
|
+
* 24-hour machine, and read the digits in the browser's zone rather than
|
|
196
|
+
* the one configured in User Settings.
|
|
197
|
+
*/
|
|
188
198
|
const fmt: (d: Date) => string = (d: Date): string => {
|
|
189
|
-
return
|
|
190
|
-
month: "short",
|
|
191
|
-
day: "numeric",
|
|
192
|
-
hour: "2-digit",
|
|
193
|
-
minute: "2-digit",
|
|
194
|
-
});
|
|
199
|
+
return OneUptimeDate.getDateAsLocalShortDateTimeString(d);
|
|
195
200
|
};
|
|
196
201
|
|
|
197
202
|
return `Time range: ${fmt(startDate)} – ${fmt(endDate)}. Try adjusting filters or expanding the time range.`;
|