@oneuptime/common 10.0.7 → 10.0.9
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/Server/Services/MonitorService.ts +9 -0
- package/Server/Types/Markdown.ts +17 -4
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +158 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +15 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +32 -0
- package/Server/Utils/VM/VMRunner.ts +131 -62
- package/Types/Monitor/CriteriaFilter.ts +12 -2
- package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +16 -0
- package/Types/Monitor/ExternalStatusPageProviderType.ts +8 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
- package/Types/Monitor/MonitorStep.ts +34 -0
- package/Types/Monitor/MonitorStepExternalStatusPageMonitor.ts +48 -0
- package/Types/Monitor/MonitorType.ts +16 -2
- package/Types/Probe/ProbeMonitorResponse.ts +2 -0
- package/Utils/Monitor/MonitorMetricType.ts +3 -1
- package/build/dist/Server/Services/MonitorService.js +7 -1
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +16 -4
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +119 -0
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +111 -52
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +11 -2
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/ExternalStatusPageProviderType.js +9 -0
- package/build/dist/Types/Monitor/ExternalStatusPageProviderType.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +22 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js +32 -0
- package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +14 -2
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +3 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -135,6 +135,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
135
135
|
monitorDestination = `${monitorDestination} @${firstStep.data.dnsMonitor.hostname}`;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
// For External Status Page monitors, use the statusPageUrl
|
|
140
|
+
if (
|
|
141
|
+
monitorType === MonitorType.ExternalStatusPage &&
|
|
142
|
+
firstStep?.data?.externalStatusPageMonitor
|
|
143
|
+
) {
|
|
144
|
+
monitorDestination =
|
|
145
|
+
firstStep.data.externalStatusPageMonitor.statusPageUrl || "";
|
|
146
|
+
}
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
|
package/Server/Types/Markdown.ts
CHANGED
|
@@ -103,6 +103,15 @@ export default class Markdown {
|
|
|
103
103
|
return htmlBody;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
private static escapeHtml(text: string): string {
|
|
107
|
+
return text
|
|
108
|
+
.replace(/&/g, "&")
|
|
109
|
+
.replace(/</g, "<")
|
|
110
|
+
.replace(/>/g, ">")
|
|
111
|
+
.replace(/"/g, """)
|
|
112
|
+
.replace(/'/g, "'");
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
private static getEmailRenderer(): Renderer {
|
|
107
116
|
if (this.emailRenderer !== null) {
|
|
108
117
|
return this.emailRenderer;
|
|
@@ -140,7 +149,8 @@ export default class Markdown {
|
|
|
140
149
|
if (language === "mermaid") {
|
|
141
150
|
return `<div class="mermaid">${code}</div>`;
|
|
142
151
|
}
|
|
143
|
-
|
|
152
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
153
|
+
return `<pre><code class="language-${language}">${escaped}</code></pre>`;
|
|
144
154
|
};
|
|
145
155
|
|
|
146
156
|
renderer.heading = function (text, level) {
|
|
@@ -160,7 +170,8 @@ export default class Markdown {
|
|
|
160
170
|
|
|
161
171
|
// Inline code
|
|
162
172
|
renderer.codespan = function (code) {
|
|
163
|
-
|
|
173
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
174
|
+
return `<code class="rounded-md bg-slate-100 px-1.5 py-0.5 text-sm text-slate-700 font-mono">${escaped}</code>`;
|
|
164
175
|
};
|
|
165
176
|
|
|
166
177
|
this.docsRenderer = renderer;
|
|
@@ -186,7 +197,8 @@ export default class Markdown {
|
|
|
186
197
|
};
|
|
187
198
|
|
|
188
199
|
renderer.code = function (code, language) {
|
|
189
|
-
|
|
200
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
201
|
+
return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${escaped}</code></pre>`;
|
|
190
202
|
};
|
|
191
203
|
|
|
192
204
|
renderer.heading = function (text, level) {
|
|
@@ -237,7 +249,8 @@ export default class Markdown {
|
|
|
237
249
|
|
|
238
250
|
// Inline code
|
|
239
251
|
renderer.codespan = function (code) {
|
|
240
|
-
|
|
252
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
253
|
+
return `<code class="rounded-md bg-gray-100 px-1.5 py-0.5 text-sm text-pink-600">${escaped}</code>`;
|
|
241
254
|
};
|
|
242
255
|
|
|
243
256
|
// Horizontal rule
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import DataToProcess from "../DataToProcess";
|
|
2
|
+
import CompareCriteria from "./CompareCriteria";
|
|
3
|
+
import {
|
|
4
|
+
CheckOn,
|
|
5
|
+
CriteriaFilter,
|
|
6
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
7
|
+
import ExternalStatusPageMonitorResponse from "../../../../Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse";
|
|
8
|
+
import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
|
|
9
|
+
import EvaluateOverTime from "./EvaluateOverTime";
|
|
10
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
11
|
+
import logger from "../../Logger";
|
|
12
|
+
|
|
13
|
+
export default class ExternalStatusPageMonitorCriteria {
|
|
14
|
+
@CaptureSpan()
|
|
15
|
+
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
|
16
|
+
dataToProcess: DataToProcess;
|
|
17
|
+
criteriaFilter: CriteriaFilter;
|
|
18
|
+
}): Promise<string | null> {
|
|
19
|
+
let threshold: number | string | undefined | null =
|
|
20
|
+
input.criteriaFilter.value;
|
|
21
|
+
|
|
22
|
+
const dataToProcess: ProbeMonitorResponse =
|
|
23
|
+
input.dataToProcess as ProbeMonitorResponse;
|
|
24
|
+
|
|
25
|
+
const externalStatusPageResponse:
|
|
26
|
+
| ExternalStatusPageMonitorResponse
|
|
27
|
+
| undefined = dataToProcess.externalStatusPageResponse;
|
|
28
|
+
|
|
29
|
+
let overTimeValue: Array<number | boolean> | number | boolean | undefined =
|
|
30
|
+
undefined;
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
input.criteriaFilter.evaluateOverTime &&
|
|
34
|
+
input.criteriaFilter.evaluateOverTimeOptions
|
|
35
|
+
) {
|
|
36
|
+
try {
|
|
37
|
+
overTimeValue = await EvaluateOverTime.getValueOverTime({
|
|
38
|
+
projectId: (input.dataToProcess as ProbeMonitorResponse).projectId,
|
|
39
|
+
monitorId: input.dataToProcess.monitorId!,
|
|
40
|
+
evaluateOverTimeOptions: input.criteriaFilter.evaluateOverTimeOptions,
|
|
41
|
+
metricType: input.criteriaFilter.checkOn,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (Array.isArray(overTimeValue) && overTimeValue.length === 0) {
|
|
45
|
+
overTimeValue = undefined;
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
logger.error(
|
|
49
|
+
`Error in getting over time value for ${input.criteriaFilter.checkOn}`,
|
|
50
|
+
);
|
|
51
|
+
logger.error(err);
|
|
52
|
+
overTimeValue = undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if external status page is online
|
|
57
|
+
if (input.criteriaFilter.checkOn === CheckOn.ExternalStatusPageIsOnline) {
|
|
58
|
+
const currentIsOnline: boolean | Array<boolean> =
|
|
59
|
+
(overTimeValue as Array<boolean>) ||
|
|
60
|
+
(input.dataToProcess as ProbeMonitorResponse).isOnline;
|
|
61
|
+
|
|
62
|
+
return CompareCriteria.compareCriteriaBoolean({
|
|
63
|
+
value: currentIsOnline,
|
|
64
|
+
criteriaFilter: input.criteriaFilter,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check external status page response time
|
|
69
|
+
if (
|
|
70
|
+
input.criteriaFilter.checkOn === CheckOn.ExternalStatusPageResponseTime
|
|
71
|
+
) {
|
|
72
|
+
threshold = CompareCriteria.convertToNumber(threshold);
|
|
73
|
+
|
|
74
|
+
if (threshold === null || threshold === undefined) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const currentResponseTime: number | Array<number> =
|
|
79
|
+
(overTimeValue as Array<number>) ||
|
|
80
|
+
externalStatusPageResponse?.responseTimeInMs ||
|
|
81
|
+
(input.dataToProcess as ProbeMonitorResponse).responseTimeInMs;
|
|
82
|
+
|
|
83
|
+
if (currentResponseTime === null || currentResponseTime === undefined) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
88
|
+
value: currentResponseTime,
|
|
89
|
+
threshold: threshold as number,
|
|
90
|
+
criteriaFilter: input.criteriaFilter,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check overall status
|
|
95
|
+
if (
|
|
96
|
+
input.criteriaFilter.checkOn === CheckOn.ExternalStatusPageOverallStatus
|
|
97
|
+
) {
|
|
98
|
+
if (!externalStatusPageResponse?.overallStatus) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return CompareCriteria.compareCriteriaStrings({
|
|
103
|
+
value: externalStatusPageResponse.overallStatus,
|
|
104
|
+
threshold: String(threshold),
|
|
105
|
+
criteriaFilter: input.criteriaFilter,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check component status
|
|
110
|
+
if (
|
|
111
|
+
input.criteriaFilter.checkOn === CheckOn.ExternalStatusPageComponentStatus
|
|
112
|
+
) {
|
|
113
|
+
if (
|
|
114
|
+
!externalStatusPageResponse?.componentStatuses ||
|
|
115
|
+
externalStatusPageResponse.componentStatuses.length === 0
|
|
116
|
+
) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if any component status matches the criteria
|
|
121
|
+
for (const component of externalStatusPageResponse.componentStatuses) {
|
|
122
|
+
const result: string | null = CompareCriteria.compareCriteriaStrings({
|
|
123
|
+
value: component.status,
|
|
124
|
+
threshold: String(threshold),
|
|
125
|
+
criteriaFilter: input.criteriaFilter,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result) {
|
|
129
|
+
return `Component "${component.name}": ${result}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check active incidents count
|
|
137
|
+
if (
|
|
138
|
+
input.criteriaFilter.checkOn === CheckOn.ExternalStatusPageActiveIncidents
|
|
139
|
+
) {
|
|
140
|
+
threshold = CompareCriteria.convertToNumber(threshold);
|
|
141
|
+
|
|
142
|
+
if (threshold === null || threshold === undefined) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const activeIncidents: number =
|
|
147
|
+
externalStatusPageResponse?.activeIncidentCount || 0;
|
|
148
|
+
|
|
149
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
150
|
+
value: activeIncidents,
|
|
151
|
+
threshold: threshold as number,
|
|
152
|
+
criteriaFilter: input.criteriaFilter,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -14,6 +14,7 @@ import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
|
|
|
14
14
|
import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
|
|
15
15
|
import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
|
|
16
16
|
import DomainMonitorCriteria from "./Criteria/DomainMonitorCriteria";
|
|
17
|
+
import ExternalStatusPageMonitorCriteria from "./Criteria/ExternalStatusPageMonitorCriteria";
|
|
17
18
|
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
|
|
18
19
|
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
|
19
20
|
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
|
|
@@ -519,6 +520,20 @@ ${contextBlock}
|
|
|
519
520
|
}
|
|
520
521
|
}
|
|
521
522
|
|
|
523
|
+
if (input.monitor.monitorType === MonitorType.ExternalStatusPage) {
|
|
524
|
+
const externalStatusPageResult: string | null =
|
|
525
|
+
await ExternalStatusPageMonitorCriteria.isMonitorInstanceCriteriaFilterMet(
|
|
526
|
+
{
|
|
527
|
+
dataToProcess: input.dataToProcess,
|
|
528
|
+
criteriaFilter: input.criteriaFilter,
|
|
529
|
+
},
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
if (externalStatusPageResult) {
|
|
533
|
+
return externalStatusPageResult;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
522
537
|
return null;
|
|
523
538
|
}
|
|
524
539
|
|
|
@@ -18,6 +18,9 @@ import DnsMonitorResponse, {
|
|
|
18
18
|
DnsRecordResponse,
|
|
19
19
|
} from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
|
|
20
20
|
import DomainMonitorResponse from "../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
|
|
21
|
+
import ExternalStatusPageMonitorResponse, {
|
|
22
|
+
ExternalStatusPageComponentStatus,
|
|
23
|
+
} from "../../../Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse";
|
|
21
24
|
import Typeof from "../../../Types/Typeof";
|
|
22
25
|
import VMUtil from "../VM/VMAPI";
|
|
23
26
|
import DataToProcess from "./DataToProcess";
|
|
@@ -298,6 +301,35 @@ export default class MonitorTemplateUtil {
|
|
|
298
301
|
dnssec: domainResponse?.dnssec,
|
|
299
302
|
} as JSONObject;
|
|
300
303
|
}
|
|
304
|
+
|
|
305
|
+
if (data.monitorType === MonitorType.ExternalStatusPage) {
|
|
306
|
+
const externalStatusPageResponse:
|
|
307
|
+
| ExternalStatusPageMonitorResponse
|
|
308
|
+
| undefined = (data.dataToProcess as ProbeMonitorResponse)
|
|
309
|
+
.externalStatusPageResponse;
|
|
310
|
+
|
|
311
|
+
storageMap = {
|
|
312
|
+
isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
|
|
313
|
+
responseTimeInMs: externalStatusPageResponse?.responseTimeInMs,
|
|
314
|
+
failureCause: externalStatusPageResponse?.failureCause,
|
|
315
|
+
overallStatus: externalStatusPageResponse?.overallStatus,
|
|
316
|
+
activeIncidentCount: externalStatusPageResponse?.activeIncidentCount,
|
|
317
|
+
} as JSONObject;
|
|
318
|
+
|
|
319
|
+
// Add component statuses
|
|
320
|
+
if (externalStatusPageResponse?.componentStatuses) {
|
|
321
|
+
storageMap["componentStatuses"] =
|
|
322
|
+
externalStatusPageResponse.componentStatuses.map(
|
|
323
|
+
(component: ExternalStatusPageComponentStatus) => {
|
|
324
|
+
return {
|
|
325
|
+
name: component.name,
|
|
326
|
+
status: component.status,
|
|
327
|
+
description: component.description,
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
301
333
|
} catch (err) {
|
|
302
334
|
logger.error(err);
|
|
303
335
|
}
|
|
@@ -143,62 +143,100 @@ export default class VMRunner {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
case "get":
|
|
150
|
-
response = await axios.get(url, config);
|
|
151
|
-
break;
|
|
152
|
-
case "head":
|
|
153
|
-
response = await axios.head(url, config);
|
|
154
|
-
break;
|
|
155
|
-
case "options":
|
|
156
|
-
response = await axios.options(url, config);
|
|
157
|
-
break;
|
|
158
|
-
case "post":
|
|
159
|
-
response = await axios.post(url, body, config);
|
|
160
|
-
break;
|
|
161
|
-
case "put":
|
|
162
|
-
response = await axios.put(url, body, config);
|
|
163
|
-
break;
|
|
164
|
-
case "patch":
|
|
165
|
-
response = await axios.patch(url, body, config);
|
|
166
|
-
break;
|
|
167
|
-
case "delete":
|
|
168
|
-
response = await axios.delete(url, config);
|
|
169
|
-
break;
|
|
170
|
-
case "request":
|
|
171
|
-
response = await axios.request(
|
|
172
|
-
config as Parameters<typeof axios.request>[0],
|
|
173
|
-
);
|
|
174
|
-
break;
|
|
175
|
-
default:
|
|
176
|
-
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/*
|
|
180
|
-
* Convert AxiosHeaders to a plain object before serializing.
|
|
181
|
-
* JSON.stringify calls AxiosHeaders.toJSON(key) with a truthy key,
|
|
182
|
-
* which makes it join array headers (like set-cookie) with commas.
|
|
183
|
-
* This produces invalid Cookie headers when user code forwards them.
|
|
146
|
+
/**
|
|
147
|
+
* Helper: convert AxiosHeaders (or any header-like object) to a
|
|
148
|
+
* plain record so it can be safely JSON-serialised.
|
|
184
149
|
*/
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
}
|
|
194
162
|
}
|
|
195
|
-
|
|
163
|
+
return plain;
|
|
164
|
+
};
|
|
196
165
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
}
|
|
199
|
+
|
|
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
|
+
}
|
|
202
240
|
},
|
|
203
241
|
);
|
|
204
242
|
|
|
@@ -236,6 +274,23 @@ export default class VMRunner {
|
|
|
236
274
|
}
|
|
237
275
|
}
|
|
238
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
|
+
|
|
239
294
|
function _makeAxiosInstance(defaults) {
|
|
240
295
|
function mergeConfig(overrides) {
|
|
241
296
|
if (!defaults && !overrides) return undefined;
|
|
@@ -252,7 +307,7 @@ export default class VMRunner {
|
|
|
252
307
|
const merged = mergeConfig(config);
|
|
253
308
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
254
309
|
const r = await _axiosRef.applySyncPromise(undefined, ['request', '', merged ? JSON.stringify(merged) : undefined]);
|
|
255
|
-
return
|
|
310
|
+
return _parseAxiosResult(r);
|
|
256
311
|
}
|
|
257
312
|
|
|
258
313
|
// Make instance callable: axios(config) or axios(url, config)
|
|
@@ -268,46 +323,46 @@ export default class VMRunner {
|
|
|
268
323
|
const merged = mergeConfig(config);
|
|
269
324
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
270
325
|
const r = await _axiosRef.applySyncPromise(undefined, ['get', url, merged ? JSON.stringify(merged) : undefined]);
|
|
271
|
-
return
|
|
326
|
+
return _parseAxiosResult(r);
|
|
272
327
|
};
|
|
273
328
|
instance.head = async (url, config) => {
|
|
274
329
|
const merged = mergeConfig(config);
|
|
275
330
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
276
331
|
const r = await _axiosRef.applySyncPromise(undefined, ['head', url, merged ? JSON.stringify(merged) : undefined]);
|
|
277
|
-
return
|
|
332
|
+
return _parseAxiosResult(r);
|
|
278
333
|
};
|
|
279
334
|
instance.options = async (url, config) => {
|
|
280
335
|
const merged = mergeConfig(config);
|
|
281
336
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
282
337
|
const r = await _axiosRef.applySyncPromise(undefined, ['options', url, merged ? JSON.stringify(merged) : undefined]);
|
|
283
|
-
return
|
|
338
|
+
return _parseAxiosResult(r);
|
|
284
339
|
};
|
|
285
340
|
instance.post = async (url, data, config) => {
|
|
286
341
|
const merged = mergeConfig(config);
|
|
287
342
|
if (data) _assertNoFunctions(data, 'data');
|
|
288
343
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
289
344
|
const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
|
|
290
|
-
return
|
|
345
|
+
return _parseAxiosResult(r);
|
|
291
346
|
};
|
|
292
347
|
instance.put = async (url, data, config) => {
|
|
293
348
|
const merged = mergeConfig(config);
|
|
294
349
|
if (data) _assertNoFunctions(data, 'data');
|
|
295
350
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
296
351
|
const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
|
|
297
|
-
return
|
|
352
|
+
return _parseAxiosResult(r);
|
|
298
353
|
};
|
|
299
354
|
instance.patch = async (url, data, config) => {
|
|
300
355
|
const merged = mergeConfig(config);
|
|
301
356
|
if (data) _assertNoFunctions(data, 'data');
|
|
302
357
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
303
358
|
const r = await _axiosRef.applySyncPromise(undefined, ['patch', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
|
|
304
|
-
return
|
|
359
|
+
return _parseAxiosResult(r);
|
|
305
360
|
};
|
|
306
361
|
instance.delete = async (url, config) => {
|
|
307
362
|
const merged = mergeConfig(config);
|
|
308
363
|
if (merged) _assertNoFunctions(merged, 'config');
|
|
309
364
|
const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, merged ? JSON.stringify(merged) : undefined]);
|
|
310
|
-
return
|
|
365
|
+
return _parseAxiosResult(r);
|
|
311
366
|
};
|
|
312
367
|
instance.create = (instanceDefaults) => {
|
|
313
368
|
if (instanceDefaults) _assertNoFunctions(instanceDefaults, 'defaults');
|
|
@@ -321,7 +376,7 @@ export default class VMRunner {
|
|
|
321
376
|
const axios = _makeAxiosInstance(null);
|
|
322
377
|
`);
|
|
323
378
|
|
|
324
|
-
// crypto (createHash, createHmac, randomBytes) - bridged via applySync
|
|
379
|
+
// crypto (createHash, createHmac, randomBytes, randomUUID, randomInt) - bridged via applySync
|
|
325
380
|
const cryptoRef: ivm.Reference<
|
|
326
381
|
(op: string, ...args: string[]) => string
|
|
327
382
|
> = new ivm.Reference((op: string, ...args: string[]): string => {
|
|
@@ -344,6 +399,13 @@ export default class VMRunner {
|
|
|
344
399
|
const [size] = args;
|
|
345
400
|
return crypto.randomBytes(parseInt(size!)).toString("hex");
|
|
346
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
|
+
}
|
|
347
409
|
default:
|
|
348
410
|
throw new Error(`Unsupported crypto operation: ${op}`);
|
|
349
411
|
}
|
|
@@ -366,6 +428,13 @@ export default class VMRunner {
|
|
|
366
428
|
randomBytes: (size) => ({
|
|
367
429
|
toString(enc) { return _cryptoRef.applySync(undefined, ['randomBytes', String(size)]); }
|
|
368
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
|
+
},
|
|
369
438
|
};
|
|
370
439
|
`);
|
|
371
440
|
|
|
@@ -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
|
+
}
|