From 5551bb554a2803d3c90cb011b94024fb17822d9c Mon Sep 17 00:00:00 2001 From: Bennet Gallein Date: Fri, 18 Apr 2025 18:13:04 +0200 Subject: [PATCH] feat: move types --- components/DetailBar.tsx | 35 ++++++++--- components/DetailChart.tsx | 2 +- components/Header.tsx | 7 ++- pages/api/data.ts | 23 ++++--- types/config.ts | 80 ++++++++++++++++++++++++ uptime.types.ts => types/uptime.types.ts | 20 +----- uptime.config.ts | 17 ++--- 7 files changed, 135 insertions(+), 49 deletions(-) create mode 100644 types/config.ts rename uptime.types.ts => types/uptime.types.ts (55%) diff --git a/components/DetailBar.tsx b/components/DetailBar.tsx index d6c33fc..f33d730 100644 --- a/components/DetailBar.tsx +++ b/components/DetailBar.tsx @@ -1,4 +1,4 @@ -import { MonitorState, MonitorTarget } from '@/uptime.types' +import { MonitorState, MonitorTarget } from '@/types/uptime.types' import { getColor } from '@/util/color' import { Box, Tooltip, Modal } from '@mantine/core' import { useResizeObserver } from '@mantine/hooks' @@ -50,13 +50,20 @@ export default function DetailBar({ if (overlap > 0) { for (let i = 0; i < incident.error.length; i++) { let partStart = incident.start[i] - let partEnd = i === incident.error.length - 1 ? (incident.end ?? currentTime) : incident.start[i + 1] + let partEnd = + i === incident.error.length - 1 ? incident.end ?? currentTime : incident.start[i + 1] partStart = Math.max(partStart, dayStart) partEnd = Math.min(partEnd, dayEnd) if (overlapLen(dayStart, dayEnd, partStart, partEnd) > 0) { - const startStr = new Date(partStart * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) - const endStr = new Date(partEnd * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) + const startStr = new Date(partStart * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }) + const endStr = new Date(partEnd * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }) incidentReasons.push(`[${startStr}-${endStr}] ${incident.error[i]}`) } } @@ -77,7 +84,10 @@ export default function DetailBar({ <>
{dayPercent + '% at ' + new Date(dayStart * 1000).toLocaleDateString()}
{dayDownTime > 0 && ( -
{`Down for ${moment.preciseDiff(moment(0), moment(dayDownTime * 1000))} (click for detail)`}
+
{`Down for ${moment.preciseDiff( + moment(0), + moment(dayDownTime * 1000) + )} (click for detail)`}
)} ) @@ -94,10 +104,14 @@ export default function DetailBar({ }} onClick={() => { if (dayDownTime > 0) { - setModalTitle(`🚨 ${monitor.name} incidents at ${new Date(dayStart * 1000).toLocaleDateString()}`) + setModalTitle( + `🚨 ${monitor.name} incidents at ${new Date(dayStart * 1000).toLocaleDateString()}` + ) setModelContent( <> - {incidentReasons.map((reason, index) => (
{reason}
))} + {incidentReasons.map((reason, index) => ( +
{reason}
+ ))} ) setModalOpened(true) @@ -110,7 +124,12 @@ export default function DetailBar({ return ( <> - setModalOpened(false)} title={modalTitle} size={'40em'}> + setModalOpened(false)} + title={modalTitle} + size={'40em'} + > {modelContent} { + const linkToElement = (link: PageConfigLink) => { return ( - {pageConfig.links.map(linkToElement)} + {pageConfig.links?.map(linkToElement)} - {pageConfig.links.filter((link) => (link as any).highlight).map(linkToElement)} + {pageConfig.links?.filter((link) => link.highlight).map(linkToElement)} diff --git a/pages/api/data.ts b/pages/api/data.ts index 6186e02..b2d29b5 100644 --- a/pages/api/data.ts +++ b/pages/api/data.ts @@ -1,11 +1,10 @@ -import { workerConfig } from "@/uptime.config" -import { MonitorState } from "@/uptime.types" -import { NextRequest } from "next/server" +import { workerConfig } from '@/uptime.config' +import { MonitorState } from '@/types/uptime.types' +import { NextRequest } from 'next/server' export const runtime = 'edge' export default async function handler(req: NextRequest): Promise { - const { UPTIMEFLARE_STATE } = process.env as unknown as { UPTIMEFLARE_STATE: KVNamespace } @@ -15,11 +14,11 @@ export default async function handler(req: NextRequest): Promise { return new Response(JSON.stringify({ error: 'No data available' }), { status: 500, headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }) } - const state = (JSON.parse(stateStr)) as unknown as MonitorState + const state = JSON.parse(stateStr) as unknown as MonitorState let monitors: any = {} @@ -29,7 +28,7 @@ export default async function handler(req: NextRequest): Promise { up: isUp, latency: state.latency[monitor.id].recent.slice(-1)[0].ping, location: state.latency[monitor.id].recent.slice(-1)[0].loc, - message: isUp ? "OK" : state.incident[monitor.id].slice(-1)[0].error.slice(-1)[0] + message: isUp ? 'OK' : state.incident[monitor.id].slice(-1)[0].error.slice(-1)[0], } } @@ -37,12 +36,12 @@ export default async function handler(req: NextRequest): Promise { up: state.overallUp, down: state.overallDown, updatedAt: state.lastUpdate, - monitors: monitors + monitors: monitors, } return new Response(JSON.stringify(ret), { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }) -} \ No newline at end of file +} diff --git a/types/config.ts b/types/config.ts new file mode 100644 index 0000000..01644ea --- /dev/null +++ b/types/config.ts @@ -0,0 +1,80 @@ +export type PageConfig = { + title?: string + links?: PageConfigLink[] + group?: PageConfigGroup + maintenances?: Maintenances[] +} + +export type Maintenances = { + // title to display + title: string + // array of monitor ids + monitors?: string[] + // body message + body?: string + // start date + start?: Date + // end Date + end?: Date + // display color + color?: string +} + +export type PageConfigGroup = { [key: string]: string[] } + +export type PageConfigLink = { + link: string + label: string + highlight?: boolean +} + +export type WorkerConfig = { + kvWriteCooldownMinutes: number + monitors: Monitor[] + notification?: Notification + callbacks?: Callbacks +} + +export type Monitor = { + id: string + name: string + method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'TCP_PING' + target: string + tooltip?: string + statusPageLink?: string + hideLatencyChart?: boolean + expectedCodes?: number[] + timeout?: number + headers?: { [key: string]: string | number } + body?: string + responseKeyword?: string + responseForbiddenKeyword?: string + checkProxy?: string + checkProxyFallback?: boolean +} + +export type Notification = { + appriseApiServer?: string + recipientUrl?: string + timeZone?: string + gracePeriod?: number + skipNotificationIds?: string[] +} + +export type Callbacks = { + onStatusChange: ( + env: any, + monitor: any, + isUp: boolean, + timeIncidentStart: number, + timeNow: number, + reason: string + ) => Promise + onIncident: ( + env: any, + monitor: any, + timeIncidentStart: number, + timeNow: number, + reason: string + ) => Promise +} diff --git a/uptime.types.ts b/types/uptime.types.ts similarity index 55% rename from uptime.types.ts rename to types/uptime.types.ts index f287622..dcb2594 100644 --- a/uptime.types.ts +++ b/types/uptime.types.ts @@ -1,3 +1,5 @@ +import { Monitor } from './config' + type MonitorState = { lastUpdate: number overallUp: number @@ -28,24 +30,8 @@ type MonitorState = { > } -type MonitorTarget = { - id: string - name: string - method: string // "TCP_PING" or Http Method (e.g. GET, POST, OPTIONS, etc.) - target: string // url for http, hostname:port for tcp - tooltip?: string - statusPageLink?: string - hideLatencyChart?: boolean - checkProxy?: string - checkProxyFallback?: boolean - - // HTTP Code - expectedCodes?: number[] - timeout?: number - headers?: Record +type MonitorTarget = Monitor & { body?: BodyInit - responseKeyword?: string - responseForbiddenKeyword?: string } export type { MonitorState, MonitorTarget } diff --git a/uptime.config.ts b/uptime.config.ts index b1bdea8..2ccaeb7 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -1,4 +1,6 @@ -const pageConfig = { +import { PageConfig, WorkerConfig } from './types/config' + +const pageConfig: PageConfig = { // Title for your status page title: "lyc8503's Status Page", // Links shown at the header of your status page, could set `highlight` to `true` @@ -11,12 +13,12 @@ const pageConfig = { // If not specified, all monitors will be shown in a single list // If specified, monitors will be grouped and ordered, not-listed monitors will be invisble (but still monitored) group: { - "🌐 Public (example group name)": ['foo_monitor', 'bar_monitor', 'more monitor ids...'], - "🔐 Private": ['test_tcp_monitor'], + '🌐 Public (example group name)': ['foo_monitor', 'bar_monitor', 'more monitor ids...'], + '🔐 Private': ['test_tcp_monitor'], }, } -const workerConfig = { +const workerConfig: WorkerConfig = { // Write KV at most every 3 minutes unless the status changed kvWriteCooldownMinutes: 3, // Enable HTTP Basic auth for status page & API by uncommenting the line below, format `:` @@ -77,12 +79,12 @@ const workerConfig = { notification: { // [Optional] apprise API server URL // if not specified, no notification will be sent - appriseApiServer: "https://apprise.example.com/notify", + appriseApiServer: 'https://apprise.example.com/notify', // [Optional] recipient URL for apprise, refer to https://github.com/caronc/apprise // if not specified, no notification will be sent - recipientUrl: "tgram://bottoken/ChatID", + recipientUrl: 'tgram://bottoken/ChatID', // [Optional] timezone used in notification messages, default to "Etc/GMT" - timeZone: "Asia/Shanghai", + timeZone: 'Asia/Shanghai', // [Optional] grace period in minutes before sending a notification // notification will be sent only if the monitor is down for N continuous checks after the initial failure // if not specified, notification will be sent immediately @@ -101,7 +103,6 @@ const workerConfig = { ) => { // This callback will be called when there's a status change for any monitor // Write any Typescript code here - // This will not follow the grace period settings and will be called immediately when the status changes // You need to handle the grace period manually if you want to implement it },