mirror of
https://github.com/rajnandan1/kener.git
synced 2026-08-07 07:14:56 +00:00
Merge pull request #799 from lokiee0/feat/795-monitoring-status-filter
Feat/795 monitoring status filter
This commit is contained in:
@@ -12,16 +12,16 @@ env:
|
||||
DOCKERHUB_IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}
|
||||
GITHUB_IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
concurrency:
|
||||
group: main-docker-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push-main:
|
||||
name: Build and push main Docker images (with docs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [debian, alpine]
|
||||
concurrency:
|
||||
group: main-docker-${{ github.ref }}-${{ matrix.variant }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TimestampStatusCount } from "$lib/server/types/db";
|
||||
import GC, { PAGE_STATUS_MESSAGES, type StatusType } from "$lib/global-constants";
|
||||
import GC, { PAGE_STATUS_MESSAGES } from "$lib/global-constants";
|
||||
import type { StatusType } from "$lib/types/status";
|
||||
|
||||
function ParseLatency(latencyMs: number): string {
|
||||
if (!!!latencyMs) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isMonitoringStatus } from "./global-constants.js";
|
||||
|
||||
describe("isMonitoringStatus", () => {
|
||||
it("accepts only statuses supported by the monitoring filter", () => {
|
||||
expect(isMonitoringStatus("UP")).toBe(true);
|
||||
expect(isMonitoringStatus("DOWN")).toBe(true);
|
||||
expect(isMonitoringStatus("DEGRADED")).toBe(true);
|
||||
expect(isMonitoringStatus("MAINTENANCE")).toBe(false);
|
||||
expect(isMonitoringStatus("")).toBe(false);
|
||||
expect(isMonitoringStatus("invalid")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { MonitoringStatus } from "$lib/types/status.js";
|
||||
|
||||
// Badge styles supported by badge-maker
|
||||
export const BADGE_STYLES = ["flat", "plastic", "flat-square", "for-the-badge", "social"] as const;
|
||||
export type BadgeStyle = (typeof BADGE_STYLES)[number];
|
||||
|
||||
// Status types
|
||||
export type StatusType = "UP" | "DOWN" | "DEGRADED" | "MAINTENANCE" | "NO_DATA";
|
||||
export const MONITORING_STATUSES = ["UP", "DOWN", "DEGRADED"] as const satisfies readonly MonitoringStatus[];
|
||||
|
||||
export function isMonitoringStatus(value: unknown): value is MonitoringStatus {
|
||||
return typeof value === "string" && MONITORING_STATUSES.includes(value as MonitoringStatus);
|
||||
}
|
||||
|
||||
// Incident states
|
||||
export type IncidentState = "INVESTIGATING" | "IDENTIFIED" | "MONITORING" | "RESOLVED";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { json, error } from "@sveltejs/kit";
|
||||
import type { APIServerRequest } from "$lib/server/types/api-server";
|
||||
import db from "$lib/server/db/db";
|
||||
import { GetMinuteStartNowTimestampUTC } from "$lib/server/tool";
|
||||
import type { StatusType } from "$lib/global-constants";
|
||||
import type { StatusType } from "$lib/types/status";
|
||||
import type { TimestampStatusCount } from "$lib/server/types/db";
|
||||
import { buildMonitorBarResponse } from "./shared";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import db from "$lib/server/db/db";
|
||||
import GC, { type StatusType } from "$lib/global-constants";
|
||||
import GC from "$lib/global-constants";
|
||||
import type { StatusType } from "$lib/types/status";
|
||||
import type { MonitorRecord, TimestampStatusCount } from "$lib/server/types/db";
|
||||
import { UptimeCalculator } from "$lib/server/tool";
|
||||
import type { MonitorBarResponse } from "./get";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { json, error } from "@sveltejs/kit";
|
||||
import type { APIServerRequest } from "$lib/server/types/api-server";
|
||||
import db from "$lib/server/db/db";
|
||||
import { GetMinuteStartNowTimestampUTC } from "$lib/server/tool";
|
||||
import type { StatusType } from "$lib/global-constants";
|
||||
import type { StatusType } from "$lib/types/status";
|
||||
import GC from "$lib/global-constants";
|
||||
import type { MonitorBarResponse } from "$lib/server/api-server/monitor-bar/get";
|
||||
import { buildMonitorBarResponseFromRawData } from "$lib/server/api-server/monitor-bar/shared";
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { MonitorFilter } from "../db/repositories/base.js";
|
||||
import db from "../db/db.js";
|
||||
import type { PaginationInput } from "../../types/common.js";
|
||||
import GC, { getBadgeStyle, type BadgeStyle } from "../../global-constants.js";
|
||||
import type { MonitoringStatus } from "../../types/status.js";
|
||||
import { makeBadge } from "badge-maker";
|
||||
import { ErrorSvg } from "../../anywhere.js";
|
||||
import { GetLastMonitoringValue, SetLastHeartbeat, DeleteMonitorCaches } from "../cache/setGet.js";
|
||||
@@ -490,7 +491,7 @@ export const GetStatusCountsByInterval = async (
|
||||
export const GetMonitoringDataPaginated = async (
|
||||
page: number,
|
||||
limit: number,
|
||||
filter?: { monitor_tag?: string; start_time?: number; end_time?: number },
|
||||
filter?: { monitor_tag?: string; status?: MonitoringStatus; start_time?: number; end_time?: number },
|
||||
): Promise<{ data: MonitoringData[]; total: number }> => {
|
||||
const data = await db.getMonitoringDataPaginated(page, limit, filter);
|
||||
const countResult = await db.getMonitoringDataCount(filter);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Knex as KnexType } from "knex";
|
||||
import { BaseRepository } from "./base.js";
|
||||
import GC from "../../../global-constants.js";
|
||||
import type { MonitoringStatus } from "../../../types/status.js";
|
||||
import { GetMinuteStartNowTimestampUTC } from "../../tool.js";
|
||||
import type {
|
||||
MonitoringData,
|
||||
@@ -82,7 +83,7 @@ export class MonitoringRepository extends BaseRepository {
|
||||
async getMonitoringDataPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
filter?: { monitor_tag?: string; start_time?: number; end_time?: number },
|
||||
filter?: { monitor_tag?: string; status?: MonitoringStatus; start_time?: number; end_time?: number },
|
||||
): Promise<MonitoringData[]> {
|
||||
let query = this.knex("monitoring_data").select("*");
|
||||
|
||||
@@ -90,6 +91,10 @@ export class MonitoringRepository extends BaseRepository {
|
||||
query = query.where("monitor_tag", filter.monitor_tag);
|
||||
}
|
||||
|
||||
if (filter?.status) {
|
||||
query = query.where("status", filter.status);
|
||||
}
|
||||
|
||||
if (filter?.start_time) {
|
||||
query = query.where("timestamp", ">=", filter.start_time);
|
||||
}
|
||||
@@ -106,6 +111,7 @@ export class MonitoringRepository extends BaseRepository {
|
||||
|
||||
async getMonitoringDataCount(filter?: {
|
||||
monitor_tag?: string;
|
||||
status?: MonitoringStatus;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
}): Promise<{ count: number }> {
|
||||
@@ -115,6 +121,10 @@ export class MonitoringRepository extends BaseRepository {
|
||||
query = query.where("monitor_tag", filter.monitor_tag);
|
||||
}
|
||||
|
||||
if (filter?.status) {
|
||||
query = query.where("status", filter.status);
|
||||
}
|
||||
|
||||
if (filter?.start_time) {
|
||||
query = query.where("timestamp", ">=", filter.start_time);
|
||||
}
|
||||
@@ -476,7 +486,7 @@ export class MonitoringRepository extends BaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMonitorDataByTag(tag?: string, start?: number, end?: number): Promise<number> {
|
||||
async deleteMonitorDataByTag(tag?: string, start?: number, end?: number, status?: MonitoringStatus): Promise<number> {
|
||||
const query = this.knex("monitoring_data");
|
||||
if (tag) {
|
||||
query.where("monitor_tag", tag);
|
||||
@@ -487,6 +497,9 @@ export class MonitoringRepository extends BaseRepository {
|
||||
if (end !== undefined) {
|
||||
query.where("timestamp", "<=", end);
|
||||
}
|
||||
if (status) {
|
||||
query.where("status", status);
|
||||
}
|
||||
return await query.del();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "$lib/types/api";
|
||||
export * from "$lib/types/monitor";
|
||||
export * from "$lib/types/result";
|
||||
export * from "$lib/types/common";
|
||||
export * from "$lib/types/status";
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export type MonitoringStatus = "UP" | "DOWN" | "DEGRADED";
|
||||
export type StatusType = MonitoringStatus | "MAINTENANCE" | "NO_DATA";
|
||||
@@ -1,7 +1,8 @@
|
||||
import { GetMonitors, GetLatestMonitoringData, GetLatestStatusActiveAll } from "$lib/server/controllers/controller.js";
|
||||
import StatusColor, { type StatusColors } from "$lib/color.js";
|
||||
import { ErrorSvg } from "$lib/anywhere.js";
|
||||
import GC, { type StatusType } from "$lib/global-constants.js";
|
||||
import GC from "$lib/global-constants.js";
|
||||
import type { StatusType } from "$lib/types/status.js";
|
||||
import { GetLastMonitoringValue } from "$lib/server/cache/setGet.js";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { format } from "date-fns";
|
||||
import sharp from "sharp";
|
||||
import { nanoid } from "nanoid";
|
||||
import db from "$lib/server/db/db";
|
||||
import GC from "$lib/global-constants.js";
|
||||
import GC, { isMonitoringStatus } from "$lib/global-constants.js";
|
||||
import type { MonitoringStatus } from "$lib/types/status.js";
|
||||
import {
|
||||
CreateUpdateMonitor,
|
||||
UpdateMonitoringData,
|
||||
@@ -219,7 +220,14 @@ export async function POST({ request, cookies }) {
|
||||
} else if (action == "deleteMonitor") {
|
||||
resp = await DeleteMonitorCompletelyUsingTag(data.tag);
|
||||
} else if (action == "deleteMonitorData") {
|
||||
await db.deleteMonitorDataByTag(data.tag || undefined, data.start, data.end);
|
||||
let status: MonitoringStatus | undefined;
|
||||
if (data.status !== undefined && data.status !== null && data.status !== "ALL") {
|
||||
if (!isMonitoringStatus(data.status)) {
|
||||
return json({ error: "Invalid monitoring status" }, { status: 400 });
|
||||
}
|
||||
status = data.status;
|
||||
}
|
||||
await db.deleteMonitorDataByTag(data.tag || undefined, data.start, data.end, status);
|
||||
resp = { success: true };
|
||||
} else if (action == "cloneMonitor") {
|
||||
resp = await CloneMonitor({
|
||||
@@ -245,10 +253,16 @@ export async function POST({ request, cookies }) {
|
||||
} else if (action == "getMonitoringDataPaginated") {
|
||||
const page = parseInt(String(data.page)) || 1;
|
||||
const limit = parseInt(String(data.limit)) || 50;
|
||||
const filter: { monitor_tag?: string; start_time?: number; end_time?: number } = {};
|
||||
const filter: { monitor_tag?: string; status?: MonitoringStatus; start_time?: number; end_time?: number } = {};
|
||||
if (data.monitor_tag && data.monitor_tag !== "ALL") {
|
||||
filter.monitor_tag = data.monitor_tag;
|
||||
}
|
||||
if (data.status !== undefined && data.status !== null && data.status !== "ALL") {
|
||||
if (!isMonitoringStatus(data.status)) {
|
||||
return json({ error: "Invalid monitoring status" }, { status: 400 });
|
||||
}
|
||||
filter.status = data.status;
|
||||
}
|
||||
if (data.start_time) {
|
||||
filter.start_time = parseInt(String(data.start_time));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import { toast } from "svelte-sonner";
|
||||
import { resolve } from "$app/paths";
|
||||
import clientResolver from "$lib/client/resolver.js";
|
||||
import GC, { isMonitoringStatus } from "$lib/global-constants";
|
||||
import type { MonitoringStatus } from "$lib/types/status.js";
|
||||
|
||||
// Types
|
||||
interface MonitoringData {
|
||||
@@ -35,6 +37,8 @@
|
||||
name: string;
|
||||
}
|
||||
|
||||
type StatusFilter = "ALL" | MonitoringStatus;
|
||||
|
||||
// Helper to format datetime as YYYY-MM-DDTHH:mm for datetime-local input
|
||||
function formatDateTimeForInput(date: Date): string {
|
||||
return format(date, "yyyy-MM-dd'T'HH:mm");
|
||||
@@ -68,12 +72,14 @@
|
||||
let pageNo = $state(1);
|
||||
let showFilters = $state(false);
|
||||
let monitorTagFilter = $state("ALL");
|
||||
let statusFilter = $state<StatusFilter>("ALL");
|
||||
let startDateTime = $state(formatDateTimeForInput(yesterday));
|
||||
let endDateTime = $state(formatDateTimeForInput(now));
|
||||
const limit = 50;
|
||||
|
||||
const hasActiveFilters = $derived(
|
||||
monitorTagFilter !== "ALL" ||
|
||||
statusFilter !== "ALL" ||
|
||||
startDateTime !== formatDateTimeForInput(yesterday) ||
|
||||
endDateTime !== formatDateTimeForInput(now)
|
||||
);
|
||||
@@ -113,6 +119,7 @@
|
||||
|
||||
function clearFilters() {
|
||||
monitorTagFilter = "ALL";
|
||||
statusFilter = "ALL";
|
||||
startDateTime = formatDateTimeForInput(yesterday);
|
||||
endDateTime = formatDateTimeForInput(now);
|
||||
pageNo = 1;
|
||||
@@ -139,6 +146,7 @@
|
||||
action: "deleteMonitorData",
|
||||
data: {
|
||||
tag: monitorTagFilter === "ALL" ? "" : monitorTagFilter,
|
||||
status: statusFilter === "ALL" ? undefined : statusFilter,
|
||||
start: startTs,
|
||||
end: endTs
|
||||
}
|
||||
@@ -189,12 +197,14 @@
|
||||
page: number;
|
||||
limit: number;
|
||||
monitor_tag: string;
|
||||
status: StatusFilter;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
} = {
|
||||
page: pageNo,
|
||||
limit,
|
||||
monitor_tag: monitorTagFilter
|
||||
monitor_tag: monitorTagFilter,
|
||||
status: statusFilter
|
||||
};
|
||||
|
||||
if (startDateTime) {
|
||||
@@ -241,6 +251,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleStatusChange(value: string | undefined) {
|
||||
if (value === "ALL" || isMonitoringStatus(value)) {
|
||||
statusFilter = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
function goToPage(page: number) {
|
||||
pageNo = page;
|
||||
@@ -304,6 +320,20 @@
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-muted-foreground text-xs font-medium">Status</span>
|
||||
<Select.Root type="single" value={statusFilter} onValueChange={handleStatusChange}>
|
||||
<Select.Trigger class="w-36" aria-label="Status">
|
||||
{statusFilter === "ALL" ? "All Statuses" : statusFilter}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="ALL">All Statuses</Select.Item>
|
||||
<Select.Item value={GC.UP}>UP</Select.Item>
|
||||
<Select.Item value={GC.DOWN}>DOWN</Select.Item>
|
||||
<Select.Item value={GC.DEGRADED}>DEGRADED</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<Button size="sm" onclick={applyFilters}>
|
||||
<SearchIcon class="size-4" />
|
||||
Search
|
||||
@@ -437,11 +467,16 @@
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>Delete Monitoring Data</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
This will delete monitoring data for
|
||||
{#if monitorTagFilter === "ALL"}
|
||||
This will delete monitoring data for <strong>all monitors</strong> from {startDateTime} to {endDateTime}.
|
||||
<strong>all monitors</strong>
|
||||
{:else}
|
||||
This will delete monitoring data for <strong>{monitorTagFilter}</strong> from {startDateTime} to {endDateTime}.
|
||||
<strong>{monitorTagFilter}</strong>
|
||||
{/if}
|
||||
{#if statusFilter !== "ALL"}
|
||||
with status <strong>{statusFilter}</strong>
|
||||
{/if}
|
||||
from {startDateTime} to {endDateTime}.
|
||||
This action cannot be undone.
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
|
||||
Reference in New Issue
Block a user