Updates documentation and Dockerfile configuration

Updates documentation to reflect the new directory structure.
The documentation now correctly references images in the `/documentation` directory.
Removes the `src/static/documentation` directory in the Dockerfile.
This commit is contained in:
Raj Nandan Sharma
2025-02-16 18:31:18 +05:30
parent 883a458ed3
commit d978c82263
64 changed files with 641 additions and 467 deletions
+2 -1
View File
@@ -58,7 +58,8 @@ COPY . .
# TODO: Reevaluate permissions (possibly reduce?)...
# Remove docs directory and ensure required directories exist
RUN rm -rf src/routes/\(docs\) && \
mkdir -p uploads database && \
rm -rf src/static/documentation && \
mkdir -p uploads database && \
# TODO: Consider changing below to `chmod -R u-rwX,g=rX,o= uploads database`
chmod -R 750 uploads database
+20 -20
View File
@@ -64,24 +64,24 @@ 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"
}
]
"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"
}
]
}
```
@@ -108,7 +108,7 @@ The discord message when alert is `TRIGGERED` will look like this
The discord message when alert is `RESOLVED` will look like this
![Discord](/discord_resolved.png)
![Discord](/documentation/discord_resolved.png)
### Slack
@@ -118,7 +118,7 @@ The slack message when alert is `TRIGGERED` will look like this
The slack message when alert is `RESOLVED` will look like this
![Slack](/slack_resolved.png)
![Slack](/documentation/slack_resolved.png)
### Add Alerts to Monitors
+3 -3
View File
@@ -21,7 +21,7 @@ A small text that will be shown below the title.
<div class="border rounded-md">
![Hero Section](/home_1.png)
![Hero Section](/documentation/home_1.png)
</div>
@@ -31,7 +31,7 @@ A small text that will be shown below the title.
You can navigation links to other urls. You can add as many as you want.
![Nav Section](/home_2.png)
![Nav Section](/documentation/home_2.png)
### Icon
@@ -45,7 +45,7 @@ The title of the link.
The URL to redirect to when the link is clicked.
![Nav Section](/home_3.png)
![Nav Section](/documentation/home_3.png)
---
+50 -50
View File
@@ -9,7 +9,7 @@ API monitors are used to monitor APIs. You can use API monitors to monitor the u
<div class="border rounded-md">
![Monitors API](/m_api.png)
![Monitors API](/documentation/m_api.png)
</div>
@@ -69,7 +69,7 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it
```js
let decodedResp = atob(responseDataBase64);
let decodedResp = atob(responseDataBase64)
//if the response is a json object
//let jsonResp = JSON.parse(decodedResp)
```
@@ -79,61 +79,61 @@ let decodedResp = atob(responseDataBase64);
The following example shows how to use the eval function to evaluate the response. The function checks if the status code is 2XX then the status is UP, if the status code is 5XX then the status is DOWN. If the response contains the word `Unknown Error` then the status is DOWN. If the response time is greater than 2000 then the status is DEGRADED.
```javascript
(async function (statusCode, responseTime, responseDataBase64) {
const resp = atob(responseDataBase64); //convert base64 to string
;(async function (statusCode, responseTime, responseDataBase64) {
const resp = atob(responseDataBase64) //convert base64 to string
let status = "DOWN";
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 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 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";
}
if (resp.includes("Unknown Error")) {
status = "DOWN"
}
return {
status: status,
latency: responseTime
};
});
return {
status: status,
latency: responseTime
}
})
```
This next example shows how to call another API withing eval. It is scrapping the second last script tag from the response and checking if the heading is "No recent issues" then the status is UP else it is DOWN.
```javascript
(async function raj(statusCode, responseTime, responseDataBase64) {
let htmlString = atob(responseDataBase64);
const scriptTags = htmlString.match(/<script[^>]*src="([^"]+)"[^>]*>/g);
if (scriptTags && scriptTags.length >= 2) {
// Extract the second last script tag's src attribute
const secondLastScript = scriptTags[scriptTags.length - 2];
const srcMatch = secondLastScript.match(/src="([^"]+)"/);
const secondLastScriptSrc = srcMatch ? srcMatch[1] : null;
;(async function raj(statusCode, responseTime, responseDataBase64) {
let htmlString = atob(responseDataBase64)
const scriptTags = htmlString.match(/<script[^>]*src="([^"]+)"[^>]*>/g)
if (scriptTags && scriptTags.length >= 2) {
// Extract the second last script tag's src attribute
const secondLastScript = scriptTags[scriptTags.length - 2]
const srcMatch = secondLastScript.match(/src="([^"]+)"/)
const secondLastScriptSrc = srcMatch ? srcMatch[1] : null
let jsResp = await fetch(secondLastScriptSrc); //api call
let jsRespText = await jsResp.text();
//check if heading":"No recent issues" exists
let noRecentIssues = jsRespText.indexOf('heading":"No recent issues"');
if (noRecentIssues != -1) {
return {
status: "UP",
latency: responseTime
};
}
}
return {
status: "DOWN",
latency: responseTime
};
});
let jsResp = await fetch(secondLastScriptSrc) //api call
let jsRespText = await jsResp.text()
//check if heading":"No recent issues" exists
let noRecentIssues = jsRespText.indexOf('heading":"No recent issues"')
if (noRecentIssues != -1) {
return {
status: "UP",
latency: responseTime
}
}
}
return {
status: "DOWN",
latency: responseTime
}
})
```
## Examples
@@ -153,7 +153,7 @@ This is an example to monitor google every 5 minute.
<div class="border rounded-md">
![Monitors API](/m_ex_website.png)
![Monitors API](/documentation/m_ex_website.png)
</div>
@@ -180,7 +180,7 @@ export SOME_TOKEN=some-token-example
<div class="border rounded-md p-1">
![Monitors API](/m_ex_2.png)
![Monitors API](/documentation/m_ex_2.png)
</div>
@@ -201,7 +201,7 @@ Example showing setting up a POST request every minute with a timeout of 2 secon
<div class="border rounded-md p-1">
![Monitors API](/m_ex_3.png)
![Monitors API](/documentation/m_ex_3.png)
</div>
@@ -228,7 +228,7 @@ export SERVICE_SECRET=secret2_secret
<div class="border rounded-md p-1">
![Monitors API](/m_ex_4.png)
![Monitors API](/documentation/m_ex_4.png)
</div>
+1 -1
View File
@@ -9,7 +9,7 @@ DNS monitors are used to monitor DNS servers. Verify DNS queries for your server
<div class="border rounded-md">
![Monitors Ping](/m_dns.png)
![Monitors Ping](/documentation/m_dns.png)
</div>
+40
View File
@@ -0,0 +1,40 @@
---
title: Group Monitors | Kener
description: Learn how to set up and work with Group monitors in kener.
---
# Group Monitors
Group monitors are used to monitor multiple monitors at once. You can use Group monitors to monitor multiple monitors at once and get notified when they are down.
<div class="border rounded-md">
![Monitors Group](/documentation/m_group.png)
</div>
## Timeout
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The timeout is used to define the time in milliseconds after which the group monitor should timeout.
Let us say the group monitor runs every minute, it will expect in the same minute all the other monitors to finish. It will wait till the timeout for them to complete. If not completed within that timeout, it will be marked as down.
## Monitors
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
You can add as many monitors as you want to monitor. The minimum number of monitors required is 2. The monitor can be any type of monitor.
## Hide
You can hide the monitors that are part of the group monitor. If you hide the monitors, the monitors inside the group will not be shown in the home page.
<div class="note">
The group status will be the worst status of the monitors in the group.
</div>
+37 -37
View File
@@ -9,7 +9,7 @@ Ping monitors are used to monitor livenees of your servers. You can use Ping mon
<div class="border rounded-md">
![Monitors Ping](/m_ping.png)
![Monitors Ping](/documentation/m_ping.png)
</div>
@@ -32,29 +32,29 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64));
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let alive = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive;
}, true);
let alive = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive
}, true)
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
};
});
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
}
})
```
- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it and the JSON parse it. Once parse it will be an array of objects.
```js
let decodedResp = atob(responseDataBase64);
let jsonResp = JSON.parse(decodedResp);
console.log(jsonResp);
let decodedResp = atob(responseDataBase64)
let jsonResp = JSON.parse(decodedResp)
console.log(jsonResp)
/*
[
{
@@ -109,28 +109,28 @@ The input to the eval function is a base64 encoded string. You will have to deco
The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.
```javascript
(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64));
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive;
}, true);
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive
}, true)
let avgLatency = latencyTotal / arrayOfPings.length;
let avgLatency = latencyTotal / arrayOfPings.length
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
};
}
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
}
}
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
};
});
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
}
})
```
+45 -45
View File
@@ -9,7 +9,7 @@ TCP monitors are used to monitor the livenees of your servers. You can use TCP m
<div class="border rounded-md">
![Monitors TCP](/m_tcp.png)
![Monitors TCP](/documentation/m_tcp.png)
</div>
@@ -32,33 +32,33 @@ This is an anonymous JS function, it should return a **Promise**, that resolves
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64));
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let alive = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true;
} else {
return false;
}
}, true);
let alive = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true
} else {
return false
}
}, true)
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
};
});
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
}
})
```
- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it and the JSON parse it. Once parse it will be an array of objects.
```js
let decodedResp = atob(responseDataBase64);
let jsonResp = JSON.parse(decodedResp);
console.log(jsonResp);
let decodedResp = atob(responseDataBase64)
let jsonResp = JSON.parse(decodedResp)
console.log(jsonResp)
/*
[
{
@@ -104,32 +104,32 @@ The input to the eval function is a base64 encoded string. You will have to deco
The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.
```javascript
(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64));
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
;(async function (responseDataBase64) {
let arrayOfPings = JSON.parse(atob(responseDataBase64))
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true;
} else {
return false;
}
}, true);
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true
} else {
return false
}
}, true)
let avgLatency = latencyTotal / arrayOfPings.length;
let avgLatency = latencyTotal / arrayOfPings.length
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
};
}
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
}
}
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
};
});
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
}
})
```
+1 -1
View File
@@ -13,7 +13,7 @@ Click on the to add a monitor.
<div class="border rounded-md">
![Monitors Main](/m_main.png)
![Monitors Main](/documentation/m_main.png)
</div>
+3 -3
View File
@@ -19,11 +19,11 @@ Example: `Kener - Open-Source and Modern looking Node.js Status Page for Effortl
```html
<title>
Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management
Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management
</title>
```
![Site Title](/ms_1.png)
![Site Title](/documentation/ms_1.png)
## Site Name
@@ -33,7 +33,7 @@ Example: `Kener - Open-Source and Modern looking Node.js Status Page for Effortl
This will be shown as a brand name on the status page on the nav bar top left.
![Site Name](/s_2.png)
![Site Name](/documentation/s_2.png)
## Home Location
+162 -162
View File
@@ -1,166 +1,166 @@
{
"sidebar": [
{
"sectionTitle": "Getting Started",
"children": [
{
"title": "Introduction",
"link": "/docs/home",
"file": "/home.md"
},
{
"title": "Get Started",
"link": "/docs/quick-start",
"file": "/quick-start.md"
},
{
"title": "Concepts",
"link": "/docs/concepts",
"file": "/concepts.md"
},
"sidebar": [
{
"sectionTitle": "Getting Started",
"children": [
{
"title": "Introduction",
"link": "/docs/home",
"file": "/home.md"
},
{
"title": "Get Started",
"link": "/docs/quick-start",
"file": "/quick-start.md"
},
{
"title": "Concepts",
"link": "/docs/concepts",
"file": "/concepts.md"
},
{
"title": "Deployment",
"link": "/docs/deployment",
"file": "/deployment.md"
},
{
"title": "Databases",
"link": "/docs/database",
"file": "/database.md"
}
]
},
{
"sectionTitle": "Guides",
"children": [
{
"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": "TCP Monitor",
"link": "/docs/monitors-tcp",
"file": "/monitors-tcp.md"
},
{
"title": "DNS Monitor",
"link": "/docs/monitors-dns",
"file": "/monitors-dns.md"
},
{
"title": "Setup Triggers",
"link": "/docs/triggers",
"file": "/triggers.md"
},
{
"title": "Setup Site",
"link": "/docs/site",
"file": "/site.md"
},
{
"title": "Setup Github",
"link": "/docs/gh-setup",
"file": "/gh-setup.md"
},
{
"title": "Setup SEO",
"link": "/docs/seo",
"file": "/seo.md"
},
{
"title": "Setup Home",
"link": "/docs/home-page",
"file": "/home-page.md"
},
{
"title": "Setup Theme",
"link": "/docs/theme",
"file": "/theme.md"
},
{
"title": "View Alerts",
"link": "/docs/alerts",
"file": "/alerts.md"
},
{
"title": "API Keys",
"link": "/docs/apikeys",
"file": "/apikeys.md"
},
{
"title": "Deployment",
"link": "/docs/deployment",
"file": "/deployment.md"
},
{
"title": "Databases",
"link": "/docs/database",
"file": "/database.md"
}
]
},
{
"sectionTitle": "Guides",
"children": [
{
"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": "TCP Monitor",
"link": "/docs/monitors-tcp",
"file": "/monitors-tcp.md"
},
{
"title": "DNS Monitor",
"link": "/docs/monitors-dns",
"file": "/monitors-dns.md"
},
{
"title": "Group Monitor",
"link": "/docs/monitors-group",
"file": "/monitors-group.md"
},
{
"title": "Setup Triggers",
"link": "/docs/triggers",
"file": "/triggers.md"
},
{
"title": "Setup Site",
"link": "/docs/site",
"file": "/site.md"
},
{
"title": "Setup SEO",
"link": "/docs/seo",
"file": "/seo.md"
},
{
"title": "Setup Home",
"link": "/docs/home-page",
"file": "/home-page.md"
},
{
"title": "Setup Theme",
"link": "/docs/theme",
"file": "/theme.md"
},
{
"title": "View Alerts",
"link": "/docs/alerts",
"file": "/alerts.md"
},
{
"title": "API Keys",
"link": "/docs/apikeys",
"file": "/apikeys.md"
},
{
"title": "Incident Management",
"link": "/docs/incident-management",
"file": "/incident-management.md"
},
{
"title": "Embed",
"link": "/docs/embed",
"file": "/embed.md"
},
{
"title": "Custom JS/CSS",
"link": "/docs/custom-js-css-guide",
"file": "/custom-js-css-guide.md"
},
{
"title": "Internationalization",
"link": "/docs/i18n",
"file": "/i18n.md"
}
]
},
{
"sectionTitle": "API Reference",
"children": [
{
"title": "Kener APIs",
"link": "/docs/kener-apis",
"file": "/kener-apis.md"
}
]
},
{
"sectionTitle": "Help",
"children": [
{
"title": "Fonts",
"link": "/docs/custom-fonts",
"file": "/custom-fonts.md"
},
{
"title": "Changelogs",
"link": "/docs/changelogs",
"file": "/changelogs.md"
},
{
"title": "Roadmap",
"link": "/docs/roadmap",
"file": "/roadmap.md"
}
]
}
]
{
"title": "Incident Management",
"link": "/docs/incident-management",
"file": "/incident-management.md"
},
{
"title": "Embed",
"link": "/docs/embed",
"file": "/embed.md"
},
{
"title": "Custom JS/CSS",
"link": "/docs/custom-js-css-guide",
"file": "/custom-js-css-guide.md"
},
{
"title": "Internationalization",
"link": "/docs/i18n",
"file": "/i18n.md"
}
]
},
{
"sectionTitle": "API Reference",
"children": [
{
"title": "Kener APIs",
"link": "/docs/kener-apis",
"file": "/kener-apis.md"
}
]
},
{
"sectionTitle": "Help",
"children": [
{
"title": "Fonts",
"link": "/docs/custom-fonts",
"file": "/custom-fonts.md"
},
{
"title": "Changelogs",
"link": "/docs/changelogs",
"file": "/changelogs.md"
},
{
"title": "Roadmap",
"link": "/docs/roadmap",
"file": "/roadmap.md"
}
]
}
]
}
+6 -6
View File
@@ -11,7 +11,7 @@ Kener provides you with the ability to customize the theme of your status page.
## Home Page Pattern
Kener can show a subtle pattern in all your pages. It is either sqaure or dots. Right now you cannot modify the color of the pattern. However, you can disable it by choosing none
Kener can show a subtle pattern in all your pages. It is either square or dots. Right now you cannot modify the color of the pattern. However, you can disable it by choosing none
---
@@ -35,13 +35,13 @@ You can change how the bars and summary of a monitor looks like.
The status bar will be a gradient from green to red/yellow based on the status of the monitor.
![Trigger API](/x1.png)
![Trigger API](/documentation/x1.png)
#### Full
The status bar will be a solid color based on the status of the monitor.
![Trigger API](/x2.png)
![Trigger API](/documentation/x2.png)
---
@@ -51,11 +51,11 @@ Adjust the roundness of the status bar.
#### SHARP
![Trigger API](/x4.png)
![Trigger API](/documentation/x4.png)
#### ROUNDED
![Trigger API](/x3.png)
![Trigger API](/documentation/x3.png)
---
@@ -97,7 +97,7 @@ You can add custom CSS to your status page. This will be added to the head of th
```css
.my-class {
color: red;
color: red;
}
```
+30 -30
View File
@@ -9,7 +9,7 @@ Triggers are used to trigger actions based on the status of your monitors. You c
<div class="border rounded-md">
![Trigger API](/trig_1.png)
![Trigger API](/documentation/trig_1.png)
</div>
@@ -38,7 +38,7 @@ Webhook triggers are used to send a HTTP POST request to a URL when a monitor go
<div class="border rounded-md">
![Trigger API](/trig_web.png)
![Trigger API](/documentation/trig_web.png)
</div>
@@ -72,24 +72,24 @@ 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"
}
]
"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"
}
]
}
```
@@ -118,7 +118,7 @@ Discord triggers are used to send a message to a discord channel when a monitor
<div class="border rounded-md">
![Trigger API](/trig_2.png)
![Trigger API](/documentation/trig_2.png)
</div>
@@ -147,7 +147,7 @@ The discord message when alert is `TRIGGERED` will look like this
The discord message when alert is `RESOLVED` will look like this
![Discord](/discord_resolved.png)
![Discord](/documentation/discord_resolved.png)
## Slack
@@ -155,7 +155,7 @@ Slack triggers are used to send a message to a slack channel when a monitor goes
<div class="border rounded-md">
![Trigger API](/trig_3.png)
![Trigger API](/documentation/trig_3.png)
</div>
@@ -185,7 +185,7 @@ The slack message when alert is `TRIGGERED` will look like this
The slack message when alert is `RESOLVED` will look like this
![Slack](/slack_resolved.png)
![Slack](/documentation/slack_resolved.png)
## Email
@@ -193,7 +193,7 @@ Email triggers are used to send an email when a monitor goes down or up. Kener s
<div class="border rounded-md">
![Trigger API](/trig_4.png)
![Trigger API](/documentation/trig_4.png)
</div>
@@ -250,11 +250,11 @@ Subject of the email when `RESOLVED`
The emaik message when alert is `TRIGGERED` will look like this
![Slack](/em_t.png)
![Slack](/documentation/em_t.png)
The emaik message when alert is `RESOLVED` will look like this
![Slack](/em_r.png)
![Slack](/documentation/em_r.png)
---
@@ -278,9 +278,9 @@ Set the URL to `https://api.telegram.org/bot[BOT_TOKEN]/sendMessage`. Replace [B
```json
{
"chat_id": "[CHAT_ID]", // Replace [CHAT_ID] with your chat id
"text": "<b>${alert_name}</b>\n\n<b>Severity:</b> <code>${severity}</code>\n<b>Status:</b> ${status}\n<b>Source:</b> Kener\n<b>Time:</b> ${timestamp}\n\n📌 <b>Details:</b>\n- <b>Metric:</b>${metric}\n- <b>Current Value:</b> <code>${current_value}</code>\n- <b>Threshold:</b> <code>${threshold}</code>\n\n🔍 <a href=\"${action_url}\">${action_text}</a>",
"parse_mode": "HTML"
"chat_id": "[CHAT_ID]", // Replace [CHAT_ID] with your chat id
"text": "<b>${alert_name}</b>\n\n<b>Severity:</b> <code>${severity}</code>\n<b>Status:</b> ${status}\n<b>Source:</b> Kener\n<b>Time:</b> ${timestamp}\n\n📌 <b>Details:</b>\n- <b>Metric:</b>${metric}\n- <b>Current Value:</b> <code>${current_value}</code>\n- <b>Threshold:</b> <code>${threshold}</code>\n\n🔍 <a href=\"${action_url}\">${action_text}</a>",
"parse_mode": "HTML"
}
```
+35 -22
View File
@@ -1,69 +1,82 @@
code:not([class^="language-"]) {
@apply rounded bg-gray-100 px-1.5 py-0.5 font-mono text-xs dark:bg-gray-800;
@apply rounded bg-gray-100 px-1.5 py-0.5 font-mono text-xs dark:bg-gray-800;
}
.sidebar-item.active,
.sidebar-item:hover {
color: #ed702d;
color: #ed702d;
}
.w-585px {
width: 585px;
width: 585px;
}
main {
scroll-behavior: smooth;
scroll-behavior: smooth;
}
.kener-home-links a {
text-decoration: none;
background-color: var(--bg-background);
text-decoration: none;
background-color: var(--bg-background);
}
.kener-home-links > div:hover {
transition: all 0.3s;
box-shadow: 0 0 8px 1.5px #3e9a4b;
transition: all 0.3s;
box-shadow: 0 0 8px 1.5px #3e9a4b;
}
.accm input:checked ~ div {
display: block;
display: block;
}
.accm input ~ div {
display: none;
display: none;
}
.accm input {
visibility: hidden;
visibility: hidden;
}
.accmt {
background-color: hsl(223, 10%, 14%);
background-color: hsl(223, 10%, 14%);
}
.accm input:checked ~ .showaccm span:first-child {
display: none;
display: none;
}
.accm input:checked ~ .showaccm span:last-child {
display: block;
display: block;
}
.accm input ~ .showaccm span:first-child {
display: block;
display: block;
}
.accm input ~ .showaccm span:last-child {
display: none;
display: none;
}
.hljs {
background: transparent !important;
background: transparent !important;
}
.note {
@apply mt-4 rounded-md border bg-background p-3 text-sm shadow-sm;
@apply mt-4 rounded-md border bg-background p-3 text-sm shadow-sm;
}
.note.danger {
border: 1px solid #e3342f;
color: #e3342f;
border: 1px solid #e3342f;
color: #e3342f;
}
.note.info {
border: 1px solid #3490dc;
color: #3490dc;
border: 1px solid #3490dc;
color: #3490dc;
}
.copybtn .copy-btn {
transform: scale(1);
}
.copybtn .check-btn {
transform: scale(0);
}
.copybtn:focus .copy-btn {
transform: scale(0);
}
.copybtn:focus .check-btn {
transform: scale(1);
}
+3 -3
View File
@@ -32,7 +32,7 @@
//broadcast a custom event named blockScroll
if (!!isMounted) {
const noScrollEvent = new CustomEvent("noScroll", {
detail: showAddMonitor
detail: showAddMonitor || draggableMenu || shareMenusToggle
});
window.dispatchEvent(noScrollEvent);
@@ -163,7 +163,7 @@
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ action: "getTriggers", data: { status: "ACTIVE" } })
body: JSON.stringify({ action: "getTriggers", data: {} })
});
triggers = await apiResp.json();
} catch (error) {
@@ -623,7 +623,7 @@
<p class="col-span-4 mt-2 text-sm font-medium">Choose Triggers</p>
{#each triggers as trigger}
<div class="col-span-1 overflow-hidden overflow-ellipsis whitespace-nowrap">
<label class="cursor-pointer">
<label class="cursor-pointer" class:line-through={trigger.trigger_status != "ACTIVE"}>
<input
type="checkbox"
class="text-sm"
+8 -1
View File
@@ -339,7 +339,14 @@ class DbImpl {
//get all alerts with given status
async getTriggers(data) {
return await this.knex("triggers").where("trigger_status", data.status).orderBy("id", "desc");
let query = this.knex("triggers").whereRaw("1=1");
if (!!data.status) {
query = query.andWhere("trigger_status", data.status);
}
if (!!data.id) {
query = query.andWhere("id", data.id);
}
return await query.orderBy("id", "desc");
}
//get trigger by id
+54 -31
View File
@@ -4,6 +4,9 @@
import "../../docs.css";
import { Button } from "$lib/components/ui/button";
import Sun from "lucide-svelte/icons/sun";
import Menu from "lucide-svelte/icons/menu";
import X from "lucide-svelte/icons/x";
import { clickOutsideAction, slide } from "svelte-legos";
import Moon from "lucide-svelte/icons/moon";
import { onMount } from "svelte";
import { base } from "$app/paths";
@@ -44,9 +47,15 @@
tableOfContents = e.detail.rightbar;
}
}
let sideBarHidden = true;
//if desktop show sidebar by default
let isMounted = false;
onMount(() => {
setTheme();
if (window.innerWidth > 768) {
sideBarHidden = false;
}
});
</script>
@@ -74,7 +83,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
</svelte:head>
<div class="dark">
<nav class="z-2 fixed left-0 right-0 top-0 z-30 h-16 bg-card">
<nav class="fixed left-0 right-0 top-0 z-40 h-16 bg-card">
<div class="mx-auto h-full border-b bg-card px-4 sm:px-6 lg:px-8">
<div class="flex h-full items-center justify-between">
<!-- Logo/Brand -->
@@ -104,10 +113,18 @@
<!-- Mobile Menu Button -->
<div class="md:hidden">
<button type="button" class="hover: text-muted-foreground">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<button
type="button"
class="mt-2 hover:text-muted-foreground"
on:click={() => {
sideBarHidden = !sideBarHidden;
}}
>
{#if sideBarHidden}
<Menu class="h-6 w-6" />
{:else}
<X class="h-6 w-6" />
{/if}
</button>
</div>
</div>
@@ -115,34 +132,40 @@
</nav>
<!-- Sidebar -->
<aside class="z-2 fixed bottom-0 left-0 top-16 w-72 overflow-y-auto">
<nav class="border-r bg-card p-6">
<!-- Getting Started Section -->
{#each sidebar as item}
<div class="mb-4">
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{item.sectionTitle}
</h3>
<div class="">
{#each item.children as child}
<a
href={child.link.startsWith("/") ? base + child.link : child.link}
class="sidebar-item group flex items-center rounded-md px-3 py-2 text-sm font-medium {!!child.active
? 'active'
: ''}"
>
{child.title}
</a>
{/each}
{#if !sideBarHidden}
<aside
transition:slide={{ direction: "left", duration: 200 }}
class="fixed bottom-0 left-0 top-16 z-30 w-72 overflow-y-auto md:block"
class:hidden={sideBarHidden}
>
<nav class="border-r bg-card p-6">
<!-- Getting Started Section -->
{#each sidebar as item}
<div class="mb-4">
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{item.sectionTitle}
</h3>
<div class="">
{#each item.children as child}
<a
href={child.link.startsWith("/") ? base + child.link : child.link}
class="sidebar-item group flex items-center rounded-md px-3 py-2 text-sm font-medium {!!child.active
? 'active'
: ''}"
>
{child.title}
</a>
{/each}
</div>
</div>
</div>
{/each}
</nav>
</aside>
{/each}
</nav>
</aside>
{/if}
<!-- Main Content -->
<main class="z-2 dark relative ml-72 min-h-screen pt-16">
<div class="mx-auto max-w-5xl px-4 py-10 sm:px-6 lg:px-8 lg:pr-64">
<main class="dark relative z-10 min-h-screen pt-16 md:ml-72">
<div class="mx-auto max-w-5xl px-4 py-10 sm:px-6 md:px-8 md:pr-64">
<!-- Content Header -->
<div
class="prose prose-stone max-w-none dark:prose-invert prose-code:rounded prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:font-normal prose-pre:bg-opacity-0 dark:prose-pre:bg-neutral-900"
@@ -152,7 +175,7 @@
</div>
</main>
{#if tableOfContents.length > 0}
<div class="blurry-bg fixed bottom-0 right-0 top-16 hidden w-64 overflow-y-auto px-6 py-10 lg:block">
<div class="blurry-bg fixed bottom-0 right-0 top-16 z-50 hidden w-64 overflow-y-auto px-6 py-10 lg:block">
<h4 class="mb-3 text-sm font-semibold uppercase tracking-wider">On this page</h4>
<nav class="space-y-2">
{#each tableOfContents as item}
+141 -51
View File
@@ -1,63 +1,153 @@
<script>
import { onMount } from "svelte";
import { afterNavigate } from "$app/navigation";
export let data;
let subHeadings = [];
let previousH2 = null;
function fillSubHeadings() {
subHeadings = [];
const headings = document.querySelectorAll("#markdown h2, #markdown h3");
headings.forEach((heading) => {
let id = heading.textContent.replace(/[^a-z0-9]/gi, "-").toLowerCase();
if (heading.tagName === "H2") {
previousH2 = id;
} else {
id = `${previousH2}-${id}`;
}
heading.id = id;
subHeadings.push({
id,
text: heading.textContent,
level: heading.tagName === "H2" ? 2 : 3
});
});
}
import { onMount } from "svelte";
import { Button } from "$lib/components/ui/button";
import { ArrowLeft, ArrowRight } from "lucide-svelte";
import { afterNavigate } from "$app/navigation";
export let data;
let subHeadings = [];
let previousH2 = null;
let subPath = data.docFilePath.split(".md")[0];
function fillSubHeadings() {
subHeadings = [];
const headings = document.querySelectorAll("#markdown h2, #markdown h3");
headings.forEach((heading) => {
let id = heading.textContent.replace(/[^a-z0-9]/gi, "-").toLowerCase();
if (heading.tagName === "H2") {
previousH2 = id;
} else {
id = `${previousH2}-${id}`;
}
heading.id = id;
subHeadings.push({
id,
text: heading.textContent,
level: heading.tagName === "H2" ? 2 : 3
});
});
}
afterNavigate(() => {
document.dispatchEvent(
new CustomEvent("pagechange", {
bubbles: true,
detail: {
docFilePath: data.docFilePath
}
})
);
fillSubHeadings();
document.dispatchEvent(
new CustomEvent("rightbar", {
bubbles: true,
detail: {
rightbar: subHeadings
}
})
);
hljs.highlightAll();
});
onMount(async () => {});
let nextPath;
let previousPath;
function scrollToId(id) {
const element = document.getElementById(id);
if (element) {
const y = element.getBoundingClientRect().top + window.pageYOffset - 100;
window.scrollTo({ top: y, behavior: "smooth" });
}
}
//function to iterate all h1,h2 nd h3 and add a button inside them
function addCopyButton() {
const headings = document.querySelectorAll("#markdown h1, #markdown h2, #markdown h3");
headings.forEach((heading) => {
const button = document.createElement("button");
button.classList.add("copybtn");
button.classList.add("relative");
button.innerHTML = `<svg class="copy-btn left-0 top-0 absolute" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
<svg class="check-btn absolute left-0 top-0" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><path d="M20 6 9 17l-5-5"/></svg>`;
button.style = "margin-left: 10px;height:16px;width:16px background-color: transparent; cursor: pointer;";
button.onclick = () => {
navigator.clipboard.writeText(`https://kener.ing/docs${subPath}#` + heading.id);
};
heading.appendChild(button);
});
}
function handleRightBarClick(e) {
if (e.target.tagName === "A") {
e.preventDefault();
const id = e.target.getAttribute("href").slice(1);
scrollToId(id);
}
}
//function to find next and previous path
function findNextAndPreviousPath() {
let structure = data.siteStructure.sidebar;
for (let i = 0; i < structure.length; i++) {
let children = structure[i].children;
for (let j = 0; j < children.length; j++) {
if (children[j].file === data.docFilePath) {
if (j > 0) {
previousPath = children[j - 1];
}
if (j < children.length - 1) {
nextPath = children[j + 1];
}
break;
}
}
}
}
afterNavigate(() => {
document.dispatchEvent(
new CustomEvent("pagechange", {
bubbles: true,
detail: {
docFilePath: data.docFilePath
}
})
);
fillSubHeadings();
document.dispatchEvent(
new CustomEvent("rightbar", {
bubbles: true,
detail: {
rightbar: subHeadings
}
})
);
hljs.highlightAll();
//if hash present scroll to hash
if (location.hash) {
scrollToId(location.hash.slice(1));
}
//if hash change scroll
window.addEventListener("hashchange", () => {
scrollToId(location.hash.slice(1));
});
window.addEventListener("click", (e) => {
handleRightBarClick(e);
});
addCopyButton();
findNextAndPreviousPath();
});
onMount(async () => {});
</script>
<svelte:head>
<title>{data.title}</title>
<meta name="description" content={data.description} />
<link rel="canonical" href="https://kener.ing/docs{data.docFilePath.split('.md')[0]}" />
<title>{data.title}</title>
<meta name="description" content={data.description} />
<link rel="canonical" href="https://kener.ing/docs{subPath}" />
</svelte:head>
<div id="markdown">
{@html data.md}
{@html data.md}
</div>
<div class="mt-4 grid grid-cols-2">
<div class="col-span-1">
{#if previousPath}
<Button variant="outline" class="no-underline" href={previousPath.link}>
<ArrowLeft class="mr-2 h-4 w-4" />
<span>{previousPath.title}</span>
</Button>
{/if}
</div>
<div class="col-span-1 flex justify-end">
{#if nextPath}
<Button variant="outline" class="no-underline" href={nextPath.link}>
<span>{nextPath.title}</span>
<ArrowRight class="ml-2 h-4 w-4" />
</Button>
{/if}
</div>
</div>
<style>
#markdown {
scroll-behavior: smooth;
}
#markdown {
scroll-behavior: smooth;
}
</style>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 149 B

Before

Width:  |  Height:  |  Size: 300 B

After

Width:  |  Height:  |  Size: 300 B

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 149 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB