@oneuptime/common 7.0.5085 → 7.0.5093

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.
Files changed (51) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.ts +1 -1
  2. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.ts +1 -1
  3. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.ts +1 -1
  4. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.ts +1 -1
  5. package/Models/DatabaseModels/Project.ts +33 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1756821449686-MigrationName.ts +17 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  8. package/Server/Services/AlertService.ts +1 -1
  9. package/Server/Services/MonitorService.ts +27 -7
  10. package/Server/Utils/Monitor/MonitorAlert.ts +20 -4
  11. package/Server/Utils/Monitor/MonitorIncident.ts +23 -5
  12. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +246 -0
  13. package/Server/Utils/VM/VMAPI.ts +11 -2
  14. package/UI/Components/Graphs/ServiceDependencyGraph.tsx +10 -5
  15. package/UI/Components/Input/Input.tsx +5 -4
  16. package/UI/Components/Workflow/ArgumentsForm.tsx +5 -3
  17. package/UI/Components/Workflow/Utils.ts +3 -8
  18. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.js +1 -1
  19. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.js.map +1 -1
  20. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js +1 -1
  21. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js.map +1 -1
  22. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js +1 -1
  23. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js.map +1 -1
  24. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js +1 -1
  25. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js.map +1 -1
  26. package/build/dist/Models/DatabaseModels/Project.js +34 -0
  27. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756821449686-MigrationName.js +12 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756821449686-MigrationName.js.map +1 -0
  30. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  31. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  32. package/build/dist/Server/Services/AlertService.js +1 -1
  33. package/build/dist/Server/Services/AlertService.js.map +1 -1
  34. package/build/dist/Server/Services/MonitorService.js +22 -7
  35. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  36. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +17 -3
  37. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  38. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -3
  39. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  40. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +180 -0
  41. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -0
  42. package/build/dist/Server/Utils/VM/VMAPI.js +9 -1
  43. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  44. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js +1 -1
  45. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js.map +1 -1
  46. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  47. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +4 -2
  48. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  49. package/build/dist/UI/Components/Workflow/Utils.js +1 -4
  50. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  51. package/package.json +1 -1
@@ -46,7 +46,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
46
46
  Permission.EditProjectOnCallDutyPolicyEscalationRule,
47
47
  ],
48
48
  })
49
- @CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule"))
49
+ @CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule"))
50
50
  @Entity({
51
51
  name: "OnCallDutyPolicyEscalationRule",
52
52
  })
@@ -47,7 +47,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
47
47
  Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule,
48
48
  ],
49
49
  })
50
- @CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-schedule"))
50
+ @CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule-schedule"))
51
51
  @Entity({
52
52
  name: "OnCallDutyPolicyEscalationRuleSchedule",
53
53
  })
@@ -47,7 +47,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
47
47
  Permission.EditProjectOnCallDutyPolicyEscalationRuleTeam,
48
48
  ],
49
49
  })
50
- @CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-team"))
50
+ @CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule-team"))
51
51
  @Entity({
52
52
  name: "OnCallDutyPolicyEscalationRuleTeam",
53
53
  })
@@ -46,7 +46,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
46
46
  Permission.EditProjectOnCallDutyPolicyEscalationRuleUser,
47
47
  ],
48
48
  })
49
- @CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-user"))
49
+ @CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule-user"))
50
50
  @Entity({
51
51
  name: "OnCallDutyPolicyEscalationRuleUser",
52
52
  })
@@ -1291,4 +1291,37 @@ export default class Project extends TenantModel {
1291
1291
  type: ColumnType.Boolean,
1292
1292
  })
1293
1293
  public letCustomerSupportAccessProject?: boolean = undefined;
1294
+
1295
+ @ColumnAccessControl({
1296
+ create: [],
1297
+ read: [
1298
+ Permission.ProjectOwner,
1299
+ Permission.ProjectAdmin,
1300
+ Permission.ProjectMember,
1301
+ Permission.ReadProject,
1302
+ Permission.UnAuthorizedSsoUser,
1303
+ Permission.ProjectUser,
1304
+ ],
1305
+ update: [
1306
+ Permission.ProjectOwner,
1307
+ Permission.ProjectAdmin,
1308
+ Permission.EditProject,
1309
+ ],
1310
+ })
1311
+ @TableColumn({
1312
+ required: false,
1313
+ type: TableColumnType.Boolean,
1314
+ isDefaultValueColumn: false,
1315
+ title: "Do NOT auto-add Global Probes to new monitors",
1316
+ description:
1317
+ "If enabled, global probes will NOT be automatically added to new monitors. Enable this only if you are using ONLY custom probes to monitor your resources.",
1318
+ defaultValue: false,
1319
+ })
1320
+ @Column({
1321
+ type: ColumnType.Boolean,
1322
+ nullable: false,
1323
+ unique: false,
1324
+ default: false,
1325
+ })
1326
+ public doNotAddGlobalProbesByDefaultOnNewMonitors?: boolean = undefined;
1294
1327
  }
@@ -0,0 +1,17 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1756821449686 implements MigrationInterface {
4
+ public name = "MigrationName1756821449686";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "Project" ADD "doNotAddGlobalProbesByDefaultOnNewMonitors" boolean NOT NULL DEFAULT false`,
9
+ );
10
+ }
11
+
12
+ public async down(queryRunner: QueryRunner): Promise<void> {
13
+ await queryRunner.query(
14
+ `ALTER TABLE "Project" DROP COLUMN "doNotAddGlobalProbesByDefaultOnNewMonitors"`,
15
+ );
16
+ }
17
+ }
@@ -164,6 +164,7 @@ import { MigrationName1755778934927 } from "./1755778934927-MigrationName";
164
164
  import { MigrationName1756293325324 } from "./1756293325324-MigrationName";
165
165
  import { MigrationName1756296282627 } from "./1756296282627-MigrationName";
166
166
  import { MigrationName1756300358095 } from "./1756300358095-MigrationName";
167
+ import { MigrationName1756821449686 } from "./1756821449686-MigrationName";
167
168
 
168
169
  export default [
169
170
  InitialMigration,
@@ -332,4 +333,5 @@ export default [
332
333
  MigrationName1756293325324,
333
334
  MigrationName1756296282627,
334
335
  MigrationName1756300358095,
336
+ MigrationName1756821449686,
335
337
  ];
@@ -440,7 +440,7 @@ export class Service extends DatabaseService<Model> {
440
440
 
441
441
  ${createdItem.description || "No description provided."}
442
442
 
443
- `;
443
+ `;
444
444
 
445
445
  if (alert.currentAlertState?.name) {
446
446
  feedInfoInMarkdown += `🔴 **Alert State**: ${alert.currentAlertState.name} \n\n`;
@@ -68,6 +68,7 @@ import { FindWhere } from "../../Types/BaseDatabase/Query";
68
68
  import logger from "../Utils/Logger";
69
69
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
70
70
  import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
71
+ import Project from "../../Models/DatabaseModels/Project";
71
72
 
72
73
  export class Service extends DatabaseService<Model> {
73
74
  public constructor() {
@@ -791,21 +792,40 @@ ${createdItem.description?.trim() || "No description provided."}
791
792
  projectId: ObjectID,
792
793
  monitorId: ObjectID,
793
794
  ): Promise<void> {
794
- const globalProbes: Array<Probe> = await ProbeService.findBy({
795
- query: {
796
- isGlobalProbe: true,
797
- shouldAutoEnableProbeOnNewMonitors: true,
798
- },
795
+ // Fetch project to see if global probes should be added automatically.
796
+ const project: Project | null = await ProjectService.findOneById({
797
+ id: projectId,
799
798
  select: {
800
799
  _id: true,
800
+ doNotAddGlobalProbesByDefaultOnNewMonitors: true,
801
801
  },
802
- skip: 0,
803
- limit: LIMIT_PER_PROJECT,
804
802
  props: {
805
803
  isRoot: true,
806
804
  },
807
805
  });
808
806
 
807
+ const shouldSkipGlobalProbes: boolean =
808
+ project?.doNotAddGlobalProbesByDefaultOnNewMonitors === true;
809
+
810
+ let globalProbes: Array<Probe> = [];
811
+
812
+ if (!shouldSkipGlobalProbes) {
813
+ globalProbes = await ProbeService.findBy({
814
+ query: {
815
+ isGlobalProbe: true,
816
+ shouldAutoEnableProbeOnNewMonitors: true,
817
+ },
818
+ select: {
819
+ _id: true,
820
+ },
821
+ skip: 0,
822
+ limit: LIMIT_PER_PROJECT,
823
+ props: {
824
+ isRoot: true,
825
+ },
826
+ });
827
+ }
828
+
809
829
  const projectProbes: Array<Probe> = await ProbeService.findBy({
810
830
  query: {
811
831
  isGlobalProbe: false,
@@ -19,6 +19,8 @@ import AlertStateTimelineService from "../../Services/AlertStateTimelineService"
19
19
  import logger from "../Logger";
20
20
  import CaptureSpan from "../Telemetry/CaptureSpan";
21
21
  import DataToProcess from "./DataToProcess";
22
+ import MonitorTemplateUtil from "./MonitorTemplateUtil";
23
+ import { JSONObject } from "../../../Types/JSON";
22
24
 
23
25
  export default class MonitorAlert {
24
26
  @CaptureSpan()
@@ -130,9 +132,20 @@ export default class MonitorAlert {
130
132
  logger.debug(`${input.monitor.id?.toString()} - Create alert.`);
131
133
 
132
134
  const alert: Alert = new Alert();
133
-
134
- alert.title = criteriaAlert.title;
135
- alert.description = criteriaAlert.description;
135
+ const storageMap: JSONObject =
136
+ MonitorTemplateUtil.buildTemplateStorageMap({
137
+ monitorType: input.monitor.monitorType!,
138
+ dataToProcess: input.dataToProcess,
139
+ });
140
+
141
+ alert.title = MonitorTemplateUtil.processTemplateString({
142
+ value: criteriaAlert.title,
143
+ storageMap,
144
+ });
145
+ alert.description = MonitorTemplateUtil.processTemplateString({
146
+ value: criteriaAlert.description,
147
+ storageMap,
148
+ });
136
149
 
137
150
  if (!criteriaAlert.alertSeverityId) {
138
151
  // pick the critical criteria.
@@ -194,7 +207,10 @@ export default class MonitorAlert {
194
207
  }
195
208
 
196
209
  if (criteriaAlert.remediationNotes) {
197
- alert.remediationNotes = criteriaAlert.remediationNotes;
210
+ alert.remediationNotes = MonitorTemplateUtil.processTemplateString({
211
+ value: criteriaAlert.remediationNotes,
212
+ storageMap,
213
+ });
198
214
  }
199
215
 
200
216
  if (DisableAutomaticAlertCreation) {
@@ -19,6 +19,8 @@ import IncidentStateTimelineService from "../../Services/IncidentStateTimelineSe
19
19
  import logger from "../Logger";
20
20
  import CaptureSpan from "../Telemetry/CaptureSpan";
21
21
  import DataToProcess from "./DataToProcess";
22
+ import MonitorTemplateUtil from "./MonitorTemplateUtil";
23
+ import { JSONObject } from "../../../Types/JSON";
22
24
 
23
25
  export default class MonitorIncident {
24
26
  @CaptureSpan()
@@ -34,7 +36,7 @@ export default class MonitorIncident {
34
36
  // check active incidents and if there are open incidents, do not cretae anothr incident.
35
37
  const openIncidents: Array<Incident> = await IncidentService.findBy({
36
38
  query: {
37
- monitors: [input.monitorId] as any,
39
+ monitors: [input.monitorId],
38
40
  currentIncidentState: {
39
41
  isResolvedState: false,
40
42
  },
@@ -136,9 +138,20 @@ export default class MonitorIncident {
136
138
  logger.debug(`${input.monitor.id?.toString()} - Create incident.`);
137
139
 
138
140
  const incident: Incident = new Incident();
139
-
140
- incident.title = criteriaIncident.title;
141
- incident.description = criteriaIncident.description;
141
+ const storageMap: JSONObject =
142
+ MonitorTemplateUtil.buildTemplateStorageMap({
143
+ monitorType: input.monitor.monitorType!,
144
+ dataToProcess: input.dataToProcess,
145
+ });
146
+
147
+ incident.title = MonitorTemplateUtil.processTemplateString({
148
+ value: criteriaIncident.title,
149
+ storageMap,
150
+ });
151
+ incident.description = MonitorTemplateUtil.processTemplateString({
152
+ value: criteriaIncident.description,
153
+ storageMap,
154
+ });
142
155
 
143
156
  if (!criteriaIncident.incidentSeverityId) {
144
157
  // pick the critical criteria.
@@ -204,7 +217,12 @@ export default class MonitorIncident {
204
217
  }
205
218
 
206
219
  if (criteriaIncident.remediationNotes) {
207
- incident.remediationNotes = criteriaIncident.remediationNotes;
220
+ incident.remediationNotes = MonitorTemplateUtil.processTemplateString(
221
+ {
222
+ value: criteriaIncident.remediationNotes,
223
+ storageMap,
224
+ },
225
+ );
208
226
  }
209
227
 
210
228
  if (DisableAutomaticIncidentCreation) {
@@ -0,0 +1,246 @@
1
+ import MonitorType from "../../../Types/Monitor/MonitorType";
2
+ import { JSONObject } from "../../../Types/JSON";
3
+ import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
4
+ import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
5
+ import ServerMonitorResponse, {
6
+ ServerProcess,
7
+ } from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse";
8
+ import BasicInfrastructureMetrics, {
9
+ BasicDiskMetrics,
10
+ } from "../../../Types/Infrastructure/BasicMetrics";
11
+ import SslMonitorResponse from "../../../Types/Monitor/SSLMonitor/SslMonitorResponse";
12
+ import CustomCodeMonitorResponse from "../../../Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse";
13
+ import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse";
14
+ import Typeof from "../../../Types/Typeof";
15
+ import VMUtil from "../VM/VMAPI";
16
+ import DataToProcess from "./DataToProcess";
17
+ import logger from "../Logger";
18
+
19
+ /**
20
+ * Utility for building template variable storage map and processing dynamic placeholders
21
+ * shared between Incident and Alert auto-creation.
22
+ */
23
+ export default class MonitorTemplateUtil {
24
+ /**
25
+ * Build a storage map of variables available for templating based on monitor type.
26
+ */
27
+ public static buildTemplateStorageMap(data: {
28
+ monitorType: MonitorType;
29
+ dataToProcess: DataToProcess;
30
+ }): JSONObject {
31
+ let storageMap: JSONObject = {};
32
+
33
+ try {
34
+ if (
35
+ data.monitorType === MonitorType.API ||
36
+ data.monitorType === MonitorType.Website
37
+ ) {
38
+ let responseBody: JSONObject | null = null;
39
+ try {
40
+ responseBody = JSON.parse(
41
+ ((data.dataToProcess as ProbeMonitorResponse)
42
+ .responseBody as string) || "{}",
43
+ );
44
+ } catch (err) {
45
+ logger.error(err);
46
+ responseBody = (data.dataToProcess as ProbeMonitorResponse)
47
+ .responseBody as JSONObject;
48
+ }
49
+
50
+ if (
51
+ typeof responseBody === Typeof.String &&
52
+ responseBody?.toString() === ""
53
+ ) {
54
+ responseBody = {};
55
+ }
56
+
57
+ storageMap = {
58
+ responseBody: responseBody,
59
+ responseHeaders: (data.dataToProcess as ProbeMonitorResponse)
60
+ .responseHeaders,
61
+ responseStatusCode: (data.dataToProcess as ProbeMonitorResponse)
62
+ .responseCode,
63
+ responseTimeInMs: (data.dataToProcess as ProbeMonitorResponse)
64
+ .responseTimeInMs,
65
+ isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
66
+ } as JSONObject;
67
+ }
68
+
69
+ if (data.monitorType === MonitorType.IncomingRequest) {
70
+ storageMap = {
71
+ requestBody: (data.dataToProcess as IncomingMonitorRequest)
72
+ .requestBody,
73
+ requestHeaders: (data.dataToProcess as IncomingMonitorRequest)
74
+ .requestHeaders,
75
+ requestMethod: (data.dataToProcess as IncomingMonitorRequest)
76
+ .requestMethod,
77
+ incomingRequestReceivedAt: (
78
+ data.dataToProcess as IncomingMonitorRequest
79
+ ).incomingRequestReceivedAt,
80
+ } as JSONObject;
81
+ }
82
+
83
+ if (
84
+ data.monitorType === MonitorType.Ping ||
85
+ data.monitorType === MonitorType.IP ||
86
+ data.monitorType === MonitorType.Port
87
+ ) {
88
+ storageMap = {
89
+ isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
90
+ responseTimeInMs: (data.dataToProcess as ProbeMonitorResponse)
91
+ .responseTimeInMs,
92
+ failureCause: (data.dataToProcess as ProbeMonitorResponse)
93
+ .failureCause,
94
+ isTimeout: (data.dataToProcess as ProbeMonitorResponse).isTimeout,
95
+ } as JSONObject;
96
+ }
97
+
98
+ if (data.monitorType === MonitorType.SSLCertificate) {
99
+ const sslResponse: SslMonitorResponse | undefined = (
100
+ data.dataToProcess as ProbeMonitorResponse
101
+ ).sslResponse;
102
+ storageMap = {
103
+ isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
104
+ isSelfSigned: sslResponse?.isSelfSigned,
105
+ createdAt: sslResponse?.createdAt,
106
+ expiresAt: sslResponse?.expiresAt,
107
+ commonName: sslResponse?.commonName,
108
+ organizationalUnit: sslResponse?.organizationalUnit,
109
+ organization: sslResponse?.organization,
110
+ locality: sslResponse?.locality,
111
+ state: sslResponse?.state,
112
+ country: sslResponse?.country,
113
+ serialNumber: sslResponse?.serialNumber,
114
+ fingerprint: sslResponse?.fingerprint,
115
+ fingerprint256: sslResponse?.fingerprint256,
116
+ failureCause: (data.dataToProcess as ProbeMonitorResponse)
117
+ .failureCause,
118
+ } as JSONObject;
119
+ }
120
+
121
+ if (data.monitorType === MonitorType.Server) {
122
+ const serverResponse: ServerMonitorResponse =
123
+ data.dataToProcess as ServerMonitorResponse;
124
+ const infraMetrics: BasicInfrastructureMetrics | undefined =
125
+ serverResponse.basicInfrastructureMetrics;
126
+
127
+ storageMap = {
128
+ hostname: serverResponse.hostname,
129
+ requestReceivedAt: serverResponse.requestReceivedAt,
130
+ failureCause: serverResponse.failureCause,
131
+ } as JSONObject;
132
+
133
+ // Add CPU metrics if available
134
+ if (infraMetrics?.cpuMetrics) {
135
+ storageMap["cpuUsagePercent"] = infraMetrics.cpuMetrics.percentUsed;
136
+ storageMap["cpuCores"] = infraMetrics.cpuMetrics.cores;
137
+ }
138
+
139
+ // Add memory metrics if available
140
+ if (infraMetrics?.memoryMetrics) {
141
+ storageMap["memoryUsagePercent"] =
142
+ infraMetrics.memoryMetrics.percentUsed;
143
+ storageMap["memoryFreePercent"] =
144
+ infraMetrics.memoryMetrics.percentFree;
145
+ storageMap["memoryTotalBytes"] = infraMetrics.memoryMetrics.total;
146
+ }
147
+
148
+ // Add disk metrics if available
149
+ if (infraMetrics?.diskMetrics) {
150
+ storageMap["diskMetrics"] = infraMetrics.diskMetrics.map(
151
+ (disk: BasicDiskMetrics) => {
152
+ return {
153
+ diskPath: disk.diskPath,
154
+ usagePercent: disk.percentUsed,
155
+ freePercent: disk.percentFree,
156
+ totalBytes: disk.total,
157
+ };
158
+ },
159
+ );
160
+ }
161
+
162
+ // Add processes if available
163
+ if (serverResponse.processes) {
164
+ storageMap["processes"] = serverResponse.processes.map(
165
+ (process: ServerProcess) => {
166
+ return {
167
+ pid: process.pid,
168
+ name: process.name,
169
+ command: process.command,
170
+ };
171
+ },
172
+ );
173
+ }
174
+ }
175
+
176
+ if (
177
+ data.monitorType === MonitorType.SyntheticMonitor ||
178
+ data.monitorType === MonitorType.CustomJavaScriptCode
179
+ ) {
180
+ const customCodeResponse: CustomCodeMonitorResponse | undefined = (
181
+ data.dataToProcess as ProbeMonitorResponse
182
+ ).customCodeMonitorResponse;
183
+ const syntheticResponse: SyntheticMonitorResponse[] | undefined = (
184
+ data.dataToProcess as ProbeMonitorResponse
185
+ ).syntheticMonitorResponse;
186
+
187
+ storageMap = {
188
+ executionTimeInMs: customCodeResponse?.executionTimeInMS,
189
+ result: customCodeResponse?.result,
190
+ scriptError: customCodeResponse?.scriptError,
191
+ logMessages: customCodeResponse?.logMessages || [],
192
+ failureCause: (data.dataToProcess as ProbeMonitorResponse)
193
+ .failureCause,
194
+ } as JSONObject;
195
+
196
+ // Add synthetic monitor specific fields if available
197
+ if (syntheticResponse && syntheticResponse.length > 0) {
198
+ const firstResponse: SyntheticMonitorResponse = syntheticResponse[0]!;
199
+ if (firstResponse) {
200
+ storageMap["screenshots"] = firstResponse.screenshots;
201
+ storageMap["browserType"] = firstResponse.browserType;
202
+ storageMap["screenSizeType"] = firstResponse.screenSizeType;
203
+ }
204
+ }
205
+ }
206
+ } catch (err) {
207
+ logger.error(err);
208
+ }
209
+
210
+ logger.debug(`Storage Map: ${JSON.stringify(storageMap, null, 2)}`);
211
+
212
+ return storageMap;
213
+ }
214
+
215
+ /**
216
+ * Replace {{var}} placeholders in the given string with values from the storage map.
217
+ */
218
+ public static processTemplateString(data: {
219
+ value: string | undefined;
220
+ storageMap: JSONObject;
221
+ }): string {
222
+ try {
223
+ const { value, storageMap } = data;
224
+
225
+ if (!value) {
226
+ return "";
227
+ }
228
+
229
+ let replaced: string = VMUtil.replaceValueInPlace(
230
+ storageMap,
231
+ value,
232
+ false,
233
+ );
234
+ replaced =
235
+ replaced !== undefined && replaced !== null ? `${replaced}` : "";
236
+
237
+ logger.debug(`Original Value: ${data.value}`);
238
+ logger.debug(`Replaced Value: ${replaced}`);
239
+
240
+ return replaced;
241
+ } catch (err) {
242
+ logger.error(err);
243
+ return data.value || "";
244
+ }
245
+ }
246
+ }
@@ -82,10 +82,19 @@ export default class VMUtil {
82
82
  }
83
83
 
84
84
  for (const variable of variablesInArgument) {
85
- const valueToReplaceInPlace: string = VMUtil.deepFind(
85
+ const foundValue: JSONValue = VMUtil.deepFind(
86
86
  storageMap as any,
87
87
  variable as any,
88
- ) as string;
88
+ );
89
+
90
+ let valueToReplaceInPlace: string;
91
+
92
+ // Properly serialize objects to JSON strings
93
+ if (typeof foundValue === "object" && foundValue !== null) {
94
+ valueToReplaceInPlace = JSON.stringify(foundValue, null, 2);
95
+ } else {
96
+ valueToReplaceInPlace = foundValue as string;
97
+ }
89
98
 
90
99
  if (valueToReplaceInPlaceCopy.trim() === "{{" + variable + "}}") {
91
100
  valueToReplaceInPlaceCopy = valueToReplaceInPlace;
@@ -14,9 +14,14 @@ import ReactFlow, {
14
14
  Position,
15
15
  } from "reactflow";
16
16
  import "reactflow/dist/style.css";
17
- import type { ElkExtendedEdge, ElkNode } from "elkjs";
17
+ import type { ElkExtendedEdge, ElkNode, LayoutOptions } from "elkjs";
18
18
  import ELK from "elkjs/lib/elk.bundled.js";
19
19
 
20
+ // Minimal interface for the ELK layout engine we rely on.
21
+ interface ElkLayoutEngine {
22
+ layout: (graph: ElkNode) => Promise<ElkNode>;
23
+ }
24
+
20
25
  export interface ServiceNodeData {
21
26
  id: string;
22
27
  name: string;
@@ -96,7 +101,7 @@ const ServiceDependencyGraph: FunctionComponent<ServiceDependencyGraphProps> = (
96
101
  const [rfEdges, setRfEdges] = useState<Edge[]>([]);
97
102
 
98
103
  useEffect((): void => {
99
- const elk: any = new ELK();
104
+ const elk: ElkLayoutEngine = new ELK() as unknown as ElkLayoutEngine;
100
105
  // fixed node dimensions for layout (px)
101
106
  const NODE_WIDTH: number = 220;
102
107
  const NODE_HEIGHT: number = 56;
@@ -123,7 +128,7 @@ const ServiceDependencyGraph: FunctionComponent<ServiceDependencyGraphProps> = (
123
128
  "elk.layered.spacing.nodeNodeBetweenLayers": "120",
124
129
  "elk.spacing.nodeNode": "60",
125
130
  "elk.edgeRouting": "POLYLINE",
126
- },
131
+ } as LayoutOptions,
127
132
  children: sortedServices.map((svc: ServiceNodeData): ElkNode => {
128
133
  return {
129
134
  id: svc.id,
@@ -142,9 +147,9 @@ const ServiceDependencyGraph: FunctionComponent<ServiceDependencyGraphProps> = (
142
147
 
143
148
  const layout: () => Promise<void> = async (): Promise<void> => {
144
149
  try {
145
- const res: any = await elk.layout(elkGraph as any);
150
+ const res: ElkNode = (await elk.layout(elkGraph)) as ElkNode; // casting to bundled ElkNode shape
146
151
  const placedNodes: Node[] = (res.children || []).map(
147
- (child: any): Node => {
152
+ (child: ElkNode): Node => {
148
153
  const svc: ServiceNodeData | undefined = sortedServices.find(
149
154
  (s: ServiceNodeData): boolean => {
150
155
  return s.id === child.id;
@@ -64,7 +64,8 @@ const Input: FunctionComponent<ComponentProps> = (
64
64
 
65
65
  const [value, setValue] = useState<string | Date>("");
66
66
  const [displayValue, setDisplayValue] = useState<string>("");
67
- const ref: any = useRef<any>(null);
67
+ const ref: React.MutableRefObject<HTMLInputElement | null> =
68
+ useRef<HTMLInputElement | null>(null);
68
69
 
69
70
  useEffect(() => {
70
71
  if (
@@ -120,9 +121,9 @@ const Input: FunctionComponent<ComponentProps> = (
120
121
  }, [value]);
121
122
 
122
123
  useEffect(() => {
123
- const input: any = ref.current;
124
+ const input: HTMLInputElement | null = ref.current;
124
125
  if (input) {
125
- (input as any).value = displayValue;
126
+ input.value = displayValue;
126
127
  }
127
128
  }, [ref, displayValue]);
128
129
 
@@ -195,7 +196,7 @@ const Input: FunctionComponent<ComponentProps> = (
195
196
  tabIndex={props.tabIndex}
196
197
  onKeyDown={
197
198
  props.onEnterPress
198
- ? (event: any) => {
199
+ ? (event: React.KeyboardEvent<HTMLInputElement>) => {
199
200
  if (event.key === "Enter") {
200
201
  props.onEnterPress?.();
201
202
  }
@@ -27,7 +27,9 @@ export interface ComponentProps {
27
27
  const ArgumentsForm: FunctionComponent<ComponentProps> = (
28
28
  props: ComponentProps,
29
29
  ): ReactElement => {
30
- const formRef: any = useRef<FormProps<FormValues<JSONObject>>>(null);
30
+ const formRef: React.MutableRefObject<FormProps<
31
+ FormValues<JSONObject>
32
+ > | null> = useRef<FormProps<FormValues<JSONObject>> | null>(null);
31
33
  const [component, setComponent] = useState<NodeDataProp>(props.component);
32
34
  const [showVariableModal, setShowVariableModal] = useState<boolean>(false);
33
35
  const [showComponentPickerModal, setShowComponentPickerModal] =
@@ -143,7 +145,7 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
143
145
  }}
144
146
  onSave={(variableId: string) => {
145
147
  setShowVariableModal(false);
146
- formRef.current.setFieldValue(
148
+ formRef.current?.setFieldValue(
147
149
  selectedArgId,
148
150
  (component.arguments && component.arguments[selectedArgId]
149
151
  ? component.arguments[selectedArgId]
@@ -161,7 +163,7 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
161
163
  }}
162
164
  onSave={(returnValuePath: string) => {
163
165
  setShowComponentPickerModal(false);
164
- formRef.current.setFieldValue(
166
+ formRef.current?.setFieldValue(
165
167
  selectedArgId,
166
168
  (component.arguments && component.arguments[selectedArgId]
167
169
  ? component.arguments[selectedArgId]