Skip to content

Commit 1f2159f

Browse files
committed
implement suggestions
1 parent 80a1b93 commit 1f2159f

File tree

3 files changed

+1155
-110
lines changed

3 files changed

+1155
-110
lines changed

server/monitor-types/globalping.js

Lines changed: 162 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@ const { log, UP, DOWN, evaluateJsonQuery } = require("../../src/util");
55
const { checkStatusCode, getOidcTokenClientCredentials, encodeBase64, getDaysRemaining, checkCertExpiryNotifications } = require("../util-server");
66
const { R } = require("redbean-node");
77

8+
/**
9+
* Globalping is a free and open-source tool that allows you to run network tests
10+
* and measurements from thousands of community hosted probes around the world.
11+
*
12+
* Library documentation: https://github.com/jsdelivr/globalping-typescript
13+
*
14+
* API documentation: https://globalping.io/docs/api.globalping.io
15+
*/
816
class GlobalpingMonitorType extends MonitorType {
917
name = "globalping";
1018

11-
agent = "";
19+
httpUserAgent = "";
1220

1321
/**
1422
* @inheritdoc
1523
*/
16-
constructor(agent) {
24+
constructor(httpUserAgent) {
1725
super();
18-
this.agent = agent;
26+
this.httpUserAgent = httpUserAgent;
1927
}
2028

2129
/**
@@ -25,7 +33,7 @@ class GlobalpingMonitorType extends MonitorType {
2533
const apiKey = await Settings.get("globalpingApiToken");
2634
const client = new Globalping({
2735
auth: apiKey,
28-
agent: this.agent,
36+
agent: this.httpUserAgent,
2937
});
3038

3139
const hasAPIToken = !!apiKey;
@@ -40,7 +48,12 @@ class GlobalpingMonitorType extends MonitorType {
4048
}
4149

4250
/**
43-
* @inheritdoc
51+
* Handles ping monitors.
52+
* @param {Client} client - The client object.
53+
* @param {Monitor} monitor - The monitor object.
54+
* @param {Heartbeat} heartbeat - The heartbeat object.
55+
* @param {boolean} hasAPIToken - Whether the monitor has an API token.
56+
* @returns {Promise<void>} A promise that resolves when the ping monitor is handled.
4457
*/
4558
async ping(client, monitor, heartbeat, hasAPIToken) {
4659
const opts = {
@@ -107,7 +120,12 @@ class GlobalpingMonitorType extends MonitorType {
107120
}
108121

109122
/**
110-
* @inheritdoc
123+
* Handles HTTP monitors.
124+
* @param {Client} client - The client object.
125+
* @param {Monitor} monitor - The monitor object.
126+
* @param {Heartbeat} heartbeat - The heartbeat object.
127+
* @param {boolean} hasAPIToken - Whether the monitor has an API token.
128+
* @returns {Promise<void>} A promise that resolves when the HTTP monitor is handled.
111129
*/
112130
async http(client, monitor, heartbeat, hasAPIToken) {
113131
const url = new URL(monitor.url);
@@ -142,6 +160,7 @@ class GlobalpingMonitorType extends MonitorType {
142160
host: url.hostname,
143161
path: url.pathname,
144162
query: url.search ? url.search.slice(1) : undefined,
163+
method: monitor.method,
145164
headers
146165
},
147166
protocol: protocol,
@@ -204,34 +223,13 @@ class GlobalpingMonitorType extends MonitorType {
204223

205224
// keyword
206225
if (monitor.keyword) {
207-
let data = result.rawOutput;
208-
let keywordFound = data.includes(monitor.keyword);
209-
210-
if (keywordFound !== !Boolean(monitor.invertKeyword)) {
211-
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ").trim();
212-
if (data.length > 50) {
213-
data = data.substring(0, 47) + "...";
214-
}
215-
throw new Error(heartbeat.msg + ", but keyword is " +
216-
(keywordFound ? "present" : "not") + " in [" + data + "]");
217-
218-
}
219-
220-
heartbeat.msg += this.formatResponse(probe, ", keyword " + (keywordFound ? "is" : "not") + " found");
221-
heartbeat.status = UP;
226+
await this.handleKeywordForHTTP(monitor, heartbeat, result, probe);
222227
return;
223228
}
224229

225230
// json-query
226231
if (monitor.expectedValue) {
227-
const { status, response } = await evaluateJsonQuery(result.rawOutput, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
228-
229-
if (!status) {
230-
throw new Error(this.formatResponse(probe, `JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`));
231-
}
232-
233-
heartbeat.msg = this.formatResponse(probe, `JSON query passes (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`);
234-
heartbeat.status = UP;
232+
await this.handleJSONQueryForHTTP(monitor, heartbeat, result, probe);
235233
return;
236234
}
237235

@@ -242,99 +240,57 @@ class GlobalpingMonitorType extends MonitorType {
242240
}
243241

244242
/**
245-
* @inheritdoc
243+
* Handles keyword for HTTP monitors.
244+
* @param {Monitor} monitor - The monitor object.
245+
* @param {Heartbeat} heartbeat - The heartbeat object.
246+
* @param {Result} result - The result object.
247+
* @param {Probe} probe - The probe object.
248+
* @returns {Promise<void>} A promise that resolves when the keyword is handled.
246249
*/
247-
formatApiError(error) {
248-
let str = `${error.type} ${error.message}.`;
249-
if (error.params) {
250-
for (const key in error.params) {
251-
str += `\n${key}: ${error.params[key]}`;
250+
async handleKeywordForHTTP(monitor, heartbeat, result, probe) {
251+
let data = result.rawOutput;
252+
let keywordFound = data.includes(monitor.keyword);
253+
254+
if (keywordFound !== !Boolean(monitor.invertKeyword)) {
255+
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ").trim();
256+
if (data.length > 50) {
257+
data = data.substring(0, 47) + "...";
252258
}
253-
}
254-
return str;
255-
}
256-
257-
/**
258-
* @inheritdoc
259-
*/
260-
formatTooManyRequestsError(hasAPIToken) {
261-
const creditsHelpLink = "https://dash.globalping.io?view=add-credits";
262-
if (hasAPIToken) {
263-
return `You have run out of credits. Get higher limits by sponsoring us or hosting probes. Learn more at ${creditsHelpLink}.`;
264-
}
265-
return `You have run out of credits. Get higher limits by creating an account. Sign up at ${creditsHelpLink}.`;
266-
}
259+
throw new Error(heartbeat.msg + ", but keyword is " +
260+
(keywordFound ? "present" : "not") + " in [" + data + "]");
267261

268-
/**
269-
* Returns the formatted probe location string. e.g "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1)"
270-
* @param {object} probe - The probe object containing location information.
271-
* @returns {string} The formatted probe location string.
272-
*/
273-
formatProbeLocation(probe) {
274-
let tag = "";
275-
276-
for (const t of probe.tags) {
277-
// If tag ends in a number, it's likely a region code and should be displayed
278-
if (Number.isInteger(Number(t.slice(-1)))) {
279-
tag = t;
280-
break;
281-
}
282262
}
283-
return `${probe.city}${probe.state ? ` (${probe.state})` : ""
284-
}, ${probe.country}, ${probe.continent}, ${probe.network
285-
} (AS${probe.asn})${tag ? `, (${tag})` : ""}`;
286-
}
287263

288-
/**
289-
* @inheritdoc
290-
*/
291-
formatResponse(probe, text) {
292-
return `${this.formatProbeLocation(probe)} : ${text}`;
264+
heartbeat.msg += ", keyword " + (keywordFound ? "is" : "not") + " found";
265+
heartbeat.status = UP;
293266
}
294267

295268
/**
296-
* @inheritdoc
269+
* Handles JSON query for HTTP monitors.
270+
* @param {Monitor} monitor - The monitor object.
271+
* @param {Heartbeat} heartbeat - The heartbeat object.
272+
* @param {Result} result - The result object.
273+
* @param {Probe} probe - The probe object.
274+
* @returns {Promise<void>} A promise that resolves when the JSON query is handled.
297275
*/
298-
async getOauth2AuthHeader(monitor) {
299-
if (monitor.auth_method !== "oauth2-cc") {
300-
return {};
301-
}
302-
303-
try {
304-
if (monitor.oauthAccessToken === undefined || new Date(monitor.oauthAccessToken.expires_at * 1000) <= new Date()) {
305-
log.debug("monitor", `[${monitor.name}] The oauth access-token undefined or expired. Requesting a new token`);
306-
const oAuthAccessToken = await getOidcTokenClientCredentials(monitor.oauth_token_url, monitor.oauth_client_id, monitor.oauth_client_secret, monitor.oauth_scopes, monitor.oauth_audience, monitor.oauth_auth_method);
307-
if (monitor.oauthAccessToken?.expires_at) {
308-
log.debug("monitor", `[${monitor.name}] Obtained oauth access-token. Expires at ${new Date(monitor.oauthAccessToken?.expires_at * 1000)}`);
309-
} else {
310-
log.debug("monitor", `[${monitor.name}] Obtained oauth access-token. Time until expiry was not provided`);
311-
}
276+
async handleJSONQueryForHTTP(monitor, heartbeat, result, probe) {
277+
const { status, response } = await evaluateJsonQuery(result.rawOutput, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
312278

313-
monitor.oauthAccessToken = oAuthAccessToken;
314-
}
315-
return {
316-
"Authorization": monitor.oauthAccessToken.token_type + " " + monitor.oauthAccessToken.access_token,
317-
};
318-
} catch (e) {
319-
throw new Error("The oauth config is invalid. " + e.message);
279+
if (!status) {
280+
throw new Error(this.formatResponse(probe, `JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`));
320281
}
321-
}
322282

323-
/**
324-
* @inheritdoc
325-
*/
326-
getBasicAuthHeader(monitor) {
327-
if (monitor.auth_method !== "basic") {
328-
return {};
329-
}
330-
331-
return {
332-
"Authorization": "Basic " + encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass),
333-
};
283+
heartbeat.msg = this.formatResponse(probe, `JSON query passes (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`);
284+
heartbeat.status = UP;
334285
}
335286

336287
/**
337-
* @inheritdoc
288+
* Updates the TLS information for a monitor.
289+
* @param {object} monitor - The monitor object.
290+
* @param {string} protocol - The protocol used for the monitor.
291+
* @param {object} probe - The probe object containing location information.
292+
* @param {object} tlsInfo - The TLS information object.
293+
* @returns {Promise<void>}
338294
*/
339295
async handleTLSInfo(monitor, protocol, probe, tlsInfo) {
340296
if (!tlsInfo) {
@@ -390,6 +346,105 @@ class GlobalpingMonitorType extends MonitorType {
390346
await checkCertExpiryNotifications(monitor, certResult);
391347
}
392348
}
349+
350+
/**
351+
* Generates the OAuth2 authorization header for the monitor if it is enabled.
352+
* @param {object} monitor - The monitor object containing authentication information.
353+
* @returns {Promise<object>} The OAuth2 authorization header.
354+
*/
355+
async getOauth2AuthHeader(monitor) {
356+
if (monitor.auth_method !== "oauth2-cc") {
357+
return {};
358+
}
359+
360+
try {
361+
if (new Date((monitor.oauthAccessToken?.expires_at || 0) * 1000) <= new Date()) {
362+
const oAuthAccessToken = await getOidcTokenClientCredentials(monitor.oauth_token_url, monitor.oauth_client_id, monitor.oauth_client_secret, monitor.oauth_scopes, monitor.oauth_audience, monitor.oauth_auth_method);
363+
log.debug("monitor", `[${monitor.name}] Obtained oauth access-token. Expires at ${new Date(oAuthAccessToken.expires_at * 1000)}`);
364+
365+
monitor.oauthAccessToken = oAuthAccessToken;
366+
}
367+
return {
368+
"Authorization": monitor.oauthAccessToken.token_type + " " + monitor.oauthAccessToken.access_token,
369+
};
370+
} catch (e) {
371+
throw new Error("The oauth config is invalid. " + e.message);
372+
}
373+
}
374+
375+
/**
376+
* Generates the basic authentication header for a monitor if it is enabled.
377+
* @param {object} monitor - The monitor object.
378+
* @returns {object} The basic authentication header.
379+
*/
380+
getBasicAuthHeader(monitor) {
381+
if (monitor.auth_method !== "basic") {
382+
return {};
383+
}
384+
385+
return {
386+
"Authorization": "Basic " + encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass),
387+
};
388+
}
389+
390+
/**
391+
* Generates a formatted error message for API errors.
392+
* @param {Error} error - The API error object.
393+
* @returns {string} The formatted error message.
394+
*/
395+
formatApiError(error) {
396+
let str = `${error.type} ${error.message}.`;
397+
if (error.params) {
398+
for (const key in error.params) {
399+
str += `\n${key}: ${error.params[key]}`;
400+
}
401+
}
402+
return str;
403+
}
404+
405+
/**
406+
* Generates a formatted error message for too many requests.
407+
* @param {boolean} hasAPIToken - Indicates whether an API token is available.
408+
* @returns {string} The formatted error message.
409+
*/
410+
formatTooManyRequestsError(hasAPIToken) {
411+
const creditsHelpLink = "https://dash.globalping.io?view=add-credits";
412+
if (hasAPIToken) {
413+
return `You have run out of credits. Get higher limits by sponsoring us or hosting probes. Learn more at ${creditsHelpLink}.`;
414+
}
415+
return `You have run out of credits. Get higher limits by creating an account. Sign up at ${creditsHelpLink}.`;
416+
}
417+
418+
/**
419+
* Returns the formatted probe location string. e.g "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1)"
420+
* @param {object} probe - The probe object containing location information.
421+
* @returns {string} The formatted probe location string.
422+
*/
423+
formatProbeLocation(probe) {
424+
let tag = "";
425+
426+
for (const t of probe.tags) {
427+
// If tag ends in a number, it's likely a region code and should be displayed
428+
if (Number.isInteger(Number(t.slice(-1)))) {
429+
tag = t;
430+
break;
431+
}
432+
}
433+
return `${probe.city}${probe.state ? ` (${probe.state})` : ""
434+
}, ${probe.country}, ${probe.continent}, ${probe.network
435+
} (AS${probe.asn})${tag ? `, (${tag})` : ""}`;
436+
}
437+
438+
/**
439+
* Formats the response text with the probe location.
440+
* @param {object} probe - The probe object containing location information.
441+
* @param {string} text - The response text to append.
442+
* @returns {string} The formatted response text.
443+
*/
444+
formatResponse(probe, text) {
445+
return `${this.formatProbeLocation(probe)} : ${text}`;
446+
}
447+
393448
}
394449

395450
module.exports = {

src/pages/EditMonitor.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@
425425

426426
<!-- Port -->
427427
<!-- For TCP Port / Steam / MQTT / Radius Type / SNMP -->
428-
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'smtp' || monitor.type === 'snmp' || (monitor.subtype === 'ping' && monitor.protocol === 'TCP') " class="my-3">
428+
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'smtp' || monitor.type === 'snmp' || (monitor.type === 'globalping' && monitor.subtype === 'ping' && monitor.protocol === 'TCP') " class="my-3">
429429
<label for="port" class="form-label">{{ $t("Port") }}</label>
430430
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
431431
</div>
@@ -774,7 +774,7 @@
774774

775775
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
776776

777-
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.subtype === 'http'" class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
777+
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || (monitor.type === 'globalping' && monitor.subtype === 'http')" class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
778778
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox" :disabled="monitor.ignoreTls">
779779
<label class="form-check-label" for="expiry-notification">
780780
{{ $t("Certificate Expiry Notification") }}
@@ -1919,7 +1919,7 @@ message HealthCheckResponse {
19191919
this.monitor.port = "1812";
19201920
} else if (this.monitor.type === "snmp") {
19211921
this.monitor.port = "161";
1922-
} else if (this.monitor.subtype === "ping") {
1922+
} else if (this.monitor.type === "globalping" && this.monitor.subtype === "ping") {
19231923
this.monitor.port = "80";
19241924
} else {
19251925
this.monitor.port = undefined;

0 commit comments

Comments
 (0)