diff --git a/docs/roadmap.md b/docs/roadmap.md index 868758f4..40ee533e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -40,4 +40,4 @@ description: Roadmap for Kener --- -Request a feature or start a discussion [here](https://github.com/rajnandan1/kener/discussions/119) +Request a feature or start a discussion [here](https://github.com/rajnandan1/kener/discussions/new/choose) diff --git a/package.json b/package.json index 2f8a440c..9ee63c0c 100644 --- a/package.json +++ b/package.json @@ -1,111 +1,111 @@ { - "name": "kener", - "version": "3.0.10", - "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.", - "author": "Raj Nandan Sharma ", - "keywords": [ - "Node.js application", - "Open-source status page", - "Service monitoring tool", - "Real-time incident management", - "Customizable reporting", - "Service outage tracker", - "User-friendly dashboard", - "Incident communication platform", - "Scalable monitoring solution", - "Community-driven software", - "Website status tracker", - "Incident response tool", - "System status monitoring", - "Service reliability management", - "Incident alert system" - ], - "repository": { - "type": "git", - "url": "https://github.com/rajnandan1/kener.git" - }, - "scripts": { - "build": "vite build", - "preview": "vite preview", - "configure": "node build.js", - "migrate": "npx knex migrate:latest", - "preseed": "npx knex migrate:latest", - "seed": "npx knex seed:run", - "predev": "npm run seed", - "devschedule": "node src/lib/server/startup.js", - "schedule": "node src/lib/server/startup.js", - "development": "vite dev", - "dev": "npm-run-all --parallel devschedule development", - "prettify": "prettier --write .", - "start": "node main.js" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/adapter-node": "^1.3.1", - "@sveltejs/kit": "^1.27.4", - "@tailwindcss/typography": "^0.5.10", - "@zerodevx/svelte-toast": "^0.9.6", - "autoprefixer": "^10.4.14", - "concurrently": "^8.2.2", - "cross-env": "^7.0.3", - "date-picker-svelte": "^2.15.1", - "postcss": "^8.4.24", - "postcss-load-config": "^4.0.1", - "prettier": "^3.2.5", - "prettier-plugin-svelte": "^3.2.3", - "prettier-plugin-tailwindcss": "^0.5.14", - "svelte": "^4.0.5", - "svelte-awesome-color-picker": "^3.1.4", - "svelte-check": "^3.6.0", - "svelte-dnd-action": "^0.9.55", - "tailwindcss": "^3.3.2", - "typescript": "^5.0.0", - "vite": "^4.4.2" - }, - "type": "module", - "dependencies": { - "@formkit/auto-animate": "^0.8.2", - "@number-flow/svelte": "^0.2.1", - "@scalar/express-api-reference": "^0.4.167", - "analytics": "^0.8.14", - "axios": "^1.6.2", - "badge-maker": "^3.3.1", - "bcrypt": "^5.1.1", - "better-sqlite3": "^11.5.0", - "bits-ui": "^0.9.9", - "clsx": "^2.0.0", - "croner": "^7.0.5", - "date-fns": "^4.1.0", - "dns2": "^2.1.0", - "dotenv": "^16.4.5", - "express": "^4.18.2", - "figlet": "^1.8.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "jsonwebtoken": "^9.0.2", - "knex": "^3.1.0", - "lucide-svelte": "^0.292.0", - "marked": "^11.1.1", - "mode-watcher": "^0.4.1", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "mysql2": "^3.12.0", - "node-cache": "^5.1.2", - "nodemailer": "^6.10.0", - "npm-run-all": "^4.1.5", - "pg": "^8.13.1", - "pg-pool": "^3.7.0", - "ping": "^0.4.4", - "queue": "^7.0.0", - "randomstring": "^1.3.0", - "resend": "^4.0.1", - "svelte-legos": "^0.2.5", - "tailwind-merge": "^2.0.0", - "tailwind-variants": "^0.1.18" - }, - "engines": { - "node": ">=20.0.0" - } + "name": "kener", + "version": "3.0.11", + "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.", + "author": "Raj Nandan Sharma ", + "keywords": [ + "Node.js application", + "Open-source status page", + "Service monitoring tool", + "Real-time incident management", + "Customizable reporting", + "Service outage tracker", + "User-friendly dashboard", + "Incident communication platform", + "Scalable monitoring solution", + "Community-driven software", + "Website status tracker", + "Incident response tool", + "System status monitoring", + "Service reliability management", + "Incident alert system" + ], + "repository": { + "type": "git", + "url": "https://github.com/rajnandan1/kener.git" + }, + "scripts": { + "build": "vite build", + "preview": "vite preview", + "configure": "node build.js", + "migrate": "npx knex migrate:latest", + "preseed": "npx knex migrate:latest", + "seed": "npx knex seed:run", + "predev": "npm run seed", + "devschedule": "node src/lib/server/startup.js", + "schedule": "node src/lib/server/startup.js", + "development": "vite dev", + "dev": "npm-run-all --parallel devschedule development", + "prettify": "prettier --write .", + "start": "node main.js" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^2.0.0", + "@sveltejs/adapter-node": "^1.3.1", + "@sveltejs/kit": "^1.27.4", + "@tailwindcss/typography": "^0.5.10", + "@zerodevx/svelte-toast": "^0.9.6", + "autoprefixer": "^10.4.14", + "concurrently": "^8.2.2", + "cross-env": "^7.0.3", + "date-picker-svelte": "^2.15.1", + "postcss": "^8.4.24", + "postcss-load-config": "^4.0.1", + "prettier": "^3.2.5", + "prettier-plugin-svelte": "^3.2.3", + "prettier-plugin-tailwindcss": "^0.5.14", + "svelte": "^4.0.5", + "svelte-awesome-color-picker": "^3.1.4", + "svelte-check": "^3.6.0", + "svelte-dnd-action": "^0.9.55", + "tailwindcss": "^3.3.2", + "typescript": "^5.0.0", + "vite": "^4.4.2" + }, + "type": "module", + "dependencies": { + "@formkit/auto-animate": "^0.8.2", + "@number-flow/svelte": "^0.2.1", + "@scalar/express-api-reference": "^0.4.167", + "analytics": "^0.8.14", + "axios": "^1.6.2", + "badge-maker": "^3.3.1", + "bcrypt": "^5.1.1", + "better-sqlite3": "^11.5.0", + "bits-ui": "^0.9.9", + "clsx": "^2.0.0", + "croner": "^7.0.5", + "date-fns": "^4.1.0", + "dns2": "^2.1.0", + "dotenv": "^16.4.5", + "express": "^4.18.2", + "figlet": "^1.8.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", + "knex": "^3.1.0", + "lucide-svelte": "^0.292.0", + "marked": "^11.1.1", + "mode-watcher": "^0.4.1", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "mysql2": "^3.12.0", + "node-cache": "^5.1.2", + "nodemailer": "^6.10.0", + "npm-run-all": "^4.1.5", + "pg": "^8.13.1", + "pg-pool": "^3.7.0", + "ping": "^0.4.4", + "queue": "^7.0.0", + "randomstring": "^1.3.0", + "resend": "^4.0.1", + "svelte-legos": "^0.2.5", + "tailwind-merge": "^2.0.0", + "tailwind-variants": "^0.1.18" + }, + "engines": { + "node": ">=20.0.0" + } } diff --git a/src/lib/components/manage/monitorSheet.svelte b/src/lib/components/manage/monitorSheet.svelte index 81855357..8d2e1e24 100644 --- a/src/lib/components/manage/monitorSheet.svelte +++ b/src/lib/components/manage/monitorSheet.svelte @@ -1,24 +1,24 @@
-
{ - dispatch("closeModal", {}); - }} - class="absolute right-0 top-0 h-screen w-[800px] bg-background px-3 shadow-xl" - > - -
- {#if newMonitor.id} -

Edit Monitor

- {:else} -

Add Monitor

- {/if} -
- -
-
-
-
-
- - {#if !!newMonitor.image} -
- - -
- {/if} - { - handleFileChangeLogo(e); - }} - /> -
-
- - -
-
- - -
-
- - -
+
{ + dispatch("closeModal", {}) + }} + class="absolute right-0 top-0 h-screen w-[800px] bg-background px-3 shadow-xl" + > + +
+ {#if newMonitor.id} +

Edit Monitor

+ {:else} +

Add Monitor

+ {/if} +
+ +
+
+
+
+
+ + {#if !!newMonitor.image} +
+ + +
+ {/if} + { + handleFileChangeLogo(e) + }} + /> +
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - (newMonitor.default_status = e.value)} - > - - - - - - Status - - NONE - - - UP - - - DOWN - - - DEGRADED - - - - -
+
+ + +
+
+ + (newMonitor.default_status = e.value)} + > + + + + + + Status + + NONE + + UP + + DOWN + + + DEGRADED + + + + +
-
- - (newMonitor.category_name = e.value)} - > - - - - - - Category +
+ + (newMonitor.category_name = e.value)} + > + + + + + + Category - {#each categories as category} - - {category.name} - - {/each} - - - -
+ {#each categories as category} + + {category.name} + + {/each} +
+
+
+
-
- - -
-
- - -
-
- - - (newMonitor.include_degraded_in_downtime = e.value)} - > - - - - - - Select YES or NO - - YES - - - NO - - - - -
-
- - (newMonitor.monitor_type = e.value)} - selected={{ - value: newMonitor.monitor_type, - label: newMonitor.monitor_type - }} - > - - - - - - Type - - NONE - - - API - - - PING - - - DNS - - - TCP - - - - -
-
- {#if newMonitor.monitor_type === "API"} -
-
- - -
-
- - (newMonitor.apiConfig.method = e.value)} - > - - - - - - Method - - GET - - - POST - - - PUT - - - PATCH - - - DELETE - - - - -
-
- - -
-
- -
- {#each newMonitor.apiConfig.headers as header, index} -
- -
-
- -
-
- -
- {/each} -
-
-
+
+ + +
+
+ + +
+
+ + (newMonitor.include_degraded_in_downtime = e.value)} + > + + + + + + Select YES or NO + YES + NO + + + +
+
+ + (newMonitor.monitor_type = e.value)} + selected={{ + value: newMonitor.monitor_type, + label: newMonitor.monitor_type + }} + > + + + + + + Type + + NONE + + API + + PING + + DNS + TCP + + + +
+
+ {#if newMonitor.monitor_type === "API"} +
+
+ + +
+
+ + (newMonitor.apiConfig.method = e.value)} + > + + + + + + Method + GET + + POST + + PUT + + PATCH + + + DELETE + + + + +
+
+ + +
+
+ +
+ {#each newMonitor.apiConfig.headers as header, index} +
+ +
+
+ +
+
+ +
+ {/each} +
+
+
- -
-
+ +
+
- {#if newMonitor.apiConfig.method[0] == "P"} -
- - -
- {/if} -
- -

- 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. Read the docs to learn -

- -
-
- {:else if newMonitor.monitor_type == "PING"} -
-
-
- {#each newMonitor.pingConfig.hosts as host, index} -
-
- - (host.type = e.value)} - selected={{ - value: host.type, - label: host.type - }} - > - - - - - - Type - - IP4 - - - IP6 - - - DOMAIN - - - - -
-
- - -
-
- - -
-
- - -
-
- -
-
- {/each} -
+ {#if newMonitor.apiConfig.method[0] == "P"} +
+ + +
+ {/if} +
+ +

+ 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. Read the docs to learn +

+ +
+
+ {:else if newMonitor.monitor_type == "PING"} +
+
+
+ {#each newMonitor.pingConfig.hosts as host, index} +
+
+ + (host.type = e.value)} + selected={{ + value: host.type, + label: host.type + }} + > + + + + + + Type + + IP4 + + + IP6 + + + DOMAIN + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {/each} +
-
-
- -
-
-
- -

- 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. Read the docs to learn -

- -
-
-
- {:else if newMonitor.monitor_type == "TCP"} -
-
-
- {#each newMonitor.tcpConfig.hosts as host, index} -
-
- - (host.type = e.value)} - selected={{ - value: host.type, - label: host.type - }} - > - - - - - - Type - - IP4 - - - IP6 - - - DOMAIN - - - - -
-
- - -
-
- - -
-
- - -
-
- -
-
- {/each} -
+
+
+ +
+
+
+ +

+ 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. Read the docs to learn +

+ +
+
+
+ {:else if newMonitor.monitor_type == "TCP"} +
+
+
+ {#each newMonitor.tcpConfig.hosts as host, index} +
+
+ + (host.type = e.value)} + selected={{ + value: host.type, + label: host.type + }} + > + + + + + + Type + + IP4 + + + IP6 + + + DOMAIN + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {/each} +
-
-
- -
-
-
- -

- 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. Read the docs to learn -

- -
-
-
- {:else if newMonitor.monitor_type == "DNS"} -
-
- - -
-
- - (newMonitor.dnsConfig.lookupRecord = e.value)} - > - - - - - - Record - {#each Object.keys(allRecordTypes) as record} - - {record} - - {/each} - - - -
-
- - -
-
- - (newMonitor.dnsConfig.matchType = e.value)} - > - - - - - - Match Type - - ANY - - - ALL - - - - -
+
+
+ +
+
+
+ +

+ 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. Read the docs to learn +

+ +
+
+
+ {:else if newMonitor.monitor_type == "DNS"} +
+
+ + +
+
+ + (newMonitor.dnsConfig.lookupRecord = e.value)} + > + + + + + + Record + {#each Object.keys(allRecordTypes) as record} + + {record} + + {/each} + + + +
+
+ + +
+
+ + (newMonitor.dnsConfig.matchType = e.value)} + > + + + + + + Match Type + ANY + ALL + + + +
-
- -
- {#each newMonitor.dnsConfig.values as value, index} -
- - -
- {/each} -
- -
-
-
-
- {/if} -
-
-
-

- {invalidFormMessage} -

-
-
- -
-
-
+
+ +
+ {#each newMonitor.dnsConfig.values as value, index} +
+ + +
+ {/each} +
+ +
+
+
+
+ {/if} +
+
+
+

+ {invalidFormMessage} +

+
+
+ +
+
+
diff --git a/src/lib/components/manage/monitorsAdd.svelte b/src/lib/components/manage/monitorsAdd.svelte index e8766b2f..5500503c 100644 --- a/src/lib/components/manage/monitorsAdd.svelte +++ b/src/lib/components/manage/monitorsAdd.svelte @@ -1,650 +1,594 @@ {#if showAddMonitor} - { - showAddMonitor = false; - loadData(); - }} - /> + { + showAddMonitor = false + loadData() + }} + /> {/if} {#if draggableMenu} -
-
- -
-

Rearrange Monitors

-
- {#each monitors as monitor (monitor.id)} -
- - {#if !!monitor.image} - {monitor.name} - {/if} - {monitor.name} -
- {/each} -
-
-
-
+
+
+ +
+

Rearrange Monitors

+
+ {#each monitors as monitor (monitor.id)} +
+ + {#if !!monitor.image} + {monitor.name} + {/if} + {monitor.name} +
+ {/each} +
+
+
+
{/if}
-
- { - status = e.value; - loadData(); - }} - selected={{ - value: status, - label: status - }} - > - - - - - - Status - - ACTIVE - - - INACTIVE - - - - +
+ { + status = e.value + loadData() + }} + selected={{ + value: status, + label: status + }} + > + + + + + + Status + + ACTIVE + + + INACTIVE + + + + - { - selectedCategory = e.value; - loadData(); - }} - selected={{ - value: selectedCategory, - label: selectedCategory - }} - > - - - - - - Category - - All Categories - - {#each categories as category} - - {category.name} - - {/each} - - - -
- {#if loadingData} - - {/if} -
-
-
- {#if status == "ACTIVE"} - - {/if} - -
+ { + selectedCategory = e.value + loadData() + }} + selected={{ + value: selectedCategory, + label: selectedCategory + }} + > + + + + + + Category + + All Categories + + {#each categories as category} + + {category.name} + + {/each} + + + +
+ {#if loadingData} + + {/if} +
+
+
+ {#if status == "ACTIVE"} + + {/if} + +
- {#each monitors as monitor} - - - - {#if !!monitor.image} - {monitor.name} - {/if} - {monitor.name} - - {#if !!monitor.description} - {@html monitor.description} - {/if} -
- - -
-
- -
-
- -

- {monitor.tag} -

-
-
- -

- {monitor.monitor_type} -

-
-
- -

- {monitor.cron} -

-
-
- -

- {!!monitor.category_name ? monitor.category_name : "-"} -

-
-
-
-
- {/each} + {#each monitors as monitor} + + + + {#if !!monitor.image} + {monitor.name} + {/if} + {monitor.name} + + {#if !!monitor.description} + {@html monitor.description} + {/if} +
+ + +
+
+ +
+
+ +

+ {monitor.tag} +

+
+
+ +

+ {monitor.monitor_type} +

+
+
+ +

+ {monitor.cron} +

+
+
+ +

+ {!!monitor.category_name ? monitor.category_name : "-"} +

+
+
+
+
+ {/each}
{#if shareMenusToggle} -
-
- -
-

- Add Alert Triggers for {currentAlertMonitor.name} -

-

- Alert triggers are used to notify you when your monitor is down or degraded. -

-
- {#each Object.entries(monitorTriggers) as [key, data]} -
-

- If Monitor {data.trigger_type} -

-
- -
-
+
+
+ +
+

+ Add Alert Triggers for {currentAlertMonitor.name} +

+

+ Alert triggers are used to notify you when your monitor is down or degraded. +

+
+ {#each Object.entries(monitorTriggers) as [key, data]} +
+

+ If Monitor {data.trigger_type} +

+
+ +
+
-
-
- - -
-
-
- - -
-
-
- - (data.createIncident = e.value)} - selected={{ - value: data.createIncident, - label: data.createIncident - }} - > - - - - - - - YES - - - NO - - - - -
-
- - (data.severity = e.value)} - selected={{ - value: data.severity, - label: data.severity.toUpperCase() - }} - > - - - - - - - CRITICAL - - - WARNING - - - - -
-
- - -
-

Choose Triggers

- {#each triggers as trigger} -
- -
- {/each} -
-
- {/each} +
+
+ + +
+
+
+ + +
+
+
+ + (data.createIncident = e.value)} + selected={{ + value: data.createIncident, + label: data.createIncident + }} + > + + + + + + + YES + + NO + + + +
+
+ + (data.severity = e.value)} + selected={{ + value: data.severity, + label: data.severity.toUpperCase() + }} + > + + + + + + + CRITICAL + + + WARNING + + + + +
+
+ + +
+

Choose Triggers

+ {#each triggers as trigger} +
+ +
+ {/each} +
+
+ {/each} -
- -
-
-
-
+
+ +
+
+
+
{/if} diff --git a/src/lib/server/cron-minute.js b/src/lib/server/cron-minute.js index dab1af63..301e24c4 100644 --- a/src/lib/server/cron-minute.js +++ b/src/lib/server/cron-minute.js @@ -3,10 +3,10 @@ import axios from "axios"; import { Ping, ExtractIPv6HostAndPort, TCP } from "./ping.js"; import { UP, DOWN, DEGRADED } from "./constants.js"; import { - GetMinuteStartNowTimestampUTC, - ReplaceAllOccurrences, - GetRequiredSecrets, - Wait + GetMinuteStartNowTimestampUTC, + ReplaceAllOccurrences, + GetRequiredSecrets, + Wait, } from "./tool.js"; import alerting from "./alerting.js"; @@ -25,14 +25,14 @@ const ERROR = "error"; const MANUAL = "manual"; const alertingQueue = new Queue({ - concurrency: 10, // Number of tasks that can run concurrently - timeout: 10000, // Timeout in ms after which a task will be considered as failed (optional) - autostart: true // Automatically start the queue (optional) + concurrency: 10, // Number of tasks that can run concurrently + timeout: 10000, // Timeout in ms after which a task will be considered as failed (optional) + autostart: true, // Automatically start the queue (optional) }); const apiQueue = new Queue({ - concurrency: 10, // Number of tasks that can run concurrently - timeout: 10000, // Timeout in ms after which a task will be considered as failed (optional) - autostart: true // Automatically start the queue (optional) + concurrency: 10, // Number of tasks that can run concurrently + timeout: 10000, // Timeout in ms after which a task will be considered as failed (optional) + autostart: true, // Automatically start the queue (optional) }); const defaultEval = `(async function (statusCode, responseTime, responseData) { @@ -85,410 +85,443 @@ const defaultTcpEval = `(async function (responseDataBase64) { })`; async function manualIncident(monitor) { - let startTs = GetMinuteStartNowTimestampUTC(); - let incidentArr = await db.getIncidentsByMonitorTagRealtime(monitor.tag, startTs); - let maintenanceArr = await db.getMaintenanceByMonitorTagRealtime(monitor.tag, startTs); - - let impactArr = incidentArr.concat(maintenanceArr); - - let impact = ""; - if (impactArr.length == 0) { - return {}; - } - - for (let i = 0; i < impactArr.length; i++) { - const element = impactArr[i]; - - let autoIncidents = await db.getActiveAlertIncident( - monitor.tag, - element.monitor_impact, - element.id - ); - - if (!!autoIncidents) { - continue; - } - - if (element.monitor_impact === "DOWN") { - impact = "DOWN"; - break; - } - if (element.monitor_impact === "DEGRADED") { - impact = "DEGRADED"; - } - } - - if (impact === "") { - return {}; - } - - let manualData = { - [startTs]: { - status: impact, - latency: 0, - type: MANUAL - } - }; - return manualData; + let startTs = GetMinuteStartNowTimestampUTC(); + let incidentArr = await db.getIncidentsByMonitorTagRealtime( + monitor.tag, + startTs, + ); + let maintenanceArr = await db.getMaintenanceByMonitorTagRealtime( + monitor.tag, + startTs, + ); + + let impactArr = incidentArr.concat(maintenanceArr); + + let impact = ""; + if (impactArr.length == 0) { + return {}; + } + + for (let i = 0; i < impactArr.length; i++) { + const element = impactArr[i]; + + let autoIncidents = await db.getActiveAlertIncident( + monitor.tag, + element.monitor_impact, + element.id, + ); + + if (!!autoIncidents) { + continue; + } + + if (element.monitor_impact === "DOWN") { + impact = "DOWN"; + break; + } + if (element.monitor_impact === "DEGRADED") { + impact = "DEGRADED"; + } + } + + if (impact === "") { + return {}; + } + + let manualData = { + [startTs]: { + status: impact, + latency: 0, + type: MANUAL, + }, + }; + return manualData; } const tcpCall = async (hosts, tcpEval, tag) => { - 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 - }; + 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, + }; }; const pingCall = async (hosts, pingEval, tag) => { - 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 - }; + 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; +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 - }; - } + 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(); - 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 - ); - - realTimeData[startOfMinute] = apiResponse; - 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) => { - await db.insertMonitoringData({ - monitor_tag: monitor.tag, - timestamp: startOfMinute, - status: data.status, - latency: data.latency, - type: data.type - }); - cb(); - }); - }); - } - } 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; - } 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; - } 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; - } - - manualData = await manualIncident(monitor); - //merge noData, apiData, webhookData, dayData - let mergedData = {}; - - if (monitor.default_status !== undefined && monitor.default_status !== null) { - if ([UP, DOWN, DEGRADED].indexOf(monitor.default_status) !== -1) { - mergedData[startOfMinute] = { - status: monitor.default_status, - latency: 0, - type: "default_status" - }; - } - } - - for (const timestamp in realTimeData) { - mergedData[timestamp] = realTimeData[timestamp]; - } - - for (const timestamp in manualData) { - mergedData[timestamp] = manualData[timestamp]; - } - - for (const timestamp in mergedData) { - const element = mergedData[timestamp]; - db.insertMonitoringData({ - monitor_tag: monitor.tag, - timestamp: parseInt(timestamp), - status: element.status, - latency: element.latency, - type: element.type - }); - } - alertingQueue.push(async (cb) => { - setTimeout(async () => { - await alerting(monitor); - cb(); - }, 1042); - }); + let realTimeData = {}; + let manualData = {}; + + const startOfMinute = GetMinuteStartNowTimestampUTC(); + 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, + ); + + realTimeData[startOfMinute] = apiResponse; + 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) => { + await db.insertMonitoringData({ + monitor_tag: monitor.tag, + timestamp: startOfMinute, + status: data.status, + latency: data.latency, + type: data.type, + }); + cb(); + }); + }); + } + } 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; + } 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; + } 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; + } + + manualData = await manualIncident(monitor); + //merge noData, apiData, webhookData, dayData + let mergedData = {}; + + if (monitor.default_status !== undefined && monitor.default_status !== null) { + if ([UP, DOWN, DEGRADED].indexOf(monitor.default_status) !== -1) { + mergedData[startOfMinute] = { + status: monitor.default_status, + latency: 0, + type: "default_status", + }; + } + } + + for (const timestamp in realTimeData) { + mergedData[timestamp] = realTimeData[timestamp]; + } + + for (const timestamp in manualData) { + mergedData[timestamp] = manualData[timestamp]; + } + + for (const timestamp in mergedData) { + const element = mergedData[timestamp]; + db.insertMonitoringData({ + monitor_tag: monitor.tag, + timestamp: parseInt(timestamp), + status: element.status, + latency: element.latency, + type: element.type, + }); + } + alertingQueue.push(async (cb) => { + setTimeout(async () => { + await alerting(monitor); + cb(); + }, 1042); + }); }; alertingQueue.start((err) => { - if (err) { - console.error("Error occurred:", err); - process.exit(1); - } + if (err) { + console.error("Error occurred:", err); + process.exit(1); + } }); apiQueue.start((err) => { - if (err) { - console.error("Error occurred:", err); - process.exit(1); - } + if (err) { + console.error("Error occurred:", err); + process.exit(1); + } }); export { Minuter }; diff --git a/src/routes/(docs)/+layout.svelte b/src/routes/(docs)/+layout.svelte index 4f65ff1d..eb41216a 100644 --- a/src/routes/(docs)/+layout.svelte +++ b/src/routes/(docs)/+layout.svelte @@ -1,195 +1,185 @@ - + - - - + - + gtag("config", "G-Q3MLRXCBFT") + + - - + +
- - - + + - -
-
- -
- -
-
-
- {#if tableOfContents.length > 0} - - {/if} + +
+
+ +
+ +
+
+
+ {#if tableOfContents.length > 0} + + {/if}