mirror of
https://github.com/rajnandan1/kener.git
synced 2026-06-23 04:10:22 +00:00
Implement cache deletion functionality and update documentation
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Raj Nandan Sharma
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
Vendored
+5
@@ -13,6 +13,11 @@ export async function setCache<T>(key: string, value: T | null | undefined, ttlS
|
|||||||
await redis.set(getCacheKey(key), payload, "EX", ttl);
|
await redis.set(getCacheKey(key), payload, "EX", ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteCache(key: string): Promise<void> {
|
||||||
|
const redis = redisIOConnection();
|
||||||
|
await redis.del(getCacheKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCache<T>(
|
export async function getCache<T>(
|
||||||
key: string,
|
key: string,
|
||||||
fetcher?: () => Promise<T | null | undefined> | T | null | undefined,
|
fetcher?: () => Promise<T | null | undefined> | T | null | undefined,
|
||||||
|
|||||||
Vendored
+6
-1
@@ -1,5 +1,5 @@
|
|||||||
import type { MonitoringData } from "../types/db.js";
|
import type { MonitoringData } from "../types/db.js";
|
||||||
import { setCache, getCache } from "./cache.js";
|
import { setCache, getCache, deleteCache } from "./cache.js";
|
||||||
export async function SetLastMonitoringValue(tag: string, value: MonitoringData): Promise<void> {
|
export async function SetLastMonitoringValue(tag: string, value: MonitoringData): Promise<void> {
|
||||||
await setCache<MonitoringData>(tag + ":last_status", value, 86400); //set ttl to 1 day
|
await setCache<MonitoringData>(tag + ":last_status", value, 86400); //set ttl to 1 day
|
||||||
}
|
}
|
||||||
@@ -20,3 +20,8 @@ export async function SetLastHeartbeat(tag: string, timestamp: number): Promise<
|
|||||||
export async function GetLastHeartbeat(tag: string): Promise<{ timestamp: number } | null> {
|
export async function GetLastHeartbeat(tag: string): Promise<{ timestamp: number } | null> {
|
||||||
return await getCache<{ timestamp: number }>("last_heartbeat:" + tag, undefined, 45 * 86400);
|
return await getCache<{ timestamp: number }>("last_heartbeat:" + tag, undefined, 45 * 86400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function DeleteMonitorCaches(tag: string): Promise<void> {
|
||||||
|
await deleteCache(tag + ":last_status");
|
||||||
|
await deleteCache("last_heartbeat:" + tag);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import type { DayWiseStatus, NumberWithChange } from "../../types/monitor.js";
|
|||||||
import GC, { getBadgeStyle, type BadgeStyle } from "../../global-constants.js";
|
import GC, { getBadgeStyle, type BadgeStyle } from "../../global-constants.js";
|
||||||
import { makeBadge } from "badge-maker";
|
import { makeBadge } from "badge-maker";
|
||||||
import { ErrorSvg } from "../../anywhere.js";
|
import { ErrorSvg } from "../../anywhere.js";
|
||||||
import { GetLastMonitoringValue, SetLastHeartbeat } from "../cache/setGet.js";
|
import { GetLastMonitoringValue, SetLastHeartbeat, DeleteMonitorCaches } from "../cache/setGet.js";
|
||||||
import type { HeartbeatMonitor } from "../types/monitor.js";
|
import type { HeartbeatMonitor, GroupMonitorTypeData } from "../types/monitor.js";
|
||||||
|
|
||||||
interface GroupUpdateData {
|
interface GroupUpdateData {
|
||||||
monitor_tag: string;
|
monitor_tag: string;
|
||||||
@@ -376,12 +376,71 @@ export const RegisterHeartbeat = async (tag: string, secret: string): Promise<st
|
|||||||
throw new Error("Invalid heartbeat secret");
|
throw new Error("Invalid heartbeat secret");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a monitor tag from all GROUP monitors that reference it,
|
||||||
|
* rebalances weights equally, and deactivates groups left with < 2 members.
|
||||||
|
*/
|
||||||
|
async function removeTagFromGroupMonitors(tag: string): Promise<void> {
|
||||||
|
const groupMonitors = await GetMonitorsParsed({ monitor_type: "GROUP" });
|
||||||
|
|
||||||
|
for (const group of groupMonitors) {
|
||||||
|
const typeData = group.type_data as GroupMonitorTypeData;
|
||||||
|
if (!typeData.monitors || !Array.isArray(typeData.monitors)) continue;
|
||||||
|
|
||||||
|
const hasMember = typeData.monitors.some((m) => m.tag === tag);
|
||||||
|
if (!hasMember) continue;
|
||||||
|
|
||||||
|
// Remove the deleted tag
|
||||||
|
const remaining = typeData.monitors.filter((m) => m.tag !== tag);
|
||||||
|
|
||||||
|
// Rebalance weights equally across remaining monitors
|
||||||
|
if (remaining.length > 0) {
|
||||||
|
const weight = Math.round((1 / remaining.length) * 1000) / 1000;
|
||||||
|
for (let i = 0; i < remaining.length; i++) {
|
||||||
|
remaining[i].weight =
|
||||||
|
i === remaining.length - 1
|
||||||
|
? Math.round((1 - weight * (remaining.length - 1)) * 1000) / 1000
|
||||||
|
: weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeData.monitors = remaining;
|
||||||
|
|
||||||
|
const updateData: Record<string, unknown> = {
|
||||||
|
id: group.id,
|
||||||
|
tag: group.tag,
|
||||||
|
name: group.name,
|
||||||
|
description: group.description,
|
||||||
|
image: group.image,
|
||||||
|
cron: group.cron,
|
||||||
|
default_status: group.default_status,
|
||||||
|
status: remaining.length < 2 ? "INACTIVE" : group.status,
|
||||||
|
category_name: group.category_name,
|
||||||
|
monitor_type: group.monitor_type,
|
||||||
|
type_data: JSON.stringify(typeData),
|
||||||
|
day_degraded_minimum_count: group.day_degraded_minimum_count,
|
||||||
|
day_down_minimum_count: group.day_down_minimum_count,
|
||||||
|
include_degraded_in_downtime: group.include_degraded_in_downtime,
|
||||||
|
is_hidden: group.is_hidden,
|
||||||
|
monitor_settings_json:
|
||||||
|
typeof group.monitor_settings_json === "string"
|
||||||
|
? group.monitor_settings_json
|
||||||
|
: JSON.stringify(group.monitor_settings_json),
|
||||||
|
external_url: group.external_url,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.updateMonitor(updateData as unknown as MonitorRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const DeleteMonitorCompletelyUsingTag = async (tag: string): Promise<number> => {
|
export const DeleteMonitorCompletelyUsingTag = async (tag: string): Promise<number> => {
|
||||||
await db.deleteMonitorDataByTag(tag);
|
await db.deleteMonitorDataByTag(tag);
|
||||||
await db.deleteIncidentMonitorsByTag(tag);
|
await db.deleteIncidentMonitorsByTag(tag);
|
||||||
await db.deleteMonitorAlertsByTag(tag);
|
await db.deleteMonitorAlertsByTag(tag);
|
||||||
await db.deletePageMonitorsByTag(tag);
|
await db.deletePageMonitorsByTag(tag);
|
||||||
await db.deleteMaintenanceMonitorsByTag(tag);
|
await db.deleteMaintenanceMonitorsByTag(tag);
|
||||||
|
await removeTagFromGroupMonitors(tag);
|
||||||
|
await DeleteMonitorCaches(tag);
|
||||||
return await db.deleteMonitorsByTag(tag);
|
return await db.deleteMonitorsByTag(tag);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -283,25 +283,22 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="bg-background min-h-screen bg-(image:--docs-home-hero-gradient) px-6 py-20 md:py-24">
|
<section class="bg-background min-h-screen bg-(image:--docs-home-hero-gradient) px-6 py-20 md:py-24">
|
||||||
<div class="mx-auto mt-20 grid max-w-[1200px] items-center gap-10 lg:grid-cols-2">
|
<div class="mx-auto mt-20 grid max-w-[1200px] items-center gap-10">
|
||||||
<div class="text-center lg:text-left">
|
<div class="text-center">
|
||||||
<Badge variant="secondary" class="mb-6 inline-flex items-center gap-2 px-3 py-1 text-xs">
|
<Badge variant="outline" class="mb-6 inline-flex items-center gap-2 px-3 py-1 text-xs">
|
||||||
<Shield class="h-3.5 w-3.5" />
|
<Shield class="h-3.5 w-3.5" />
|
||||||
Production-ready status page platform
|
Production-ready status page platform
|
||||||
</Badge>
|
</Badge>
|
||||||
<h1 class="text-foreground mb-6 text-2xl leading-tight font-bold tracking-tight md:text-4xl">
|
<h1 class="text-foreground mb-6 text-2xl leading-tight font-bold tracking-tight md:text-4xl">
|
||||||
Build trust with
|
Build stunning status pages with
|
||||||
<span class="from-primary to-accent-foreground bg-linear-to-r bg-clip-text text-transparent"
|
<span class="from-primary to-accent-foreground bg-linear-to-r bg-clip-text text-transparent"> Kener </span>
|
||||||
>{data.config.name}</span
|
|
||||||
>
|
|
||||||
documentation that actually gets used
|
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted-foreground mb-8 max-w-[760px] text-sm leading-relaxed md:text-base">
|
<p class="text-muted-foreground mx-auto mb-8 max-w-[760px] text-sm leading-relaxed md:text-base">
|
||||||
From quick setup to advanced operations, Kener gives you open-source monitoring, incident workflows,
|
From quick setup to advanced operations, Kener gives you open-source monitoring, incident workflows,
|
||||||
notifications, maintenance scheduling, embeds, and automation APIs—all in one modern platform.
|
notifications, maintenance scheduling, embeds, and automation APIs—all in one modern platform.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mb-8 flex flex-wrap justify-center gap-3 md:gap-4 lg:justify-start">
|
<div class="mb-8 flex flex-wrap justify-center gap-3 md:gap-4 lg:justify-center">
|
||||||
{#each getCtaButtons() as button (button.title)}
|
{#each getCtaButtons() as button (button.title)}
|
||||||
<Button
|
<Button
|
||||||
href={getHref(button.href)}
|
href={getHref(button.href)}
|
||||||
|
|||||||
@@ -4,53 +4,9 @@ description: Kener is a feature-rich and modern status page system built with Sv
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="/newbg.png" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
|
<img src="/og.jpg" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="flex space-x-2 justify-center">
|
|
||||||
<a href="https://github.com/rajnandan1/kener/stargazers" >
|
|
||||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/rajnandan1/kener?label=Star%20Repo&
|
|
||||||
style=social">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/ivbeg/awesome-status-pages" >
|
|
||||||
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome status page" />
|
|
||||||
</a>
|
|
||||||
<a href="https://hub.docker.com/r/rajnandan1/kener" >
|
|
||||||
<img src="https://img.shields.io/docker/pulls/rajnandan1/kener" alt="Docker Kener" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-4 justify-center">
|
|
||||||
<picture>
|
|
||||||
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f38a/512.webp" type="image/webp">
|
|
||||||
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f38a/512.gif" alt="🎊" width="32" height="32">
|
|
||||||
</picture>
|
|
||||||
<picture>
|
|
||||||
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f514/512.webp" type="image/webp">
|
|
||||||
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f514/512.gif" alt="🔔" width="32" height="32">
|
|
||||||
</picture>
|
|
||||||
<picture>
|
|
||||||
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/2049_fe0f/512.webp" type="image/webp">
|
|
||||||
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/2049_fe0f/512.gif" alt="⁉" width="32" height="32">
|
|
||||||
</picture>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 kener-home-links">
|
|
||||||
<a href="https://kener.ing" class="flex-1 border rounded-md py-4 px-2 text-center">
|
|
||||||
Live Demo
|
|
||||||
</a>
|
|
||||||
<a href="https://kener.ing/docs/quick-start" class="flex-1 border rounded-md py-4 px-2 text-center">
|
|
||||||
Quick Start
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/rajnandan1/kener" class="flex-1 border rounded-md py-4 px-2 text-center">
|
|
||||||
Clone
|
|
||||||
</a>
|
|
||||||
<a href="https://kener.ing/docs/deployment" class="flex-1 border rounded-md py-4 px-2 text-center">
|
|
||||||
Deploy
|
|
||||||
</a>
|
|
||||||
<a href="https://kener.ing/docs/kener-apis" class="flex-1 border rounded-md py-4 px-2 text-center">
|
|
||||||
APIs
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Forget Boring Status Pages. Hello Kener!
|
## Forget Boring Status Pages. Hello Kener!
|
||||||
|
|
||||||
**Kener** isn't just another status page – it's a beautifully crafted, lightning-fast monitoring system that makes your services look good even when they're down. Built with modern tech (**SvelteKit** + **NodeJS**), it delivers enterprise-grade reliability monitoring without the enterprise price tag (it's completely free!).
|
**Kener** isn't just another status page – it's a beautifully crafted, lightning-fast monitoring system that makes your services look good even when they're down. Built with modern tech (**SvelteKit** + **NodeJS**), it delivers enterprise-grade reliability monitoring without the enterprise price tag (it's completely free!).
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 85 KiB |
Reference in New Issue
Block a user