@oneuptime/common 10.0.6 → 10.0.8

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 (57) hide show
  1. package/Server/Middleware/UserAuthorization.ts +36 -0
  2. package/Server/Services/MonitorService.ts +9 -0
  3. package/Server/Services/UserNotificationRuleService.ts +83 -0
  4. package/Server/Types/Markdown.ts +17 -4
  5. package/Server/Utils/Browser.ts +3 -3
  6. package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +158 -0
  7. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +15 -0
  8. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +32 -0
  9. package/Server/Utils/VM/VMRunner.ts +273 -52
  10. package/Server/Views/Partials/Head.ejs +3 -1
  11. package/Tests/Server/Middleware/UserAuthorization.test.ts +1 -0
  12. package/Types/Monitor/CriteriaFilter.ts +12 -2
  13. package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +16 -0
  14. package/Types/Monitor/ExternalStatusPageProviderType.ts +8 -0
  15. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  16. package/Types/Monitor/MonitorStep.ts +34 -0
  17. package/Types/Monitor/MonitorStepExternalStatusPageMonitor.ts +48 -0
  18. package/Types/Monitor/MonitorType.ts +16 -2
  19. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  20. package/Utils/Monitor/MonitorMetricType.ts +3 -1
  21. package/build/dist/Server/Middleware/UserAuthorization.js +30 -1
  22. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  23. package/build/dist/Server/Services/MonitorService.js +7 -1
  24. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  25. package/build/dist/Server/Services/UserNotificationRuleService.js +58 -5
  26. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  27. package/build/dist/Server/Types/Markdown.js +16 -4
  28. package/build/dist/Server/Types/Markdown.js.map +1 -1
  29. package/build/dist/Server/Utils/Browser.js +3 -3
  30. package/build/dist/Server/Utils/Browser.js.map +1 -1
  31. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +119 -0
  32. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -0
  33. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  34. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  35. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
  36. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  37. package/build/dist/Server/Utils/VM/VMRunner.js +243 -49
  38. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  39. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +1 -0
  40. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  41. package/build/dist/Types/Monitor/CriteriaFilter.js +11 -2
  42. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  43. package/build/dist/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.js +2 -0
  44. package/build/dist/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.js.map +1 -0
  45. package/build/dist/Types/Monitor/ExternalStatusPageProviderType.js +9 -0
  46. package/build/dist/Types/Monitor/ExternalStatusPageProviderType.js.map +1 -0
  47. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  48. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  49. package/build/dist/Types/Monitor/MonitorStep.js +22 -0
  50. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  51. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js +32 -0
  52. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js.map +1 -0
  53. package/build/dist/Types/Monitor/MonitorType.js +14 -2
  54. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  55. package/build/dist/Utils/Monitor/MonitorMetricType.js +3 -1
  56. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  57. package/package.json +1 -1
@@ -77,22 +77,46 @@ export default class VMRunner {
77
77
  };
78
78
  `);
79
79
 
80
- // axios (get, post, put, delete) - bridged via applySyncPromise
80
+ /*
81
+ * axios (get, head, options, post, put, patch, delete, request)
82
+ * bridged via applySyncPromise.
83
+ *
84
+ * For GET/HEAD/OPTIONS/DELETE: args = [method, url, configJson?]
85
+ * For POST/PUT/PATCH: args = [method, url, bodyJson?, configJson?]
86
+ * For REQUEST: args = ['request', '', configJson]
87
+ */
81
88
  const axiosRef: ivm.Reference<
82
- (method: string, url: string, dataOrConfig?: string) => Promise<string>
89
+ (
90
+ method: string,
91
+ url: string,
92
+ arg1?: string,
93
+ arg2?: string,
94
+ ) => Promise<string>
83
95
  > = new ivm.Reference(
84
96
  async (
85
97
  method: string,
86
98
  url: string,
87
- dataOrConfig?: string,
99
+ arg1?: string,
100
+ arg2?: string,
88
101
  ): Promise<string> => {
89
- const parsed: JSONObject | undefined = dataOrConfig
90
- ? (JSON.parse(dataOrConfig) as JSONObject)
102
+ const methodsWithBody: string[] = ["post", "put", "patch"];
103
+ const hasBody: boolean = methodsWithBody.includes(method);
104
+
105
+ /*
106
+ * For POST/PUT/PATCH: arg1=body, arg2=config
107
+ * For GET/HEAD/OPTIONS/DELETE/REQUEST: arg1=config
108
+ */
109
+ const body: JSONObject | undefined =
110
+ hasBody && arg1 ? (JSON.parse(arg1) as JSONObject) : undefined;
111
+
112
+ const configStr: string | undefined = hasBody ? arg2 : arg1;
113
+ const config: JSONObject | undefined = configStr
114
+ ? (JSON.parse(configStr) as JSONObject)
91
115
  : undefined;
92
116
 
93
117
  // Reconstruct real http/https Agents from serialized markers
94
- if (parsed) {
95
- const httpsAgentConfig: JSONObject | undefined = parsed[
118
+ if (config) {
119
+ const httpsAgentConfig: JSONObject | undefined = config[
96
120
  "httpsAgent"
97
121
  ] as JSONObject | undefined;
98
122
 
@@ -100,12 +124,12 @@ export default class VMRunner {
100
124
  httpsAgentConfig &&
101
125
  httpsAgentConfig["__agentType"] === "__https_agent__"
102
126
  ) {
103
- parsed["httpsAgent"] = new https.Agent(
127
+ config["httpsAgent"] = new https.Agent(
104
128
  httpsAgentConfig["options"] as https.AgentOptions,
105
129
  ) as unknown as JSONObject;
106
130
  }
107
131
 
108
- const httpAgentConfig: JSONObject | undefined = parsed[
132
+ const httpAgentConfig: JSONObject | undefined = config[
109
133
  "httpAgent"
110
134
  ] as JSONObject | undefined;
111
135
 
@@ -113,63 +137,246 @@ export default class VMRunner {
113
137
  httpAgentConfig &&
114
138
  httpAgentConfig["__agentType"] === "__http_agent__"
115
139
  ) {
116
- parsed["httpAgent"] = new http.Agent(
140
+ config["httpAgent"] = new http.Agent(
117
141
  httpAgentConfig["options"] as http.AgentOptions,
118
142
  ) as unknown as JSONObject;
119
143
  }
120
144
  }
121
145
 
122
- let response: AxiosResponse;
123
-
124
- switch (method) {
125
- case "get":
126
- response = await axios.get(url, parsed);
127
- break;
128
- case "post":
129
- response = await axios.post(url, parsed);
130
- break;
131
- case "put":
132
- response = await axios.put(url, parsed);
133
- break;
134
- case "delete":
135
- response = await axios.delete(url, parsed);
136
- break;
137
- default:
138
- throw new Error(`Unsupported HTTP method: ${method}`);
139
- }
146
+ /**
147
+ * Helper: convert AxiosHeaders (or any header-like object) to a
148
+ * plain record so it can be safely JSON-serialised.
149
+ */
150
+ const toPlainHeaders: (
151
+ headers: unknown,
152
+ ) => Record<string, unknown> = (
153
+ headers: unknown,
154
+ ): Record<string, unknown> => {
155
+ const plain: Record<string, unknown> = {};
156
+ if (headers) {
157
+ for (const hKey of Object.keys(
158
+ headers as Record<string, unknown>,
159
+ )) {
160
+ plain[hKey] = (headers as Record<string, unknown>)[hKey];
161
+ }
162
+ }
163
+ return plain;
164
+ };
165
+
166
+ try {
167
+ let response: AxiosResponse;
168
+
169
+ switch (method) {
170
+ case "get":
171
+ response = await axios.get(url, config);
172
+ break;
173
+ case "head":
174
+ response = await axios.head(url, config);
175
+ break;
176
+ case "options":
177
+ response = await axios.options(url, config);
178
+ break;
179
+ case "post":
180
+ response = await axios.post(url, body, config);
181
+ break;
182
+ case "put":
183
+ response = await axios.put(url, body, config);
184
+ break;
185
+ case "patch":
186
+ response = await axios.patch(url, body, config);
187
+ break;
188
+ case "delete":
189
+ response = await axios.delete(url, config);
190
+ break;
191
+ case "request":
192
+ response = await axios.request(
193
+ config as Parameters<typeof axios.request>[0],
194
+ );
195
+ break;
196
+ default:
197
+ throw new Error(`Unsupported HTTP method: ${method}`);
198
+ }
140
199
 
141
- return JSON.stringify({
142
- status: response.status,
143
- headers: response.headers,
144
- data: response.data,
145
- });
200
+ /*
201
+ * Convert AxiosHeaders to a plain object before serializing.
202
+ * JSON.stringify calls AxiosHeaders.toJSON(key) with a truthy key,
203
+ * which makes it join array headers (like set-cookie) with commas.
204
+ * This produces invalid Cookie headers when user code forwards them.
205
+ */
206
+ return JSON.stringify({
207
+ status: response.status,
208
+ headers: toPlainHeaders(response.headers),
209
+ data: response.data,
210
+ });
211
+ } catch (err: unknown) {
212
+ /*
213
+ * If this is an axios error with a response (4xx, 5xx, etc.),
214
+ * return the error details as JSON so the sandbox-side axios
215
+ * wrapper can reconstruct error.response for user code.
216
+ */
217
+ const axiosErr: {
218
+ isAxiosError?: boolean;
219
+ response?: AxiosResponse<any, any, Record<string, unknown>>;
220
+ message?: string;
221
+ } = err as {
222
+ isAxiosError?: boolean;
223
+ response?: AxiosResponse;
224
+ message?: string;
225
+ };
226
+
227
+ if (axiosErr.isAxiosError && axiosErr.response) {
228
+ return JSON.stringify({
229
+ __isAxiosError: true,
230
+ message: axiosErr.message || "Request failed",
231
+ status: axiosErr.response.status,
232
+ statusText: axiosErr.response.statusText,
233
+ headers: toPlainHeaders(axiosErr.response.headers),
234
+ data: axiosErr.response.data,
235
+ });
236
+ }
237
+
238
+ throw err;
239
+ }
146
240
  },
147
241
  );
148
242
 
149
243
  await jail.set("_axiosRef", axiosRef);
150
244
 
151
245
  await context.eval(`
152
- const axios = {
153
- get: async (url, config) => {
154
- const r = await _axiosRef.applySyncPromise(undefined, ['get', url, config ? JSON.stringify(config) : undefined]);
155
- return JSON.parse(r);
156
- },
157
- post: async (url, data) => {
158
- const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined]);
159
- return JSON.parse(r);
160
- },
161
- put: async (url, data) => {
162
- const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined]);
163
- return JSON.parse(r);
164
- },
165
- delete: async (url, config) => {
166
- const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, config ? JSON.stringify(config) : undefined]);
167
- return JSON.parse(r);
168
- },
169
- };
246
+ function _assertNoFunctions(obj, path) {
247
+ if (!obj || typeof obj !== 'object') return;
248
+ if (Array.isArray(obj)) {
249
+ for (let i = 0; i < obj.length; i++) {
250
+ const fullPath = path + '[' + i + ']';
251
+ if (typeof obj[i] === 'function') {
252
+ throw new Error(
253
+ 'Functions are not supported in axios config because of security. ' +
254
+ 'Found a function at "' + fullPath + '". Please remove it or replace it with a plain value.'
255
+ );
256
+ }
257
+ if (obj[i] && typeof obj[i] === 'object') {
258
+ _assertNoFunctions(obj[i], fullPath);
259
+ }
260
+ }
261
+ return;
262
+ }
263
+ for (const key of Object.keys(obj)) {
264
+ const fullPath = path ? path + '.' + key : key;
265
+ if (typeof obj[key] === 'function') {
266
+ throw new Error(
267
+ 'Functions are not supported in axios config because of security. ' +
268
+ 'Found a function at "' + fullPath + '". Please remove it or replace it with a plain value.'
269
+ );
270
+ }
271
+ if (obj[key] && typeof obj[key] === 'object') {
272
+ _assertNoFunctions(obj[key], fullPath);
273
+ }
274
+ }
275
+ }
276
+
277
+ function _parseAxiosResult(r) {
278
+ const parsed = JSON.parse(r);
279
+ if (parsed && parsed.__isAxiosError) {
280
+ const err = new Error(parsed.message);
281
+ err.response = {
282
+ status: parsed.status,
283
+ statusText: parsed.statusText,
284
+ headers: parsed.headers,
285
+ data: parsed.data,
286
+ };
287
+ err.isAxiosError = true;
288
+ err.status = parsed.status;
289
+ throw err;
290
+ }
291
+ return parsed;
292
+ }
293
+
294
+ function _makeAxiosInstance(defaults) {
295
+ function mergeConfig(overrides) {
296
+ if (!defaults && !overrides) return undefined;
297
+ if (!defaults) return overrides;
298
+ if (!overrides) return Object.assign({}, defaults);
299
+ const merged = Object.assign({}, defaults, overrides);
300
+ if (defaults.headers && overrides.headers) {
301
+ merged.headers = Object.assign({}, defaults.headers, overrides.headers);
302
+ }
303
+ return merged;
304
+ }
305
+
306
+ async function _request(config) {
307
+ const merged = mergeConfig(config);
308
+ if (merged) _assertNoFunctions(merged, 'config');
309
+ const r = await _axiosRef.applySyncPromise(undefined, ['request', '', merged ? JSON.stringify(merged) : undefined]);
310
+ return _parseAxiosResult(r);
311
+ }
312
+
313
+ // Make instance callable: axios(config) or axios(url, config)
314
+ const instance = async function(urlOrConfig, config) {
315
+ if (typeof urlOrConfig === 'object') {
316
+ return _request(urlOrConfig);
317
+ }
318
+ return _request(Object.assign({}, config || {}, { url: urlOrConfig }));
319
+ };
320
+
321
+ instance.request = _request;
322
+ instance.get = async (url, config) => {
323
+ const merged = mergeConfig(config);
324
+ if (merged) _assertNoFunctions(merged, 'config');
325
+ const r = await _axiosRef.applySyncPromise(undefined, ['get', url, merged ? JSON.stringify(merged) : undefined]);
326
+ return _parseAxiosResult(r);
327
+ };
328
+ instance.head = async (url, config) => {
329
+ const merged = mergeConfig(config);
330
+ if (merged) _assertNoFunctions(merged, 'config');
331
+ const r = await _axiosRef.applySyncPromise(undefined, ['head', url, merged ? JSON.stringify(merged) : undefined]);
332
+ return _parseAxiosResult(r);
333
+ };
334
+ instance.options = async (url, config) => {
335
+ const merged = mergeConfig(config);
336
+ if (merged) _assertNoFunctions(merged, 'config');
337
+ const r = await _axiosRef.applySyncPromise(undefined, ['options', url, merged ? JSON.stringify(merged) : undefined]);
338
+ return _parseAxiosResult(r);
339
+ };
340
+ instance.post = async (url, data, config) => {
341
+ const merged = mergeConfig(config);
342
+ if (data) _assertNoFunctions(data, 'data');
343
+ if (merged) _assertNoFunctions(merged, 'config');
344
+ const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
345
+ return _parseAxiosResult(r);
346
+ };
347
+ instance.put = async (url, data, config) => {
348
+ const merged = mergeConfig(config);
349
+ if (data) _assertNoFunctions(data, 'data');
350
+ if (merged) _assertNoFunctions(merged, 'config');
351
+ const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
352
+ return _parseAxiosResult(r);
353
+ };
354
+ instance.patch = async (url, data, config) => {
355
+ const merged = mergeConfig(config);
356
+ if (data) _assertNoFunctions(data, 'data');
357
+ if (merged) _assertNoFunctions(merged, 'config');
358
+ const r = await _axiosRef.applySyncPromise(undefined, ['patch', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
359
+ return _parseAxiosResult(r);
360
+ };
361
+ instance.delete = async (url, config) => {
362
+ const merged = mergeConfig(config);
363
+ if (merged) _assertNoFunctions(merged, 'config');
364
+ const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, merged ? JSON.stringify(merged) : undefined]);
365
+ return _parseAxiosResult(r);
366
+ };
367
+ instance.create = (instanceDefaults) => {
368
+ if (instanceDefaults) _assertNoFunctions(instanceDefaults, 'defaults');
369
+ const combinedDefaults = mergeConfig(instanceDefaults);
370
+ return _makeAxiosInstance(combinedDefaults);
371
+ };
372
+
373
+ return instance;
374
+ }
375
+
376
+ const axios = _makeAxiosInstance(null);
170
377
  `);
171
378
 
172
- // crypto (createHash, createHmac, randomBytes) - bridged via applySync
379
+ // crypto (createHash, createHmac, randomBytes, randomUUID, randomInt) - bridged via applySync
173
380
  const cryptoRef: ivm.Reference<
174
381
  (op: string, ...args: string[]) => string
175
382
  > = new ivm.Reference((op: string, ...args: string[]): string => {
@@ -192,6 +399,13 @@ export default class VMRunner {
192
399
  const [size] = args;
193
400
  return crypto.randomBytes(parseInt(size!)).toString("hex");
194
401
  }
402
+ case "randomUUID": {
403
+ return crypto.randomUUID();
404
+ }
405
+ case "randomInt": {
406
+ const [min, max] = args;
407
+ return String(crypto.randomInt(parseInt(min!), parseInt(max!)));
408
+ }
195
409
  default:
196
410
  throw new Error(`Unsupported crypto operation: ${op}`);
197
411
  }
@@ -214,6 +428,13 @@ export default class VMRunner {
214
428
  randomBytes: (size) => ({
215
429
  toString(enc) { return _cryptoRef.applySync(undefined, ['randomBytes', String(size)]); }
216
430
  }),
431
+ randomUUID: () => {
432
+ return _cryptoRef.applySync(undefined, ['randomUUID']);
433
+ },
434
+ randomInt: (minOrMax, max) => {
435
+ if (max === undefined) { max = minOrMax; minOrMax = 0; }
436
+ return Number(_cryptoRef.applySync(undefined, ['randomInt', String(minOrMax), String(max)]));
437
+ },
217
438
  };
218
439
  `);
219
440
 
@@ -1,10 +1,11 @@
1
1
  <link rel="preconnect" href="https://fonts.googleapis.com">
2
2
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3
+ <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" as="style">
3
4
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
4
5
  rel="stylesheet">
5
6
  <style>
6
7
  * {
7
- font-family: Inter;
8
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
8
9
  }
9
10
 
10
11
 
@@ -34,6 +35,7 @@
34
35
  width: auto;
35
36
  }
36
37
  </style>
38
+ <link rel="preload" href="https://cdn.tailwindcss.com" as="script">
37
39
  <script src="https://cdn.tailwindcss.com"></script>
38
40
 
39
41
  <!-- Google Tag Manager -->
@@ -93,6 +93,7 @@ describe("UserMiddleware", () => {
93
93
 
94
94
  const req: Partial<ExpressRequest> = {
95
95
  cookies: { "sso-token": mockedAccessToken },
96
+ headers: {},
96
97
  };
97
98
 
98
99
  test("should return an empty object when ssoToken is not passed", () => {
@@ -74,6 +74,13 @@ export enum CheckOn {
74
74
  DomainNameServer = "Domain Name Server",
75
75
  DomainStatusCode = "Domain Status Code",
76
76
  DomainIsExpired = "Domain Is Expired",
77
+
78
+ // External Status Page monitors.
79
+ ExternalStatusPageIsOnline = "External Status Page Is Online",
80
+ ExternalStatusPageOverallStatus = "External Status Page Overall Status",
81
+ ExternalStatusPageComponentStatus = "External Status Page Component Status",
82
+ ExternalStatusPageActiveIncidents = "External Status Page Active Incidents",
83
+ ExternalStatusPageResponseTime = "External Status Page Response Time (in ms)",
77
84
  }
78
85
 
79
86
  export interface ServerMonitorOptions {
@@ -159,7 +166,8 @@ export class CriteriaFilterUtil {
159
166
  checkOn === CheckOn.IsOnline ||
160
167
  checkOn === CheckOn.SnmpIsOnline ||
161
168
  checkOn === CheckOn.DnsIsOnline ||
162
- checkOn === CheckOn.DomainIsExpired
169
+ checkOn === CheckOn.DomainIsExpired ||
170
+ checkOn === CheckOn.ExternalStatusPageIsOnline
163
171
  ) {
164
172
  return false;
165
173
  }
@@ -229,7 +237,9 @@ export class CriteriaFilterUtil {
229
237
  checkOn === CheckOn.SnmpResponseTime ||
230
238
  checkOn === CheckOn.SnmpIsOnline ||
231
239
  checkOn === CheckOn.DnsResponseTime ||
232
- checkOn === CheckOn.DnsIsOnline
240
+ checkOn === CheckOn.DnsIsOnline ||
241
+ checkOn === CheckOn.ExternalStatusPageResponseTime ||
242
+ checkOn === CheckOn.ExternalStatusPageIsOnline
233
243
  );
234
244
  }
235
245
  }
@@ -0,0 +1,16 @@
1
+ export interface ExternalStatusPageComponentStatus {
2
+ name: string;
3
+ status: string;
4
+ description?: string | undefined;
5
+ }
6
+
7
+ export default interface ExternalStatusPageMonitorResponse {
8
+ isOnline: boolean;
9
+ overallStatus: string;
10
+ componentStatuses: Array<ExternalStatusPageComponentStatus>;
11
+ activeIncidentCount: number;
12
+ responseTimeInMs: number;
13
+ failureCause: string;
14
+ rawBody?: string | undefined;
15
+ isTimeout?: boolean | undefined;
16
+ }
@@ -0,0 +1,8 @@
1
+ enum ExternalStatusPageProviderType {
2
+ AtlassianStatuspage = "Atlassian Statuspage",
3
+ RSS = "RSS",
4
+ Atom = "Atom",
5
+ Auto = "Auto",
6
+ }
7
+
8
+ export default ExternalStatusPageProviderType;
@@ -448,6 +448,33 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
448
448
  return monitorCriteriaInstance;
449
449
  }
450
450
 
451
+ if (arg.monitorType === MonitorType.ExternalStatusPage) {
452
+ const monitorCriteriaInstance: MonitorCriteriaInstance =
453
+ new MonitorCriteriaInstance();
454
+
455
+ monitorCriteriaInstance.data = {
456
+ id: ObjectID.generate().toString(),
457
+ monitorStatusId: arg.monitorStatusId,
458
+ filterCondition: FilterCondition.All,
459
+ filters: [
460
+ {
461
+ checkOn: CheckOn.ExternalStatusPageIsOnline,
462
+ filterType: FilterType.True,
463
+ value: undefined,
464
+ },
465
+ ],
466
+ incidents: [],
467
+ alerts: [],
468
+ createAlerts: false,
469
+ changeMonitorStatus: true,
470
+ createIncidents: false,
471
+ name: `Check if ${arg.monitorName} is online`,
472
+ description: `This criteria checks if the ${arg.monitorName} external status page is reachable`,
473
+ };
474
+
475
+ return monitorCriteriaInstance;
476
+ }
477
+
451
478
  return null;
452
479
  }
453
480
 
@@ -629,6 +656,46 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
629
656
  };
630
657
  }
631
658
 
659
+ if (arg.monitorType === MonitorType.ExternalStatusPage) {
660
+ monitorCriteriaInstance.data = {
661
+ id: ObjectID.generate().toString(),
662
+ monitorStatusId: arg.monitorStatusId,
663
+ filterCondition: FilterCondition.Any,
664
+ filters: [
665
+ {
666
+ checkOn: CheckOn.ExternalStatusPageIsOnline,
667
+ filterType: FilterType.False,
668
+ value: undefined,
669
+ },
670
+ ],
671
+ incidents: [
672
+ {
673
+ title: `${arg.monitorName} is offline`,
674
+ description: `${arg.monitorName} external status page is currently unreachable.`,
675
+ incidentSeverityId: arg.incidentSeverityId,
676
+ autoResolveIncident: true,
677
+ id: ObjectID.generate().toString(),
678
+ onCallPolicyIds: [],
679
+ },
680
+ ],
681
+ changeMonitorStatus: true,
682
+ createIncidents: true,
683
+ createAlerts: false,
684
+ alerts: [
685
+ {
686
+ title: `${arg.monitorName} is offline`,
687
+ description: `${arg.monitorName} external status page is currently unreachable.`,
688
+ alertSeverityId: arg.alertSeverityId,
689
+ autoResolveAlert: true,
690
+ id: ObjectID.generate().toString(),
691
+ onCallPolicyIds: [],
692
+ },
693
+ ],
694
+ name: `Check if ${arg.monitorName} is offline`,
695
+ description: `This criteria checks if the ${arg.monitorName} external status page is unreachable`,
696
+ };
697
+ }
698
+
632
699
  if (
633
700
  arg.monitorType === MonitorType.API ||
634
701
  arg.monitorType === MonitorType.Website
@@ -35,6 +35,9 @@ import MonitorStepDnsMonitor, {
35
35
  import MonitorStepDomainMonitor, {
36
36
  MonitorStepDomainMonitorUtil,
37
37
  } from "./MonitorStepDomainMonitor";
38
+ import MonitorStepExternalStatusPageMonitor, {
39
+ MonitorStepExternalStatusPageMonitorUtil,
40
+ } from "./MonitorStepExternalStatusPageMonitor";
38
41
  import Zod, { ZodSchema } from "../../Utils/Schema/Zod";
39
42
 
40
43
  export interface MonitorStepType {
@@ -84,6 +87,9 @@ export interface MonitorStepType {
84
87
 
85
88
  // Domain monitor
86
89
  domainMonitor?: MonitorStepDomainMonitor | undefined;
90
+
91
+ // External Status Page monitor
92
+ externalStatusPageMonitor?: MonitorStepExternalStatusPageMonitor | undefined;
87
93
  }
88
94
 
89
95
  export default class MonitorStep extends DatabaseProperty {
@@ -112,6 +118,7 @@ export default class MonitorStep extends DatabaseProperty {
112
118
  snmpMonitor: undefined,
113
119
  dnsMonitor: undefined,
114
120
  domainMonitor: undefined,
121
+ externalStatusPageMonitor: undefined,
115
122
  };
116
123
  }
117
124
 
@@ -145,6 +152,7 @@ export default class MonitorStep extends DatabaseProperty {
145
152
  snmpMonitor: undefined,
146
153
  dnsMonitor: undefined,
147
154
  domainMonitor: undefined,
155
+ externalStatusPageMonitor: undefined,
148
156
  };
149
157
 
150
158
  return monitorStep;
@@ -252,6 +260,13 @@ export default class MonitorStep extends DatabaseProperty {
252
260
  return this;
253
261
  }
254
262
 
263
+ public setExternalStatusPageMonitor(
264
+ externalStatusPageMonitor: MonitorStepExternalStatusPageMonitor,
265
+ ): MonitorStep {
266
+ this.data!.externalStatusPageMonitor = externalStatusPageMonitor;
267
+ return this;
268
+ }
269
+
255
270
  public setCustomCode(customCode: string): MonitorStep {
256
271
  this.data!.customCode = customCode;
257
272
  return this;
@@ -380,6 +395,16 @@ export default class MonitorStep extends DatabaseProperty {
380
395
  }
381
396
  }
382
397
 
398
+ if (monitorType === MonitorType.ExternalStatusPage) {
399
+ if (!value.data.externalStatusPageMonitor) {
400
+ return "External status page configuration is required";
401
+ }
402
+
403
+ if (!value.data.externalStatusPageMonitor.statusPageUrl) {
404
+ return "Status page URL is required";
405
+ }
406
+ }
407
+
383
408
  return null;
384
409
  }
385
410
 
@@ -431,6 +456,11 @@ export default class MonitorStep extends DatabaseProperty {
431
456
  domainMonitor: this.data.domainMonitor
432
457
  ? MonitorStepDomainMonitorUtil.toJSON(this.data.domainMonitor)
433
458
  : undefined,
459
+ externalStatusPageMonitor: this.data.externalStatusPageMonitor
460
+ ? MonitorStepExternalStatusPageMonitorUtil.toJSON(
461
+ this.data.externalStatusPageMonitor,
462
+ )
463
+ : undefined,
434
464
  },
435
465
  });
436
466
  }
@@ -542,6 +572,9 @@ export default class MonitorStep extends DatabaseProperty {
542
572
  domainMonitor: json["domainMonitor"]
543
573
  ? (json["domainMonitor"] as JSONObject)
544
574
  : undefined,
575
+ externalStatusPageMonitor: json["externalStatusPageMonitor"]
576
+ ? (json["externalStatusPageMonitor"] as JSONObject)
577
+ : undefined,
545
578
  }) as any;
546
579
 
547
580
  return monitorStep;
@@ -569,6 +602,7 @@ export default class MonitorStep extends DatabaseProperty {
569
602
  snmpMonitor: Zod.any().optional(),
570
603
  dnsMonitor: Zod.any().optional(),
571
604
  domainMonitor: Zod.any().optional(),
605
+ externalStatusPageMonitor: Zod.any().optional(),
572
606
  }).openapi({
573
607
  type: "object",
574
608
  example: {