Merge pull request #265 from rajnandan1/release/3.0.13

feat: test monitors in manage monitor dashboard
This commit is contained in:
Raj Nandan Sharma
2025-02-11 07:08:50 +05:30
committed by GitHub
13 changed files with 1439 additions and 1345 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
"semi": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100
"printWidth": 120
}
},
{
@@ -24,7 +24,7 @@
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
"printWidth": 120
}
},
{
@@ -34,7 +34,7 @@
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100
"printWidth": 120
}
},
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kener",
"version": "3.0.12",
"version": "3.1.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.",
+79 -69
View File
@@ -9,6 +9,7 @@
import * as Card from "$lib/components/ui/card";
import * as Select from "$lib/components/ui/select";
import { storeSiteData, SortMonitor } from "$lib/clientTools.js";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { dndzone } from "svelte-dnd-action";
import { flip } from "svelte/animate";
@@ -239,6 +240,32 @@
}
let dropTargetStyle;
let draggableMenu = false;
function testMonitor(i) {
if (monitors[i].isTestRunning) {
return;
}
monitors[i].isTestRunning = true;
fetch(base + "/manage/app/api/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "testMonitor",
data: { monitor_id: monitors[i].id }
})
})
.then((resp) => resp.json())
.then((data) => {
monitors[i].isTestRunning = false;
monitors[i].testResult = data;
})
.catch((error) => {
monitors[i].isTestRunning = false;
monitors[i].testResult = data;
});
}
</script>
{#if showAddMonitor}
@@ -252,9 +279,7 @@
/>
{/if}
{#if draggableMenu}
<div
class="moldal-container fixed left-0 top-0 z-50 h-screen w-full bg-card bg-opacity-30 backdrop-blur-sm"
>
<div class="moldal-container fixed left-0 top-0 z-50 h-screen w-full bg-card bg-opacity-30 backdrop-blur-sm">
<div
class="absolute left-1/2 top-1/2 h-fit w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md border bg-background shadow-lg backdrop-blur-lg"
>
@@ -279,11 +304,7 @@
<div animate:flip={{ duration: flipDurationMs }} class="mb-2 rounded-md bg-card p-2">
<Grip class="mr-2 inline h-4 w-4" />
{#if !!monitor.image}
<img
src={base + monitor.image}
alt={monitor.name}
class="mr-1 inline-block h-4 w-4"
/>
<img src={base + monitor.image} alt={monitor.name} class="mr-1 inline-block h-4 w-4" />
{/if}
{monitor.name}
</div>
@@ -312,12 +333,8 @@
<Select.Content>
<Select.Group>
<Select.Label>Status</Select.Label>
<Select.Item value="ACTIVE" label="ACTIVE" class="text-sm font-medium">
ACTIVE
</Select.Item>
<Select.Item value="INACTIVE" label="INACTIVE" class="text-sm font-medium">
INACTIVE
</Select.Item>
<Select.Item value="ACTIVE" label="ACTIVE" class="text-sm font-medium">ACTIVE</Select.Item>
<Select.Item value="INACTIVE" label="INACTIVE" class="text-sm font-medium">INACTIVE</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
@@ -370,7 +387,7 @@
</div>
<div class="mt-4">
{#each monitors as monitor}
{#each monitors as monitor, i}
<Card.Root class="mb-4">
<Card.Header class="relative">
<Card.Title>
@@ -382,8 +399,41 @@
{#if !!monitor.description}
<Card.Description>{@html monitor.description}</Card.Description>
{/if}
<div class="absolute right-2 top-0.5">
<Button variant="secondary" class="h-8 w-8 p-2" on:click={() => openAlertMenu(monitor)}>
<div class="absolute right-2 top-0.5 flex gap-x-1">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="secondary" class="h-8 p-2 text-xs" on:click={() => testMonitor(i)}>TEST</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="max-w-md">
<DropdownMenu.Group>
<DropdownMenu.Label class="text-xs">Test Result</DropdownMenu.Label>
<DropdownMenu.Separator />
<div class="px-2 text-xs">
{#if monitor.isTestRunning}
<div class="text-center">
<Loader class="mx-auto inline h-4 w-4 animate-spin" />
</div>
{:else if !!monitor.testResult}
{#if !!monitor.testResult.error}
<div class="text-red-500">
{monitor.testResult.error}
</div>
{:else if !!monitor.testResult.status && !!monitor.testResult.latency}
<p class="text-muted-foreground">
Status: <span class="text-api-{monitor.testResult.status.toLowerCase()}"
>{monitor.testResult.status}</span
><br /> Response Time: <span class="text-card-foreground">{monitor.testResult.latency}ms</span>
</p>
{:else}
<div class="text-muted-foreground">No Test Result</div>
{/if}
{/if}
</div>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button variant="secondary" class="h-8 w-8 p-2 " on:click={() => openAlertMenu(monitor)}>
<Bell class="inline h-4 w-4" />
</Button>
<Button variant="secondary" class="h-8 w-8 p-2" href="#{monitor.tag}">
@@ -424,9 +474,7 @@
</div>
{#if shareMenusToggle}
<div
class="moldal-container fixed left-0 top-0 z-50 h-screen w-full bg-card bg-opacity-30 backdrop-blur-sm"
>
<div class="moldal-container fixed left-0 top-0 z-50 h-screen w-full bg-card bg-opacity-30 backdrop-blur-sm">
<div
class="absolute left-1/2 top-1/2 h-fit w-full max-w-2xl -translate-x-1/2 -translate-y-1/2 rounded-md border bg-background shadow-lg backdrop-blur-lg"
>
@@ -449,10 +497,7 @@
<hr class="my-4" />
{#each Object.entries(monitorTriggers) as [key, data]}
<div class="flex justify-between">
<h3
class="font-semibold"
style="color:{data.trigger_type == 'DOWN' ? colorDown : colorDegraded};"
>
<h3 class="font-semibold" style="color:{data.trigger_type == 'DOWN' ? colorDown : colorDegraded};">
If Monitor {data.trigger_type}
</h3>
<div>
@@ -479,12 +524,7 @@
Failure Threshold
<span class="text-red-500">*</span>
</Label>
<Input
bind:value={data.failureThreshold}
min="1"
id="{key}failureThreshold"
type="number"
/>
<Input bind:value={data.failureThreshold} min="1" id="{key}failureThreshold" type="number" />
</div>
<div class="col-span-1">
<div class="col-span-1">
@@ -492,12 +532,7 @@
Success Threshold
<span class="text-red-500">*</span>
</Label>
<Input
bind:value={data.successThreshold}
min="1"
id="{key}successThreshold"
type="number"
/>
<Input bind:value={data.successThreshold} min="1" id="{key}successThreshold" type="number" />
</div>
</div>
<div class="col-span-1">
@@ -511,16 +546,11 @@
}}
>
<Select.Trigger id="{key}createIncident">
<Select.Value
bind:value={data.createIncident}
placeholder={data.createIncident}
/>
<Select.Value bind:value={data.createIncident} placeholder={data.createIncident} />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Item value="YES" label="YES" class="text-sm font-medium">
YES
</Select.Item>
<Select.Item value="YES" label="YES" class="text-sm font-medium">YES</Select.Item>
<Select.Item value="NO" label="NO" class="text-sm font-medium">NO</Select.Item>
</Select.Group>
</Select.Content>
@@ -544,12 +574,8 @@
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Item value="critical" label="CRITICAL" class="text-sm font-medium">
CRITICAL
</Select.Item>
<Select.Item value="warning" label="WARNING" class="text-sm font-medium">
WARNING
</Select.Item>
<Select.Item value="critical" label="CRITICAL" class="text-sm font-medium">CRITICAL</Select.Item>
<Select.Item value="warning" label="WARNING" class="text-sm font-medium">WARNING</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
@@ -576,29 +602,13 @@
}}
/>
{#if trigger.trigger_type == "webhook"}
<img
src={base + "/webhooks.svg"}
alt={trigger.trigger_type}
class="ml-2 inline-block h-4 w-4"
/>
<img src={base + "/webhooks.svg"} alt={trigger.trigger_type} class="ml-2 inline-block h-4 w-4" />
{:else if trigger.trigger_type == "email"}
<img
src={base + "/email.png"}
alt={trigger.trigger_type}
class="ml-2 inline-block h-4 w-4"
/>
<img src={base + "/email.png"} alt={trigger.trigger_type} class="ml-2 inline-block h-4 w-4" />
{:else if trigger.trigger_type == "slack"}
<img
src={base + "/slack.svg"}
alt={trigger.trigger_type}
class="ml-2 inline-block h-4 w-4"
/>
<img src={base + "/slack.svg"} alt={trigger.trigger_type} class="ml-2 inline-block h-4 w-4" />
{:else if trigger.trigger_type == "discord"}
<img
src={base + "/discord.svg"}
alt={trigger.trigger_type}
class="ml-2 inline-block h-4 w-4"
/>
<img src={base + "/discord.svg"} alt={trigger.trigger_type} class="ml-2 inline-block h-4 w-4" />
{/if}
{trigger.name}
</label>
+104 -83
View File
@@ -10,90 +10,111 @@ const DOWN = "DOWN";
const DEGRADED = "DEGRADED";
const API_TIMEOUT = 10 * 1000; // 10 seconds
const AnalyticsProviders = {
GA: "https://unpkg.com/@analytics/google-analytics@1.0.7/dist/@analytics/google-analytics.min.js",
AMPLITUDE: "https://unpkg.com/@analytics/amplitude@0.1.3/dist/@analytics/amplitude.min.js",
MIXPANEL: "https://unpkg.com/@analytics/mixpanel@0.4.0/dist/@analytics/mixpanel.min.js"
GA: "https://unpkg.com/@analytics/google-analytics@1.0.7/dist/@analytics/google-analytics.min.js",
AMPLITUDE:
"https://unpkg.com/@analytics/amplitude@0.1.3/dist/@analytics/amplitude.min.js",
MIXPANEL:
"https://unpkg.com/@analytics/mixpanel@0.4.0/dist/@analytics/mixpanel.min.js",
};
const AllRecordTypes = {
A: 1,
NS: 2,
MD: 3,
MF: 4,
CNAME: 5,
SOA: 6,
MB: 7,
MG: 8,
MR: 9,
NULL: 10,
WKS: 11,
PTR: 12,
HINFO: 13,
MINFO: 14,
MX: 15,
TXT: 16,
RP: 17,
AFSDB: 18,
X25: 19,
ISDN: 20,
RT: 21,
NSAP: 22,
NSAP_PTR: 23,
SIG: 24,
KEY: 25,
PX: 26,
GPOS: 27,
AAAA: 28,
LOC: 29,
NXT: 30,
EID: 31,
NIMLOC: 32,
SRV: 33,
ATMA: 34,
NAPTR: 35,
KX: 36,
CERT: 37,
A6: 38,
DNAME: 39,
SINK: 40,
OPT: 41,
APL: 42,
DS: 43,
SSHFP: 44,
IPSECKEY: 45,
RRSIG: 46,
NSEC: 47,
DNSKEY: 48,
DHCID: 49,
NSEC3: 50,
NSEC3PARAM: 51,
TLSA: 52,
SMIMEA: 53,
HIP: 55,
NINFO: 56,
RKEY: 57,
TALINK: 58,
CDS: 59,
CDNSKEY: 60,
OPENPGPKEY: 61,
CSYNC: 62,
SPF: 99,
UINFO: 100,
UID: 101,
GID: 102,
UNSPEC: 103,
NID: 104,
L32: 105,
L64: 106,
LP: 107,
EUI48: 108,
EUI64: 109,
TKEY: 249,
TSIG: 250,
IXFR: 251,
AXFR: 252,
MAILB: 253,
MAILA: 254,
ANY: 255
A: 1,
NS: 2,
MD: 3,
MF: 4,
CNAME: 5,
SOA: 6,
MB: 7,
MG: 8,
MR: 9,
NULL: 10,
WKS: 11,
PTR: 12,
HINFO: 13,
MINFO: 14,
MX: 15,
TXT: 16,
RP: 17,
AFSDB: 18,
X25: 19,
ISDN: 20,
RT: 21,
NSAP: 22,
NSAP_PTR: 23,
SIG: 24,
KEY: 25,
PX: 26,
GPOS: 27,
AAAA: 28,
LOC: 29,
NXT: 30,
EID: 31,
NIMLOC: 32,
SRV: 33,
ATMA: 34,
NAPTR: 35,
KX: 36,
CERT: 37,
A6: 38,
DNAME: 39,
SINK: 40,
OPT: 41,
APL: 42,
DS: 43,
SSHFP: 44,
IPSECKEY: 45,
RRSIG: 46,
NSEC: 47,
DNSKEY: 48,
DHCID: 49,
NSEC3: 50,
NSEC3PARAM: 51,
TLSA: 52,
SMIMEA: 53,
HIP: 55,
NINFO: 56,
RKEY: 57,
TALINK: 58,
CDS: 59,
CDNSKEY: 60,
OPENPGPKEY: 61,
CSYNC: 62,
SPF: 99,
UINFO: 100,
UID: 101,
GID: 102,
UNSPEC: 103,
NID: 104,
L32: 105,
L64: 106,
LP: 107,
EUI48: 108,
EUI64: 109,
TKEY: 249,
TSIG: 250,
IXFR: 251,
AXFR: 252,
MAILB: 253,
MAILA: 254,
ANY: 255,
};
// Export the constants
export { MONITOR, UP, DOWN, SITE, DEGRADED, API_TIMEOUT, ENV, AnalyticsProviders, AllRecordTypes };
const REALTIME = "realtime";
const TIMEOUT = "timeout";
const ERROR = "error";
const MANUAL = "manual";
export {
MONITOR,
UP,
DOWN,
SITE,
DEGRADED,
API_TIMEOUT,
ENV,
AnalyticsProviders,
AllRecordTypes,
REALTIME,
TIMEOUT,
ERROR,
MANUAL,
};
+14 -352
View File
@@ -2,12 +2,8 @@
import axios from "axios";
import { Ping, ExtractIPv6HostAndPort, TCP } from "./ping.js";
import { UP, DOWN, DEGRADED } from "./constants.js";
import {
GetMinuteStartNowTimestampUTC,
ReplaceAllOccurrences,
GetRequiredSecrets,
Wait,
} from "./tool.js";
import Service from "./services/service.js";
import { GetMinuteStartNowTimestampUTC, ReplaceAllOccurrences, GetRequiredSecrets, Wait } from "./tool.js";
import alerting from "./alerting.js";
import Queue from "queue";
@@ -35,65 +31,10 @@ const apiQueue = new Queue({
autostart: true, // Automatically start the queue (optional)
});
const defaultEval = `(async function (statusCode, responseTime, responseData) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
status: 'UP',
latency: responseTime,
}
}
return {
status: 'DOWN',
latency: responseTime,
}
})`;
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) => {
return acc && ping.alive;
}, true);
return {
status: alive ? 'UP' : 'DOWN',
latency: latencyTotal / arrayOfPings.length,
}
})`;
const defaultTcpEval = `(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: latencyTotal / arrayOfPings.length,
}
})`;
async function manualIncident(monitor) {
let startTs = GetMinuteStartNowTimestampUTC();
let incidentArr = await db.getIncidentsByMonitorTagRealtime(
monitor.tag,
startTs,
);
let maintenanceArr = await db.getMaintenanceByMonitorTagRealtime(
monitor.tag,
startTs,
);
let incidentArr = await db.getIncidentsByMonitorTagRealtime(monitor.tag, startTs);
let maintenanceArr = await db.getMaintenanceByMonitorTagRealtime(monitor.tag, startTs);
let impactArr = incidentArr.concat(maintenanceArr);
@@ -105,11 +46,7 @@ async function manualIncident(monitor) {
for (let i = 0; i < impactArr.length; i++) {
const element = impactArr[i];
let autoIncidents = await db.getActiveAlertIncident(
monitor.tag,
element.monitor_impact,
element.id,
);
let autoIncidents = await db.getActiveAlertIncident(monitor.tag, element.monitor_impact, element.id);
if (!!autoIncidents) {
continue;
@@ -160,275 +97,24 @@ const tcpCall = async (hosts, tcpEval, tag) => {
type: REALTIME,
};
};
const pingCall = async (hosts, pingEval, tag) => {
if (hosts === undefined) {
console.log(
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag",
tag,
);
return {
status: DOWN,
latency: 0,
type: ERROR,
};
}
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),
);
}
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: evalResp.status,
latency: evalResp.latency,
type: REALTIME,
};
};
const apiCall = async (
envSecrets,
url,
method,
headers,
body,
timeout,
monitorEval,
tag,
) => {
let axiosHeaders = {};
axiosHeaders["User-Agent"] = "Kener/3.0.2";
axiosHeaders["Accept"] = "*/*";
const start = Date.now();
//replace all secrets
for (let i = 0; i < envSecrets.length; i++) {
const secret = envSecrets[i];
if (!!body) {
body = ReplaceAllOccurrences(body, secret.find, secret.replace);
}
if (!!url) {
url = ReplaceAllOccurrences(url, secret.find, secret.replace);
}
if (!!headers) {
headers = ReplaceAllOccurrences(headers, secret.find, secret.replace);
}
}
if (!!headers) {
headers = JSON.parse(headers);
headers = headers.reduce((acc, header) => {
acc[header.key] = header.value;
return acc;
}, {});
axiosHeaders = { ...axiosHeaders, ...headers };
}
const options = {
method: method,
headers: headers,
timeout: timeout,
transformResponse: (r) => r,
};
if (!!headers) {
options.headers = headers;
}
if (!!body) {
options.data = body;
}
let statusCode = 500;
let latency = 0;
let resp = "";
let timeoutError = false;
try {
let data = await axios(url, options);
statusCode = data.status;
resp = data.data;
} catch (err) {
console.log(`Error in apiCall ${tag}`, err.message);
if (
err.message.startsWith("timeout of") &&
err.message.endsWith("exceeded")
) {
timeoutError = true;
}
if (err.response !== undefined && err.response.status !== undefined) {
statusCode = err.response.status;
}
if (err.response !== undefined && err.response.data !== undefined) {
resp = err.response.data;
} else {
resp = JSON.stringify(resp);
}
} finally {
const end = Date.now();
latency = end - start;
if (resp === undefined || resp === null) {
resp = "";
}
}
resp = Buffer.from(resp).toString("base64");
let evalResp = undefined;
try {
evalResp = await eval(
monitorEval + `(${statusCode}, ${latency}, "${resp}")`,
);
} catch (error) {
console.log(`Error in monitorEval for ${tag}`, error.message);
}
if (evalResp === undefined || evalResp === null) {
evalResp = {
status: DOWN,
latency: latency,
type: ERROR,
};
} else if (
evalResp.status === undefined ||
evalResp.status === null ||
[UP, DOWN, DEGRADED].indexOf(evalResp.status) === -1
) {
evalResp = {
status: DOWN,
latency: latency,
type: ERROR,
};
} else {
evalResp.type = REALTIME;
}
let toWrite = {
status: DOWN,
latency: latency,
type: ERROR,
};
if (evalResp.status !== undefined && evalResp.status !== null) {
toWrite.status = evalResp.status;
}
if (evalResp.latency !== undefined && evalResp.latency !== null) {
toWrite.latency = evalResp.latency;
}
if (evalResp.type !== undefined && evalResp.type !== null) {
toWrite.type = evalResp.type;
}
if (timeoutError) {
toWrite.type = TIMEOUT;
}
return toWrite;
};
async function dsnChecker(dnsResolver, host, recordType, matchType, values) {
try {
let queryStartTime = Date.now();
let dnsRes = await dnsResolver.getRecord(host, recordType);
let latency = Date.now() - queryStartTime;
if (dnsRes[recordType] === undefined) {
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
let data = dnsRes[recordType];
let dnsData = data.map((d) => d.data);
if (matchType === "ALL") {
for (let i = 0; i < values.length; i++) {
if (dnsData.indexOf(values[i].trim()) === -1) {
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
}
return {
status: UP,
latency: latency,
type: REALTIME,
};
} else if (matchType === "ANY") {
for (let i = 0; i < values.length; i++) {
if (dnsData.indexOf(values[i].trim()) !== -1) {
return {
status: UP,
latency: latency,
type: REALTIME,
};
}
}
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
} catch (error) {
console.log("Error in dnsChecker", error);
return {
status: DOWN,
latency: 0,
type: REALTIME,
};
}
}
const Minuter = async (monitor) => {
let realTimeData = {};
let manualData = {};
const startOfMinute = GetMinuteStartNowTimestampUTC();
const serviceClient = new Service(monitor);
if (monitor.monitor_type === "API") {
let envSecrets = GetRequiredSecrets(
`${monitor.type_data.url} ${monitor.type_data.body} ${JSON.stringify(monitor.type_data.headers)}`,
);
if (monitor.type_data.eval === "") {
monitor.type_data.eval = defaultEval;
}
let apiResponse = await apiCall(
envSecrets,
monitor.type_data.url,
monitor.type_data.method,
JSON.stringify(monitor.type_data.headers),
monitor.type_data.body,
monitor.type_data.timeout,
monitor.type_data.eval,
monitor.tag,
);
let apiResponse = await serviceClient.execute();
realTimeData[startOfMinute] = apiResponse;
//if timeout, retry after 500ms
if (apiResponse.type === TIMEOUT) {
apiQueue.push(async (cb) => {
await Wait(500); //wait for 500ms
console.log(
"Retrying api call for " +
monitor.name +
" at " +
startOfMinute +
" due to timeout",
);
apiCall(
envSecrets,
monitor.type_data.url,
monitor.type_data.method,
JSON.stringify(monitor.type_data.headers),
monitor.type_data.body,
monitor.type_data.timeout,
monitor.type_data.eval,
monitor.tag,
).then(async (data) => {
console.log("Retrying api call for " + monitor.name + " at " + startOfMinute + " due to timeout");
serviceClient.execute().then(async (data) => {
await db.insertMonitoringData({
monitor_tag: monitor.tag,
timestamp: startOfMinute,
@@ -441,35 +127,11 @@ const Minuter = async (monitor) => {
});
}
} else if (monitor.monitor_type === "PING") {
if (!!!monitor.type_data.pingEval) {
monitor.type_data.pingEval = defaultPingEval;
}
let pingResponse = await pingCall(
monitor.type_data.hosts,
monitor.type_data.pingEval,
monitor.tag,
);
realTimeData[startOfMinute] = pingResponse;
realTimeData[startOfMinute] = await serviceClient.execute();
} else if (monitor.monitor_type === "TCP") {
if (!!!monitor.type_data.tcpEval) {
monitor.type_data.tcpEval = defaultTcpEval;
}
let pingResponse = await tcpCall(
monitor.type_data.hosts,
monitor.type_data.tcpEval,
monitor.tag,
);
realTimeData[startOfMinute] = pingResponse;
realTimeData[startOfMinute] = await serviceClient.execute();
} else if (monitor.monitor_type === "DNS") {
const dnsResolver = new DNSResolver(monitor.type_data.nameServer);
let dnsResponse = await dsnChecker(
dnsResolver,
monitor.type_data.host,
monitor.type_data.lookupRecord,
monitor.type_data.matchType,
monitor.type_data.values,
);
realTimeData[startOfMinute] = dnsResponse;
realTimeData[startOfMinute] = await serviceClient.execute();
}
manualData = await manualIncident(monitor);
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
// @ts-nocheck
import axios from "axios";
import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import { UP, DOWN, DEGRADED, REALTIME, TIMEOUT, ERROR, MANUAL } from "../constants.js";
const defaultEval = `(async function (statusCode, responseTime, responseData) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
status: 'UP',
latency: responseTime,
}
}
return {
status: 'DOWN',
latency: responseTime,
}
})`;
class ApiCall {
monitor;
envSecrets;
constructor(monitor) {
this.monitor = monitor;
this.envSecrets = GetRequiredSecrets(
`${monitor.type_data.url} ${monitor.type_data.body} ${JSON.stringify(monitor.type_data.headers)}`,
);
}
async execute() {
let axiosHeaders = {};
axiosHeaders["User-Agent"] = "Kener/" + "3.1.0";
axiosHeaders["Accept"] = "*/*";
let body = this.monitor.type_data.body;
let url = this.monitor.type_data.url;
//headers to string
let headers = "";
if (!!this.monitor.type_data.headers) {
headers = JSON.stringify(this.monitor.type_data.headers);
}
let method = this.monitor.type_data.method;
let timeout = this.monitor.type_data.timeout || 5000;
let tag = this.monitor.tag;
let monitorEval = !!this.monitor.type_data.monitorEval ? this.monitor.type_data.monitorEval : defaultEval;
for (let i = 0; i < this.envSecrets.length; i++) {
const secret = this.envSecrets[i];
if (!!body) {
body = ReplaceAllOccurrences(body, secret.find, secret.replace);
}
if (!!url) {
url = ReplaceAllOccurrences(url, secret.find, secret.replace);
}
if (!!headers) {
headers = ReplaceAllOccurrences(headers, secret.find, secret.replace);
}
}
if (!!headers) {
try {
headers = JSON.parse(headers);
headers = headers.reduce((acc, header) => {
acc[header.key] = header.value;
return acc;
}, {});
axiosHeaders = { ...axiosHeaders, ...headers };
} catch (e) {
console.log(e);
}
}
const options = {
method: method,
headers: axiosHeaders,
timeout: timeout,
transformResponse: (r) => r,
};
if (!!body) {
options.data = body;
}
let statusCode = 500;
let latency = 0;
let resp = "";
let timeoutError = false;
const start = Date.now();
try {
let data = await axios(url, options);
statusCode = data.status;
resp = data.data;
} catch (err) {
console.log(`Error in apiCall ${tag}`, err.message);
if (err.message.startsWith("timeout of") && err.message.endsWith("exceeded")) {
timeoutError = true;
}
if (err.response !== undefined && err.response.status !== undefined) {
statusCode = err.response.status;
}
if (err.response !== undefined && err.response.data !== undefined) {
resp = err.response.data;
} else {
resp = JSON.stringify(resp);
}
} finally {
const end = Date.now();
latency = end - start;
if (resp === undefined || resp === null) {
resp = "";
}
}
resp = Buffer.from(resp).toString("base64");
let evalResp = undefined;
try {
evalResp = await eval(monitorEval + `(${statusCode}, ${latency}, "${resp}")`);
} catch (error) {
console.log(`Error in monitorEval for ${tag}`, error.message);
}
if (evalResp === undefined || evalResp === null) {
evalResp = {
status: DOWN,
latency: latency,
type: ERROR,
};
} else if (
evalResp.status === undefined ||
evalResp.status === null ||
[UP, DOWN, DEGRADED].indexOf(evalResp.status) === -1
) {
evalResp = {
status: DOWN,
latency: latency,
type: ERROR,
};
} else {
evalResp.type = REALTIME;
}
let toWrite = {
status: DOWN,
latency: latency,
type: ERROR,
};
if (evalResp.status !== undefined && evalResp.status !== null) {
toWrite.status = evalResp.status;
}
if (evalResp.latency !== undefined && evalResp.latency !== null) {
toWrite.latency = evalResp.latency;
}
if (evalResp.type !== undefined && evalResp.type !== null) {
toWrite.type = evalResp.type;
}
if (timeoutError) {
toWrite.type = TIMEOUT;
}
return toWrite;
}
}
export default ApiCall;
+76
View File
@@ -0,0 +1,76 @@
// @ts-nocheck
import axios from "axios";
import DNSResolver from "../dns.js";
import { UP, DOWN, DEGRADED, REALTIME, TIMEOUT, ERROR, MANUAL } from "../constants.js";
class DnsCall {
monitor;
constructor(monitor) {
this.monitor = monitor;
}
async execute() {
const dnsResolver = new DNSResolver(this.monitor.type_data.nameServer);
let host = this.monitor.type_data.host;
let recordType = this.monitor.type_data.lookupRecord;
let matchType = this.monitor.type_data.matchType;
let values = this.monitor.type_data.values;
try {
let queryStartTime = Date.now();
let dnsRes = await dnsResolver.getRecord(host, recordType);
let latency = Date.now() - queryStartTime;
if (dnsRes[recordType] === undefined) {
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
let data = dnsRes[recordType];
let dnsData = data.map((d) => d.data);
if (matchType === "ALL") {
for (let i = 0; i < values.length; i++) {
if (dnsData.indexOf(values[i].trim()) === -1) {
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
}
return {
status: UP,
latency: latency,
type: REALTIME,
};
} else if (matchType === "ANY") {
for (let i = 0; i < values.length; i++) {
if (dnsData.indexOf(values[i].trim()) !== -1) {
return {
status: UP,
latency: latency,
type: REALTIME,
};
}
}
return {
status: DOWN,
latency: latency,
type: REALTIME,
};
}
} catch (error) {
console.log("Error in dnsChecker", error);
return {
status: DOWN,
latency: 0,
type: REALTIME,
};
}
}
}
export default DnsCall;
+81
View File
@@ -0,0 +1,81 @@
// @ts-nocheck
import axios from "axios";
import { Ping } from "../ping.js";
import {
UP,
DOWN,
DEGRADED,
REALTIME,
TIMEOUT,
ERROR,
MANUAL,
} from "../constants.js";
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) => {
return acc && ping.alive;
}, true);
return {
status: alive ? 'UP' : 'DOWN',
latency: latencyTotal / arrayOfPings.length,
}
})`;
class PingCall {
monitor;
constructor(monitor) {
this.monitor = monitor;
}
async execute() {
let hosts = this.monitor.type_data.hosts;
let pingEval = !!this.monitor.type_data.pingEval
? this.monitor.type_data.pingEval
: defaultPingEval;
let tag = this.monitor.tag;
if (hosts === undefined) {
console.log(
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag",
tag,
);
return {
status: DOWN,
latency: 0,
type: ERROR,
};
}
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),
);
}
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: evalResp.status,
latency: evalResp.latency,
type: REALTIME,
};
}
}
export default PingCall;
+31
View File
@@ -0,0 +1,31 @@
// @ts-nocheck
import ApiCall from "./apiCall.js";
import PingCall from "./pingCall.js";
import TcpCall from "./tcpCall.js";
import DnsCall from "./dnsCall.js";
class Service {
service;
constructor(monitor) {
if (monitor.monitor_type === "API") {
this.service = new ApiCall(monitor);
} else if (monitor.monitor_type === "PING") {
this.service = new PingCall(monitor);
} else if (monitor.monitor_type === "TCP") {
this.service = new TcpCall(monitor);
} else if (monitor.monitor_type === "DNS") {
this.service = new DnsCall(monitor);
} else if (monitor.monitor_type === "NONE") {
this.service = null;
} else {
console.log("Invalid monitor.monitor_type ", monitor.monitor_type);
process.exit(1);
}
}
async execute() {
return await this.service.execute();
}
}
export default Service;
+72
View File
@@ -0,0 +1,72 @@
// @ts-nocheck
import axios from "axios";
import { TCP } from "../ping.js";
import { UP, DOWN, DEGRADED, REALTIME, TIMEOUT, ERROR, MANUAL } from "../constants.js";
const defaultTcpEval = `(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: latencyTotal / arrayOfPings.length,
}
})`;
class TcpCall {
monitor;
constructor(monitor) {
this.monitor = monitor;
}
async execute() {
let hosts = this.monitor.type_data.hosts;
let tcpEval = !!this.monitor.type_data.tcpEval ? this.monitor.type_data.tcpEval : defaultTcpEval;
let tag = this.monitor.tag;
if (hosts === undefined) {
console.log(
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag",
tag,
);
return {
status: DOWN,
latency: 0,
type: ERROR,
};
}
let arrayOfPings = [];
for (let i = 0; i < hosts.length; i++) {
const host = hosts[i];
arrayOfPings.push(await TCP(host.type, host.host, host.port, host.timeout));
}
let respBase64 = Buffer.from(JSON.stringify(arrayOfPings)).toString("base64");
let evalResp = undefined;
try {
evalResp = await eval(tcpEval + `("${respBase64}")`);
} catch (error) {
console.log(`Error in tcpEval for ${tag}`, error.message);
}
//reduce to get the status
return {
status: evalResp.status,
latency: evalResp.latency,
type: REALTIME,
};
}
}
export default TcpCall;
+35 -48
View File
@@ -1,34 +1,34 @@
<script>
import "../../app.postcss"
import "../../kener.css"
import "../../docs.css"
import { Button } from "$lib/components/ui/button"
import Sun from "lucide-svelte/icons/sun"
import Moon from "lucide-svelte/icons/moon"
import { onMount } from "svelte"
import { base } from "$app/paths"
let defaultTheme = "light"
export let data
let siteStructure = data.siteStructure
let sidebar = siteStructure.sidebar
let docFilePath = data.docFilePath
let tableOfContents = []
import "../../app.postcss";
import "../../kener.css";
import "../../docs.css";
import { Button } from "$lib/components/ui/button";
import Sun from "lucide-svelte/icons/sun";
import Moon from "lucide-svelte/icons/moon";
import { onMount } from "svelte";
import { base } from "$app/paths";
let defaultTheme = "light";
export let data;
let siteStructure = data.siteStructure;
let sidebar = siteStructure.sidebar;
let docFilePath = data.docFilePath;
let tableOfContents = [];
function setTheme() {
document.documentElement.classList.add("dark")
document.documentElement.classList.add("dark");
}
function activateSidebar(selectedDoc) {
for (let i = 0; i < sidebar.length; i++) {
const item = sidebar[i]
const item = sidebar[i];
for (let j = 0; j < item.children.length; j++) {
const subItem = item.children[j]
const subItem = item.children[j];
if (subItem.file == selectedDoc) {
subItem.active = true
sidebar[i].children[j].active = true
subItem.active = true;
sidebar[i].children[j].active = true;
} else {
subItem.active = false
sidebar[i].children[j].active = false
subItem.active = false;
sidebar[i].children[j].active = false;
}
}
}
@@ -36,18 +36,18 @@
function pageChange(e) {
if (e.detail.docFilePath) {
activateSidebar(e.detail.docFilePath)
activateSidebar(e.detail.docFilePath);
}
}
function updateTableOfContents(e) {
if (e.detail.rightbar) {
tableOfContents = e.detail.rightbar
tableOfContents = e.detail.rightbar;
}
}
onMount(() => {
setTheme()
})
setTheme();
});
</script>
<svelte:window on:pagechange={pageChange} on:rightbar={updateTableOfContents} />
@@ -57,13 +57,13 @@
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q3MLRXCBFT"></script>
<script>
window.dataLayer = window.dataLayer || []
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments)
dataLayer.push(arguments);
}
gtag("js", new Date())
gtag("js", new Date());
gtag("config", "G-Q3MLRXCBFT")
gtag("config", "G-Q3MLRXCBFT");
</script>
<link
rel="stylesheet"
@@ -71,9 +71,7 @@
/>
<!-- Highlight.js JS -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
</svelte:head>
<div class="dark">
<nav class="z-2 fixed left-0 right-0 top-0 z-30 h-16 bg-card">
@@ -85,7 +83,7 @@
<!-- Document Icon - Replace with your own logo -->
<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"> 3.0.12 </span>
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium"> 3.1.0 </span>
</a>
</div>
@@ -99,12 +97,8 @@
/>
</a>
<a href="/api-reference" class="text-sm font-medium"> API Reference </a>
<a href="https://github.com/rajnandan1/kener/issues" class="text-sm font-medium">
Report Issue
</a>
<a href="https://github.com/sponsors/rajnandan1" class="text-sm font-medium">
Sponsor
</a>
<a href="https://github.com/rajnandan1/kener/issues" class="text-sm font-medium"> Report Issue </a>
<a href="https://github.com/sponsors/rajnandan1" class="text-sm font-medium"> Sponsor </a>
</div>
</div>
@@ -112,12 +106,7 @@
<div class="md:hidden">
<button type="button" class="hover: text-muted-foreground">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
@@ -163,9 +152,7 @@
</div>
</main>
{#if tableOfContents.length > 0}
<div
class="blurry-bg fixed bottom-0 right-0 top-16 hidden w-64 overflow-y-auto px-6 py-10 lg:block"
>
<div class="blurry-bg fixed bottom-0 right-0 top-16 hidden w-64 overflow-y-auto px-6 py-10 lg:block">
<h4 class="mb-3 text-sm font-semibold uppercase tracking-wider">On this page</h4>
<nav class="space-y-2">
{#each tableOfContents as item}
@@ -2,154 +2,147 @@
// @ts-ignore
import { json } from "@sveltejs/kit";
import notification from "$lib/server/notification/notif.js";
import Service from "$lib/server/services/service.js";
import {
CreateUpdateMonitor,
InsertKeyValue,
GetMonitors,
CreateUpdateTrigger,
GetAllTriggers,
UpdateTriggerData,
GetAllAlertsPaginated,
GetAllAPIKeys,
GetAllSiteData,
CreateNewAPIKey,
UpdateApiKeyStatus,
VerifyToken,
GetIncidentsDashboard,
CreateIncident,
AddIncidentMonitor,
RemoveIncidentMonitor,
GetIncidentActiveComments,
UpdateCommentStatusByID,
AddIncidentComment,
UpdateCommentByID,
UpdateIncident,
GetTriggerByID
CreateUpdateMonitor,
InsertKeyValue,
GetMonitors,
CreateUpdateTrigger,
GetAllTriggers,
UpdateTriggerData,
GetAllAlertsPaginated,
GetMonitorsParsed,
GetAllAPIKeys,
GetAllSiteData,
CreateNewAPIKey,
UpdateApiKeyStatus,
VerifyToken,
GetIncidentsDashboard,
CreateIncident,
AddIncidentMonitor,
RemoveIncidentMonitor,
GetIncidentActiveComments,
UpdateCommentStatusByID,
AddIncidentComment,
UpdateCommentByID,
UpdateIncident,
GetTriggerByID,
} from "$lib/server/controllers/controller.js";
export async function POST({ request, cookies }) {
const payload = await request.json();
let action = payload.action;
let data = payload.data || {};
let resp = {};
const payload = await request.json();
let action = payload.action;
let data = payload.data || {};
let resp = {};
let tokenData = cookies.get("kener-user");
let tokenData = cookies.get("kener-user");
if (!!!tokenData) {
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
if (!!!tokenData) {
return json(
{
error: "Unauthorized",
},
{ status: 401 },
);
}
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
//redirect to signin page if user is not authenticated
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
//redirect to signin page if user is not authenticated
return json(
{
error: "Unauthorized",
},
{ status: 401 },
);
}
try {
if (action === "storeSiteData") {
resp = await storeSiteData(data);
} else if (action == "storeMonitorData") {
resp = await CreateUpdateMonitor(data);
} else if (action == "getMonitors") {
resp = await GetMonitors(data);
} else if (action == "createUpdateTrigger") {
resp = await CreateUpdateTrigger(data);
} else if (action == "getTriggers") {
resp = await GetAllTriggers(data);
} else if (action == "updateMonitorTriggers") {
resp = await UpdateTriggerData(data);
} else if (action == "getAllAlertsPaginated") {
resp = await GetAllAlertsPaginated(data);
} else if (action == "getAPIKeys") {
resp = await GetAllAPIKeys();
} else if (action == "createNewApiKey") {
resp = await CreateNewAPIKey(data);
} else if (action == "updateApiKeyStatus") {
resp = await UpdateApiKeyStatus(data);
} else if (action == "getIncidents") {
resp = await GetIncidentsDashboard(data);
} else if (action == "createIncident") {
resp = await CreateIncident(data);
} else if (action == "updateIncident") {
resp = await UpdateIncident(data.id, data);
} else if (action == "addMonitor") {
resp = await AddIncidentMonitor(
data.incident_id,
data.monitor_tag,
data.monitor_impact
);
} else if (action == "removeMonitor") {
resp = await RemoveIncidentMonitor(data.incident_id, data.monitor_tag);
} else if (action == "getComments") {
resp = await GetIncidentActiveComments(data.incident_id);
} else if (action == "addComment") {
resp = await AddIncidentComment(
data.incident_id,
data.comment,
data.state,
data.commented_at
);
} else if (action == "deleteComment") {
resp = await UpdateCommentStatusByID(data.incident_id, data.comment_id, "INACTIVE");
} else if (action == "updateComment") {
resp = await UpdateCommentByID(
data.incident_id,
data.comment_id,
data.comment,
data.state,
data.commented_at
);
} else if (action == "testTrigger") {
let trigger = await GetTriggerByID(data.trigger_id);
let siteData = await GetAllSiteData();
const notificationClient = new notification(trigger, siteData, {});
const testObj = {
id: "test",
alert_name:
"Test Alert " + (Math.floor(Math.random() * 100) % 2) == 0
? "DOWN"
: "DEGRADED",
severity: Math.floor(Math.random() * 100) % 2 == 0 ? "critical" : "warning",
status: Math.floor(Math.random() * 100) % 2 == 0 ? "TRIGGERED" : "RESOLVED",
source: "Kener",
timestamp: new Date().toISOString(),
description: "Monitor has failed",
details: {
metric: "Test",
current_value: Math.floor(Math.random() * 100),
threshold: Math.floor(Math.random() * 100)
},
actions: [
{
text: "View Monitor",
url: siteData.siteURL + "/monitor-test"
}
]
};
resp = await notificationClient.send(testObj);
}
} catch (error) {
resp = { error: error.message };
return json(resp, { status: 500 });
}
return json(resp, { status: 200 });
try {
if (action === "storeSiteData") {
resp = await storeSiteData(data);
} else if (action == "storeMonitorData") {
resp = await CreateUpdateMonitor(data);
} else if (action == "getMonitors") {
resp = await GetMonitors(data);
} else if (action == "createUpdateTrigger") {
resp = await CreateUpdateTrigger(data);
} else if (action == "getTriggers") {
resp = await GetAllTriggers(data);
} else if (action == "updateMonitorTriggers") {
resp = await UpdateTriggerData(data);
} else if (action == "getAllAlertsPaginated") {
resp = await GetAllAlertsPaginated(data);
} else if (action == "getAPIKeys") {
resp = await GetAllAPIKeys();
} else if (action == "createNewApiKey") {
resp = await CreateNewAPIKey(data);
} else if (action == "updateApiKeyStatus") {
resp = await UpdateApiKeyStatus(data);
} else if (action == "getIncidents") {
resp = await GetIncidentsDashboard(data);
} else if (action == "createIncident") {
resp = await CreateIncident(data);
} else if (action == "updateIncident") {
resp = await UpdateIncident(data.id, data);
} else if (action == "addMonitor") {
resp = await AddIncidentMonitor(data.incident_id, data.monitor_tag, data.monitor_impact);
} else if (action == "removeMonitor") {
resp = await RemoveIncidentMonitor(data.incident_id, data.monitor_tag);
} else if (action == "getComments") {
resp = await GetIncidentActiveComments(data.incident_id);
} else if (action == "addComment") {
resp = await AddIncidentComment(data.incident_id, data.comment, data.state, data.commented_at);
} else if (action == "deleteComment") {
resp = await UpdateCommentStatusByID(data.incident_id, data.comment_id, "INACTIVE");
} else if (action == "updateComment") {
resp = await UpdateCommentByID(data.incident_id, data.comment_id, data.comment, data.state, data.commented_at);
} else if (action == "testTrigger") {
let trigger = await GetTriggerByID(data.trigger_id);
let siteData = await GetAllSiteData();
const notificationClient = new notification(trigger, siteData, {});
const testObj = {
id: "test",
alert_name: "Test Alert " + (Math.floor(Math.random() * 100) % 2) == 0 ? "DOWN" : "DEGRADED",
severity: Math.floor(Math.random() * 100) % 2 == 0 ? "critical" : "warning",
status: Math.floor(Math.random() * 100) % 2 == 0 ? "TRIGGERED" : "RESOLVED",
source: "Kener",
timestamp: new Date().toISOString(),
description: "Monitor has failed",
details: {
metric: "Test",
current_value: Math.floor(Math.random() * 100),
threshold: Math.floor(Math.random() * 100),
},
actions: [
{
text: "View Monitor",
url: siteData.siteURL + "/monitor-test",
},
],
};
resp = await notificationClient.send(testObj);
} else if (action == "testMonitor") {
let monitorID = data.monitor_id;
let monitors = await GetMonitorsParsed({ id: monitorID });
let monitor = monitors[0];
if (monitor.monitor_type === "NONE") {
throw new Error("Tests can't be run on monitor type NONE");
}
const serviceClient = new Service(monitor);
resp = await serviceClient.execute();
}
} catch (error) {
resp = { error: error.message };
return json(resp, { status: 500 });
}
return json(resp, { status: 200 });
}
async function storeSiteData(data) {
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
const element = data[key];
await InsertKeyValue(key, element);
}
}
return { success: true };
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
const element = data[key];
await InsertKeyValue(key, element);
}
}
return { success: true };
}