Skip to content

Commit

Permalink
Merge pull request #228 from rajnandan1/release/3.0.6
Browse files Browse the repository at this point in the history
feat: support of promises in eval
  • Loading branch information
rajnandan1 authored Jan 29, 2025
2 parents 198bd6c + d9b900f commit eb87647
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 41 deletions.
40 changes: 33 additions & 7 deletions docs/monitors-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ The headers are used to define the headers that should be sent with the request.

The eval is used to define the JavaScript code that should be used to evaluate the response. It is optional and has be a valid JavaScript code.

This is a anonymous JS function, by default it looks like this.
This is an anonymous JS function, it should return a **Promise**, that resolves or rejects to `{status, latency}`, by default it looks like this.

> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and latency(number)
> `{status:"DEGRADED", latency: 200}`.
```javascript
(function (statusCode, responseTime, responseDataBase64) {
(async function (statusCode, responseTime, responseDataBase64) {
let statusCodeShort = Math.floor(statusCode/100);
let status = 'DOWN'
if(statusCodeShort >=2 && statusCodeShort <= 3) {
Expand All @@ -74,16 +74,12 @@ let decodedResp = atob(responseDataBase64);
//let jsonResp = JSON.parse(decodedResp)
```

<div class="note danger">
The eval is validated against (200, 1000, "e30=") which translates to (200, 1000, '{}') within the function. So whatever function you write should be able to handle these values otherwise you won't be able to save the monitor.
</div>

#### Example

The following example shows how to use the eval function to evaluate the response. The function checks if the status code is 2XX then the status is UP, if the status code is 5XX then the status is DOWN. If the response contains the word `Unknown Error` then the status is DOWN. If the response time is greater than 2000 then the status is DEGRADED.

```javascript
(function (statusCode, responseTime, responseDataBase64) {
(async function (statusCode, responseTime, responseDataBase64) {
const resp = atob(responseDataBase64); //convert base64 to string

let status = "DOWN";
Expand All @@ -110,6 +106,36 @@ The following example shows how to use the eval function to evaluate the respons
});
```

This next example shows how to call another API withing eval. It is scrapping the second last script tag from the response and checking if the heading is "No recent issues" then the status is UP else it is DOWN.

```javascript
(async function raj(statusCode, responseTime, responseDataBase64) {
let htmlString = atob(responseDataBase64);
const scriptTags = htmlString.match(/<script[^>]*src="([^"]+)"[^>]*>/g);
if (scriptTags && scriptTags.length >= 2) {
// Extract the second last script tag's src attribute
const secondLastScript = scriptTags[scriptTags.length - 2];
const srcMatch = secondLastScript.match(/src="([^"]+)"/);
const secondLastScriptSrc = srcMatch ? srcMatch[1] : null;

let jsResp = await fetch(secondLastScriptSrc); //api call
let jsRespText = await jsResp.text();
//check if heading":"No recent issues" exists
let noRecentIssues = jsRespText.indexOf('heading":"No recent issues"');
if (noRecentIssues != -1) {
return {
status: "UP",
latency: responseTime
};
}
}
return {
status: "DOWN",
latency: responseTime
};
});
```

## Examples

### Website Monitor
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kener",
"version": "3.0.5",
"version": "3.0.6",
"private": false,
"license": "MIT",
"description": "Kener: An open-source Node.js status page application for real-time service monitoring, incident management, and customizable reporting. Simplify service outage tracking, enhance incident communication, and ensure a seamless user experience.",
Expand Down
60 changes: 35 additions & 25 deletions src/lib/components/manage/monitorSheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
const dispatch = createEventDispatcher();
const defaultEval = `(function (statusCode, responseTime, responseData) {
const defaultEval = `(async function (statusCode, responseTime, responseData) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
Expand Down Expand Up @@ -81,25 +81,25 @@
let invalidFormMessage = "";
function isValidEval() {
async function isValidEval() {
try {
let evalResp = eval(newMonitor.apiConfig.eval + `(200, 1000, "e30=")`);
if (
evalResp === undefined ||
evalResp === null ||
evalResp.status === undefined ||
evalResp.status === null ||
evalResp.latency === undefined ||
evalResp.latency === null
) {
return false;
}
// let evalResp = await eval(newMonitor.apiConfig.eval + `(200, 1000, "e30=")`);
new Function(newMonitor.apiConfig.eval);
return true; // The code is valid
// if (
// evalResp === undefined ||
// evalResp === null ||
// evalResp.status === undefined ||
// evalResp.status === null ||
// evalResp.latency === undefined ||
// evalResp.latency === null
// ) {
// return false;
// }
} catch (error) {
invalidFormMessage = error.message + " in eval.";
return false;
}
return true;
}
const IsValidURL = function (url) {
return /^(http|https):\/\/[^ "]+$/.test(url);
Expand Down Expand Up @@ -154,18 +154,28 @@
return;
}
//if evali ends with semicolor throw error
if (!!newMonitor.apiConfig.eval && newMonitor.apiConfig.eval.endsWith(";")) {
invalidFormMessage = "Eval should not end with semicolon";
return;
}
//validating eval
if (!!newMonitor.apiConfig.eval && !isValidEval()) {
invalidFormMessage = invalidFormMessage + "Invalid eval";
return;
}
if (!!newMonitor.apiConfig.eval) {
newMonitor.apiConfig.eval = newMonitor.apiConfig.eval.trim();
if (newMonitor.apiConfig.eval.endsWith(";")) {
invalidFormMessage = "Eval should not end with semicolon";
return;
}
//has to start with ( and end with )
if (
!newMonitor.apiConfig.eval.startsWith("(") ||
!newMonitor.apiConfig.eval.endsWith(")")
) {
invalidFormMessage =
"Eval should start with ( and end with ). It is an anonymous function";
return;
}
if (!(await isValidEval())) {
invalidFormMessage = invalidFormMessage + "Invalid eval";
return;
}
}
newMonitor.type_data = JSON.stringify(newMonitor.apiConfig);
} else if (newMonitor.monitor_type === "PING") {
//validating hostsV4
Expand Down
16 changes: 9 additions & 7 deletions src/lib/server/cron-minute.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const apiQueue = new Queue({
autostart: true // Automatically start the queue (optional)
});

const defaultEval = `(function (statusCode, responseTime, responseData) {
const defaultEval = `(async function (statusCode, responseTime, responseData) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
Expand Down Expand Up @@ -141,7 +141,7 @@ const pingCall = async (hostsV4, hostsV6) => {
type: REALTIME
};
};
const apiCall = async (envSecrets, url, method, headers, body, timeout, monitorEval) => {
const apiCall = async (envSecrets, url, method, headers, body, timeout, monitorEval, tag) => {
let axiosHeaders = {};
axiosHeaders["User-Agent"] = "Kener/3.0.2";
axiosHeaders["Accept"] = "*/*";
Expand Down Expand Up @@ -189,7 +189,7 @@ const apiCall = async (envSecrets, url, method, headers, body, timeout, monitorE
statusCode = data.status;
resp = data.data;
} catch (err) {
console.log(`Error in apiCall ${url}`, err.message);
console.log(`Error in apiCall ${tag}`, err.message);
if (err.message.startsWith("timeout of") && err.message.endsWith("exceeded")) {
timeoutError = true;
}
Expand All @@ -213,9 +213,9 @@ const apiCall = async (envSecrets, url, method, headers, body, timeout, monitorE
let evalResp = undefined;

try {
evalResp = eval(monitorEval + `(${statusCode}, ${latency}, "${resp}")`);
evalResp = await eval(monitorEval + `(${statusCode}, ${latency}, "${resp}")`);
} catch (error) {
console.log("Error in monitorEval", error.message);
console.log(`Error in monitorEval for ${tag}`, error.message);
}

if (evalResp === undefined || evalResp === null) {
Expand Down Expand Up @@ -336,7 +336,8 @@ const Minuter = async (monitor) => {
JSON.stringify(monitor.type_data.headers),
monitor.type_data.body,
monitor.type_data.timeout,
monitor.type_data.eval
monitor.type_data.eval,
monitor.tag
);

realTimeData[startOfMinute] = apiResponse;
Expand All @@ -357,7 +358,8 @@ const Minuter = async (monitor) => {
JSON.stringify(monitor.type_data.headers),
monitor.type_data.body,
monitor.type_data.timeout,
monitor.type_data.eval
monitor.type_data.eval,
monitor.tag
).then(async (data) => {
await db.insertMonitoringData({
monitor_tag: monitor.tag,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(docs)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<img src="https://kener.ing/logo.png" class="h-8 w-8" alt="" />
<span class="text-xl font-medium">Kener Documentation</span>
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium">
v3.0.5
v3.0.6
</span>
</a>
</div>
Expand Down

0 comments on commit eb87647

Please sign in to comment.