mirror of
https://github.com/rajnandan1/kener.git
synced 2026-05-05 10:19:58 -05:00
+2
-1
@@ -26,4 +26,5 @@ uploads/*
|
||||
|
||||
static/uploads/*
|
||||
!static/uploads/upload.dir
|
||||
temp.txt
|
||||
temp.txt
|
||||
temp.js
|
||||
@@ -24,3 +24,113 @@ You can add as many IP addresses as you want to monitor. The IP address should b
|
||||
<p class="note danger">
|
||||
Please note that atleast one of the Host V4 or Host V6 is required.
|
||||
<p>
|
||||
|
||||
## Eval
|
||||
|
||||
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 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
|
||||
(async function (responseDataBase64) {
|
||||
let arrayOfPings = JSON.parse(atob(responseDataBase64));
|
||||
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
|
||||
return acc + ping.latency;
|
||||
}, 0);
|
||||
|
||||
let alive = arrayOfPings.reduce((acc, ping) => {
|
||||
if (ping.status === "open") {
|
||||
return acc && true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
return {
|
||||
status: alive ? "UP" : "DOWN",
|
||||
latency: parseInt(latencyTotal / arrayOfPings.length)
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
- `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.
|
||||
|
||||
```js
|
||||
let decodedResp = atob(responseDataBase64);
|
||||
let jsonResp = JSON.parse(decodedResp);
|
||||
console.log(jsonResp);
|
||||
/*
|
||||
[
|
||||
{
|
||||
"host": "smtp.resend.com",
|
||||
"port": 587,
|
||||
"type": "IP4",
|
||||
"status": "open",
|
||||
"latency": 36.750917
|
||||
},
|
||||
{
|
||||
"host": "66.51.120.219",
|
||||
"port": 465,
|
||||
"type": "IP4",
|
||||
"status": "open",
|
||||
"latency": 27.782792
|
||||
},
|
||||
{
|
||||
"host": "2606:4700:4700::1111",
|
||||
"port": 443,
|
||||
"status": "open",
|
||||
"type": "IP6",
|
||||
"latency": 5.684375
|
||||
}
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Understanding the Input
|
||||
|
||||
- `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));
|
||||
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
|
||||
return acc + ping.latency;
|
||||
}, 0);
|
||||
|
||||
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
|
||||
if (ping.status === "open") {
|
||||
return acc && true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
let avgLatency = latencyTotal / arrayOfPings.length;
|
||||
|
||||
if (areAllOpen && avgLatency > 10) {
|
||||
return {
|
||||
status: "DEGRADED",
|
||||
latency: avgLatency
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: areAllOpen ? "UP" : "DOWN",
|
||||
latency: avgLatency
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
@@ -81,21 +81,11 @@
|
||||
|
||||
let invalidFormMessage = "";
|
||||
|
||||
async function isValidEval() {
|
||||
async function isValidEval(ev) {
|
||||
try {
|
||||
// let evalResp = await eval(newMonitor.apiConfig.eval + `(200, 1000, "e30=")`);
|
||||
new Function(newMonitor.apiConfig.eval);
|
||||
new Function(ev);
|
||||
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;
|
||||
@@ -171,7 +161,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await isValidEval())) {
|
||||
if (!(await isValidEval(newMonitor.apiConfig.eval))) {
|
||||
invalidFormMessage = invalidFormMessage + "Invalid eval";
|
||||
return;
|
||||
}
|
||||
@@ -208,6 +198,27 @@
|
||||
invalidFormMessage = "hostsV4 or hostsV6 is required";
|
||||
return;
|
||||
}
|
||||
if (!!newMonitor.pingConfig.pingEval) {
|
||||
newMonitor.pingConfig.pingEval = newMonitor.pingConfig.pingEval.trim();
|
||||
if (newMonitor.pingConfig.pingEval.endsWith(";")) {
|
||||
invalidFormMessage = "Eval should not end with semicolon";
|
||||
return;
|
||||
}
|
||||
//has to start with ( and end with )
|
||||
if (
|
||||
!newMonitor.pingConfig.pingEval.startsWith("(") ||
|
||||
!newMonitor.pingConfig.pingEval.endsWith(")")
|
||||
) {
|
||||
invalidFormMessage =
|
||||
"Eval should start with ( and end with ). It is an anonymous function";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await isValidEval(newMonitor.pingConfig.pingEval))) {
|
||||
invalidFormMessage = invalidFormMessage + "Invalid eval";
|
||||
return;
|
||||
}
|
||||
}
|
||||
newMonitor.type_data = JSON.stringify(newMonitor.pingConfig);
|
||||
} else if (newMonitor.monitor_type === "DNS") {
|
||||
//validating host
|
||||
@@ -620,7 +631,9 @@
|
||||
<Label for="eval">Eval</Label>
|
||||
<p class="my-1 text-xs text-muted-foreground">
|
||||
You can write a custom eval function to evaluate the response. The
|
||||
function should return an object with status and latency. <a
|
||||
function should return a promise that resolves to an object with status
|
||||
and latency. <a
|
||||
target="_blank"
|
||||
class="font-medium text-primary"
|
||||
href="https://kener.ing/docs/monitors-api#eval">Read the docs</a
|
||||
> to learn
|
||||
@@ -714,6 +727,25 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Label for="pingEval">Eval</Label>
|
||||
<p class="my-1 text-xs text-muted-foreground">
|
||||
You can write a custom eval function to evaluate the response. The
|
||||
function should return a promise that resolves to an object with
|
||||
status and latency. <a
|
||||
target="_blank"
|
||||
class="font-medium text-primary"
|
||||
href="https://kener.ing/docs/monitors-ping#eval"
|
||||
>Read the docs</a
|
||||
> to learn
|
||||
</p>
|
||||
<textarea
|
||||
bind:value={newMonitor.pingConfig.pingEval}
|
||||
id="pingEval"
|
||||
class="h-96 w-full rounded-sm border p-2"
|
||||
placeholder="Leave blank or write a custom eval function"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if newMonitor.monitor_type == "DNS"}
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
},
|
||||
pingConfig: {
|
||||
hostsV4: [],
|
||||
hostsV6: []
|
||||
hostsV6: [],
|
||||
pingEval: ""
|
||||
},
|
||||
dnsConfig: {
|
||||
host: "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import { Ping, ExtractIPv6HostAndPort } from "./ping.js";
|
||||
import { UP, DOWN, DEGRADED } from "./constants.js";
|
||||
import {
|
||||
GetMinuteStartNowTimestampUTC,
|
||||
@@ -49,6 +49,26 @@ const defaultEval = `(async function (statusCode, responseTime, responseData) {
|
||||
}
|
||||
})`;
|
||||
|
||||
const defaultPingEval = `(async function (responseDataBase64) {
|
||||
let arrayOfPings = JSON.parse(atob(responseDataBase64));
|
||||
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
|
||||
return acc + ping.latency;
|
||||
}, 0);
|
||||
|
||||
let alive = arrayOfPings.reduce((acc, ping) => {
|
||||
if (ping.status === "open") {
|
||||
return acc && true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
return {
|
||||
status: alive ? 'UP' : 'DOWN',
|
||||
latency: parseInt(latencyTotal / arrayOfPings.length),
|
||||
}
|
||||
})`;
|
||||
|
||||
async function manualIncident(monitor) {
|
||||
let startTs = GetMinuteStartNowTimestampUTC();
|
||||
let incidentArr = await db.getIncidentsByMonitorTagRealtime(monitor.tag, startTs);
|
||||
@@ -97,50 +117,65 @@ async function manualIncident(monitor) {
|
||||
return manualData;
|
||||
}
|
||||
|
||||
const pingCall = async (hostsV4, hostsV6) => {
|
||||
const pingCall = async (hostsV4, hostsV6, pingEval, tag) => {
|
||||
if (hostsV4 === undefined) hostsV4 = [];
|
||||
if (hostsV6 === undefined) hostsV6 = [];
|
||||
let alive = true;
|
||||
let latencyTotal = 0;
|
||||
let countHosts = hostsV4.length + hostsV6.length;
|
||||
|
||||
let arrayOfPings = [];
|
||||
for (let i = 0; i < hostsV4.length; i++) {
|
||||
const host = hostsV4[i].trim();
|
||||
const hostFull = hostsV4[i].trim();
|
||||
let res;
|
||||
let splitHost = hostFull.split(":");
|
||||
let host = splitHost[0];
|
||||
let port = 80;
|
||||
if (splitHost.length > 1) {
|
||||
port = parseInt(splitHost[1]);
|
||||
}
|
||||
try {
|
||||
let res = await ping.promise.probe(host);
|
||||
alive = alive && res.alive;
|
||||
latencyTotal += res.time;
|
||||
if (!res.alive) {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
res = await Ping(host, port, 3000);
|
||||
} catch (error) {
|
||||
alive = alive && false;
|
||||
latencyTotal += 30;
|
||||
console.log(`Error in pingCall IP4 for ${host}`, error);
|
||||
console.log(`Error in pingCall IP4 for ${hostFull}`, error);
|
||||
} finally {
|
||||
arrayOfPings.push({
|
||||
host: host,
|
||||
port: port,
|
||||
type: "IP4",
|
||||
status: res.status,
|
||||
latency: res.latency
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < hostsV6.length; i++) {
|
||||
const host = hostsV6[i].trim();
|
||||
const hostFull = hostsV6[i].trim();
|
||||
let { host, port } = ExtractIPv6HostAndPort(hostFull);
|
||||
let res;
|
||||
try {
|
||||
let res = await ping.promise.probe(host, {
|
||||
v6: true,
|
||||
timeout: false
|
||||
});
|
||||
alive = alive && res.alive;
|
||||
if (!res.alive) {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
latencyTotal += res.time;
|
||||
res = await Ping(host, port, 3000);
|
||||
} catch (error) {
|
||||
alive = alive && false;
|
||||
latencyTotal += 30;
|
||||
console.log(`Error in pingCall IP6 for ${host}`, error);
|
||||
console.log(`Error in pingCall IP6 for ${hostFull}`, error);
|
||||
} finally {
|
||||
arrayOfPings.push({
|
||||
host: host,
|
||||
port: port,
|
||||
status: res.status,
|
||||
type: "IP6",
|
||||
latency: res.latency
|
||||
});
|
||||
}
|
||||
}
|
||||
let respBase64 = Buffer.from(JSON.stringify(arrayOfPings)).toString("base64");
|
||||
|
||||
let evalResp = undefined;
|
||||
|
||||
try {
|
||||
evalResp = await eval(pingEval + `("${respBase64}")`);
|
||||
} catch (error) {
|
||||
console.log(`Error in pingEval for ${tag}`, error.message);
|
||||
}
|
||||
//reduce to get the status
|
||||
return {
|
||||
status: alive ? UP : DOWN,
|
||||
latency: parseInt(latencyTotal / countHosts),
|
||||
status: evalResp.status,
|
||||
latency: evalResp.latency,
|
||||
type: REALTIME
|
||||
};
|
||||
};
|
||||
@@ -376,7 +411,15 @@ const Minuter = async (monitor) => {
|
||||
});
|
||||
}
|
||||
} else if (monitor.monitor_type === "PING") {
|
||||
let pingResponse = await pingCall(monitor.type_data.hostsV4, monitor.type_data.hostsV6);
|
||||
if (!!!monitor.type_data.pingEval) {
|
||||
monitor.type_data.pingEval = defaultPingEval;
|
||||
}
|
||||
let pingResponse = await pingCall(
|
||||
monitor.type_data.hostsV4,
|
||||
monitor.type_data.hostsV6,
|
||||
monitor.type_data.pingEval,
|
||||
monitor.tag
|
||||
);
|
||||
realTimeData[startOfMinute] = pingResponse;
|
||||
} else if (monitor.monitor_type === "DNS") {
|
||||
const dnsResolver = new DNSResolver(monitor.type_data.nameServer);
|
||||
|
||||
@@ -78,34 +78,3 @@ class DNSResolver {
|
||||
}
|
||||
|
||||
export default DNSResolver;
|
||||
// const resolver = new DNSResolver();
|
||||
// const domain = process.argv[2] || "google.com";
|
||||
// const recordType = process.argv[3] || "A";
|
||||
// resolver.getRecord(domain, recordType).then(
|
||||
// function (records) {
|
||||
// Object.entries(records).forEach(([type, records]) => {
|
||||
// if (records.length === 0) return []; // Skip empty records
|
||||
// return records;
|
||||
// console.log(">>>>>>---- dns:167 ", records);
|
||||
// console.log(`\n${type} Records:`);
|
||||
// records.forEach((record) => {
|
||||
// console.log("----------------------------------------");
|
||||
// console.log(`Type: ${type}`);
|
||||
// console.log(`Name: ${record.name}`);
|
||||
// console.log(`TTL: ${record.ttl}`);
|
||||
|
||||
// // Format the output based on record type
|
||||
// if (type === "MX") {
|
||||
// console.log(`Priority: ${record.data.priority}`);
|
||||
// console.log(`Exchange: ${record.data.exchange}`);
|
||||
// } else {
|
||||
// console.log(`Data: ${record.data}`);
|
||||
// // console.log(">>>>>>---- dns-resolver:120 ", record);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
// function (err) {
|
||||
// console.error(err);
|
||||
// }
|
||||
// );
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// @ts-nocheck
|
||||
import net from "net"; // Use import instead of require
|
||||
|
||||
/**
|
||||
* Check if a TCP port is open on a given IPv4/IPv6 host and measure latency.
|
||||
*
|
||||
* @param {string} host - The IP address or hostname (IPv4 or IPv6) to check.
|
||||
* @param {number} port - The port number to check.
|
||||
* @param {number} timeout - Connection timeout in milliseconds.
|
||||
* @returns {Promise<{ status: string, latency: number }>} - Resolves to an object with status ("open" or "closed") and latency in ms.
|
||||
*/
|
||||
const Ping = function (host, port, timeout = 3000) {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket();
|
||||
const start = process.hrtime.bigint(); // High-precision timestamp
|
||||
let resolved = false;
|
||||
|
||||
const onFinish = (status) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
const end = process.hrtime.bigint();
|
||||
const latency = Number(end - start) / 1e6; // Convert nanoseconds to milliseconds
|
||||
socket.destroy();
|
||||
resolve({ status, latency });
|
||||
}
|
||||
};
|
||||
|
||||
socket.setTimeout(timeout);
|
||||
socket.once("connect", () => onFinish("open"));
|
||||
socket.once("timeout", () => onFinish("timeout"));
|
||||
socket.once("error", () => onFinish("error"));
|
||||
|
||||
// Check if it's an IPv6 address (contains ':')
|
||||
const options = host.includes(":") ? { host, port, family: 6 } : { host, port };
|
||||
socket.connect(options);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
*/
|
||||
function ExtractIPv6HostAndPort(input) {
|
||||
const parts = input.split(":"); // Split by colons
|
||||
|
||||
// If there's a valid port at the end, extract it
|
||||
const lastPart = parts[parts.length - 1];
|
||||
const port = /^\d+$/.test(lastPart) ? parseInt(parts.pop(), 10) : null; // Check if last part is a number
|
||||
|
||||
// Reconstruct the IPv6 address
|
||||
const host = parts.join(":");
|
||||
|
||||
// Ensure it's a valid IPv6 format
|
||||
if (host.includes(":")) {
|
||||
return { host, port }; // Port may be null if not present
|
||||
}
|
||||
return null; // Return null if the format is incorrect
|
||||
}
|
||||
|
||||
export { Ping, ExtractIPv6HostAndPort };
|
||||
Reference in New Issue
Block a user