@oneuptime/common 11.1.2 → 11.2.1
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/Alert.ts +55 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/Index.ts +12 -0
- package/Models/DatabaseModels/IoTDevice.ts +690 -0
- package/Models/DatabaseModels/IoTFleet.ts +862 -0
- package/Models/DatabaseModels/IoTFleetLabelRule.ts +514 -0
- package/Models/DatabaseModels/IoTFleetOwnerRule.ts +596 -0
- package/Models/DatabaseModels/IoTFleetOwnerTeam.ts +485 -0
- package/Models/DatabaseModels/IoTFleetOwnerUser.ts +484 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Server/API/IoTDeviceAPI.ts +128 -0
- package/Server/API/TelemetryAPI.ts +104 -35
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.ts +399 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.ts +97 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.ts +979 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AlertService.ts +24 -3
- package/Server/Services/IncidentService.ts +24 -3
- package/Server/Services/IoTDeviceService.ts +356 -0
- package/Server/Services/IoTFleetLabelRuleEngineService.ts +198 -0
- package/Server/Services/IoTFleetLabelRuleService.ts +14 -0
- package/Server/Services/IoTFleetOwnerRuleEngineService.ts +216 -0
- package/Server/Services/IoTFleetOwnerRuleService.ts +14 -0
- package/Server/Services/IoTFleetOwnerTeamService.ts +10 -0
- package/Server/Services/IoTFleetOwnerUserService.ts +10 -0
- package/Server/Services/IoTFleetService.ts +376 -0
- package/Server/Services/OpenTelemetryIngestService.ts +16 -0
- package/Server/Services/StatusPageService.ts +69 -26
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +36 -0
- package/Server/Services/TelemetryUsageBillingService.ts +18 -0
- package/Server/Services/TraceAggregationService.ts +174 -0
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorAlert.ts +24 -4
- package/Server/Utils/Monitor/MonitorClusterContext.ts +26 -2
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +4 -2
- package/Server/Utils/Monitor/MonitorIncident.ts +39 -5
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +14 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +22 -0
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +408 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +36 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/IotAlertTemplates.ts +593 -0
- package/Types/Monitor/IotMetricCatalog.ts +146 -0
- package/Types/Monitor/MonitorStep.ts +31 -0
- package/Types/Monitor/MonitorStepIoTMonitor.ts +65 -0
- package/Types/Monitor/MonitorType.ts +12 -1
- package/Types/Permission.ts +230 -0
- package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +3 -0
- package/Types/Telemetry/ServiceType.ts +1 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
- package/UI/Components/Icon/Icon.tsx +18 -2
- package/UI/Components/Page/Page.tsx +1 -2
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTDevice.js +722 -0
- package/build/dist/Models/DatabaseModels/IoTDevice.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IoTFleet.js +882 -0
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/API/IoTDeviceAPI.js +92 -0
- package/build/dist/Server/API/IoTDeviceAPI.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +76 -27
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js +187 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js +340 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +22 -3
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +22 -3
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +249 -0
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +166 -0
- package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetLabelRuleService.js +13 -0
- package/build/dist/Server/Services/IoTFleetLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +186 -0
- package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/IoTFleetOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/IoTFleetOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetOwnerUserService.js +9 -0
- package/build/dist/Server/Services/IoTFleetOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/IoTFleetService.js +340 -0
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +26 -11
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +57 -23
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +34 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +13 -0
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +136 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +15 -15
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +37 -19
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +27 -4
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +4 -2
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +39 -11
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +17 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +300 -0
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -0
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/IotAlertTemplates.js +467 -0
- package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/IotMetricCatalog.js +108 -0
- package/build/dist/Types/Monitor/IotMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +23 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js +32 -0
- package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +11 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Permission.js +204 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +2 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +1 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +3 -3
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +12 -2
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -2
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/package.json +1 -1
|
@@ -45,6 +45,10 @@ import StatusPageSubscriberService from "./StatusPageSubscriberService";
|
|
|
45
45
|
import StatusPageSubscriber from "../../Models/DatabaseModels/StatusPageSubscriber";
|
|
46
46
|
import MailService from "./MailService";
|
|
47
47
|
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
|
|
48
|
+
import StatusPageSubscriberNotificationTemplateService from "./StatusPageSubscriberNotificationTemplateService";
|
|
49
|
+
import StatusPageSubscriberNotificationTemplate from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
|
|
50
|
+
import StatusPageSubscriberNotificationEventType from "../../Types/StatusPage/StatusPageSubscriberNotificationEventType";
|
|
51
|
+
import StatusPageSubscriberNotificationMethod from "../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
|
|
48
52
|
import { StatusPageApiRoute } from "../../ServiceRoute";
|
|
49
53
|
import ProjectSMTPConfigService from "./ProjectSmtpConfigService";
|
|
50
54
|
import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
|
|
@@ -867,6 +871,25 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
867
871
|
historyDays: statuspage.reportDataInDays || 14,
|
|
868
872
|
});
|
|
869
873
|
|
|
874
|
+
/*
|
|
875
|
+
* Look up a custom report email template for this status page (if any).
|
|
876
|
+
* When present (and a custom SMTP is configured, mirroring the gating used
|
|
877
|
+
* for other subscriber notifications), the subscriber receives the custom
|
|
878
|
+
* template instead of the built-in StatusPageSubscriberReport.hbs. The
|
|
879
|
+
* custom body is rendered through Handlebars on the notification side
|
|
880
|
+
* (templateType omitted => MailService compiles the body string with the
|
|
881
|
+
* same `report` vars, helpers and partials), so it supports loops/
|
|
882
|
+
* conditionals such as {{#each report.resources}}.
|
|
883
|
+
*/
|
|
884
|
+
const customReportEmailTemplate: StatusPageSubscriberNotificationTemplate | null =
|
|
885
|
+
await StatusPageSubscriberNotificationTemplateService.getTemplateForStatusPage(
|
|
886
|
+
{
|
|
887
|
+
statusPageId: statuspage.id!,
|
|
888
|
+
eventType: StatusPageSubscriberNotificationEventType.SubscriberReport,
|
|
889
|
+
notificationMethod: StatusPageSubscriberNotificationMethod.Email,
|
|
890
|
+
},
|
|
891
|
+
);
|
|
892
|
+
|
|
870
893
|
type SendEmailFunction = (
|
|
871
894
|
email: Email,
|
|
872
895
|
unsubscribeUrl: URL | null,
|
|
@@ -878,33 +901,53 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
878
901
|
): Promise<void> => {
|
|
879
902
|
// send email here.
|
|
880
903
|
|
|
904
|
+
const vars: Dictionary<string | JSONObject> = {
|
|
905
|
+
statusPageName: statusPageName,
|
|
906
|
+
subscriberEmailNotificationFooterText:
|
|
907
|
+
Service.getSubscriberEmailFooterText(statuspage),
|
|
908
|
+
statusPageUrl: statusPageURL,
|
|
909
|
+
detailsUrl: statusPageURL,
|
|
910
|
+
hasResources: report.totalResources > 0 ? "true" : "false",
|
|
911
|
+
report: report as any,
|
|
912
|
+
logoUrl:
|
|
913
|
+
statuspage.logoFileId && statusPageIdString
|
|
914
|
+
? new URL(httpProtocol, host)
|
|
915
|
+
.addRoute(StatusPageApiRoute)
|
|
916
|
+
.addRoute(`/logo/${statusPageIdString}`)
|
|
917
|
+
.toString()
|
|
918
|
+
: "",
|
|
919
|
+
isPublicStatusPage: statuspage.isPublicStatusPage ? "true" : "false",
|
|
920
|
+
|
|
921
|
+
unsubscribeUrl: unsubscribeUrl?.toString() || "",
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
/*
|
|
925
|
+
* Use the custom template only when a custom SMTP is configured for the
|
|
926
|
+
* status page, matching the gating used by the other subscriber
|
|
927
|
+
* notifications (e.g. Incident). Custom-authored HTML is then sent from
|
|
928
|
+
* the customer's own mail server rather than the shared servers.
|
|
929
|
+
*/
|
|
930
|
+
const useCustomTemplate: boolean = Boolean(
|
|
931
|
+
customReportEmailTemplate?.templateBody && statuspage.smtpConfig,
|
|
932
|
+
);
|
|
933
|
+
|
|
881
934
|
MailService.sendMail(
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
.toString()
|
|
899
|
-
: "",
|
|
900
|
-
isPublicStatusPage: statuspage.isPublicStatusPage
|
|
901
|
-
? "true"
|
|
902
|
-
: "false",
|
|
903
|
-
|
|
904
|
-
unsubscribeUrl: unsubscribeUrl?.toString() || "",
|
|
905
|
-
},
|
|
906
|
-
subject: "[Report] " + statusPageName,
|
|
907
|
-
},
|
|
935
|
+
useCustomTemplate
|
|
936
|
+
? {
|
|
937
|
+
toEmail: email,
|
|
938
|
+
// templateType omitted => body is compiled as a Handlebars string.
|
|
939
|
+
body: customReportEmailTemplate!.templateBody!,
|
|
940
|
+
vars: vars,
|
|
941
|
+
subject: customReportEmailTemplate!.emailSubject
|
|
942
|
+
? customReportEmailTemplate!.emailSubject
|
|
943
|
+
: "[Report] " + statusPageName,
|
|
944
|
+
}
|
|
945
|
+
: {
|
|
946
|
+
toEmail: email,
|
|
947
|
+
templateType: EmailTemplateType.StatusPageSubscriberReport,
|
|
948
|
+
vars: vars,
|
|
949
|
+
subject: "[Report] " + statusPageName,
|
|
950
|
+
},
|
|
908
951
|
{
|
|
909
952
|
mailServer: ProjectSMTPConfigService.toEmailServer(
|
|
910
953
|
statuspage.smtpConfig,
|
|
@@ -245,6 +245,42 @@ export class Service extends DatabaseService<Model> {
|
|
|
245
245
|
},
|
|
246
246
|
];
|
|
247
247
|
|
|
248
|
+
case StatusPageSubscriberNotificationEventType.SubscriberReport:
|
|
249
|
+
/*
|
|
250
|
+
* The report template is rendered through the full Handlebars engine
|
|
251
|
+
* with the structured `report` object, so the per-resource fields are
|
|
252
|
+
* accessed inside {{#each report.resources}} rather than as flat
|
|
253
|
+
* scalars. The entries below document the top-level paths.
|
|
254
|
+
*/
|
|
255
|
+
return [
|
|
256
|
+
...commonVariables,
|
|
257
|
+
{
|
|
258
|
+
name: "report.reportDates",
|
|
259
|
+
description: "The reporting period",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "report.averageUptimePercent",
|
|
263
|
+
description: "Average uptime across all resources",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "report.totalDowntimeInHoursAndMinutes",
|
|
267
|
+
description: "Total downtime in the period",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "report.totalIncidents",
|
|
271
|
+
description: "Total number of incidents in the period",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "report.totalResources",
|
|
275
|
+
description: "Number of resources on the status page",
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "report.resources",
|
|
279
|
+
description:
|
|
280
|
+
"Array of per-resource rows (resourceName, uptimePercentAsString, downtimeInHoursAndMinutes, totalIncidentCount) to loop over with {{#each}}",
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
|
|
248
284
|
default:
|
|
249
285
|
throw new BadDataException(`Unknown event type: ${eventType}`);
|
|
250
286
|
}
|
|
@@ -29,11 +29,13 @@ import PodmanHostService from "./PodmanHostService";
|
|
|
29
29
|
import KubernetesClusterService from "./KubernetesClusterService";
|
|
30
30
|
import ProxmoxClusterService from "./ProxmoxClusterService";
|
|
31
31
|
import CephClusterService from "./CephClusterService";
|
|
32
|
+
import IoTFleetService from "./IoTFleetService";
|
|
32
33
|
import Host from "../../Models/DatabaseModels/Host";
|
|
33
34
|
import DockerHost from "../../Models/DatabaseModels/DockerHost";
|
|
34
35
|
import PodmanHost from "../../Models/DatabaseModels/PodmanHost";
|
|
35
36
|
import KubernetesCluster from "../../Models/DatabaseModels/KubernetesCluster";
|
|
36
37
|
import ProxmoxCluster from "../../Models/DatabaseModels/ProxmoxCluster";
|
|
38
|
+
import IoTFleet from "../../Models/DatabaseModels/IoTFleet";
|
|
37
39
|
import CephCluster from "../../Models/DatabaseModels/CephCluster";
|
|
38
40
|
import ServiceType from "../../Types/Telemetry/ServiceType";
|
|
39
41
|
import {
|
|
@@ -478,6 +480,22 @@ export class Service extends DatabaseService<Model> {
|
|
|
478
480
|
}
|
|
479
481
|
}
|
|
480
482
|
|
|
483
|
+
const iotFleets: Array<IoTFleet> = await IoTFleetService.findBy({
|
|
484
|
+
query: { projectId: projectId },
|
|
485
|
+
select: { _id: true, retainTelemetryDataForDays: true },
|
|
486
|
+
skip: 0,
|
|
487
|
+
limit: LIMIT_MAX,
|
|
488
|
+
props: { isRoot: true },
|
|
489
|
+
});
|
|
490
|
+
for (const iotFleet of iotFleets) {
|
|
491
|
+
if (iotFleet.id && iotFleet.retainTelemetryDataForDays) {
|
|
492
|
+
retentionByServiceId.set(
|
|
493
|
+
iotFleet.id.toString(),
|
|
494
|
+
iotFleet.retainTelemetryDataForDays,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
481
499
|
return retentionByServiceId;
|
|
482
500
|
}
|
|
483
501
|
|
|
@@ -568,6 +568,180 @@ export class TraceAggregationService {
|
|
|
568
568
|
return { serviceCounts, statusCounts };
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
+
/*
|
|
572
|
+
* Exact root-span vs non-root-span counts over the full window, backing the
|
|
573
|
+
* "Span Type" sidebar facet. Like getResourceFacetCounts this rides the
|
|
574
|
+
* proj_hist_by_minute aggregate projection (isRootSpan is a projection key),
|
|
575
|
+
* so it reads a few pre-aggregated minute rows in single-digit ms rather than
|
|
576
|
+
* scanning raw spans — same toStartOfMinute()/optimize_use_projections
|
|
577
|
+
* requirements apply.
|
|
578
|
+
*
|
|
579
|
+
* rootOnly is deliberately forced OFF here: a facet must not filter by its
|
|
580
|
+
* own dimension, or the unselected bucket would always read 0 and the user
|
|
581
|
+
* could never see how many root vs non-root spans exist (the exact confusion
|
|
582
|
+
* the facet is meant to resolve). Every OTHER active filter (service /
|
|
583
|
+
* status / ...) still applies, so the counts narrow with the rest of the
|
|
584
|
+
* sidebar.
|
|
585
|
+
*/
|
|
586
|
+
@CaptureSpan()
|
|
587
|
+
public static async getRootSpanCounts(request: MultiFacetRequest): Promise<{
|
|
588
|
+
rootCount: number;
|
|
589
|
+
nonRootCount: number;
|
|
590
|
+
}> {
|
|
591
|
+
const statement: Statement = new Statement();
|
|
592
|
+
statement.append(
|
|
593
|
+
`SELECT isRootSpan, count() AS cnt FROM ${TraceAggregationService.TABLE_NAME}`,
|
|
594
|
+
);
|
|
595
|
+
statement.append(
|
|
596
|
+
SQL` WHERE projectId = ${{
|
|
597
|
+
type: TableColumnType.ObjectID,
|
|
598
|
+
value: request.projectId,
|
|
599
|
+
}} AND toStartOfMinute(startTime) >= toStartOfMinute(${{
|
|
600
|
+
type: TableColumnType.Date,
|
|
601
|
+
value: request.startTime,
|
|
602
|
+
}}) AND toStartOfMinute(startTime) <= toStartOfMinute(${{
|
|
603
|
+
type: TableColumnType.Date,
|
|
604
|
+
value: request.endTime,
|
|
605
|
+
}})`,
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
// Force rootOnly off so both buckets survive (see method comment).
|
|
609
|
+
TraceAggregationService.appendCommonFilters(statement, {
|
|
610
|
+
...request,
|
|
611
|
+
rootOnly: false,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
statement.append(" GROUP BY isRootSpan");
|
|
615
|
+
|
|
616
|
+
statement.append(
|
|
617
|
+
getQuerySettings({
|
|
618
|
+
maxExecutionTimeInSeconds: 45,
|
|
619
|
+
timeoutOverflowMode: "break",
|
|
620
|
+
additionalSettings: { optimize_use_projections: 1 },
|
|
621
|
+
}),
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
let rootCount: number = 0;
|
|
625
|
+
let nonRootCount: number = 0;
|
|
626
|
+
|
|
627
|
+
const dbResult: Results = await SpanService.executeQuery(statement);
|
|
628
|
+
|
|
629
|
+
let rows: Array<JSONObject> = [];
|
|
630
|
+
try {
|
|
631
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
632
|
+
data?: Array<JSONObject>;
|
|
633
|
+
}>();
|
|
634
|
+
rows = response.data || [];
|
|
635
|
+
} catch {
|
|
636
|
+
// 'break' mode can truncate JSON on timeout — degrade to zero counts.
|
|
637
|
+
logger.warn(
|
|
638
|
+
"Root span count query returned unparseable response, returning zero counts",
|
|
639
|
+
);
|
|
640
|
+
return { rootCount, nonRootCount };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
for (const row of rows) {
|
|
644
|
+
const cnt: number = Number(row["cnt"] || 0);
|
|
645
|
+
const raw: unknown = row["isRootSpan"];
|
|
646
|
+
// ClickHouse Bool may serialize as true/false, 1/0, or "1"/"true".
|
|
647
|
+
const isRoot: boolean =
|
|
648
|
+
raw === true || raw === 1 || raw === "1" || raw === "true";
|
|
649
|
+
if (isRoot) {
|
|
650
|
+
rootCount += cnt;
|
|
651
|
+
} else {
|
|
652
|
+
nonRootCount += cnt;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return { rootCount, nonRootCount };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/*
|
|
660
|
+
* Exact has-exception vs no-exception counts over the window, backing the
|
|
661
|
+
* "Has Exception" sidebar facet. Unlike getResourceFacetCounts /
|
|
662
|
+
* getRootSpanCounts, hasException is not a proj_hist_by_minute key, so this
|
|
663
|
+
* is a base-table GROUP BY (still bounded by max_execution_time / 'break').
|
|
664
|
+
* It is a single low-cardinality column read, so it is cheap for typical
|
|
665
|
+
* windows and degrades gracefully on very wide ones.
|
|
666
|
+
*
|
|
667
|
+
* Exactness matters specifically here: exception spans are usually rare, so
|
|
668
|
+
* the recent-N sample (getFacetValuesFromSample) can miss them entirely and
|
|
669
|
+
* report 0 — actively misleading for the one facet whose purpose is to find
|
|
670
|
+
* exceptions. Every active filter (incl. rootOnly) still applies via
|
|
671
|
+
* appendCommonFilters, so the count narrows with the rest of the sidebar.
|
|
672
|
+
*/
|
|
673
|
+
@CaptureSpan()
|
|
674
|
+
public static async getHasExceptionCounts(
|
|
675
|
+
request: MultiFacetRequest,
|
|
676
|
+
): Promise<{
|
|
677
|
+
withExceptionCount: number;
|
|
678
|
+
withoutExceptionCount: number;
|
|
679
|
+
}> {
|
|
680
|
+
const statement: Statement = new Statement();
|
|
681
|
+
statement.append(
|
|
682
|
+
`SELECT hasException, count() AS cnt FROM ${TraceAggregationService.TABLE_NAME}`,
|
|
683
|
+
);
|
|
684
|
+
statement.append(
|
|
685
|
+
SQL` WHERE projectId = ${{
|
|
686
|
+
type: TableColumnType.ObjectID,
|
|
687
|
+
value: request.projectId,
|
|
688
|
+
}} AND startTime >= ${{
|
|
689
|
+
type: TableColumnType.Date,
|
|
690
|
+
value: request.startTime,
|
|
691
|
+
}} AND startTime <= ${{
|
|
692
|
+
type: TableColumnType.Date,
|
|
693
|
+
value: request.endTime,
|
|
694
|
+
}}`,
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
statement.append(TraceAggregationService.RETENTION_FILTER);
|
|
698
|
+
|
|
699
|
+
TraceAggregationService.appendCommonFilters(statement, request);
|
|
700
|
+
|
|
701
|
+
statement.append(" GROUP BY hasException");
|
|
702
|
+
|
|
703
|
+
statement.append(
|
|
704
|
+
getQuerySettings({
|
|
705
|
+
maxExecutionTimeInSeconds: 45,
|
|
706
|
+
timeoutOverflowMode: "break",
|
|
707
|
+
}),
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
let withExceptionCount: number = 0;
|
|
711
|
+
let withoutExceptionCount: number = 0;
|
|
712
|
+
|
|
713
|
+
const dbResult: Results = await SpanService.executeQuery(statement);
|
|
714
|
+
|
|
715
|
+
let rows: Array<JSONObject> = [];
|
|
716
|
+
try {
|
|
717
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
718
|
+
data?: Array<JSONObject>;
|
|
719
|
+
}>();
|
|
720
|
+
rows = response.data || [];
|
|
721
|
+
} catch {
|
|
722
|
+
// 'break' mode can truncate JSON on timeout — degrade to zero counts.
|
|
723
|
+
logger.warn(
|
|
724
|
+
"Has-exception count query returned unparseable response, returning zero counts",
|
|
725
|
+
);
|
|
726
|
+
return { withExceptionCount, withoutExceptionCount };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
for (const row of rows) {
|
|
730
|
+
const cnt: number = Number(row["cnt"] || 0);
|
|
731
|
+
const raw: unknown = row["hasException"];
|
|
732
|
+
// ClickHouse Bool may serialize as true/false, 1/0, or "1"/"true".
|
|
733
|
+
const hasException: boolean =
|
|
734
|
+
raw === true || raw === 1 || raw === "1" || raw === "true";
|
|
735
|
+
if (hasException) {
|
|
736
|
+
withExceptionCount += cnt;
|
|
737
|
+
} else {
|
|
738
|
+
withoutExceptionCount += cnt;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return { withExceptionCount, withoutExceptionCount };
|
|
743
|
+
}
|
|
744
|
+
|
|
571
745
|
private static mapStatusCodeToSeries(code: number): string {
|
|
572
746
|
if (code === 1) {
|
|
573
747
|
return "ok";
|
|
@@ -192,7 +192,7 @@ export default class ServerMonitorCriteria {
|
|
|
192
192
|
|
|
193
193
|
const diskMetric: BasicDiskMetrics | undefined = (
|
|
194
194
|
input.dataToProcess as ServerMonitorResponse
|
|
195
|
-
).basicInfrastructureMetrics?.diskMetrics
|
|
195
|
+
).basicInfrastructureMetrics?.diskMetrics?.find(
|
|
196
196
|
(item: BasicDiskMetrics) => {
|
|
197
197
|
return normalizeDiskPath(item.diskPath) === normalizedDiskPath;
|
|
198
198
|
},
|
|
@@ -8,6 +8,7 @@ import Label from "../../../Models/DatabaseModels/Label";
|
|
|
8
8
|
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
9
9
|
import OnCallDutyPolicy from "../../../Models/DatabaseModels/OnCallDutyPolicy";
|
|
10
10
|
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
11
|
+
import IoTFleet from "../../../Models/DatabaseModels/IoTFleet";
|
|
11
12
|
import Service from "../../../Models/DatabaseModels/Service";
|
|
12
13
|
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
13
14
|
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
@@ -217,11 +218,19 @@ export default class MonitorAlert {
|
|
|
217
218
|
continue;
|
|
218
219
|
}
|
|
219
220
|
|
|
221
|
+
/*
|
|
222
|
+
* Mirror the create path: `createdCriteriaId` is only set when the
|
|
223
|
+
* criteria has an `id`. Guard the `.toString()` with `?.` (a criteria
|
|
224
|
+
* with a missing id otherwise threw "Cannot read properties of
|
|
225
|
+
* undefined (reading 'toString')" and failed the queue job every
|
|
226
|
+
* cycle) and normalise both sides to `undefined` on missing so dedupe
|
|
227
|
+
* stays correct instead of creating a duplicate alert each cycle.
|
|
228
|
+
*/
|
|
220
229
|
const alreadyOpenAlert: Alert | undefined = openAlerts.find(
|
|
221
230
|
(alert: Alert) => {
|
|
222
231
|
return (
|
|
223
|
-
alert.createdCriteriaId ===
|
|
224
|
-
input.criteriaInstance.data?.id
|
|
232
|
+
(alert.createdCriteriaId || undefined) ===
|
|
233
|
+
(input.criteriaInstance.data?.id?.toString() || undefined) &&
|
|
225
234
|
(alert.seriesFingerprint || undefined) === seriesFingerprint
|
|
226
235
|
);
|
|
227
236
|
},
|
|
@@ -318,7 +327,9 @@ export default class MonitorAlert {
|
|
|
318
327
|
JSON.stringify(input.dataToProcess, null, 2),
|
|
319
328
|
);
|
|
320
329
|
|
|
321
|
-
|
|
330
|
+
if (input.criteriaInstance.data?.id) {
|
|
331
|
+
alert.createdCriteriaId = input.criteriaInstance.data.id.toString();
|
|
332
|
+
}
|
|
322
333
|
|
|
323
334
|
if (seriesFingerprint) {
|
|
324
335
|
alert.seriesFingerprint = seriesFingerprint;
|
|
@@ -430,6 +441,15 @@ export default class MonitorAlert {
|
|
|
430
441
|
},
|
|
431
442
|
);
|
|
432
443
|
}
|
|
444
|
+
if (clusterContext.iotFleetIds.length > 0) {
|
|
445
|
+
alert.iotFleets = clusterContext.iotFleetIds.map(
|
|
446
|
+
(id: string): IoTFleet => {
|
|
447
|
+
const fleet: IoTFleet = new IoTFleet();
|
|
448
|
+
fleet._id = id;
|
|
449
|
+
return fleet;
|
|
450
|
+
},
|
|
451
|
+
);
|
|
452
|
+
}
|
|
433
453
|
|
|
434
454
|
alert.onCallDutyPolicies =
|
|
435
455
|
criteriaAlert.onCallPolicyIds?.map((id: ObjectID) => {
|
|
@@ -610,7 +630,7 @@ export default class MonitorAlert {
|
|
|
610
630
|
|
|
611
631
|
if (
|
|
612
632
|
input.openAlert.createdCriteriaId?.toString() ===
|
|
613
|
-
input.criteriaInstance?.data?.id
|
|
633
|
+
input.criteriaInstance?.data?.id?.toString()
|
|
614
634
|
) {
|
|
615
635
|
// same alert active. So, do not close.
|
|
616
636
|
return false;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import CephCluster from "../../../Models/DatabaseModels/CephCluster";
|
|
2
2
|
import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
|
|
3
|
+
import IoTFleet from "../../../Models/DatabaseModels/IoTFleet";
|
|
3
4
|
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
4
5
|
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
5
6
|
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
6
7
|
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
7
8
|
import CephClusterService from "../../Services/CephClusterService";
|
|
8
9
|
import DockerSwarmClusterService from "../../Services/DockerSwarmClusterService";
|
|
10
|
+
import IoTFleetService from "../../Services/IoTFleetService";
|
|
9
11
|
import ProxmoxClusterService from "../../Services/ProxmoxClusterService";
|
|
10
12
|
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
11
13
|
import logger from "../Logger";
|
|
@@ -26,6 +28,7 @@ export interface MonitorClusterContext {
|
|
|
26
28
|
proxmoxClusterIds: Array<string>;
|
|
27
29
|
cephClusterIds: Array<string>;
|
|
28
30
|
dockerSwarmClusterIds: Array<string>;
|
|
31
|
+
iotFleetIds: Array<string>;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export default class MonitorClusterContextUtil {
|
|
@@ -46,6 +49,7 @@ export default class MonitorClusterContextUtil {
|
|
|
46
49
|
proxmoxClusterIds: [],
|
|
47
50
|
cephClusterIds: [],
|
|
48
51
|
dockerSwarmClusterIds: [],
|
|
52
|
+
iotFleetIds: [],
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
const monitorType: MonitorType | undefined = input.monitor.monitorType;
|
|
@@ -53,7 +57,8 @@ export default class MonitorClusterContextUtil {
|
|
|
53
57
|
if (
|
|
54
58
|
monitorType !== MonitorType.Proxmox &&
|
|
55
59
|
monitorType !== MonitorType.Ceph &&
|
|
56
|
-
monitorType !== MonitorType.DockerSwarm
|
|
60
|
+
monitorType !== MonitorType.DockerSwarm &&
|
|
61
|
+
monitorType !== MonitorType.IoTDevice
|
|
57
62
|
) {
|
|
58
63
|
return context;
|
|
59
64
|
}
|
|
@@ -77,6 +82,8 @@ export default class MonitorClusterContextUtil {
|
|
|
77
82
|
} else if (monitorType === MonitorType.DockerSwarm) {
|
|
78
83
|
clusterIdentifier =
|
|
79
84
|
monitorStep.data?.dockerSwarmMonitor?.clusterIdentifier;
|
|
85
|
+
} else if (monitorType === MonitorType.IoTDevice) {
|
|
86
|
+
clusterIdentifier = monitorStep.data?.iotMonitor?.fleetIdentifier;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
if (clusterIdentifier && clusterIdentifier.trim().length > 0) {
|
|
@@ -127,7 +134,7 @@ export default class MonitorClusterContextUtil {
|
|
|
127
134
|
if (cephCluster?._id) {
|
|
128
135
|
context.cephClusterIds.push(String(cephCluster._id));
|
|
129
136
|
}
|
|
130
|
-
} else {
|
|
137
|
+
} else if (monitorType === MonitorType.DockerSwarm) {
|
|
131
138
|
const dockerSwarmCluster: DockerSwarmCluster | null =
|
|
132
139
|
await DockerSwarmClusterService.findOneBy({
|
|
133
140
|
query: {
|
|
@@ -145,6 +152,23 @@ export default class MonitorClusterContextUtil {
|
|
|
145
152
|
if (dockerSwarmCluster?._id) {
|
|
146
153
|
context.dockerSwarmClusterIds.push(String(dockerSwarmCluster._id));
|
|
147
154
|
}
|
|
155
|
+
} else {
|
|
156
|
+
const iotFleet: IoTFleet | null = await IoTFleetService.findOneBy({
|
|
157
|
+
query: {
|
|
158
|
+
projectId: input.monitor.projectId,
|
|
159
|
+
name: QueryHelper.findWithSameText(clusterIdentifier),
|
|
160
|
+
},
|
|
161
|
+
select: {
|
|
162
|
+
_id: true,
|
|
163
|
+
},
|
|
164
|
+
props: {
|
|
165
|
+
isRoot: true,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (iotFleet?._id) {
|
|
170
|
+
context.iotFleetIds.push(String(iotFleet._id));
|
|
171
|
+
}
|
|
148
172
|
}
|
|
149
173
|
} catch (err) {
|
|
150
174
|
logger.error(
|
|
@@ -246,7 +246,8 @@ ${contextBlock}
|
|
|
246
246
|
input.monitor.monitorType !== MonitorType.Podman &&
|
|
247
247
|
input.monitor.monitorType !== MonitorType.DockerSwarm &&
|
|
248
248
|
input.monitor.monitorType !== MonitorType.Proxmox &&
|
|
249
|
-
input.monitor.monitorType !== MonitorType.Ceph
|
|
249
|
+
input.monitor.monitorType !== MonitorType.Ceph &&
|
|
250
|
+
input.monitor.monitorType !== MonitorType.IoTDevice
|
|
250
251
|
) {
|
|
251
252
|
return [];
|
|
252
253
|
}
|
|
@@ -697,7 +698,8 @@ ${contextBlock}
|
|
|
697
698
|
input.monitor.monitorType === MonitorType.Podman ||
|
|
698
699
|
input.monitor.monitorType === MonitorType.DockerSwarm ||
|
|
699
700
|
input.monitor.monitorType === MonitorType.Proxmox ||
|
|
700
|
-
input.monitor.monitorType === MonitorType.Ceph
|
|
701
|
+
input.monitor.monitorType === MonitorType.Ceph ||
|
|
702
|
+
input.monitor.monitorType === MonitorType.IoTDevice
|
|
701
703
|
) {
|
|
702
704
|
const metricMonitorResult: string | null =
|
|
703
705
|
await MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
|
|
@@ -12,6 +12,7 @@ import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
|
12
12
|
import OnCallDutyPolicy from "../../../Models/DatabaseModels/OnCallDutyPolicy";
|
|
13
13
|
import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
|
|
14
14
|
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
15
|
+
import IoTFleet from "../../../Models/DatabaseModels/IoTFleet";
|
|
15
16
|
import Service from "../../../Models/DatabaseModels/Service";
|
|
16
17
|
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
17
18
|
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
@@ -267,13 +268,25 @@ export default class MonitorIncident {
|
|
|
267
268
|
continue;
|
|
268
269
|
}
|
|
269
270
|
|
|
271
|
+
/*
|
|
272
|
+
* Dedupe match must mirror the create path below (which sets
|
|
273
|
+
* `createdCriteriaId` / `createdIncidentTemplateId` only when the
|
|
274
|
+
* corresponding id is present). A criteria incident template can be
|
|
275
|
+
* missing its `id` (legacy/API-authored criteria), so guard the
|
|
276
|
+
* `.toString()` with `?.` — an unguarded call previously threw
|
|
277
|
+
* "Cannot read properties of undefined (reading 'toString')" here and
|
|
278
|
+
* failed the probe/telemetry queue job on every cycle for the affected
|
|
279
|
+
* monitor. Normalise both sides to `undefined` on missing so a created
|
|
280
|
+
* incident (whose template id was left NULL) still matches itself next
|
|
281
|
+
* cycle instead of being recreated as a duplicate.
|
|
282
|
+
*/
|
|
270
283
|
const alreadyOpenIncident: Incident | undefined = openIncidents.find(
|
|
271
284
|
(incident: Incident) => {
|
|
272
285
|
return (
|
|
273
|
-
incident.createdCriteriaId ===
|
|
274
|
-
input.criteriaInstance.data?.id
|
|
275
|
-
incident.createdIncidentTemplateId ===
|
|
276
|
-
criteriaIncident.id
|
|
286
|
+
(incident.createdCriteriaId || undefined) ===
|
|
287
|
+
(input.criteriaInstance.data?.id?.toString() || undefined) &&
|
|
288
|
+
(incident.createdIncidentTemplateId || undefined) ===
|
|
289
|
+
(criteriaIncident.id?.toString() || undefined) &&
|
|
277
290
|
(incident.seriesFingerprint || undefined) === seriesFingerprint
|
|
278
291
|
);
|
|
279
292
|
},
|
|
@@ -808,6 +821,27 @@ export default class MonitorIncident {
|
|
|
808
821
|
|
|
809
822
|
input.incident.dockerSwarmClusters = merged;
|
|
810
823
|
}
|
|
824
|
+
|
|
825
|
+
if (input.clusterContext.iotFleetIds.length > 0) {
|
|
826
|
+
const existingIds: Set<string> = new Set<string>(
|
|
827
|
+
(input.incident.iotFleets || []).map((fleet: IoTFleet) => {
|
|
828
|
+
return String(fleet._id);
|
|
829
|
+
}),
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
const merged: Array<IoTFleet> = [...(input.incident.iotFleets || [])];
|
|
833
|
+
|
|
834
|
+
for (const id of input.clusterContext.iotFleetIds) {
|
|
835
|
+
if (existingIds.has(id)) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const fleet: IoTFleet = new IoTFleet();
|
|
839
|
+
fleet._id = id;
|
|
840
|
+
merged.push(fleet);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
input.incident.iotFleets = merged;
|
|
844
|
+
}
|
|
811
845
|
}
|
|
812
846
|
|
|
813
847
|
/*
|
|
@@ -1002,7 +1036,7 @@ export default class MonitorIncident {
|
|
|
1002
1036
|
|
|
1003
1037
|
if (
|
|
1004
1038
|
input.openIncident.createdCriteriaId?.toString() ===
|
|
1005
|
-
input.criteriaInstance?.data?.id
|
|
1039
|
+
input.criteriaInstance?.data?.id?.toString()
|
|
1006
1040
|
) {
|
|
1007
1041
|
// same incident active. So, do not close.
|
|
1008
1042
|
return false;
|