Skip to content

Commit

Permalink
feat: upgrade to version 3.2.0 with improved monitor evaluation funct…
Browse files Browse the repository at this point in the history
…ions and enhanced API support
  • Loading branch information
rajnandan1 committed Feb 27, 2025
1 parent 6a2375a commit af65404
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 78 deletions.
86 changes: 86 additions & 0 deletions docs/changelogs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,92 @@ description: Changelogs for Kener

Here are the changelogs for Kener. Changelogs are only published when there are new features or breaking changes.

## v3.2.0

<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.gif" alt="🚀" width="32" height="32">
</picture>

### Features

- **Improved Monitor Evaluation Functions**

- Replaced unsafe `eval()` with secure `Function()` constructors across all monitor types
- Added support for using modules (like cheerio) directly in evaluation functions

- **Enhanced API Monitors**

- Raw response data is now passed directly to eval functions instead of base64 encoded
- Added `modules` parameter with access to cheerio for HTML parsing
- Updated default evaluation function to use the new parameter structure

- **Improved TCP & Ping Monitors**

- Simplified evaluation functions with direct access to ping/TCP data
- Removed unnecessary base64 encoding/decoding steps
- Better error handling for invalid evaluation functions

- **Documentation Updates**
- Updated all examples and documentation for the new evaluation function signatures
- Added more detailed explanations of input parameters
- Improved examples showing usage with the new parameter structure

### Breaking Changes

- **Monitor Evaluation Functions**
- Custom evaluation functions will need to be updated to the new parameter structure
- API monitors: `(statusCode, responseTime, responseRaw, modules)` instead of `(statusCode, responseTime, responseDataBase64)`
- TCP/Ping monitors: `(arrayOfPings)` instead of `(responseDataBase64)`

### Fixes

- Fixed "cheerio is undefined" errors in API monitor evaluations
- Improved error handling and logging for monitor evaluation failures
- Security enhancements by removing `eval()` usage

### Migration

If you're using custom evaluation functions in your monitors, you'll need to update them to the new format:

#### API Monitors

```javascript
// Old format
(async function (statusCode, responseTime, responseDataBase64) {
const resp = atob(responseDataBase64)
// Your logic here
})
```

```javascript
// New format
(async function (statusCode, responseTime, responseRaw, modules) {
// responseRaw is the direct response - no need to decode
// Access cheerio with modules.cheerio
// Your logic here
})
```

#### TCP/Ping Monitors

```javascript
// Old format
(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
// Your logic here
})
```

```javascript
// New format
(async function (arrayOfPings) {
// arrayOfPings is directly available - no need to decode
// Your logic here
})
```


## v3.1.8

<picture>
Expand Down
24 changes: 10 additions & 14 deletions docs/monitors-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (statusCode, responseTime, responseDataBase64) {
(async function (statusCode, responseTime, responseRaw, modules) {
let statusCodeShort = Math.floor(statusCode/100);
let status = 'DOWN'
if(statusCodeShort >=2 && statusCodeShort <= 3) {
Expand All @@ -66,21 +66,17 @@ This is an anonymous JS function, it should return a **Promise**, that resolves

- `statusCode` **REQUIRED** is a number. It is the HTTP status code
- `responseTime` **REQUIRED**is a number. It is the latency in milliseconds
- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it
- `responseRaw` **REQUIRED** is the raw response of the API
- `modules` is an object that has [`cheerio`](https://www.npmjs.com/package/cheerio) that you can use to parse HTML. `const $ = modules.cheerio.load(responseRaw)`


```js
let decodedResp = atob(responseDataBase64)
//if the response is a json object
//let jsonResp = JSON.parse(decodedResp)
```

### Example 1

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
(async function (statusCode, responseTime, responseDataBase64) {
const resp = atob(responseDataBase64) //convert base64 to string
(async function (statusCode, responseTime, responseRaw, modules) {

let status = "DOWN"

Expand All @@ -95,7 +91,7 @@ The following example shows how to use the eval function to evaluate the respons
//if the status code is 5XX then the status is DOWN
if (/^[5]\d{2}$/.test(statusCode)) status = "DOWN"

if (resp.includes("Unknown Error")) {
if (responseRaw.includes("Unknown Error")) {
status = "DOWN"
}

Expand All @@ -111,8 +107,8 @@ 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.

```js
(async function (statusCode, responseTime, responseDataBase64) {
let htmlString = atob(responseDataBase64)
(async function (statusCode, responseTime, responseRaw, modules) {
let htmlString = responseRaw;
const scriptTags = htmlString.match(/<script[^>]*src="([^"]+)"[^>]*>/g)
if (scriptTags && scriptTags.length >= 2) {
// Extract the second last script tag's src attribute
Expand Down Expand Up @@ -143,9 +139,9 @@ This next example shows how to call another API withing eval. It is scrapping th
The next example shows how to use cheerio to parse bitbucket status page and check if all the components are operational. If all the components are operational then the status is UP else it is DOWN.

```js
(async function (statusCode, responseTime, responseDataBase64) {
(async function (statusCode, responseTime, responseDataBase64, modules) {
let html = atob(responseDataBase64)
const $ = cheerio.load(html)
const $ = modules.cheerio.load(html)
const components = $(".components-section .components-container .component-container")
let status = true
components.each((index, element) => {
Expand Down
15 changes: 5 additions & 10 deletions docs/monitors-ping.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
> `{status:"DEGRADED", latency: 200}`.
```javascript
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
Expand All @@ -49,12 +48,9 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
})
```

- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it and the JSON parse it. Once parse it will be an array of objects.
- `arrayOfPings` **REQUIRED** is an array of Ping Response Objects as shown below.

```js
let decodedResp = atob(responseDataBase64)
let jsonResp = JSON.parse(decodedResp)
console.log(jsonResp)
```json
/*
[
{
Expand Down Expand Up @@ -93,7 +89,7 @@ console.log(jsonResp)

### Understanding the Input

The input to the eval function is a base64 encoded string. You will have to decode it and then parse it to get the array of objects. Each object in the array represents the ping response of a host.
Each object in the array represents the ping response of a host.

- `alive` is a boolean. It is true if the host is alive and false if the host is down.
- `min` is a string. It is the minimum latency of the pings.
Expand All @@ -109,8 +105,7 @@ The input to the eval function is a base64 encoded string. You will have to deco
The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.

```javascript
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
Expand Down
41 changes: 18 additions & 23 deletions docs/monitors-tcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to set up and work with TCP monitors in kener.

# TCP Monitors

TCP monitors are used to monitor the livenees of your servers. You can use TCP monitors to monitor the uptime of your servers and get notified when they are down.
TCP monitors are used to monitor the liveness of your servers. You can use TCP monitors to monitor the uptime of your servers and get notified when they are down.

<div class="border rounded-md">

Expand All @@ -17,10 +17,10 @@ TCP monitors are used to monitor the livenees of your servers. You can use TCP m

You can add as many hosts as you want to monitor. The host can be an IP(IP4 and IP6) address or a domain name.

- Type: Choose the type of host you want to monitor. It can be either `IP4` or `IP6` or `DOMAIN`.
- Host: Enter the IP address or domain name of the host you want to monitor.
- Port: Enter the port number of the host you want to monitor.
- Timeout: Enter the timeout in milliseconds for each ping request of each host
- Type: Choose the type of host you want to monitor. It can be either `IP4` or `IP6` or `DOMAIN`.
- Host: Enter the IP address or domain name of the host you want to monitor.
- Port: Enter the port number of the host you want to monitor.
- Timeout: Enter the timeout in milliseconds for each ping request of each host

## Eval

Expand All @@ -32,8 +32,7 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
> `{status:"DEGRADED", latency: 200}`.
```javascript
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
Expand All @@ -53,12 +52,9 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
})
```

- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it and the JSON parse it. Once parse it will be an array of objects.
- `arrayOfPings` **REQUIRED** is an array of TCP Response Objects as shown below.

```js
let decodedResp = atob(responseDataBase64)
let jsonResp = JSON.parse(decodedResp)
console.log(jsonResp)
```json
/*
[
{
Expand Down Expand Up @@ -88,24 +84,23 @@ console.log(jsonResp)

### Understanding the Input

The input to the eval function is a base64 encoded string. You will have to decode it and then parse it to get the array of objects. Each object in the array represents the ping response of a host.
Each object in the array represents the tcp response of a host.

- `host`: The host that was pinged.
- `port`: The port that was pinged. Defaults to 80 if not provided.
- `type`: The type of IP address. Can be `IP4` or `IP6`.
- `status`: The status of the ping. Can be `open` , `error` or `timeout`.
- `open`: The host is reachable.
- `error`: There was an error while pinging the host.
- `timeout`: The host did not respond in time.
- `latency`: The time taken to ping the host. This is in milliseconds.
- `host`: The host that was pinged.
- `port`: The port that was pinged. Defaults to 80 if not provided.
- `type`: The type of IP address. Can be `IP4` or `IP6`.
- `status`: The status of the ping. Can be `open` , `error` or `timeout`.
- `open`: The host is reachable.
- `error`: There was an error while pinging the host.
- `timeout`: The host did not respond in time.
- `latency`: The time taken to ping the host. This is in milliseconds.

### Example

The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.

```javascript
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
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.1.10",
"version": "3.2.0",
"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
14 changes: 10 additions & 4 deletions src/lib/server/services/apiCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import { UP, DOWN, DEGRADED, REALTIME, TIMEOUT, ERROR, MANUAL } from "../constants.js";
import * as cheerio from "cheerio";

const defaultEval = `(async function (statusCode, responseTime, responseData) {
const defaultEval = `(async function (statusCode, responseTime, responseRaw, modules) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
Expand Down Expand Up @@ -115,12 +115,18 @@ class ApiCall {
}
}

resp = Buffer.from(resp).toString("base64");

let evalResp = undefined;
let modules = { cheerio };

try {
evalResp = await eval(monitorEval + `(${statusCode}, ${latency}, "${resp}")`);
const evalFunction = new Function(
"statusCode",
"responseTime",
"responseData",
"modules",
`return (${monitorEval})(statusCode, responseTime, responseData, modules);`,
);
evalResp = await evalFunction(statusCode, latency, resp, modules);
} catch (error) {
console.log(`Error in monitorEval for ${tag}`, error.message);
}
Expand Down
32 changes: 11 additions & 21 deletions src/lib/server/services/pingCall.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
// @ts-nocheck
import axios from "axios";
import { Ping } from "../ping.js";
import {
UP,
DOWN,
DEGRADED,
REALTIME,
TIMEOUT,
ERROR,
MANUAL,
} from "../constants.js";
import { UP, DOWN, DEGRADED, REALTIME, TIMEOUT, ERROR, MANUAL } from "../constants.js";

const defaultPingEval = `(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64));
const defaultPingEval = `(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
Expand All @@ -36,9 +27,7 @@ class PingCall {

async execute() {
let hosts = this.monitor.type_data.hosts;
let pingEval = !!this.monitor.type_data.pingEval
? this.monitor.type_data.pingEval
: defaultPingEval;
let pingEval = !!this.monitor.type_data.pingEval ? this.monitor.type_data.pingEval : defaultPingEval;
let tag = this.monitor.tag;
if (hosts === undefined) {
console.log(
Expand All @@ -54,20 +43,21 @@ class PingCall {
let arrayOfPings = [];
for (let i = 0; i < hosts.length; i++) {
const host = hosts[i];
arrayOfPings.push(
await Ping(host.type, host.host, host.timeout, host.count),
);
arrayOfPings.push(await Ping(host.type, host.host, host.timeout, host.count));
}
let respBase64 = Buffer.from(JSON.stringify(arrayOfPings)).toString(
"base64",
);

let evalResp = undefined;

try {
evalResp = await eval(pingEval + `("${respBase64}")`);
const evalFunction = new Function("arrayOfPings", `return (${pingEval})(arrayOfPings);`);
evalResp = await evalFunction(arrayOfPings);
} catch (error) {
console.log(`Error in pingEval for ${tag}`, error.message);
return {
status: DOWN,
latency: 0,
type: ERROR,
};
}
//reduce to get the status
return {
Expand Down
Loading

0 comments on commit af65404

Please sign in to comment.