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
},