Kener has been tested from Node18. It should work with Node 16 and above. It uses SvelteKit and shadcn-svelte
git clone https://github.com/rajnandan1/kener.git
cd kener
npm install
- Rename
config/site.example.yaml
->config/site.yaml
- Rename
config/monitors.example.yaml
->config/monitors.yaml
mv config/site.example.yaml config/site.yaml
mv config/monitors.example.yaml config/monitors.yaml
mkdir -p ./static/kener
npm run kener:dev
Kener Development Server would be running at PORT 5173. Go to http://localhost:5173
Kener has two parts. One is a svelte app which you can find in the src folder and there are code for monitors which you would find in scripts folder. If you want to update the frontend application then you should modify the src folder.
├── src (svelte frontend files)
├── static (things put here can be referenced directly example static/logo.png -> /logo.png)
├── scripts (nodejs server files)
├── prod.js(starts an express server, runs the scripts and serves the svelte site)
├── dev.js (starts the dev server)
export PUBLIC_KENER_FOLDER=/path/to/a/directory
Defaults to 3000 if not specified
export PORT=4242
A github token to read issues and create labels
export GH_TOKEN=your-github-token
To talk to kener apis you will need to set up a token. It uses Bearer Authorization
export API_TOKEN=sometoken
While using API you can set this variable to accept request from a specific IP
export API_IP=127.0.0.1
export MONITOR_YAML_PATH=/your/path/monitors.yaml
export SITE_YAML_PATH=/your/path/site.yaml
By default kener runs on /
but you can change it to /status
or any other path.
- Important: The base path should not have a trailing slash and should start with
/
- 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
export KENER_BASE_PATH=/status
If you do not specify MONITOR_YAML_PATH or SITE_YAML_PATH it will take the values from /config/site.yaml and /config/monitor.yaml respectively
export NODE_ENV=production
npm i
npm run build
npm run serve
It also needs 2 yaml files to work
- site.yaml: Contains information about the site
- monitors.yaml: Contains your monitors and their related specifications
By default these are present in config/
. However you can use different location either passing them as argument or having the path as enviorment variable
export MONITOR_YAML_PATH=/your/path/monitors.yaml
export SITE_YAML_PATH=/your/path/site.yaml
npm run serve -- --monitors /your/path/monitors.yaml --site /your/path/site.yaml
docker.io/rajnandan1/kener:latest
ghcr.io/rajnandan1/kener:latest
You should mount a host directory to persist your configuration and expose the web port. Environmental variables found above can be passed with -e
An example docker run
command:
Make sure you have a /static
folder inside your config folder
docker run -d -v /path/on/host/config:/config -p 3000:3000 -e "GH_TOKEN=1234" rajnandan1/kener
Or use Docker Compose with the example docker-compose.yaml
If you are
- running on a linux host (ie unraid) and
- not using rootless containers with Podman
then you must set the environmental variables PUID and PGID. in the container in order for it to generate files/folders your normal user can interact it.
Run these commands from your terminal
id -u
-- prints UID for PUIDid -g
-- prints GID for PGID
Then add to your docker command like so:
docker run -d ... -e "PUID=1000" -e "PGID=1000" ... rajnandan1/kener
or substitute them in docker-compose.yml
Kener uses github for incident management. Issues created in github using certain tags go to kener as incidents.
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
github:
owner: "username"
repo: "repository"
You can create either a classic token or personal access token
- Go to Personal Access Token
- Token Name: kener
- Expiration: Use custom to select a calendar date
- Description: My Kener
- Repository access: Check Only Selected Repositories. Select your github repository
- Repository Permission: Select Issues Read Write
- Click on generate token
- Go to Tokens
- Note: kener
- Expiration: No Expiration
- Scopes: write:packages
- Click on generate Token
Set the token as an environment variable
export GH_TOKEN=github_pat_11AD3ZA3Y0
There is a folder called /config
. Inside which there is a site.yaml
file. You can modify this file to have your own branding.
title: "Kener"
home: "/"
logo: "/logo.svg"
github:
owner: "rajnandan1"
repo: "kener"
incidentSince: 72
metaTags:
description: "Your description"
keywords: "keyword1, keyword2"
nav:
- name: "Documentation"
url: "/docs"
hero:
title: Kener is a Open-Source Status Page System
subtitle: Let your users know what's going on.
This translates to
<title>Your Title</title>
It can be set by modifying the <html>
class in src/app.html
file
<!doctype html>
<html lang="en" class="dark dark:bg-background"></html>
<!doctype html>
<html lang="en"></html>
Can be light
or dark
. Defaults to light
Location when someone clicks on the your brand in the top nav bar
...
home: "https://www.example.com
...
URL of the logo that will be shown in the nav bar. You can also add your logo in the static
folder
...
logo: "https://www.example.com/logo.png
...
It can be set by modifying the <head>
tag in src/app.html
file.
Example add a png called logo.png
file in static/
and then
...
<link rel="icon" href="/logo.png" />
...
For incident kener uses github comments. Create an empty github repo and add them to site.yaml
github:
owner: "username"
repo: "repository"
incidentSince: 72
incidentSince
is in hours. It means if an issue is created before 72 hours then kener would not honor it. Default is 24
Meta tags are nothing but html <meta>
. You can use them for SEO purposes
metaTags:
description: "Your description"
keywords: "keyword1, keyword2"
og:image: "https://example.com/og.png"
will become
<head>
<meta name="description" content="Your description" />
<meta name="keywords" content="keyword1, keyword2" />
<meta name="og:image" content="https://example.com/og.png" />
</head>
You can set this to generate SiteMaps
siteURL: https://kener.ing
Sitemaps urls will be https://kener.ing/sitemap.xml
Use hero to add a banner to your kener page
hero:
title: Kener is a Open-Source Status Page System
subtitle: Let your users know what's going on.
You can add more links to your navbar.
nav:
- name: "Home"
url: "/home"
You can add translations to your site. By default it is set to en
. Available translations are present in locales/
folders in the root directory. You can add more translations by adding a new file in the locales
folder.
i18n:
defaultLocale: en
locales:
en: English
hi: Hindi
- defaultLocale: The default locale to be used. This will be the language used when a user visits the site for the first time. It is important to note that the default locale json file should be present in the locales folder.
- locales: A list of locales that you want to enable. The key is the locale code and the value is the name of the language. The locale code should be the same as the json file name in the locales folder.
en
meansen.json
should be present in the locales folder. - Adding more than one locales will enable a dropdown in the navbar to select the language.
- Selected languages are stored in cookies and will be used when the user visits the site again.
- There is no auto detection of the language. The user has to manually select the language.
You can define categories for your monitors. Each category can have a description. The monitors can be grouped by categories.
name=home
will be shown in the home page. Categories are shown in the order they are defined in the yaml file. A dropdown will appear in the nav bar to select the category.
categories:
- name: API
description: "Kener provides a simple API for you to use to update your status page."
- name: home
description: "loroem ipsum lorem ipsum"
To set the base path of your kener instance you can set the KENER_BASE_PATH
environment variable. This will be used to prefix all the routes in the application. The env variable should not have a trailing slash and should start with /
It 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
Please also adjust files in static folder by prefixing them with the base path. For example if you set KENER_BASE_PATH=/status
then the logo should be /status/logo.png
You can include any script in the app.html
file like google analytics etc
You can add custom css in static/kener.css
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.
Each monitor runs at 1 minute interval by default. Monitor runs in below priorty order.
- defaultStatus Data
- API call Data overrides above data(if specified)
- Pushed Status Data overrides API Data using Kener Update Statue API
- Manual Incident Data overrides Pushed Status Data
Sample
- name: Google Search
description: Search the world's information, including webpages, images, videos and more.
tag: "google-search"
image: "/google.png"
cron: "* * * * *"
defaultStatus: "UP"
api:
timeout: 4000
method: POST
url: https://www.google.com/webhp
headers:
Content-Type: application/json
body: '{"order_amount":1,"order_currency":"INR"}'
eval: |
(function(statusCode, responseTime, responseDataBase64){
const resp = JSON.parse(atob(responseDataBase64));
return {
status: statusCode == 200 ? 'UP':'DOWN',
latency: responseTime,
}
})
name | Required | This will be shown in the UI to your users. Keep it short and unique |
---|---|---|
name | Required + Unique | This will be shown in the UI to your users. Keep it short and unique |
description | Optional | This will be show below your name |
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 cron expression to specify the interval to run the monitors. Defaults to * * * * * i.e every minute |
api.timeout | Optional | timeout for the api in milliseconds. Default is 10000(10 secs) |
api.method | Optional | HTTP Method |
api.url | Optional | HTTP URL |
api.headers | Optional | HTTP headers |
api.body | Optional | HTTP Body as string |
api.eval | Optional | Evaluator written in JS, to parse HTTP response and calculate uptime and latency |
defaultStatus | Optional | If no API is given this will be the default status. 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 here |
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) |
ping.hostsV4 | Optional | Array of hosts / IP to monitor ping response. Either domain name or IP4 |
ping.hostsV6 | Optional | Array of hosts / IP to monitor ping response. Either domain name or IP6 |
Kener fills data every minute in UTC so if you give an expression that is not per minute, kener will backfill data using the latest status.
Example for cron: "*/15 * * * *"
- First run at "2023-12-02T18:00:00.000Z" - Status DOWN
- Second run at "2023-12-02T18:15:00.000Z" - Status UP
Kener will fill data from 18:01:00 to 18:14:00 as UP
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}
.
(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 coderesponseTime
REQUIREDis a number. It is the latency in millisecondsresponseDataBase64
REQUIRED is a string. It is the base64 encoded response data. To use it you will have to decode it
let decodedResp = atob(responseDataBase64);
//let jsonResp = JSON.parse(decodedResp)
Here are some exhaustive examples for monitors
- name: Google Search
tag: "google-search"
api:
method: GET
url: https://www.google.com/webhp
Use tailwind classes to style your description
- name: Google Search
tag: "google-search"
description: "Hello <b>world</b>"
api:
method: GET
url: https://www.google.com/webhp
google.png is in the static folder
- name: Google Search
tag: "google-search"
image: "/google.png"
api:
method: GET
url: https://www.google.com/webhp
- name: Google Search
description: Search the world's information, including webpages, images, videos and more.
tag: "google-search"
cron: "*/15 * * * *"
api:
method: GET
url: https://www.google.com/webhp
- name: Google Search
description: Google Search
tag: "google-search-post"
api:
method: POST
url: https://www.google.com/webhp
headers:
Content-Type: application/json
body: '{"order_amount":22222.1,"order_currency":"INR"}'
You can set ENV variables in your machine and use them in your monitors. Example below has GH_TOKEN
as an environment variable. It uses process.env.GH_TOKEN.
export GH_TOKEN=some.token.for.github
NOTE: DO NOT forget the
$
sign in your monitor secret, otherwise it will not be picked up.
- name: Github Issues
description: Github Issues Fetch
tag: "gh-search-issue"
api:
method: GET
url: https://api.github.com/repos/rajnandan1/kener/issues
headers:
Authorization: Bearer $GH_TOKEN
Assuming ORDER_ID
is present in env
- name: Github Issues
description: Github Issues Fetch
tag: "gh-search-issue"
api:
method: POST
url: https://api.github.com/repos/rajnandan1/kener/issues
headers:
Content-Type: application/json
body: '{"order_amount":22222.1,"order_currency":"INR", "order_id": "$ORDER_ID"}'
Read more about eval
- name: Github Issues
description: Github Issues Fetch
tag: "gh-search-issue"
api:
method: GET
url: https://api.github.com/repos/rajnandan1/kener/issues
eval: |
(function(statusCode, responseTime, responseDataBase64){
const resp = JSON.parse(atob(responseDataBase64));
let status = 'DOWN'
if(statusCode == 200) status = 'UP';
if(Object.keys(resp).length == 0) status = 'DOWN';
if(statusCode == 200 && responseTime > 2000) status = 'DEGRADED';
return {
status: status,
latency: responseTime,
}
})
Each minute it will set the status as UP
- name: Earth
description: Our Planent
tag: "earth"
defaultStatus: UP
Add this monitor to the API category instead of the default home category
- name: Earth
description: Our Planent
tag: "earth"
category: API
Add this monitor to the API category instead of the default home category
- name: Earth
description: Our Planent
tag: "earth"
ping:
hostsV4:
- www.frogment.com
- 52.84.205.24
hostsV6:
- ipv6.google.com
If both ping and api monitors are present then API data will overwrite ping data
Add this monitor to the API category instead of the default home category
- name: Earth
description: Our blue planet
tag: "earth"
defaultStatus: "UP"
dayDegradedMinimumCount: 3
dayDownMinimumCount: 2
includeDegradedInDowntime: true
Kener uses Github to power incident management using labels
Kener auto creates labels for your monitors using the tag
parameter
incident
: If an issue is marked as incident it will show up in kener home pageincident-down
: If an issue is marked as incident-down and incident kener would make that monitor downincident-degraded
: If an issue is marked as incident-degraded and incident then kener would make the monitor degradedresolved
: Use this tag to mark the incident has RESOLVEDidentified
: Use this tag to show that the root cause of the incident has been identified
- Go to your github repo of kener
- Go to issues
- Create an issue. Give it a title
- In the body add [start_datetime:1702651340] and [end_datetime:1702651140] and add some description. Time is UTC
- Add
incident
,incident-down
and the monitor tag. This will make the monitor down for 4 minutes
Here is a sample incident for your reference.
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.
export API_TOKEN=some-token-set-by-you
Additonally you can set IP whitelisting by setting another environment token called API_IP
export API_IP=127.0.0.1
The update status API can be used to manually update the state of a monitor from a remote server.
Parameter | Description |
---|---|
status | Required Can be only UP/DOWN/DEGRADED |
latency | Required In Seconds. Leave 0 if not required |
timestampInSeconds | Optional Timestamp in UTC seconds. Defaults to now. Should between 90 Days and now |
tag | Required Monitor Tag set in monitors.yaml |
curl --request POST \
--url http://your-kener.host/api/status \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"status": "DOWN",
"latency": 1213,
"timestampInSeconds": 1702405860,
"tag": "google-search"
}'
{
"status": 200,
"message": "success at 1702405860"
}
This will update the status of the monitor with tag google-search
to DOWN at UTC 1702405860
Use this API to get the status of a monitor.
Replace google-search
with your monitor tag in query param
curl --request GET \
--url 'http://your-kener.host/api/status?tag=google-search' \
--header 'Authorization: Bearer some-token-set-by-you'
{
"status": "UP",
"uptime": "9.0026",
"lastUpdatedAt": 1706447160
}
Can be use to create an incident from a remote server
Parameter | Description |
---|---|
startDatetime | Optional When did the incident start in UTC second |
endDatetime | Optional When did the incident end in UTC seconds |
title | Required Title of the incident |
body | Optional Body of the incident |
tags | Required Array of String, Monitor Tags of the incident |
impact | Optional Can be only DOWN/DEGRADED |
isMaintenance | Optional Boolean if incident is a maintenance |
isIdentified | Optional Incident identified |
isResolved | Optional Incident resolved |
curl --request POST \
--url http://your-kener.host/api/incident \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"title": "Outage in Mumbai",
"body": "Login cluster is down in mumbai region",
"tags": ["google-search"],
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}'
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
Can be use to update an incident from a remote server. It will clear values if not passed
incidentNumber
: Number of the incident
Parameter | Description |
---|---|
startDatetime | Optional When did the incident start in UTC second |
endDatetime | Optional When did the incident end in UTC seconds |
title | Required Title of the incident |
body | Optional Body of the incident |
tags | Required Array of String, Monitor Tags of the incident |
impact | Optional Can be only DOWN/DEGRADED |
isMaintenance | Optional Boolean if incident is a maintenance |
isIdentified | Optional Incident identified |
isResolved | Optional Incident resolved |
curl --request PATCH \
--url http://your-kener.host/api/incident/{incidentNumber} \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"title": "Outage in Mumbai",
"body": "Login cluster is down in mumbai region",
"tags": ["google-search"],
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}'
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
Use incidentNumber
to fetch an incident
curl --request GET \
--url http://your-kener.host/api/incident/{incidentNumber} \
--header 'Authorization: Bearer some-token-set-by-you' \
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
Add comments for incident using incidentNumber
curl --request POST \
--url http://your-kener.host/api/incident/{incidentNumber}/comment \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"body": "comment 1"
}'
{
"commentID": 1873376745,
"body": "comment 1",
"createdAt": 1704123938
}
Use this API to fetch all the comments for an incident
curl --request GET \
--url http://your-kener.host/api/incident/{incidentNumber}/comment \
--header 'Authorization: Bearer some-token-set-by-you' \
[
{
"commentID": 1873372042,
"body": "comment 1",
"createdAt": 1704123116
},
{
"commentID": 1873372169,
"body": "comment 2",
"createdAt": 1704123139
}
]
Use this to API to update the status of an ongoing incident.
Parameter | Description |
---|---|
isIdentified | Optional Boolean, set it when incident has been identified |
isResolved | Optional Boolean, set it when incident has been resolved |
endDatetime | Optional When did the incident end in UTC seconds |
curl --request POST \
--url http://your-kener.host/api/incident/{incidentNumber}/status \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"isIdentified": true,
"isResolved": false
"endDatetime": 1702405920
}'
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
Use this to API to search incidents.
Parameter | Description |
---|---|
state | Optional open or closed. Default is open |
tags | Optional Comma separated monitor tags, example: earth,google-seach |
page | Optional Page number, starts with 1, defaults to 1 |
per_page | Optional Page size, defaults to 10, max is 100 |
created_after_utc | Optional timestamp in UTC seconds when the incident was created after. Example: 1702405920 |
created_before_utc | Optional timestamp in UTC seconds when the incident was created before . Example: 1702405920 |
title_like | Optional search incidents with title |
Search incidents that are closed and title contains hello incident
curl --request POST \
--url http://your-kener.host/api/incident?state=closed&title_like=Hello%20Incident \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"isIdentified": true,
"isResolved": false
"endDatetime": 1702405920
}'
[
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai - Hello Incident",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
]
There are two types of badges
Syntax
http://[hostname]/badge/[tag]/status
http://[hostname]/badge/[tag]/uptime
Shows the last health check was UP/DOWN/DEGRADED
Example in HTML
<img src="https://kener.ing/badge/earth/status" />
Example in MarkDown
![Status Badge](https://kener.ing/badge/[monitor.tag]/status)
Shows the 90 Day uptime by default. You can sinceLast
as query param to get uptime since last x seconds.
Example in HTML
<img src="https://kener.ing/badge/earth/uptime" />
Example in MarkDown
![Uptime Badge](https://kener.ing/badge/[monitor.tag]/uptime)
Example in HTML
<img src="https://kener.ing/badge/earth/uptime?sinceLast=900" />
Example in MarkDown
![Uptime Badge](https://kener.ing/badge/[monitor.tag]/uptime?sinceLast=900)
You can set different colors for badges and style.
![Earth Status](https://kener.ing/badge/earth/status?labelColor=F2BED1)
![Earth Status](https://kener.ing/badge/earth/status?color=FFC0D9)
![Earth Status](https://kener.ing/badge/earth/uptime?color=D0BFFF&labelColor=FFF3DA)
You can change the style of the badge. Supported Styles are plastic
, flat
, flat-square
, for-the-badge
or social
. Default is flat
![Earth Uptime](https://kener.ing/badge/earth/uptime?style=plastic)
![Earth Uptime](https://kener.ing/badge/earth/uptime?style=flat)
![Earth Uptime](https://kener.ing/badge/earth/uptime?style=flat-square)
![Earth Uptime](https://kener.ing/badge/earth/uptime?style=for-the-badge)
![Earth Uptime](https://kener.ing/badge/earth/uptime?style=social)