feat: added analytics

This commit is contained in:
Raj Nandan Sharma
2024-11-14 23:58:22 +05:30
parent e45b016cca
commit 0f5dd0fd31
35 changed files with 723 additions and 2601 deletions
+17 -1
View File
@@ -8,7 +8,7 @@ import {
IsValidHTTPMethod,
ValidateIpAddress
} from "./src/lib/server/tool.js";
import { API_TIMEOUT } from "./src/lib/server/constants.js";
import { API_TIMEOUT, AnalyticsProviders } from "./src/lib/server/constants.js";
const configPathFolder = "./config";
const databaseFolder = process.argv[2] || "./database";
@@ -251,6 +251,22 @@ async function Build() {
if (site.github.incidentSince === undefined || site.github.incidentSince === null) {
site.github.incidentSince = 48;
}
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",
+7 -1
View File
@@ -41,4 +41,10 @@ i18n:
zh-CN: "中文"
ja: "日本語"
vi: "Tiếng Việt"
theme: dark
pattern: "squares"
font:
cssSrc: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
family: "\"Lato\", sans-serif"
analytics:
- id: "G-Q3MLRXCBFT"
type: "GA"
+45
View File
@@ -0,0 +1,45 @@
---
title: Categorize Monitors Guide | Kener
description: Categorize Monitors in Kener
---
# Categorize Monitors
Let us add a category to our monitors.
## Sample monitors.yaml
```yaml
- 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"
category: "Hello"
- 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
```
## Sample site.yaml
```yaml
#...
categories:
- name: Hello
description: Say Hello to the world
#...
```
The above will have OkBookmarks and Frogment under home. Earth will be under Hello category.
+22
View File
@@ -0,0 +1,22 @@
---
title: Changelogs | Kener
description: Changelogs for Kener
---
# Changelogs
## v0.0.16
Here are the changes in this release
### Features
- Added support for `hideURLForGet` in monitors. Read more [here](/docs/monitors)
- New SVG badges for LIVE status. Read more [here](/docs/status-badges#live)
- `[Breaking Change]` Removed dependency on Environment variable `PUBLIC_KENER_FOLDER`. Read more [here](#migration)
- Simplified build and deploy process
- Added support for fonts. Read more [here](/docs/customize-site#font)
- Added support for home page pattern. Read more [here](/docs/customize-site#pattern)
- Added support for adding your analytics provider. Read more [here](/docs/site-analytics)
- New Documentation Site
- Redesigned the UI for better consistency
+24
View File
@@ -0,0 +1,24 @@
---
title: Custom JS and CSS Guide | Kener
description: Custom JS and CSS Guide for Kener
---
Here is a guide to add custom JS and CSS to your Kener instance.
## Adding Custom JS
Add your custom JS to `static/` file. And in the `src/app.html` file, add the following line:
```html
<script src="/your-custom-js-file.js"></script>
```
## Adding Custom CSS
Add your custom CSS to `static/` file. And in the `src/app.html` file, add the following line:
```html
<link rel="stylesheet" href="/your-custom-css-file.css" />
```
Do not forget to add the base path if you are using a subpath. For example, if you are using a subpath `/kener`, then the path should be `/kener/your-custom-js-file.js`.
+29 -1
View File
@@ -1,6 +1,11 @@
---
title: Customize Site - Site.yaml - Kener
description: Customize your Kener site using site.yaml
---
# Customize Site
There is a folder called `src/lib/server/config`. Inside which there is a `site.yaml` file. You can modify this file to have your own branding and do few other things.
There is a folder called `./config`. Inside which there is a `site.yaml` file. You can modify this file to have your own branding and do few other things.
## Sample site.yaml
@@ -46,6 +51,13 @@ pattern: "squares"
font:
cssSrc: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
family: '"Lato", sans-serif'
analytics:
- id: "G-QsFT"
type: "GA"
- id: "deasf0d350"
type: "AMPLITUDE"
- id: "FKOdsKener"
type: "MIXPANEL"
```
---
@@ -299,3 +311,19 @@ URL of the font css
### family
Font family
---
## analytics
You can add analytics to your site. You can add multiple analytics. Supported analytics are `GA`, `AMPLITUDE`, `MIXPANEL`
```yaml
analytics:
- id: "G-QsFT"
type: "GA"
- id: "deasf0d350"
type: "AMPLITUDE"
- id: "FKOdsKener"
type: "MIXPANEL"
```
+67
View File
@@ -0,0 +1,67 @@
---
title: Embed Monitor | Kener
description: Embed your monitor in your website
---
# Embed Monitor
There are two ways to embed your monitor in your website
## Javascript
You can embed your monitor in your website using javascript. We recommend using this method as it takes care of the height of the embedded monitor.
```html
<script
async
src="http://[hostname]/embed-[tag]/js?theme=light&monitor=http://[hostname]/embed-[tag]"
></script>
```
Here is an example
```html
<script
async
src="https://kener.ing/embed-okbookmarks/js?theme=light&monitor=http://localhost:3000/embed-okbookmarks"
></script>
```
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
## Iframe
This is the simplest way to embed your monitor in your website. You can use the following code to embed your monitor in your website.
```html
<iframe
src="http://[hostname]/embed-[tag]?theme=light"
width="100%"
height="200"
allowfullscreen="allowfullscreen"
allowpaymentrequest
frameborder="0"
></iframe>
```
Here is an example
```html
<iframe
src="http://localhost:3000/embed-okbookmarks?theme=light"
width="100%"
height="200"
allowfullscreen="allowfullscreen"
allowpaymentrequest
frameborder="0"
></iframe>
```
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
## Parameters
You can pass the following parameters to the embed code
- `theme`: You can pass `light` or `dark` theme
- `monitor`: The monitor url
+5
View File
@@ -1,3 +1,8 @@
---
title: Environment Variables | Kener
description: Kener needs some environment variables to be set to run properly. Here are the list of environment variables that you need to set.
---
# Environment Variables
Kener needs some environment variables to be set to run properly. Here are the list of environment variables that you need to set.
+6 -2
View File
@@ -1,3 +1,8 @@
---
title: Github Setup | Kener
description: Kener uses github for incident management. Issues created in github using certain tags go to kener as incidents.
---
# Github Setup
Kener uses github for incident management. Issues created in github using certain tags go to kener as incidents.
@@ -36,7 +41,6 @@ You can create either a classic token or personal access token
## Step 3: Set environment
```shell
export GH_TOKEN=github_pat_11AD3ZA3Y0
```
```
+5
View File
@@ -1,3 +1,8 @@
---
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
<p align="center">
+9
View File
@@ -1,3 +1,8 @@
---
title: How it works | Kener
description: Folder structure and how Kener works
---
# How it works
Kener has two parts.
@@ -27,3 +32,7 @@ This is the configuration file for your site. This is where you define the name
## Monitors.yaml
This is the configuration file for your monitors. This is where you define the monitors you want to show on your site. Read more about it [here](/docs/monitors)
## Data
Kener stores its data in a folder which is `./database`. This is where all the data is stored. You can delete this folder if you want to start fresh.
+5
View File
@@ -1,3 +1,8 @@
---
title: i18n | Kener
description: Kener supports multiple languages. You can add translations to your site.
---
# i18n
You can add translations to your site. By default it is set to `en`. Available translations are present in `/src/lib/locales/` folders in the root directory. You can add more translations by adding a new file in the `/src/lib/locales` folder.
+6 -1
View File
@@ -1,3 +1,8 @@
---
title: Incident Management | Kener
description: Kener uses Github to power incident management using labels
---
# Incident Management
Kener uses Github to power incident management using labels
@@ -22,4 +27,4 @@ Kener auto creates labels for your monitors using the `tag` parameter
If you clone the repo it gives you an issue template to create incidents
Here is a [sample incident](https://github.com/rajnandan1/kener/issues/15) for your reference.
Here is a [sample incident](https://github.com/rajnandan1/kener/issues/15) for your reference.
+5
View File
@@ -1,3 +1,8 @@
---
title: Kener APIs
description: Kener gives APIs to push data and create incident.
---
# Kener APIs
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.
+5
View File
@@ -1,3 +1,8 @@
---
title: Monitor Examples | Kener
description: Here are some exhaustive examples for monitors
---
# Monitor Examples
Here are some exhaustive examples for monitors
+6 -1
View File
@@ -1,3 +1,8 @@
---
title: Monitors | monitors.yaml | 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.
@@ -53,7 +58,7 @@ Sample
| 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 |
| 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) |
+5
View File
@@ -1,3 +1,8 @@
---
title: Quick Start | Kener
description: Get started with Kener
---
# Quick Start
Please make sure you have [Node](https://nodejs.org/en) installed in your system. Minimum version required is `v16.17.0`.
+54
View File
@@ -0,0 +1,54 @@
---
title: Site Analytics | Kener
description: Add Google Analytics, Amplitude, Mixpanel etc to your Kener site
---
# Site Analytics
You can add Google Analytics, Amplitude, Mixpanel etc to your Kener site. In order to do this, you need to add the following configuration to your `site.yaml` file.
```yaml
analytics:
- id: "G-QsFT"
type: "GA"
- id: "deasf0d350"
type: "AMPLITUDE"
- id: "FKOdsKener"
type: "MIXPANEL"
```
## Google Analytics
To add Google Analytics to your site, you need to add the following configuration to your `site.yaml` file.
```yaml
analytics:
- id: "G-QsFT"
type: "GA"
```
`id` is the tracking ID of your Google Analytics account. You can find this in your Google Analytics account.
## Amplitude
To add Amplitude to your site, you need to add the following configuration to your `site.yaml` file.
```yaml
analytics:
- id: "deasf0d350"
type: "AMPLITUDE"
```
`id` is the API key of your Amplitude account. You can find this in your Amplitude account.
## Mixpanel
To add Mixpanel to your site, you need to add the following configuration to your `site.yaml` file.
```yaml
analytics:
- id: "FKOdsKener"
type: "MIXPANEL"
```
`id` is the token of your Mixpanel account. You can find this in your Mixpanel account.
+18 -7
View File
@@ -1,3 +1,8 @@
---
title: Status Badges | Kener
description: Status badges for your monitors
---
# Status Badges
There are three types of badges
@@ -14,8 +19,6 @@ http://[hostname]/badge/[tag]/uptime
## Status
### Badge SVG
Shows the last health check was UP/DOWN/DEGRADED
![Earth Status](https://kener.ing/badge/earth/status)
@@ -32,21 +35,29 @@ Example in MarkDown
![Status Badge](https://kener.ing/badge/[monitor.tag]/status)
```
### Icon SVG
---
## Live
Shows the last health check was UP/DOWN/DEGRADED as SVG dot
#### Standard
```shell
http://[hostname]/badge/[tag]/dot
#or
http://[hostname]/badge/[tag]/dot?animate=ping
```
![Earth Status](http://localhost:3000/badge/google-search/dot)
### Standard
![Earth Status](/badge/earth/dot)
```html
<img src="https://kener.ing/badge/earth/dot" />
```
#### Animated
### Animated
![Earth Status](http://localhost:3000/badge/google-search/dot?animate=ping)
![Earth Status](/badge/earth/dot?animate=ping)
```html
<img src="https://kener.ing/badge/earth/dot?animate=ping" />
+30
View File
@@ -17,6 +17,11 @@
"title": "How it works",
"link": "/docs/how-it-works",
"file": "/how-it-works.md"
},
{
"title": "Changelogs",
"link": "/docs/changelogs",
"file": "/changelogs.md"
}
]
},
@@ -52,6 +57,16 @@
"title": "Badges",
"link": "/docs/status-badges",
"file": "/status-badges.md"
},
{
"title": "Embed",
"link": "/docs/embed",
"file": "/embed.md"
},
{
"title": "Analytics",
"link": "/docs/site-analytics",
"file": "/site-analytics.md"
}
]
},
@@ -84,6 +99,21 @@
"file": "/kener-apis.md"
}
]
},
{
"sectionTitle": "Guides",
"children": [
{
"title": "Categorize Monitors",
"link": "/docs/categorize-guide",
"file": "/categorize-guide.md"
},
{
"title": "Custom JS/CSS",
"link": "/docs/custom-js-css-guide",
"file": "/custom-js-css-guide.md"
}
]
}
]
}
+141 -2351
View File
File diff suppressed because it is too large Load Diff
+4 -8
View File
@@ -1,6 +1,6 @@
{
"name": "kener",
"version": "0.0.15",
"version": "0.0.16",
"private": false,
"license": "MIT",
"description": "Kener: An open-source Node.js status page application for real-time service monitoring, incident management, and customizable reporting. Simplify service outage tracking, enhance incident communication, and ensure a seamless user experience.",
@@ -58,7 +58,7 @@
},
"type": "module",
"dependencies": {
"@types/prismjs": "^1.26.5",
"analytics": "^0.8.14",
"axios": "^1.6.2",
"badge-maker": "^3.3.1",
"bits-ui": "^0.9.9",
@@ -79,12 +79,8 @@
"prismjs": "^1.29.0",
"queue": "^7.0.0",
"randomstring": "^1.3.0",
"remark": "^15.0.1",
"remark-html": "^16.0.1",
"remark-parse": "^11.0.0",
"remark-prism": "^1.3.6",
"svelte-legos": "^0.2.5",
"tailwind-merge": "^2.0.0",
"tailwind-variants": "^0.1.18",
"vite-node": "^2.1.4"
"tailwind-variants": "^0.1.18"
}
}
-12
View File
@@ -10,17 +10,5 @@
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q3MLRXCBFT"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-Q3MLRXCBFT");
</script>
</body>
</html>
+6 -1
View File
@@ -1,3 +1,8 @@
code:not([class^="language-"]) {
@apply rounded bg-gray-100 px-1.5 py-0.5 font-mono text-sm 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;
}
+13
View File
@@ -0,0 +1,13 @@
// @ts-ignore
export const analyticsEvent = (event, data) => {
// Do something with the event and data
document.dispatchEvent(
new CustomEvent("analyticsEvent", {
bubbles: true,
detail: {
event,
data
}
})
);
};
+71 -21
View File
@@ -22,6 +22,8 @@
import { Label } from "$lib/components/ui/label";
import axios from "axios";
import { l, summaryTime, n, ampm } from "$lib/i18n/client";
import { analyticsEvent } from "$lib/analytics";
import { hoverAction } from "svelte-legos";
const dispatch = createEventDispatcher();
/**
@@ -54,6 +56,9 @@
let pathMonitorLink;
function copyLinkToClipboard() {
analyticsEvent("monitor_link_copied", {
tag: monitor.tag
});
navigator.clipboard.writeText(pathMonitorLink);
copiedLink = true;
setTimeout(function () {
@@ -63,6 +68,9 @@
let pathMonitorBadgeUptime;
function copyUptimeBadge() {
analyticsEvent("monitor_uptime_badge_copied", {
tag: monitor.tag
});
navigator.clipboard.writeText(pathMonitorBadgeUptime);
copiedBadgeUptime = true;
setTimeout(function () {
@@ -72,6 +80,9 @@
let pathMonitorBadgeStatus;
function copyStatusBadge() {
analyticsEvent("monitor_status_badge_copied", {
tag: monitor.tag
});
navigator.clipboard.writeText(pathMonitorBadgeStatus);
copiedBadgeStatus = true;
setTimeout(function () {
@@ -81,6 +92,10 @@
let pathMonitorBadgeDot;
function copyDotStandard() {
analyticsEvent("monitor_svg_standard_copied", {
tag: monitor.tag
});
navigator.clipboard.writeText(pathMonitorBadgeDot);
copiedBadgeDotStandard = true;
setTimeout(function () {
@@ -90,6 +105,9 @@
let pathMonitorBadgeDotPing;
function copyDotPing() {
analyticsEvent("monitor_svg_pinging_copied", {
tag: monitor.tag
});
navigator.clipboard.writeText(pathMonitorBadgeDotPing);
copiedBadgeDotPing = true;
setTimeout(function () {
@@ -100,6 +118,11 @@
function copyScriptTagToClipboard() {
//get domain with port number
analyticsEvent("monitor_embed_copied", {
tag: monitor.tag,
type: embedType
});
let path = `${base}/embed-${monitor.tag}`;
let scriptTag =
`<script async src="${protocol + "//" + domain + path}/js?theme=${theme}&monitor=${protocol + "//" + domain + path}"><` +
@@ -145,9 +168,15 @@
function switchView(s) {
view = s;
if (Object.keys(_0Day).length == 0) {
analyticsEvent("monitor_view_today", {
tag: monitor.tag
});
getToday();
}
if (view == "90day") {
analyticsEvent("monitor_view_90day", {
tag: monitor.tag
});
scrollToRight();
}
}
@@ -166,6 +195,13 @@
afterUpdate(() => {
dispatch("heightChange", {});
});
function show90Inline(e, bar) {
if (e.detail.hover) {
_90Day[bar.timestamp].showDetails = true;
} else {
_90Day[bar.timestamp].showDetails = false;
}
}
</script>
<div class="monitor relative grid w-full grid-cols-12 gap-2 pb-2 md:w-[655px]">
@@ -192,7 +228,15 @@
<Popover.Root>
<Popover.Trigger class="absolute right-14 top-5 h-5 w-5 p-0">
<Button class="h-5 p-0" variant="link">
<Button
class="h-5 p-0"
variant="link"
on:click={(e) => {
analyticsEvent("monitor_share_menu_open", {
tag: monitor.tag
});
}}
>
<Share2 class="h-4 w-4 text-muted-foreground" />
</Button>
</Popover.Trigger>
@@ -205,7 +249,7 @@
{l(lang, "monitor.share_desc")}
</p>
<Button
class="h-8 text-xs"
class="h-8 pr-4 text-xs"
variant="secondary"
on:click={copyLinkToClipboard}
>
@@ -273,7 +317,7 @@
</div>
</div>
<Button
class="h-8 px-2 text-xs"
class="h-8 px-2 pr-4 text-xs"
variant="secondary"
on:click={copyScriptTagToClipboard}
>
@@ -300,7 +344,7 @@
{l(lang, "monitor.badge_desc")}
</p>
<Button
class="h-8 px-2 text-xs"
class="h-8 px-2 pr-4 text-xs"
variant="secondary"
on:click={copyStatusBadge}
>
@@ -319,7 +363,7 @@
{/if}
</Button>
<Button
class="h-8 px-2 text-xs"
class="h-8 px-2 pr-4 text-xs"
variant="secondary"
on:click={copyUptimeBadge}
>
@@ -349,7 +393,7 @@
{l(lang, "monitor.status_svg_desc")}
</p>
<Button
class="h-8 px-2 text-xs"
class="h-8 px-2 pr-4 text-xs"
variant="secondary"
on:click={copyDotStandard}
>
@@ -370,7 +414,7 @@
{/if}
</Button>
<Button
class="h-8 px-2 text-xs"
class="h-8 px-2 pr-4 text-xs"
variant="secondary"
on:click={copyDotPing}
>
@@ -449,17 +493,28 @@
<div class="chart-status relative col-span-12 mt-1">
<div class="daygrid90 flex overflow-x-auto overflow-y-hidden py-1">
{#each Object.entries(_90Day) as [ts, bar]}
<div class="oneline h-[30px] w-[6px] rounded-sm">
<div
use:hoverAction
on:hover={(e) => {
show90Inline(e, bar);
}}
class="oneline h-[30px] w-[6px] rounded-sm"
>
<div
class="h-[30px] bg-{bar.cssClass} mr-[2px] w-[4px] rounded-sm"
></div>
</div>
<div class="show-hover absolute bg-background text-sm">
<div class="text-{bar.cssClass} pt-1 text-xs font-semibold">
{n(lang, new Date(bar.timestamp * 1000).toLocaleDateString())}
{summaryTime(lang, bar.message)}
{#if bar.showDetails}
<div class="show-hover absolute bg-background text-sm">
<div class="text-{bar.cssClass} pt-1 text-xs font-semibold">
{n(
lang,
new Date(bar.timestamp * 1000).toLocaleDateString()
)}
{summaryTime(lang, bar.message)}
</div>
</div>
</div>
{/if}
{/each}
</div>
</div>
@@ -477,7 +532,7 @@
<div class="hiddenx relative">
<div
data-index={ts.index}
class="message rounded border bg-black p-2 text-sm font-semibold text-white"
class="message rounded border bg-black p-2 text-xs font-semibold text-white"
>
<p>
<span class="text-{bar.cssClass}"></span>
@@ -490,11 +545,11 @@
)}
</p>
{#if bar.status != "NO_DATA"}
<p class="pl-4">
<p class="pl-2">
{l(lang, "statuses." + bar.status)}
</p>
{:else}
<p class="pl-4">-</p>
<p class="pl-2">-</p>
{/if}
</div>
</div>
@@ -530,12 +585,7 @@
transform: scaleY(1.2);
}
.oneline:hover + .show-hover {
display: block !important;
}
.show-hover {
display: none;
top: 40px;
padding: 0px;
text-align: left;
+2 -2
View File
@@ -54,8 +54,8 @@
"pm": "pm",
"standard": "Standard",
"pinging": "Pinging",
"status_svg": "Status SVG",
"status_svg_desc": "Get a SVG dot for this monitor"
"status_svg": "Live Status",
"status_svg_desc": "Get a LIVE Status for this monitor"
},
"numbers": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
}
+6 -1
View File
@@ -9,5 +9,10 @@ const UP = "UP";
const DOWN = "DOWN";
const DEGRADED = "DEGRADED";
const API_TIMEOUT = 10 * 1000; // 10 seconds
const AnalyticsProviders = {
GA: "https://unpkg.com/@analytics/google-analytics@1.0.7/dist/@analytics/google-analytics.min.js",
AMPLITUDE: "https://unpkg.com/@analytics/amplitude@0.1.3/dist/@analytics/amplitude.min.js",
MIXPANEL: "https://unpkg.com/@analytics/mixpanel@0.4.0/dist/@analytics/mixpanel.min.js"
};
// Export the constants
export { MONITOR, UP, DOWN, SITE, DEGRADED, API_TIMEOUT, ENV };
export { MONITOR, UP, DOWN, SITE, DEGRADED, API_TIMEOUT, ENV, AnalyticsProviders };
-1
View File
@@ -1 +0,0 @@
None
-94
View File
@@ -1,94 +0,0 @@
[
{
"name": "Google Search",
"description": "Search the world's <span class=\"text-red-500\">information</span>, including webpages, images, videos and more.",
"tag": "google-search",
"image": "/google.png",
"api": {
"method": "GET",
"url": "https://www.google.com/webhp",
"hideURLForGet": true,
"eval": "(function (statusCode, responseTime, responseData) {\n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})",
"timeout": 10000
},
"folderName": "google-search",
"dayDegradedMinimumCount": 1,
"dayDownMinimumCount": 1,
"includeDegradedInDowntime": false,
"path0Day": "/Users/rajnandan1/Code/kener/db/google-search.0day.utc.json",
"path90Day": "/Users/rajnandan1/Code/kener/db/google-search.90day.utc.json",
"hasAPI": true
},
{
"name": "Svelte Website",
"description": "Cybernetically enhanced web apps <a href=\"https://svelte.dev/\" class=\"font-medium underline underline-offset-4\" target=\"_blank\">https://svelte.dev/</a>",
"tag": "svelte-website",
"api": {
"method": "GET",
"url": "https://svelte.dev/",
"eval": "(function (statusCode, responseTime, responseData) {\n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})",
"timeout": 10000
},
"image": "/svelte.svg",
"folderName": "svelte-website",
"dayDegradedMinimumCount": 1,
"dayDownMinimumCount": 1,
"includeDegradedInDowntime": false,
"path0Day": "/Users/rajnandan1/Code/kener/db/svelte-website.0day.utc.json",
"path90Day": "/Users/rajnandan1/Code/kener/db/svelte-website.90day.utc.json",
"hasAPI": true
},
{
"name": "Earth",
"description": "Our blue planet",
"tag": "earth",
"defaultStatus": "UP",
"image": "/earth.png",
"cron": "*/2 * * * *",
"folderName": "earth",
"dayDegradedMinimumCount": 1,
"dayDownMinimumCount": 1,
"includeDegradedInDowntime": false,
"path0Day": "/Users/rajnandan1/Code/kener/db/earth.0day.utc.json",
"path90Day": "/Users/rajnandan1/Code/kener/db/earth.90day.utc.json",
"hasAPI": false
},
{
"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 <a href=\"https://www.frogment.com\" class=\"font-medium underline underline-offset-4\" target=\"_blank\">https://www.frogment.com</a>",
"tag": "frogment",
"image": "/frogment.png",
"api": {
"method": "GET",
"url": "https://www.frogment.com",
"eval": "(function (statusCode, responseTime, responseData) {\n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})",
"timeout": 10000
},
"folderName": "frogment",
"dayDegradedMinimumCount": 1,
"dayDownMinimumCount": 1,
"includeDegradedInDowntime": false,
"path0Day": "/Users/rajnandan1/Code/kener/db/frogment.0day.utc.json",
"path90Day": "/Users/rajnandan1/Code/kener/db/frogment.90day.utc.json",
"hasAPI": true
},
{
"name": "OkBookmarks",
"description": "Stop forgetting about your bookmarks <a href=\"https://okbookmarks.com/\" class=\"font-medium underline underline-offset-4\" target=\"_blank\">https://okbookmarks.com/</a>",
"tag": "okbookmarks",
"image": "https://okbookmarks.com/app/mybookmark.png",
"api": {
"method": "GET",
"url": "https://okbookmarks.com/",
"eval": "(function (statusCode, responseTime, responseData) {\n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})",
"timeout": 10000
},
"folderName": "okbookmarks",
"dayDegradedMinimumCount": 1,
"dayDownMinimumCount": 1,
"includeDegradedInDowntime": false,
"path0Day": "/Users/rajnandan1/Code/kener/db/okbookmarks.0day.utc.json",
"path90Day": "/Users/rajnandan1/Code/kener/db/okbookmarks.90day.utc.json",
"hasAPI": true
}
]
-64
View File
@@ -1,64 +0,0 @@
{
"title": "Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management",
"siteName": "Kener.ing",
"home": "/",
"logo": "/logo.png",
"favicon": "/logo96.png",
"github": {
"owner": "rajnandan1",
"repo": "kener",
"incidentSince": 48
},
"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 a Modern Open-Source Status Page System",
"subtitle": "Let your users know what's going on."
},
"footerHTML": "Made using \n<a href=\"https://github.com/rajnandan1/kener\" target=\"_blank\" rel=\"noreferrer\" class=\"font-medium underline underline-offset-4\">\n Kener\n</a>\nan open source status page system built with Svelte and TailwindCSS.\n",
"i18n": {
"defaultLocale": "en",
"locales": {
"en": "English",
"hi": "हिन्दी",
"zh-CN": "中文",
"ja": "日本語",
"vi": "Tiếng Việt"
}
},
"theme": "dark",
"pattern": "squares",
"font": {
"cssSrc": "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap",
"family": "\"Lato\", sans-serif"
}
}
+31 -12
View File
@@ -68,9 +68,20 @@
<svelte:window on:pagechange={pageChange} on:rightbar={updateTableOfContents} />
<svelte:head>
<link rel="icon" id="kener-app-favicon" href="{base}/logo96.png" />
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q3MLRXCBFT"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-Q3MLRXCBFT");
</script>
</svelte:head>
<div class="squares-pattern"></div>
<nav class="fixed left-0 right-0 top-0 z-30 h-16 border-b bg-background">
<div class="squares-pattern z-0"></div>
<nav class="z-2 fixed left-0 right-0 top-0 z-30 h-16 bg-background shadow-sm">
<div class="mx-auto h-full px-4 sm:px-6 lg:px-8">
<div class="flex h-full items-center justify-between">
<!-- Logo/Brand -->
@@ -78,7 +89,7 @@
<a href="/" class="flex items-center space-x-3">
<!-- Document Icon - Replace with your own logo -->
<img src="https://kener.ing/logo.png" class="h-8 w-8" alt="" />
<span class="text-xl font-semibold">Kener Documentation</span>
<span class="text-xl font-medium">Kener Documentation</span>
<span
class="me-2 rounded bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300"
>
@@ -96,8 +107,14 @@
src="https://img.shields.io/github/stars/rajnandan1/kener?label=Star%20Repo&style=social"
/>
</a>
<a
href="https://github.com/rajnandan1/kener/issues"
class="text-sm font-medium"
>
Report Issue
</a>
<a href="https://github.com/sponsors/rajnandan1" class="text-sm font-medium">
Support
Sponsor
</a>
</div>
</div>
@@ -120,8 +137,8 @@
</nav>
<!-- Sidebar -->
<aside class="fixed bottom-0 left-0 top-16 w-72 overflow-y-auto">
<nav class="p-6">
<aside class="z-2 fixed bottom-0 left-0 top-16 w-72 overflow-y-auto">
<nav class="bg-background p-6">
<!-- Getting Started Section -->
{#each sidebar as item}
<div class="mb-4">
@@ -134,8 +151,8 @@
{#each item.children as child}
<a
href={child.link.startsWith("/") ? base + child.link : child.link}
class="group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:underline {!!child.active
? 'bg-muted'
class="sidebar-item group flex items-center rounded-md px-3 py-2 text-sm font-medium {!!child.active
? 'active'
: ''}"
>
{child.title}
@@ -148,18 +165,20 @@
</aside>
<!-- Main Content -->
<main class="z-1 ml-72 min-h-screen pt-16">
<main class="z-2 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">
<!-- 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 dark:prose-pre:bg-neutral-900"
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"
>
<slot />
</div>
</div>
</main>
{#if tableOfContents.length > 0}
<div class="fixed bottom-0 right-0 top-16 hidden w-64 overflow-y-auto px-6 py-10 lg:block">
<div
class="blurry-bg fixed bottom-0 right-0 top-16 hidden w-64 overflow-y-auto px-6 py-10 lg:block"
>
<h4 class="mb-3 text-sm font-semibold uppercase tracking-wider">On this page</h4>
<nav class="space-y-2">
{#each tableOfContents as item}
@@ -176,7 +195,7 @@
</nav>
</div>
{/if}
<div class="fixed bottom-4 right-4">
<div class=" fixed bottom-4 right-4">
<Button on:click={toggleMode} variant="ghost" size="icon" class="flex">
<Sun
class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
+64 -3
View File
@@ -10,6 +10,7 @@
import Moon from "lucide-svelte/icons/moon";
import { Languages } from "lucide-svelte";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { analyticsEvent } from "$lib/analytics";
export let data;
@@ -27,6 +28,10 @@
classList.add("dark");
localStorage.setItem("theme", "dark");
}
analyticsEvent("theme_change", {
theme: classList.contains("dark") ? "light" : "dark"
});
}
let defaultLocaleValue;
if (!allLocales) {
@@ -41,6 +46,9 @@
document.cookie = `localLang=${locale};max-age=${60 * 60 * 24 * 365 * 30}`;
if (locale === defaultLocaleKey) return;
defaultLocaleValue = allLocales[locale];
analyticsEvent("language_change", {
locale: locale
});
location.reload();
}
@@ -52,8 +60,8 @@
document.documentElement.classList.remove("dark");
}
}
onMount(() => {
let Analytics;
onMount(async () => {
let localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (localTz != data.localTz) {
if (data.isBot === false) {
@@ -62,9 +70,55 @@
}
}
setTheme();
const providers = data.site.analytics;
const analyticsPlugins = [];
if (providers) {
//loop object
Object.keys(providers).forEach((key) => {
const provider = providers[key];
if (key == "GA") {
analyticsPlugins.push(
analyticsGa.default({
measurementIds: provider.measurementIds
})
);
} else if (key == "AMPLITUDE") {
analyticsPlugins.push(
analyticsAmplitude({
apiKey: provider.measurementIds[0],
options: {
trackingOptions: {
ip_address: false
}
}
})
);
} else if (key == "MIXPANEL") {
analyticsPlugins.push(
analyticsMixpanel({
token: provider.measurementIds[0]
})
);
}
});
}
Analytics = _analytics.init({
app: "kener",
debug: true,
version: 100,
plugins: analyticsPlugins
});
Analytics.page();
//import googleAnalyticsV3 from '@analytics/google-analytics-v3'
});
function captureAnalytics(e) {
const { event, data } = e.detail;
Analytics.track(event, data);
}
</script>
<svelte:window on:analyticsEvent={captureAnalytics} />
<svelte:head>
<title>{data.site.title}</title>
{#if data.site.favicon && data.site.favicon[0] == "/"}
@@ -76,12 +130,19 @@
{#each Object.entries(data.site.metaTags) as [key, value]}
<meta name={key} content={value} />
{/each}
<script src="https://unpkg.com/analytics/dist/analytics.min.js"></script>
{#if data.site.analytics}
{#each Object.entries(data.site.analytics) as [key, value]}
<script data-type={key} src={value.script}></script>
{/each}
{/if}
</svelte:head>
<main style="--font-family: {data.site.font.family}">
{#if data.showNav}
<Nav {data} />
{/if}
<div class="min-h-screen">
<div class="min-h-[70vh]">
<slot />
</div>
+9 -11
View File
@@ -6,6 +6,7 @@
import { Badge } from "$lib/components/ui/badge";
import { l } from "$lib/i18n/client";
import { base } from "$app/paths";
import { ArrowRight } from "lucide-svelte";
export let data;
let hasActiveIncidents = data.openIncidents.length > 0;
@@ -111,26 +112,23 @@
{/if}
{#if data.site.categories}
<section
class="mx-auto mb-8 w-full max-w-[890px] flex-1 flex-col items-start backdrop-blur-[2px]"
class="mx-auto mb-8 w-full max-w-[890px] flex-1 flex-col items-start backdrop-blur-[2px] md:w-[655px]"
>
<h2 class="mb-2 mt-2 px-2 text-xl font-semibold">
{l(data.lang, "root.other_monitors")}
</h2>
{#each data.site.categories as category}
<Card.Root class="mb-2 w-full">
<Card.Header>
<Card.Title>{category.name}</Card.Title>
<Card.Description class="relative pr-[100px]">
<Card.Header class="relative pr-[100px]">
<Card.Title class="">{category.name}</Card.Title>
<Card.Description>
{#if category.description}
{category.description}
{@html category.description}
{/if}
<a
href="{base}/category-{category.name}"
class="{buttonVariants({
variant: 'secondary'
})} absolute -top-4 right-2"
variant: 'ghost'
})} absolute right-2 top-1/2 -translate-y-1/2 transform"
>
View
<ArrowRight class="h-4 w-4" />
</a>
</Card.Description>
</Card.Header>
@@ -42,7 +42,7 @@
{/if}
{#if hasActiveIncidents}
<section
class="mx-auto mb-4 flex w-full max-w-[890px] flex-1 flex-col items-start justify-center bg-transparent"
class="mx-auto mb-4 flex w-full flex-1 flex-col items-start justify-center bg-transparent backdrop-blur-[2px] md:w-[655px]"
id=""
>
<div class="grid w-full grid-cols-2 gap-4">
@@ -54,7 +54,7 @@
</div>
</section>
<section
class="mx-auto mb-8 flex w-full max-w-[890px] flex-1 flex-col items-start justify-center backdrop-blur-[2px]"
class="mx-auto mb-8 flex w-full flex-1 flex-col items-start justify-center backdrop-blur-[2px] md:w-[655px]"
id=""
>
{#each data.openIncidents as incident, i}
@@ -70,17 +70,17 @@
{/if}
{#if data.monitors.length > 0}
<section
class="mx-auto mb-4 flex w-full max-w-[890px] flex-1 flex-col items-start justify-center bg-transparent"
class="mx-auto mb-2 flex w-full flex-1 flex-col items-start justify-center bg-transparent md:w-[655px]"
id=""
>
<div class="grid w-full grid-cols-2 gap-4">
<div class="col-span-2 text-center md:col-span-1 md:text-left">
<Badge class="" variant="outline">
<Badge class="border-0 pl-0" variant="outline">
{l(data.lang, "root.availability_per_component")}
</Badge>
</div>
<div class="col-span-2 text-center md:col-span-1 md:text-right">
<Badge variant="outline">
<Badge variant="outline" class="border-0 pr-0">
<span class="bg-api-up mr-1 inline-flex h-[8px] w-[8px] rounded-full opacity-75"
></span>
<span class="mr-3">
@@ -105,7 +105,7 @@
</div>
</section>
<section
class="mx-auto mb-8 flex w-full max-w-[890px] flex-1 flex-col items-start justify-center backdrop-blur-[2px]"
class="mx-auto mb-8 flex w-full flex-1 flex-col items-start justify-center backdrop-blur-[2px] md:w-[655px]"
>
<Card.Root class="w-full">
<Card.Content class="monitors-card p-0">