fix: Centralize and validate the monitoring status-filter contract

This commit is contained in:
Logesh K
2026-07-25 00:34:14 +05:30
parent 067d4526a5
commit b0a4279273
6 changed files with 36 additions and 14 deletions
+12
View File
@@ -0,0 +1,12 @@
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("invalid")).toBe(false);
});
});
+7 -1
View File
@@ -3,7 +3,13 @@ export const BADGE_STYLES = ["flat", "plastic", "flat-square", "for-the-badge",
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;
export type MonitoringStatus = (typeof MONITORING_STATUSES)[number];
export type StatusType = MonitoringStatus | "MAINTENANCE" | "NO_DATA";
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";
@@ -21,7 +21,7 @@ import type {
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 GC, { getBadgeStyle, type BadgeStyle, type MonitoringStatus } from "../../global-constants.js";
import { makeBadge } from "badge-maker";
import { ErrorSvg } from "../../anywhere.js";
import { GetLastMonitoringValue, SetLastHeartbeat, DeleteMonitorCaches } from "../cache/setGet.js";
@@ -490,7 +490,7 @@ export const GetStatusCountsByInterval = async (
export const GetMonitoringDataPaginated = async (
page: number,
limit: number,
filter?: { monitor_tag?: string; status?: 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);
+3 -3
View File
@@ -1,6 +1,6 @@
import type { Knex as KnexType } from "knex";
import { BaseRepository } from "./base.js";
import GC from "../../../global-constants.js";
import GC, { type MonitoringStatus } from "../../../global-constants.js";
import { GetMinuteStartNowTimestampUTC } from "../../tool.js";
import type {
MonitoringData,
@@ -82,7 +82,7 @@ export class MonitoringRepository extends BaseRepository {
async getMonitoringDataPaginated(
page: number,
limit: number,
filter?: { monitor_tag?: string; status?: 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("*");
@@ -110,7 +110,7 @@ export class MonitoringRepository extends BaseRepository {
async getMonitoringDataCount(filter?: {
monitor_tag?: string;
status?: string;
status?: MonitoringStatus;
start_time?: number;
end_time?: number;
}): Promise<{ count: number }> {
+5 -2
View File
@@ -4,7 +4,7 @@ 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, type MonitoringStatus } from "$lib/global-constants.js";
import {
CreateUpdateMonitor,
UpdateMonitoringData,
@@ -245,11 +245,14 @@ 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; status?: 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 && data.status !== "ALL") {
if (!isMonitoringStatus(data.status)) {
return json({ error: "Invalid monitoring status" }, { status: 400 });
}
filter.status = data.status;
}
if (data.start_time) {
@@ -19,6 +19,7 @@
import { toast } from "svelte-sonner";
import { resolve } from "$app/paths";
import clientResolver from "$lib/client/resolver.js";
import GC, { isMonitoringStatus, type MonitoringStatus } from "$lib/global-constants.js";
// Types
interface MonitoringData {
@@ -35,7 +36,7 @@
name: string;
}
type StatusFilter = "ALL" | "UP" | "DOWN" | "DEGRADED";
type StatusFilter = "ALL" | MonitoringStatus;
// Helper to format datetime as YYYY-MM-DDTHH:mm for datetime-local input
function formatDateTimeForInput(date: Date): string {
@@ -249,8 +250,8 @@
}
function handleStatusChange(value: string | undefined) {
if (value) {
statusFilter = value as StatusFilter;
if (value === "ALL" || isMonitoringStatus(value)) {
statusFilter = value;
}
}
@@ -325,9 +326,9 @@
</Select.Trigger>
<Select.Content>
<Select.Item value="ALL">All Statuses</Select.Item>
<Select.Item value="UP">UP</Select.Item>
<Select.Item value="DOWN">DOWN</Select.Item>
<Select.Item value="DEGRADED">DEGRADED</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>