mirror of
https://github.com/rajnandan1/kener.git
synced 2026-05-25 13:48:42 -05: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);
|
||||
}
|
||||
|
||||
export async function deleteCache(key: string): Promise<void> {
|
||||
const redis = redisIOConnection();
|
||||
await redis.del(getCacheKey(key));
|
||||
}
|
||||
|
||||
export async function getCache<T>(
|
||||
key: string,
|
||||
fetcher?: () => Promise<T | null | undefined> | T | null | undefined,
|
||||
|
||||
Vendored
+6
-1
@@ -1,5 +1,5 @@
|
||||
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> {
|
||||
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> {
|
||||
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 { makeBadge } from "badge-maker";
|
||||
import { ErrorSvg } from "../../anywhere.js";
|
||||
import { GetLastMonitoringValue, SetLastHeartbeat } from "../cache/setGet.js";
|
||||
import type { HeartbeatMonitor } from "../types/monitor.js";
|
||||
import { GetLastMonitoringValue, SetLastHeartbeat, DeleteMonitorCaches } from "../cache/setGet.js";
|
||||
import type { HeartbeatMonitor, GroupMonitorTypeData } from "../types/monitor.js";
|
||||
|
||||
interface GroupUpdateData {
|
||||
monitor_tag: string;
|
||||
@@ -376,12 +376,71 @@ export const RegisterHeartbeat = async (tag: string, secret: string): Promise<st
|
||||
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> => {
|
||||
await db.deleteMonitorDataByTag(tag);
|
||||
await db.deleteIncidentMonitorsByTag(tag);
|
||||
await db.deleteMonitorAlertsByTag(tag);
|
||||
await db.deletePageMonitorsByTag(tag);
|
||||
await db.deleteMaintenanceMonitorsByTag(tag);
|
||||
await removeTagFromGroupMonitors(tag);
|
||||
await DeleteMonitorCaches(tag);
|
||||
return await db.deleteMonitorsByTag(tag);
|
||||
};
|
||||
|
||||
|
||||
@@ -283,25 +283,22 @@
|
||||
</header>
|
||||
|
||||
<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="text-center lg:text-left">
|
||||
<Badge variant="secondary" class="mb-6 inline-flex items-center gap-2 px-3 py-1 text-xs">
|
||||
<div class="mx-auto mt-20 grid max-w-[1200px] items-center gap-10">
|
||||
<div class="text-center">
|
||||
<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" />
|
||||
Production-ready status page platform
|
||||
</Badge>
|
||||
<h1 class="text-foreground mb-6 text-2xl leading-tight font-bold tracking-tight md:text-4xl">
|
||||
Build trust with
|
||||
<span class="from-primary to-accent-foreground bg-linear-to-r bg-clip-text text-transparent"
|
||||
>{data.config.name}</span
|
||||
>
|
||||
documentation that actually gets used
|
||||
Build stunning status pages with
|
||||
<span class="from-primary to-accent-foreground bg-linear-to-r bg-clip-text text-transparent"> Kener </span>
|
||||
</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,
|
||||
notifications, maintenance scheduling, embeds, and automation APIs—all in one modern platform.
|
||||
</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)}
|
||||
<Button
|
||||
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">
|
||||
<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 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!
|
||||
|
||||
**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