diff --git a/.env.example b/.env.example index 264a84a..1131e92 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ +KENER_SECRET_KEY=please_change_me NODE_ENV=production PORT=3000 GH_TOKEN=your_github_token -API_TOKEN=your_api_token -API_IP="" -API_IP_REGEX="" -KENER_BASE_PATH="" \ No newline at end of file +KENER_BASE_PATH="" +RESEND_API_KEY= \ No newline at end of file diff --git a/build.js b/build.js deleted file mode 100644 index bdde3e3..0000000 --- a/build.js +++ /dev/null @@ -1,455 +0,0 @@ -import yaml from "js-yaml"; -import fs from "fs-extra"; -import axios from "axios"; -import { - IsValidURL, - checkIfDuplicateExists, - IsValidHTTPMethod, - ValidateIpAddress, - IsValidHost, - IsValidRecordType, - IsValidNameServer -} from "./src/lib/server/tool.js"; -import { API_TIMEOUT, AnalyticsProviders } from "./src/lib/server/constants.js"; -import { GetAllGHLabels, CreateGHLabel } from "./src/lib/server/github.js"; - -const configPathFolder = "./config"; -const databaseFolder = process.argv[2] || "./database"; -const defaultEval = `(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, - } -})`; - -function validateServerFile(server) { - //if empty return true - if (Object.keys(server).length === 0) { - return true; - } - //server.triggers is present then it should be an array - if (server.triggers !== undefined && !Array.isArray(server.triggers)) { - console.log("triggers should be an array"); - return false; - } - ///each trigger should have a name, type, and url - if (server.triggers !== undefined) { - for (let i = 0; i < server.triggers.length; i++) { - const trigger = server.triggers[i]; - if ( - trigger.name === undefined || - trigger.type === undefined || - trigger.url === undefined - ) { - console.log("trigger should have name, type, and url"); - return false; - } - } - } - //if database is present then it should be an object, and they key can be either postgres or sqlite - if (server.database !== undefined && typeof server.database !== "object") { - console.log("database should be an object"); - return false; - } - if (server.database !== undefined) { - let dbtype = Object.keys(server.database); - if (dbtype.length !== 1) { - console.log("database should have only one key"); - return false; - } - if (dbtype[0] !== "postgres" && dbtype[0] !== "sqlite") { - console.log("database should be either postgres or sqlite"); - return false; - } - } - - return true; -} - -async function Build() { - console.log("ℹ️ Building Kener..."); - let site = {}; - let server = {}; - let monitors = []; - try { - site = yaml.load(fs.readFileSync(configPathFolder + "/site.yaml", "utf8")); - monitors = yaml.load(fs.readFileSync(configPathFolder + "/monitors.yaml", "utf8")); - } catch (error) { - console.log(error); - process.exit(1); - } - try { - server = yaml.load(fs.readFileSync(configPathFolder + "/server.yaml", "utf8")); - if (!validateServerFile(server)) { - process.exit(1); - } - } catch (error) { - console.warn("server.yaml not found"); - server = {}; - } - - if ( - site.github === undefined || - site.github.owner === undefined || - site.github.repo === undefined - ) { - console.log("github owner and repo are required"); - site.hasGithub = false; - // process.exit(1); - } else { - site.hasGithub = true; - } - - if (site.hasGithub && !!!site.github.incidentSince) { - site.github.incidentSince = 720; - } - if (site.hasGithub && !!!site.github.apiURL) { - site.github.apiURL = "https://api.github.com"; - } - - const FOLDER_DB = databaseFolder; - const FOLDER_SITE = FOLDER_DB + "/site.json"; - const FOLDER_MONITOR = FOLDER_DB + "/monitors.json"; - const FOLDER_SERVER = FOLDER_DB + "/server.json"; - - for (let i = 0; i < monitors.length; i++) { - const monitor = monitors[i]; - - let name = monitor.name; - let tag = monitor.tag; - let hasAPI = monitor.api !== undefined && monitor.api !== null; - let hasPing = monitor.ping !== undefined && monitor.ping !== null; - let hasDNS = monitor.dns !== undefined && monitor.dns !== null; - let folderName = name.replace(/[^a-z0-9]/gi, "-").toLowerCase(); - monitors[i].folderName = folderName; - - if (!name || !tag) { - console.log("name, tag are required"); - process.exit(1); - } - - if ( - monitor.dayDegradedMinimumCount && - (isNaN(monitor.dayDegradedMinimumCount) || monitor.dayDegradedMinimumCount < 1) - ) { - console.log("dayDegradedMinimumCount is not a number or it is less than 1"); - process.exit(1); - } else if (monitor.dayDegradedMinimumCount === undefined) { - monitors[i].dayDegradedMinimumCount = 1; - } - - if ( - monitor.dayDownMinimumCount && - (isNaN(monitor.dayDownMinimumCount) || monitor.dayDownMinimumCount < 1) - ) { - console.log("dayDownMinimumCount is not a number or it is less than 1"); - process.exit(1); - } else if (monitor.dayDownMinimumCount === undefined) { - monitors[i].dayDownMinimumCount = 1; - } - - if ( - monitor.includeDegradedInDowntime === undefined || - monitor.includeDegradedInDowntime !== true - ) { - monitors[i].includeDegradedInDowntime = false; - } - if (hasPing) { - let hostsV4 = monitor.ping.hostsV4; - let hostsV6 = monitor.ping.hostsV6; - let hasV4 = false; - let hasV6 = false; - if (hostsV4 && Array.isArray(hostsV4) && hostsV4.length > 0) { - hostsV4.forEach((host) => { - if (ValidateIpAddress(host) == "Invalid") { - console.log(`hostsV4 ${host} is not valid`); - process.exit(1); - } - }); - hasV4 = true; - } - if (hostsV6 && Array.isArray(hostsV6) && hostsV6.length > 0) { - hostsV6.forEach((host) => { - if (ValidateIpAddress(host) == "Invalid") { - console.log(`hostsV6 ${host} is not valid`); - process.exit(1); - } - }); - hasV6 = true; - } - - if (!hasV4 && !hasV6) { - console.log("hostsV4 or hostsV6 is required"); - process.exit(1); - } - monitors[i].hasPing = true; - } - if (hasDNS) { - let dnsData = monitor.dns; - let domain = dnsData.host; - //check if domain is valid - if (!!!domain || !IsValidHost(domain)) { - console.log("domain is not valid"); - process.exit(1); - } - - let recordType = dnsData.lookupRecord; - //check if recordType is valid - if (!!!recordType || !IsValidRecordType(recordType)) { - console.log("recordType is not valid"); - process.exit(1); - } - - let nameServer = dnsData.nameServer; - //check if nameserver is valid - if (!!nameServer && !IsValidNameServer(nameServer)) { - console.log("nameServer is not valid"); - process.exit(1); - } - - // matchType: "ANY" # ANY, ALL - let matchType = dnsData.matchType; - if (!!!matchType || (matchType !== "ANY" && matchType !== "ALL")) { - console.log("matchType is not valid"); - process.exit(1); - } - - //values array of string at least one - let values = dnsData.values; - if (!!!values || !Array.isArray(values) || values.length === 0) { - console.log("values is not valid"); - process.exit(1); - } - monitors[i].hasDNS = true; - } - if (hasAPI) { - let url = monitor.api.url; - let method = monitor.api.method; - let headers = monitor.api.headers; - let evaluator = monitor.api.eval; - let body = monitor.api.body; - let timeout = monitor.api.timeout; - let hideURLForGet = !!monitor.api.hideURLForGet; - //url - if (!!url) { - if (!IsValidURL(url)) { - console.log("url is not valid"); - process.exit(1); - } - } - if (!!method) { - if (!IsValidHTTPMethod(method)) { - console.log("method is not valid"); - process.exit(1); - } - method = method.toUpperCase(); - } else { - method = "GET"; - } - monitors[i].api.method = method; - //headers - if (headers === undefined || headers === null) { - monitors[i].api.headers = undefined; - } else { - //check if headers is a valid json - try { - JSON.parse(JSON.stringify(headers)); - } catch (error) { - console.log("headers are not valid. Quitting"); - process.exit(1); - } - } - //eval - if (evaluator === undefined || evaluator === null) { - monitors[i].api.eval = defaultEval; - } else { - let evalResp = eval(evaluator + `(200, 1000, "e30=")`); - - if ( - evalResp === undefined || - evalResp === null || - evalResp.status === undefined || - evalResp.status === null || - evalResp.latency === undefined || - evalResp.latency === null - ) { - console.log("eval is not valid "); - process.exit(1); - } - monitors[i].api.eval = evaluator; - } - //body - if (body === undefined || body === null) { - monitors[i].api.body = undefined; - } else { - //check if body is a valid string - if (typeof body !== "string") { - console.log("body is not valid should be a string"); - process.exit(1); - } - } - //timeout - if (timeout === undefined || timeout === null) { - monitors[i].api.timeout = API_TIMEOUT; - } else { - //check if timeout is a valid number - if (isNaN(timeout) || timeout < 0) { - console.log("timeout is not valid "); - process.exit(1); - } - } - - //add a description to the monitor if it is website using api.url and method = GET and headers == undefined - //call the it to see if received content-type is text/html - //if yes, append to description - if ( - !hideURLForGet && - (headers === undefined || headers === null) && - url !== undefined && - method === "GET" - ) { - try { - const response = await axios({ - method: "GET", - url: url, - timeout: API_TIMEOUT - }); - if (response.headers["content-type"].includes("text/html")) { - let link = `${url}`; - if (monitors[i].description === undefined) { - monitors[i].description = link; - } else { - monitors[i].description = monitors[i].description?.trim() + " " + link; - } - } - } catch (error) { - console.log(`error while fetching ${url}`); - } - } - } - - monitors[i].hasAPI = hasAPI; - } - - if (site.siteName === undefined) { - site.siteName = site.title; - } - if (site.theme === undefined) { - site.theme = "system"; - } - if (site.themeToggle === undefined) { - site.themeToggle = true; - } - if (site.barStyle === undefined) { - site.barStyle = "FULL"; - } - if (site.barRoundness === undefined) { - site.barRoundness = "ROUNDED"; - } else { - site.barRoundness = site.barRoundness.toLowerCase(); - } - if (site.summaryStyle === undefined) { - site.summaryStyle = "DAY"; - } - site.colors = { - UP: site.colors?.UP || "#4ead94", - DOWN: site.colors?.DOWN || "#ca3038", - DEGRADED: site.colors?.DEGRADED || "#e6ca61" - }; - if (!!site.analytics) { - const providers = {}; - - for (let i = 0; i < site.analytics.length; i++) { - const element = site.analytics[i]; - if (!!AnalyticsProviders[element.type]) { - if (providers[element.type] === undefined) { - providers[element.type] = {}; - providers[element.type].measurementIds = []; - providers[element.type].script = AnalyticsProviders[element.type]; - } - providers[element.type].measurementIds.push(element.id); - } - } - site.analytics = providers; - } - if (!!!site.font || !!!site.font.cssSrc || !!!site.font.family) { - site.font = { - cssSrc: "https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,100..900;1,100..900&display=swap", - family: "Albert Sans" - }; - } - if (checkIfDuplicateExists(monitors.map((monitor) => monitor.folderName)) === true) { - console.log("duplicate monitor detected"); - process.exit(1); - } - if (checkIfDuplicateExists(monitors.map((monitor) => monitor.tag)) === true) { - console.log("duplicate tag detected"); - process.exit(1); - } - - fs.ensureFileSync(FOLDER_MONITOR); - fs.ensureFileSync(FOLDER_SITE); - fs.ensureFileSync(FOLDER_SERVER); - try { - fs.writeFileSync(FOLDER_MONITOR, JSON.stringify(monitors, null, 4)); - fs.writeFileSync(FOLDER_SITE, JSON.stringify(site, null, 4)); - fs.writeFileSync(FOLDER_SERVER, JSON.stringify(server, null, 4)); - } catch (error) { - console.log(error); - process.exit(1); - } - - console.log("✅ Kener built successfully"); - - if (site.hasGithub) { - const ghLabels = await GetAllGHLabels(); - const tagsAndDescription = monitors.map((monitor) => { - return { tag: monitor.tag, description: monitor.name }; - }); - //add incident label if does not exist - - if (ghLabels.indexOf("incident") === -1) { - await CreateGHLabel("incident", "Status of the site"); - } - if (ghLabels.indexOf("resolved") === -1) { - await CreateGHLabel("resolved", "Incident is resolved", "65dba6"); - } - if (ghLabels.indexOf("identified") === -1) { - await CreateGHLabel("identified", "Incident is Identified", "EBE3D5"); - } - if (ghLabels.indexOf("manual") === -1) { - await CreateGHLabel("manual", "Manually Created Incident", "6499E9"); - } - if (ghLabels.indexOf("auto") === -1) { - await CreateGHLabel("auto", "Automatically Created Incident", "D6C0B3"); - } - if (ghLabels.indexOf("investigating") === -1) { - await CreateGHLabel("investigating", "Incident is investigated", "D4E2D4"); - } - if (ghLabels.indexOf("incident-degraded") === -1) { - await CreateGHLabel("incident-degraded", "Status is degraded of the site", "f5ba60"); - } - if (ghLabels.indexOf("incident-down") === -1) { - await CreateGHLabel("incident-down", "Status is down of the site", "ea3462"); - } - //add tags if does not exist - for (let i = 0; i < tagsAndDescription.length; i++) { - const tag = tagsAndDescription[i].tag; - const description = tagsAndDescription[i].description; - if (ghLabels.indexOf(tag) === -1) { - await CreateGHLabel(tag, description); - } - } - - console.log("✅ Github labels created successfully"); - } -} - -Build(); diff --git a/config/monitors.example.yaml b/config/monitors.example.yaml deleted file mode 100644 index ed5b088..0000000 --- a/config/monitors.example.yaml +++ /dev/null @@ -1,48 +0,0 @@ -- name: OkBookmarks - description: A free bookmark manager that lets you save and search your bookmarks in the cloud. - tag: "okbookmarks" - image: "https://okbookmarks.com/assets/img/extension_icon128.png" - api: - method: GET - url: https://okbookmarks.com -- name: Earth - description: Our blue planet - tag: "earth" - defaultStatus: "UP" - image: "/earth.png" -- name: Frogment - description: A free openAPI spec editor and linter that breaks down your spec into fragments to make editing easier and more intuitive. Visit https://www.frogment.com - tag: "frogment" - image: "/frogment.png" - api: - method: GET - url: https://www.frogment.com - alerts: - DOWN: - failureThreshold: 5 - successThreshold: 2 - createIncident: false - description: "Write a description here please" - triggers: - - MyWebhook - - Discord Test -- name: CNAME Lookup - description: Monitor example showing how to lookup CNAME record for a domain. The site www.rajnandan.com is hosted on GitHub Pages. - tag: "cname-rajnandan" - image: "https://www.rajnandan.com/assets/images/me.jpg" - defaultStatus: "UP" - dns: - host: "www.rajnandan.com" - lookupRecord: "CNAME" - nameServer: "8.8.8.8" - matchType: "ANY" # ANY, ALL - values: - - "rajnandan1.github.io" -- name: "Frogment APP Ping" - description: "Ping www.frogment.app" - image: https://www.frogment.app/icons/Square107x107Logo.png - tag: "pingFrogmentApp" - defaultStatus: "UP" - ping: - hostsV4: - - "www.frogment.app" \ No newline at end of file diff --git a/config/server.example.yaml b/config/server.example.yaml deleted file mode 100644 index 4ed8f8c..0000000 --- a/config/server.example.yaml +++ /dev/null @@ -1,13 +0,0 @@ -triggers: - - name: MyWebhook - type: webhook - url: https://kener.requestcatcher.com/test - method: POST - headers: - Authorization: Bearer SomeToken - - name: Discord Test - type: discord - url: https://discord.com/api/webhooks/1310641119767302164/XJvq4MO2lz5yp9XRCJgfc4dbUfcQdHsttFUKTFJx4y_Oo1jNkUXf-CS3RSnamnNNv4Lx -database: - sqlite: - dbName: kener.db \ No newline at end of file diff --git a/config/site.example.yaml b/config/site.example.yaml deleted file mode 100644 index 5b57d1d..0000000 --- a/config/site.example.yaml +++ /dev/null @@ -1,58 +0,0 @@ -title: "Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management" -siteName: "Kener.ing" -home: "/" -logo: "/logo.png" -siteURL: "https://kener.ing" -favicon: "/logo96.png" -github: - owner: "rajnandan1" - repo: "kener" - incidentSince: 720 -metaTags: - description: "Kener: Open-source modern looking Node.js status page tool, designed to make service monitoring and incident handling a breeze. It offers a sleek and user-friendly interface that simplifies tracking service outages and improves how we communicate during incidents. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment." - keywords: "Node.js status page, Incident management tool, Service monitoring, Service outage tracking, Real-time status updates, GitHub integration for incidents, Open-source status page, Node.js monitoring application, Service reliability, User-friendly incident management, Collaborative incident resolution, Seamless outage communication, Service disruption tracker, Real-time incident alerts, Node.js status reporting" - og:description: "Kener: Open-source Node.js status page tool, designed to make service monitoring and incident handling a breeze. It offers a sleek and user-friendly interface that simplifies tracking service outages and improves how we communicate during incidents. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment." - og:image: "https://kener.ing/ss.png" - og:title: "Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management" - og:type: "website" - og:site_name: "Kener" - twitter:card: "summary_large_image" - twitter:site: "@_rajnandan_" - twitter:creator: "@_rajnandan_" - twitter:image: "https://kener.ing/ss.png" - twitter:title: "Kener: Open-Source and Modern looking Node.js Status Page for Effortless Incident Management" - twitter:description: "Kener: Open-source Node.js status page tool, designed to make service monitoring and incident handling a breeze. It offers a sleek and user-friendly interface that simplifies tracking service outages and improves how we communicate during incidents. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment." -nav: - - name: "Documentation" - url: "/docs/home" - - name: "Github" - iconURL: "/github.svg" - url: "https://github.com/rajnandan1/kener" - - name: "Buy me a coffee" - iconURL: "/buymeacoffee.svg" - url: "https://buymeacoffee.com/rajnandan1" -hero: - title: Kener is an Modern Open-Source Status Page System - subtitle: Let your users know what's going on. -footerHTML: | - Made using - - Kener - - an open source status page system built with Svelte and TailwindCSS. -i18n: - defaultLocale: "en" - locales: - en: "English" - hi: "हिन्दी" - zh-CN: "中文" - ja: "日本語" - vi: "Tiếng Việt" -pattern: "squares" -analytics: - - id: "G-Q3MLRXCBFT" - type: "GA" -barRoundness: SHARP -summaryStyle: CURRENT -barStyle: PARTIAL - diff --git a/docs/concepts.md b/docs/concepts.md new file mode 100644 index 0000000..c4b6cbc --- /dev/null +++ b/docs/concepts.md @@ -0,0 +1,104 @@ +--- +title: Concepts | Kener +description: Concepts for Kener +--- + +# Concepts + +Here are the different concepts that Kener uses to make your life easier. + +## Monitors + +Monitors are the heart of Kener. They are the ones that keep an eye on your services and let you know when something goes wrong. Monitors can be of different types like HTTP, DNS, Ping etc. They can be configured to check your services at different intervals and alert you when something goes wrong. + +### HTTP Monitor + +HTTP Monitor is used to check the status of a website or any http api. It can be configured to check the status of the website at different intervals. + +It can also be configured to check for a specific status code or a specific response. + +You can also add secrets to the HTTP request using environment variables. + +### DNS Monitor + +DNS Monitor is used to check the status of a DNS server. It can be configured to check the status of the DNS server at different intervals. + +### Ping Monitor + +Ping Monitor is used to check the status of a server. It can be configured to check the status of the server at different intervals. + +--- + +## Triggers + +Triggers are the ones that are responsible for sending alerts when something goes wrong. They can be configured to send alerts to different channels like Email, Slack, Discord, Webhook etc. + +You can set multiple triggers of the same type. + +### Email + +Email Trigger is used to send an email when something goes wrong. It can be configured to send an email to different email addresses. + +Kener uses [resend.com](https://resend.com) to send emails. Please make sure to sign up in [resend.com](https://resend.com) and get the API key. You will need to set the API key in the environment variable `RESEND_API_KEY`. Learn more about environment variables [here](/docs/environment-vars). + +### Slack + +Slack Trigger is used to send a message to a slack channel when something goes wrong. It can be configured to send a message to different slack channels. Needs slack URL + +### Discord + +Discord Trigger is used to send a message to a discord channel when something goes wrong. It can be configured to send a message to different discord channels. Needs discord URL + +### Webhook + +Webhook Trigger is used to send a message to a webhook when something goes wrong. It can be configured to send a message to different webhooks. Needs webhook URL. Method will be POST. + +Body of the webhook will be sent as below: + +```json +{ + "id": "mockoon-9", + "alert_name": "Mockoon DOWN", + "severity": "critical", + "status": "TRIGGERED", + "source": "Kener", + "timestamp": "2024-11-27T04:55:00.369Z", + "description": "🚨 **Service Alert**: Check the details below", + "details": { + "metric": "Mockoon", + "current_value": 1, + "threshold": 1 + }, + "actions": [ + { + "text": "View Monitor", + "url": "https://kener.ing/monitor-mockoon" + } + ] +} +``` + +| Key | Description | +| --------------------- | ----------------------------------------------------------- | +| id | Unique ID of the alert | +| alert_name | Name of the alert | +| severity | Severity of the alert. Can be `critical`, `warn` | +| status | Status of the alert. Can be `TRIGGERED`, `RESOLVED` | +| source | Source of the alert. Can be `Kener` | +| timestamp | Timestamp of the alert | +| description | Description of the alert. This you can customize. See below | +| details | Details of the alert. | +| details.metric | Name of the monitor | +| details.current_value | Current value of the monitor | +| details.threshold | Alert trigger hreshold of the monitor | +| actions | Actions to be taken. Link to view the monitor. | + +--- + +## Incidents + +Incidents are the ones that are created when something goes wrong. They can be created using Github Issues or using APIs. + +There can be two types of incidents - `DOWN` and `DEGRADED`. Incident can have a title, description, severity and status. Status can be `OPEN`, `RESOLVED`. + +To learn more about incidents, click [here](/docs/aleincident-managementrting). diff --git a/docs/deployment.md b/docs/deployment.md index b25b792..5c088fc 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -20,7 +20,7 @@ Make sure you have the following installed: ## NPM -```shell +```bash npm i npm run build npm run configure @@ -29,7 +29,7 @@ npm run prod ## PM2 -```shell +```bash npm i npm run build #build the frontend npm run configure #build the backend @@ -41,13 +41,13 @@ pm2 start main.js [Dockerhub](https://hub.docker.com/r/rajnandan1/kener) -```shell +```bash docker.io/rajnandan1/kener:latest ``` [Github Packages](https://github.com/rajnandan1/kener/pkgs/container/kener) -```shell +```bash ghcr.io/rajnandan1/kener:latest ``` @@ -55,7 +55,7 @@ You should mount two host directories to persist your configuration and database Make sure `./database` and `./config` directories are present in the root directory -```shell +```bash mkdir database mkdir config curl -o config/site.yaml https://raw.githubusercontent.com/rajnandan1/kener/refs/heads/main/config/site.example.yaml @@ -71,7 +71,7 @@ docker run \ You can also use a .env file -```shell +```bash docker run \ -v $(pwd)/database:/app/database \ -v $(pwd)/config:/app/config \ @@ -98,7 +98,7 @@ Run these commands from your terminal Then add to your docker command like so: -```shell +```bash docker run -d ... -e "PUID=1000" -e "PGID=1000" ... rajnandan1/kener ``` diff --git a/docs/environment-vars.md b/docs/environment-vars.md index f1b5acf..81d250f 100644 --- a/docs/environment-vars.md +++ b/docs/environment-vars.md @@ -9,11 +9,19 @@ Kener needs some environment variables to be set to run properly. Here are the l All of these are optional but are required for specific features. +## KENER_SECRET_KEY [Required] + +Please set a strong secret key for Kener to use for encrypting the data. This is required to run Kener. + +```bash +export KENER_SECRET_KEY=a-strong-secret-key +``` + ## PORT Defaults to 3000 if not specified -```shell +```bash export PORT=4242 ``` @@ -21,36 +29,10 @@ export PORT=4242 A github token to read issues and create labels. This is required for **incident management** -```shell +```bash export GH_TOKEN=your-github-token ``` -## API_TOKEN - -To talk to **kener apis** you will need to set up a token. It uses Bearer Authorization - -```shell -export API_TOKEN=sometoken -``` - -## API_IP - -While using API you can set this variable to accept request from a **specific IP** - -```shell -export API_IP=127.0.0.1 -``` - -## API_IP_REGEX - -While using API you can set this variable to accept request from a specific IP that matches the regex. Below example shows an **IPv6 regex** - -```shell -export API_IP_REGEX=^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$ -``` - -If you set both API_IP and API_IP_REGEX, API_IP will be given preference - ## KENER_BASE_PATH By default kener runs on `/` but you can change it to `/status` or any other path. @@ -59,20 +41,27 @@ By default kener runs on `/` but you can change it to `/status` or any other pat - Important: This env variable should be present during both build and run time - If you are using docker you will have to do your own build and set this env variable during `docker build` -```shell +```bash export KENER_BASE_PATH=/status ``` +## RESEND_API_KEY + +Kener uses [resend.com](https://resend.com) to send emails. Please make sure to sign up in [resend.com](https://resend.com) and get the API key. You will need to set the API key in the environment variable `RESEND_API_KEY`. + +```bash +export RESEND_API_KEY=re_sometoken +``` + ## Using .env You can also use a `.env` file to set these variables. Create a `.env` file in the root of the project and add the variables like below -```shell +```bash +KENER_SECRET_KEY=please_change_me PORT=4242 GH_TOKEN=your-github-token -API_TOKEN=sometoken -API_IP= -API_IP_REGEX= +RESEND_API_KEY=re_sometoken KENER_BASE_PATH=/status ``` @@ -93,7 +82,7 @@ Kener supports secrets in monitors. Let us say you have a monitor that is API ba In the above example, the `CLIENT_SECRET` is a secret that you can set in the monitor. To properly make this work you will have to set up environment variables like below -```shell +```bash export CLIENT_SECRET=your-api-key ``` diff --git a/docs/gh-setup.md b/docs/gh-setup.md index 7e7689e..17e3a1c 100644 --- a/docs/gh-setup.md +++ b/docs/gh-setup.md @@ -9,13 +9,7 @@ Kener uses github for incident management. Issues created in github using certai ## Step 1: Create Github Repository -Create a Github Repository. It can be either public or private. After you have created a repository open `site.yaml` and add them like this - -```yaml -github: - owner: "username" - repo: "repository" -``` +Create a [Github Repository](https://github.com/new). It can be either public or private. ## Step 2: Create Github Token @@ -41,6 +35,28 @@ You can create either a classic token or personal access token ## Step 3: Set environment -```shell +```bash export GH_TOKEN=github_pat_11AD3ZA3Y0 ``` + +## Step 4: Add to Kener + +Add your repository details to kener. + +![Monitors API](/gh_s.png) + +### Github API URL + +If you are on github enterprise you can set the github api url. For most users, it will be `https://api.github.com` + +### Github Repo + +The repository name you created in step 1 + +### Github Username + +Your github username + +### Incident History + +It is in hours. It means if an issue is created before X hours then kener would not honor it. What it means, is that kener would not show it under active incidents nor it will update the uptime. Default is 30\*24 hours = 720 hours. diff --git a/docs/home.md b/docs/home.md index 92aa309..6c59349 100644 --- a/docs/home.md +++ b/docs/home.md @@ -3,7 +3,7 @@ title: Kener - A Sveltekit NodeJS Status Page System description: Kener is an open-source Node.js status page tool, designed to make service monitoring and incident handling a breeze. It offers a sleek and user-friendly interface that simplifies tracking service outages and improves how we communicate during incidents. --- -# Kener - A Sveltekit NodeJS Status Page System +# Kener - A Feature Rich & Modern Status Page

kener example illustration diff --git a/docs/how-it-works.md b/docs/how-it-works.md index af60c34..b97f216 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -12,7 +12,7 @@ Kener has two parts. ## Folder structure -```shell +```bash ├── src (svelte frontend files) ├── static (things put here can be referenced directly example static/logo.png -> /logo.png) ├── src diff --git a/docs/kener-apis.md b/docs/kener-apis.md index 4b682a3..7034ca3 100644 --- a/docs/kener-apis.md +++ b/docs/kener-apis.md @@ -7,7 +7,7 @@ description: Kener gives APIs to push data and create incident. Kener also gives APIs to push data and create incident. Before you use kener apis you will have to set an authorization token called `API_TOKEN`. This also has to be set as an environment variable. -```shell +```bash export API_TOKEN=some-token-set-by-you ``` @@ -48,7 +48,7 @@ The update status API can be used to manually update the state of a monitor from | timestampInSeconds | `Optional` Timestamp in UTC seconds. Defaults to now. Should between 90 Days and now | | tag | `Required` Monitor Tag set in monitors.yaml | -```shell +```bash curl --request POST \ --url http://your-kener.host/api/status \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -84,7 +84,7 @@ Use this API to get the status of a monitor. Replace `google-search` with your monitor tag in query param -```shell +```bash curl --request GET \ --url 'http://your-kener.host/api/status?tag=google-search' \ --header 'Authorization: Bearer some-token-set-by-you' @@ -122,7 +122,7 @@ Can be use to create an incident from a remote server | isIdentified | `Optional` Incident identified | | isResolved | `Optional` Incident resolved | -```shell +```bash curl --request POST \ --url http://your-kener.host/api/incident \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -185,7 +185,7 @@ Can be use to update an incident from a remote server. It will clear values if n | isIdentified | `Optional` Incident identified | | isResolved | `Optional` Incident resolved | -```shell +```bash curl --request PATCH \ --url http://your-kener.host/api/incident/{incidentNumber} \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -232,7 +232,7 @@ Use `incidentNumber` to fetch an incident ### Request Body -```shell +```bash curl --request GET \ --url http://your-kener.host/api/incident/{incidentNumber} \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -267,7 +267,7 @@ Add comments for incident using `incidentNumber` ### Request -```shell +```bash curl --request POST \ --url http://your-kener.host/api/incident/{incidentNumber}/comment \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -297,7 +297,7 @@ Use this API to fetch all the comments for an incident ### Request -```shell +```bash curl --request GET \ --url http://your-kener.host/api/incident/{incidentNumber}/comment \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -338,7 +338,7 @@ Use this to API to update the status of an ongoing incident. ### Request -```shell +```bash curl --request POST \ --url http://your-kener.host/api/incident/{incidentNumber}/status \ --header 'Authorization: Bearer some-token-set-by-you' \ @@ -393,7 +393,7 @@ Use this to API to search incidents. Search incidents that are closed and title contains `hello incident` -```shell +```bash curl --request POST \ --url http://your-kener.host/api/incident?state=closed&title_like=Hello%20Incident \ --header 'Authorization: Bearer some-token-set-by-you' \ diff --git a/docs/monitors-api.md b/docs/monitors-api.md new file mode 100644 index 0000000..9b1fd71 --- /dev/null +++ b/docs/monitors-api.md @@ -0,0 +1,205 @@ +--- +title: API Monitors | Kener +description: Learn how to set up and work with API monitors in kener. +--- + +# API Monitors + +API monitors are used to monitor APIs. You can use API monitors to monitor the uptime of your Website or APIs and get notified when they are down. + +

+ +![Monitors API](/m_api.png) + +
+ +## Timeout + + + REQUIRED + + +The timeout is used to define the time in milliseconds after which the monitor should timeout. If the monitor does not respond within the timeout period, the monitor will be marked as down. For example, `5000` will set the timeout to 5 seconds. It is required and has to be a number greater than 0. + +## URL + + + REQUIRED + + +The URL is used to define the URL of the API that you want to monitor. It is required and has to be a valid URL. + +## Method + + + REQUIRED + + +The method is used to define the HTTP method that should be used to make the request. It is required and has to be one of the following: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. + +## Headers + +The headers are used to define the headers that should be sent with the request. It is optional and has to be a valid JSON object. + +## 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 a anonymous JS function, 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 lantecy(number) +> `{status:"DEGRADED", latency: 200}`. + +```javascript +(function (statusCode, responseTime, responseDataBase64) { + let statusCodeShort = Math.floor(statusCode/100); + let status = 'DOWN' + if(statusCodeShort >=2 && statusCodeShort <= 3) { + status = 'UP', + } + return { + status: 'DOWN', + latency: responseTime, + } +}) +``` + +- `statusCode` **REQUIRED** is a number. It is the HTTP status code +- `responseTime` **REQUIRED**is a number. It is the latency in milliseconds +- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it + +```js +let decodedResp = atob(responseDataBase64); +//if the response is a json object +//let jsonResp = JSON.parse(decodedResp) +``` + +#### Example + +The following example shows how to use the eval function to evaluate the response. The function checks if the status code is 2XX then the status is UP, if the status code is 5XX then the status is DOWN. If the response contains the word `Unknown Error` then the status is DOWN. If the response time is greater than 2000 then the status is DEGRADED. + +```javascript +(function (statusCode, responseTime, responseDataBase64) { + const resp = atob(responseDataBase64); //convert base64 to string + + let status = "DOWN"; + + //if the status code is 2XX then the status is UP + if (/^[2]\d{2}$/.test(statusCode)) { + status = "UP"; + if (responseTime > 2000) { + status = "DEGRADED"; + } + } + + //if the status code is 5XX then the status is DOWN + if (/^[5]\d{2}$/.test(statusCode)) status = "DOWN"; + + if (resp.includes("Unknown Error")) { + status = "DOWN"; + } + + return { + status: status, + latency: responseTime + }; +}); +``` + +## Examples + +### Website Monitor + +This is an example to monitor google every 5 minute. + + + +### Get Request + +Example to monitor a GET Request with an Authorization header set to `$SOME_TOKEN`. + +`$SOME_TOKEN` is set in environment variables as `SOME_TOKEN` + +```bash +export SOME_TOKEN=some-token-example +``` + + + +### POST Request + +Example showing setting up a POST request every minute with a timeout of 2 seconds. + + + +### Custom Response Eval + +Example showing how to use a custom response eval function. It also shows how to set secrets in body for POST Request. +The secrets have to be present in environment variables. + +```bash +export SERVICE_TOKEN=secret1_tone +export SERVICE_SECRET=secret2_secret +``` + + diff --git a/docs/monitors-dns.md b/docs/monitors-dns.md new file mode 100644 index 0000000..198a546 --- /dev/null +++ b/docs/monitors-dns.md @@ -0,0 +1,59 @@ +--- +title: DNS Monitors | Kener +description: Learn how to set up and work with DNS monitors in kener. +--- + +# DNS Monitors + +DNS monitors are used to monitor DNS servers. Verify DNS queries for your server and match values with the expected values to get notified when they are different. + +
+ +![Monitors Ping](/m_dns.png) + +
+ +## Host + + + REQUIRED + + +The host is used to define the host of the DNS server that you want to monitor. It is required and has to be a valid host. + +## Lookup Record + + + REQUIRED + + +The query is used to define the query that you want to monitor. It is required and has to be a valid query. Example: `A`, `MX`, `TXT`, `CNAME`. + +## Name Server + + + REQUIRED + + +The name server is the DNS server that you want to query. It is required and has to be a valid DNS server. + +Few examples: + +- 8.8.8.8 (Google) +- 1.1.1.1 (Cloudflare) + +## Match Type + + + REQUIRED + + +Match type for DNS response. Can be ANY or ALL. If ANY is selected, the monitor will be marked as UP if any of the expected values match the response. If ALL is selected, all the expected values should match the response. + +## Expected Values + + + REQUIRED + + +The expected values are the values that you expect the DNS server to return. You can add multiple values. Atleast one is required. diff --git a/docs/monitors-ping.md b/docs/monitors-ping.md new file mode 100644 index 0000000..216400e --- /dev/null +++ b/docs/monitors-ping.md @@ -0,0 +1,26 @@ +--- +title: Ping Monitors | Kener +description: Learn how to set up and work with Ping monitors in kener. +--- + +# Ping Monitors + +Ping monitors are used to monitor livenees of your servers. You can use Ping monitors to monitor the uptime of your servers and get notified when they are down. + +
+ +![Monitors Ping](/m_ping.png) + +
+ +## Host V4 + +You can add as many IP addresses as you want to monitor. The IP address should be a valid IPv4 address. Example of IP4 address is `106.12.43.232`. + +## Host V6 + +You can add as many IP addresses as you want to monitor. The IP address should be a valid IPv6 address. Example of IP6 address is `2001:0db8:85a3:0000:0000:8a2e:0370:7334`. + +

+ Please note that atleast one of the Host V4 or Host V6 is required. +

diff --git a/docs/monitors.md b/docs/monitors.md index 5487a11..6b127a1 100644 --- a/docs/monitors.md +++ b/docs/monitors.md @@ -1,181 +1,100 @@ --- -title: Monitors | monitors.yaml | Kener +title: Monitors | Kener description: Monitors are the heart of Kener. This is where you define the monitors you want to show on your site. --- # Monitors -Inside `config/` folder there is a file called `monitors.yaml`. We will be adding our monitors here. Please note that your yaml must be valid. It is an array. +Monitors are the heart of Kener. This is where you define the monitors you want to show on your site. -## Understanding monitors +

-Each monitor runs at 1 minute interval by default. Monitor runs in below priority order. +![Monitors Main](/m_main.png) -- `defaultStatus` Data. Used to set the default status of the monitor -- PING/API/DNS call Data overrides above data(if present) -- Pushed Status Data overrides status Data using [Kener Update Statue API](/docs/kener-apis#update-status---api) -- [Manual Incident](/docs/incident-management) Data overrides Pushed Status Data +
-## General Attributes +## Tag -A list of attributes that can be used in all types of monitors. + + UNIQUE + +& + + REQUIRED + -| Key | Required? | Explanation | -| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| name | Required + Unique | This will be shown in the UI to your users. Keep it short and unique | -| description | Optional | This is a breif description for the monitor | -| tag | Required + Unique | This is used to tag incidents created in Github using comments | -| image | Optional | To show a logo before the name | -| cron | Optional | Use a valid cron expression to specify the interval to run the monitors. Defaults to `* * * * *` i.e every minute | -| defaultStatus | Optional | This will be the default status if no other way is specified to check the monitor. can be `UP`/`DOWN`/`DEGRADED` | -| hidden | Optional | If set to `true` will not show the monitor in the UI | -| category | Optional | Use this to group your monitors. Make sure you have defined category in `site.yaml` and use the `name` attribute. More about it [here](/docs/customize-site#categories). | -| dayDegradedMinimumCount | Optional | Default is 1. It means, minimum this number of count for the day to be classified as DEGRADED(Yellow Bar) in 90 day view. Has to be `number` greater than 0 | -| dayDownMinimumCount | Optional | Default is 1. It means, minimum this number of count for the day to be classified as DOWN(Red Bar) in 90 day view. Has to be `number` greater than 0 | -| includeDegradedInDowntime | Optional | By deafault uptime percentage is calculated as (UP+DEGRADED/UP+DEGRADED+DOWN). Setting it as `true` will change the calculation to (UP/UP+DEGRADED+DOWN) | +The tag is unique to each monitor and is used to identify the monitor in the code. It is also used to generate the URL for the monitor. -### Example +## Name -```yaml -- name: "Google" - description: "Google Search Engine" - tag: "google" - image: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" - defaultStatus: "UP" - hidden: false - dayDegradedMinimumCount: 2 - dayDownMinimumCount: 3 - includeDegradedInDowntime: false -``` + + UNIQUE + +& + + REQUIRED + -`dayDegradedMinimumCount` and `dayDownMinimumCount` only works when `summaryStyle` is set as `DAY` in `site.yaml`. More about it [here](/docs/customize-site#summarystyle) +Name is also unique to each monitor and is used to identify the monitor in the admin panel or in the status page. -## API Monitor Attributes +## Image -A list of attributes that can be used in API monitors. +The image is used to display a logo or an image for the monitor. It is displayed on the status page and in the admin panel. It is optional. -| Key | Required? | Explanation | -| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| api.url | Required | HTTP URL | -| api.method | Optional | HTTP Method. Default is `GET` | -| api.headers | Optional | HTTP headers | -| api.body | Optional | HTTP Body as string | -| api.timeout | Optional | timeout for the api in milliseconds. Default is 10000(10 secs) | -| api.eval | Optional | Evaluator written in JS, to parse HTTP response and calculate uptime and latency | -| api.hideURLForGet | Optional | if the monitor is a GET URL and no headers are specified and the response body content-type is a text/html then kener shows a GET hyperlink in monitor description. To hide that set this as false. Default is `true` | +## Description -### Eval +The description is used to describe the monitor. It is displayed on the status page and in the admin panel. It is optional. -This is a anonymous JS function, by default it looks like this. +## Cron -> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and lantecy(number) -> `{status:"DEGRADED", latency: 200}`. + + REQUIRED + -```javascript -(function (statusCode, responseTime, responseDataBase64) { - let statusCodeShort = Math.floor(statusCode/100); - let status = 'DOWN' - if(statusCodeShort >=2 && statusCodeShort <= 3) { - status = 'UP', - } - return { - status: 'DOWN', - latency: responseTime, - } -}) -``` +The cron is used to define the interval at which the monitor should run. It is a cron expression. For example, `* * * * *` will run the monitor every minute. -- `statusCode` **REQUIRED** is a number. It is the HTTP status code -- `responseTime` **REQUIRED**is a number. It is the latency in milliseconds -- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it +## Default Status -```js -let decodedResp = atob(responseDataBase64); -//let jsonResp = JSON.parse(decodedResp) -``` +Using default status you can set a predefined status for the monitor. The predefined status can be `UP`, `DOWN`, or `DEGRADED`. If the monitor does not have api/dns/ping or if the monitor status is not updated using webbhook, manual incident this will the status of the monitor. -### Example +## Category Name -```yaml -- name: "Google" - description: "Google Search Engine" - tag: "google" - image: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" - defaultStatus: "UP" - hidden: false - api: - url: "https://www.google.com" - method: "GET" - headers: - "Content-Type": "application/json" - body: "" - timeout: 10000 - eval: | - (function (statusCode, responseTime, responseDataBase64) { - let statusCodeShort = Math.floor(statusCode/100); - let status = 'DOWN' - if(statusCodeShort >=2 && statusCodeShort <= 3) { - status = 'UP', - } - return { - status: 'DOWN', - latency: responseTime, - } - }) -``` +The category name is used to group monitors. It is displayed on the status page and in the admin panel. It is optional. By a home category is already present. Adding monitor to the home category will show the monitor on the home page. -To view more examples of API monitors, please visit [here](/docs/monitors-examples) +## Day Degraded Min Count -## PING Monitor Attributes + + REQUIRED + -A list of attributes that can be used in PING monitors. +The day degraded minimum count is used to define the minimum number of degraded incidents in a day to mark the monitor as degraded for the day. A degraded day means if you are using Monitor Style as FULL under Theme, the whole bar will be shown as Degraded (yellow color) . It is optional. It has to be more than equal to 1 -| Key | Required? | Explanation | -| ------------ | --------- | ----------------------------------------------------------------------- | -| ping.hostsV4 | Required | Array of hosts / IP to monitor ping response. Either domain name or IP4 | -| ping.hostsV6 | Required | Array of hosts / IP to monitor ping response. Either domain name or IP6 | +## Day Down Min Count -Either one of `hostsV4` or `hostsV6` is required + + REQUIRED + -### Example +The day down minimum count is used to define the minimum number of down incidents in a day to mark the monitor as down for the day. A down day means if you are using Monitor Style as FULL under Theme, the whole bar will be shown as Down (red color) . It is optional. It has to be more than equal to 1 -```yaml -- name: "Ping All" - description: "Ping All is where I ping all the hosts" - tag: "pingall" - defaultStatus: "UP" - ping: - hostsV4: - - "www.rajnandan.com" - - "103.125.217.243" -``` +## Include Degraded -You can find more examples of PING monitors [here](/docs/monitor-examples#ping-monitor) +The include degraded is used to include the degraded checks in the total bad checks count. It is optional. By default uptime percentage is calculated as (UP+DEGRADED/UP+DEGRADED+DOWN). Setting it as `YES` will change the calculation to (UP/UP+DEGRADED+DOWN) -## DNS Monitor Attributes +## Monitor Type -A list of attributes that can be used in DNS monitors. + + REQUIRED + -| Key | Required? | Explanation | -| ---------------- | --------- | ------------------------------------------------------------------------ | -| dns.hosts | Required | Array of hosts to monitor DNS response. Either domain name or IP4 or IP6 | -| dns.lookupRecord | Required | DNS record type. | -| dns.nameServer | Required | DNS server to use. | -| dns.matchType | Required | Match type for DNS response. Can be `ANY` or `ALL`. Default is `ALL` | -| dns.values | Required | Expected values for the DNS response. Array of string | +### API -### Example +To monitor an API, you need to provide the URL of the API and the expected status code. If the status code is not received, the monitor will be marked as down. You can read more about API monitoring [here](/docs/monitors-api). -```yaml -- name: "DNS All" - description: "DNS All is where I check all the DNS" - tag: "dnsall" - defaultStatus: "UP" - dns: - host: "www.rajnandan.com" - lookupRecord: "CNAME" - nameServer: "8.8.8.8" - matchType: "ANY" - values: - - "rajnandan1.github.io" -``` +### DNS + +To monitor the DNS records, you need to provide the domain name and the record type. If the record is not found, the monitor will be marked as down. You can read more about DNS monitoring [here](/docs/monitors-dns). + +### PING + +To monitor the ping, you need to provide the IP address or the domain name. If the ping is not successful, the monitor will be marked as down. You can read more about PING monitoring [here](/docs/monitors-ping). diff --git a/docs/quick-start.md b/docs/quick-start.md index c78dd91..5786794 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -1,41 +1,35 @@ --- -title: Quick Start | Kener +title: Get started | Kener description: Get started with Kener --- -# Quick Start +# Get Started Here is a demonstration of how to get started with Kener in seconds ## Requirements -- Node.js Minimum version required is `v22.12.0`. +- Node.js Minimum version required is `v18`. - Git - sqlite3 ## Clone the repository -```shell +```bash git clone https://github.com/rajnandan1/kener.git cd kener ``` ## Install Dependencies -```shell +```bash npm install ``` -## Setup Configuration +## Set up Environment Variables -- Rename `config/site.example.yaml` -> `config/site.yaml` -- Rename `config/monitors.example.yaml` -> `config/monitors.yaml` -- Rename `config/server.example.yaml` -> `config/server.yaml` - -```shell -cp config/site.example.yaml config/site.yaml -cp config/monitors.example.yaml config/monitors.yaml -cp config/server.example.yaml config/server.yaml +```bash +cp .env.example .env ``` ## Start Kener @@ -46,8 +40,22 @@ npm run dev Kener Development Server would be running at PORT 3000. Go to [http://localhost:3000](http://localhost:3000) +## Create a new User + +If this is the first time your are launching kener then you would be redirected to the [set up page](/setup). Fill in the details and click on `Let's Go` button. + +- **Name**: Your Name +- **Email**: Your Email +- **Password**: Your Password + +Please note that the email should be a valid email address and password should be atleast 8 characters long with uppercase lowercase and numbers. + +Please remember your password as it is not recoverable. + +## Login + +Once you have created the user, you can login with the credentials you have provided by going to the [login page](/signin) + ## Next Steps -- [Configure Site](/docs/customize-site) -- [Add Monitors](/docs/monitors) -- [Alerting](/docs/alerting) +Learn how to manage kener by going through the [documentation](/docs/manage-kener) diff --git a/docs/status-badges.md b/docs/status-badges.md index 21cd8f1..681baa1 100644 --- a/docs/status-badges.md +++ b/docs/status-badges.md @@ -51,7 +51,7 @@ You can set custom label for the status badge. If you do not set a label it will Shows the last health check was UP/DOWN/DEGRADED as SVG dot -```shell +```bash http://[hostname]/badge/[tag]/dot #or http://[hostname]/badge/[tag]/dot?animate=ping diff --git a/docs/structure.json b/docs/structure.json index 7e3c172..027305c 100644 --- a/docs/structure.json +++ b/docs/structure.json @@ -9,9 +9,14 @@ "file": "/home.md" }, { - "title": "Quick Start", + "title": "Get Started", "link": "/docs/quick-start", - "file": "/docs/quick-start.md" + "file": "/quick-start.md" + }, + { + "title": "Concepts", + "link": "/docs/concepts", + "file": "/concepts.md" }, { "title": "Changelogs", @@ -26,18 +31,48 @@ ] }, { - "sectionTitle": "Core Concepts", + "sectionTitle": "Guides", "children": [ { - "title": "Customization (site.yaml)", - "link": "/docs/customize-site", - "file": "/customize-site.md" + "title": "Setup Github", + "link": "/docs/gh-setup", + "file": "/gh-setup.md" }, { - "title": "Monitors (monitors.yaml)", + "title": "Setup Environment", + "link": "/docs/environment-vars", + "file": "/environment-vars.md" + }, + { + "title": "Use Badges", + "link": "/docs/status-badges", + "file": "/status-badges.md" + }, + { + "title": "Setup Monitors", "link": "/docs/monitors", "file": "/monitors.md" }, + { + "title": "API/Website Monitor", + "link": "/docs/monitors-api", + "file": "/monitors-api.md" + }, + { + "title": "Ping Monitor", + "link": "/docs/monitors-ping", + "file": "/monitors-ping.md" + }, + { + "title": "DNS Monitor", + "link": "/docs/monitors-dns", + "file": "/monitors-dns.md" + }, + { + "title": "Setup Triggers", + "link": "/docs/triggers", + "file": "/triggers.md" + }, { "title": "Alerting (server.yaml)", "link": "/docs/alerting", @@ -63,11 +98,6 @@ "link": "/docs/i18n", "file": "/i18n.md" }, - { - "title": "Badges", - "link": "/docs/status-badges", - "file": "/status-badges.md" - }, { "title": "Embed", "link": "/docs/embed", @@ -92,11 +122,6 @@ "title": "Deployment", "link": "/docs/deployment", "file": "/deployment.md" - }, - { - "title": "Github Setup", - "link": "/docs/gh-setup", - "file": "/gh-setup.md" } ] }, diff --git a/docs/triggers.md b/docs/triggers.md new file mode 100644 index 0000000..9c705e2 --- /dev/null +++ b/docs/triggers.md @@ -0,0 +1,109 @@ +--- +title: Triggers | Kener +description: Learn how to set up and work with triggers in kener. +--- + +# Triggers + +Triggers are used to trigger actions based on the status of your monitors. You can use triggers to send notifications, or call webhooks when a monitor goes down or up. + +
+ +![Trigger API](/trig_1.png) + +
+ +### Name + + + REQUIRED + + +The name is used to define the name of the webhook. It is required and has to be a string. + +### Description + +The description is used to define the description of the webhook. It is optional and has to be a string. + +Kener supports the following triggers: + +- [Webhook](#webhook) +- [Discord](#discord) +- [Slack](#slack) +- [Email](#email) + +## Webhook + +Webhook triggers are used to send a HTTP POST request to a URL when a monitor goes down or up. + +
+ +![Trigger API](/trig_web.png) + +
+ +### URL + + + REQUIRED + + +The URL is used to define the URL of the webhook. It is required and has to be a valid URL. + +You can also pass secrets that are set in the environment variables. + +Example: `https://example.com/webhook?secret=$SECRET_X`. Make sure `$SECRET_X` is set in the environment variables. + +### Method + +Method will always be `POST` + +### Headers + +The headers are used to define the headers that should be sent with the request. It is optional and has to be a valid JSON object. You can add secrets that are set in the environment variables. + +Example: `Authorization: Bearer $SECRET_Y`. Make sure `$SECRET_Y` is set in the environment variables. + +While sending webhook kener will add two more headers: `Content-Type: application/json` and `User-Agent: Kener/3.0.0`. + +### Body + +Body of the webhook will be sent as below: + +```json +{ + "id": "mockoon-9", + "alert_name": "Mockoon DOWN", + "severity": "critical", + "status": "TRIGGERED", + "source": "Kener", + "timestamp": "2024-11-27T04:55:00.369Z", + "description": "🚨 **Service Alert**: Check the details below", + "details": { + "metric": "Mockoon", + "current_value": 1, + "threshold": 1 + }, + "actions": [ + { + "text": "View Monitor", + "url": "https://kener.ing/monitor-mockoon" + } + ] +} +``` + +| Key | Description | +| --------------------- | ----------------------------------------------------------- | +| id | Unique ID of the alert | +| alert_name | Name of the alert | +| severity | Severity of the alert. Can be `critical`, `warn` | +| status | Status of the alert. Can be `TRIGGERED`, `RESOLVED` | +| source | Source of the alert. Can be `Kener` | +| timestamp | Timestamp of the alert | +| description | Description of the alert. This you can customize. See below | +| details | Details of the alert. | +| details.metric | Name of the monitor | +| details.current_value | Current value of the monitor | +| details.threshold | Alert trigger hreshold of the monitor | +| actions | Actions to be taken. Link to view the monitor. | diff --git a/src/docs.css b/src/docs.css index 7d3862b..3ffe54f 100644 --- a/src/docs.css +++ b/src/docs.css @@ -22,3 +22,31 @@ main { transition: all 0.3s; box-shadow: 0 0 8px 1.5px #3e9a4b; } + +.accm input:checked ~ div { + display: block; +} +.accm input ~ div { + display: none; +} + +.accm input { + visibility: hidden; +} + +.accmt { + background-color: hsl(223, 10%, 14%); +} + +.accm input:checked ~ .showaccm span:first-child { + display: none; +} +.accm input:checked ~ .showaccm span:last-child { + display: block; +} +.accm input ~ .showaccm span:first-child { + display: block; +} +.accm input ~ .showaccm span:last-child { + display: none; +} diff --git a/src/lib/components/manage/monitorSheet.svelte b/src/lib/components/manage/monitorSheet.svelte index 68001ea..2063832 100644 --- a/src/lib/components/manage/monitorSheet.svelte +++ b/src/lib/components/manage/monitorSheet.svelte @@ -115,13 +115,13 @@ return; } //dayDegradedMinimumCount should be positive number - if (newMonitor.dayDegradedMinimumCount < 0) { - invalidFormMessage = "Day Degraded Minimum Count should be positive number"; + if (newMonitor.dayDegradedMinimumCount < 1) { + invalidFormMessage = "Day Degraded Minimum Count should be greater than 0"; return; } //dayDownMinimumCount should be positive number - if (newMonitor.dayDownMinimumCount < 0) { - invalidFormMessage = "Day Down Minimum Count should be positive number"; + if (newMonitor.dayDownMinimumCount < 1) { + invalidFormMessage = "Day Down Minimum Count should be greater than 0"; return; } @@ -145,11 +145,25 @@ invalidFormMessage = "Invalid URL"; return; } + + //timeout should be positive number + if (newMonitor.apiConfig.timeout < 1) { + invalidFormMessage = "Timeout should be greater than 0"; + return; + } + + //if evali ends with semicolor throw error + if (!!newMonitor.apiConfig.eval && newMonitor.apiConfig.eval.endsWith(";")) { + invalidFormMessage = "Eval should not end with semicolon"; + return; + } + //validating eval if (!!newMonitor.apiConfig.eval && !isValidEval()) { invalidFormMessage = "Invalid eval"; return; } + newMonitor.typeData = JSON.stringify(newMonitor.apiConfig); } else if (newMonitor.monitorType === "PING") { //validating hostsV4 @@ -371,14 +385,18 @@
- +
- +
@@ -435,7 +453,10 @@ {#if newMonitor.monitorType === "API"}
- +
@@ -490,14 +511,14 @@
- +
-
+
{#each newMonitor.apiConfig.headers as header, index} @@ -539,9 +560,9 @@
@@ -563,7 +584,7 @@ bind:value={newMonitor.apiConfig.eval} id="eval" class="h-96 w-full rounded-sm border p-2" - placeholder={defaultEval} + placeholder="Leave blank or write a custom eval function" >
@@ -571,7 +592,7 @@
-
+
{#each newMonitor.pingConfig.hostsV4 as host, index}
- +
{#each newMonitor.dnsConfig.values as value, index}
diff --git a/src/lib/components/manage/monitorsAdd.svelte b/src/lib/components/manage/monitorsAdd.svelte index ab7abaa..983e8f9 100644 --- a/src/lib/components/manage/monitorsAdd.svelte +++ b/src/lib/components/manage/monitorsAdd.svelte @@ -37,8 +37,8 @@ categoryName: "Home", monitorType: "NONE", typeData: "", - dayDegradedMinimumCount: 0, - dayDownMinimumCount: 0, + dayDegradedMinimumCount: 1, + dayDownMinimumCount: 1, includeDegradedInDowntime: "NO", apiConfig: { url: "", @@ -56,8 +56,8 @@ dnsConfig: { host: "", lookupRecord: "", - nameServer: "", - matchType: "", + nameServer: "8.8.8.8", + matchType: "ANY", values: [] } }; diff --git a/src/lib/components/manage/triggerInfo.svelte b/src/lib/components/manage/triggerInfo.svelte index b655d10..cc789e0 100644 --- a/src/lib/components/manage/triggerInfo.svelte +++ b/src/lib/components/manage/triggerInfo.svelte @@ -15,6 +15,7 @@ let showAddTrigger = false; let triggers = []; let loadingData = false; + export let data; let newTrigger = { id: 0, @@ -56,6 +57,24 @@ ]; } + //validate this pattern Alerts + function validateNameEmailPattern(input) { + const pattern = /^([\w\s]+)\s*<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>$/; + const match = input.match(pattern); + if (match) { + return { + isValid: true, + name: match[1].trim(), + email: match[2] + }; + } + return { + isValid: false, + name: null, + email: null + }; + } + async function addNewTrigger() { invalidFormMessage = ""; formState = "loading"; @@ -68,6 +87,13 @@ if (newTrigger.triggerType == "email") { newTrigger.triggerMeta.url = ""; + + let emValid = validateNameEmailPattern(newTrigger.triggerMeta.from); + if (!emValid.isValid) { + invalidFormMessage = "Invalid Name and Email Address for Sender"; + formState = "idle"; + return; + } } //newTrigger.name present not empty if (newTrigger.name == "") { @@ -382,9 +408,9 @@
@@ -400,7 +426,13 @@ > to send emails. Please make sure you have created an account with resend. Also add the resend api key as environment variable - RESEND_API_KEY + RESEND_API_KEY. + {#if !!!data.RESEND_API_KEY} + The RESEND_API_KEY is not set in your environment + variable. Please set it and restart the server. + {/if}

diff --git a/src/lib/components/nav.svelte b/src/lib/components/nav.svelte index 0bf525f..fc98072 100644 --- a/src/lib/components/nav.svelte +++ b/src/lib/components/nav.svelte @@ -29,19 +29,17 @@
{#if data.site.nav} -
diff --git a/src/routes/(docs)/+layout.svelte b/src/routes/(docs)/+layout.svelte index 883115f..0964545 100644 --- a/src/routes/(docs)/+layout.svelte +++ b/src/routes/(docs)/+layout.svelte @@ -64,11 +64,19 @@ gtag("config", "G-Q3MLRXCBFT"); + + + +
-
-