diff --git a/components/Header.tsx b/components/Header.tsx index a1c3d51..2b1be33 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -1,6 +1,6 @@ import { Container, Group, Text } from '@mantine/core' import classes from '@/styles/Header.module.css' -import config from '@/uptime.config' +import { pageConfig } from '@/uptime.config' export default function Header() { const linkToElement = (link: { label: string; link: string; highlight?: boolean }) => { @@ -38,11 +38,11 @@ export default function Header() { - {config.page.links.map(linkToElement)} + {pageConfig.links.map(linkToElement)} - {config.page.links.filter((link) => (link as any).highlight).map(linkToElement)} + {pageConfig.links.filter((link) => (link as any).highlight).map(linkToElement)} diff --git a/components/MonitorList.tsx b/components/MonitorList.tsx index 9070886..fec88f9 100644 --- a/components/MonitorList.tsx +++ b/components/MonitorList.tsx @@ -2,7 +2,7 @@ import { MonitorState, MonitorTarget } from '@/uptime.types' import { Card, Center, Divider } from '@mantine/core' import MonitorDetail from './MonitorDetail' -export default function MonitorList({ config, state }: { config: any; state: MonitorState }) { +export default function MonitorList({ monitors, state }: { monitors: any; state: MonitorState }) { return (
- {config.monitors.map((monitor: MonitorTarget) => ( + {monitors.map((monitor: MonitorTarget) => (
diff --git a/pages/index.tsx b/pages/index.tsx index b2be9b0..587e290 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,9 +1,9 @@ import Head from 'next/head' import { Inter } from 'next/font/google' -import { MonitorState } from '@/uptime.types' +import { MonitorState, MonitorTarget } from '@/uptime.types' import { KVNamespace } from '@cloudflare/workers-types' -import config from '@/uptime.config' +import { pageConfig, workerConfig } from '@/uptime.config' import OverallStatus from '@/components/OverallStatus' import Header from '@/components/Header' import MonitorList from '@/components/MonitorList' @@ -12,11 +12,17 @@ import { Center, Divider, Text } from '@mantine/core' export const runtime = 'experimental-edge' const inter = Inter({ subsets: ['latin'] }) -export default function Home({ state }: { state: MonitorState }) { +export default function Home({ + state, + monitors, +}: { + state: MonitorState + monitors: MonitorTarget[] +}) { return ( <> - {config.page.title} + {pageConfig.title} @@ -33,7 +39,7 @@ export default function Home({ state }: { state: MonitorState }) { ) : (
- +
)} @@ -66,5 +72,14 @@ export async function getServerSideProps() { } const state = (await UPTIMEFLARE_STATE?.get('state', 'json')) as unknown as MonitorState - return { props: { state } } + // Only present these values to client + const monitors = workerConfig.monitors.map((monitor) => { + return { + id: monitor.id, + name: monitor.name, + tooltip: monitor.tooltip, + } + }) + + return { props: { state, monitors } } } diff --git a/uptime.config.ts b/uptime.config.ts index 8b90e6c..8d770cd 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -1,26 +1,15 @@ -const config = { - // Your locale for server-side callback. (client-side will always follow browser settings) - dateLocale: 'zh-CN', - // Your timezone for server-side callback. (client-side will always follow browser settings) - timezone: 'Asia/Shanghai', - page: { - // Title for your status page - title: "lyc8503's Status Page", - // Links shown at the header of your status page, could set `highlight` to `true` - links: [ - { link: 'https://github.com/lyc8503', label: 'GitHub' }, - { link: 'https://blog.lyc8503.site/', label: 'Blog' }, - { link: 'mailto:me@lyc8503.site', label: 'Email Me', highlight: true }, - ], - }, - callback: async (statusChangeMsg: string) => { - // Write any typescript here - // Example `statusChangeMsg` string: - // "❌My Blog went down at 2023/11/18 14:08:59 with error Timeout after 10000ms" - // "✔️My Blog came back up at 2023/11/18 14:10:48 after 2 minutes of downtime" - // Example: - // await fetch('https://api.example.com/callback?msg=' + statusChangeMsg) - }, +const 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` + links: [ + { link: 'https://github.com/lyc8503', label: 'GitHub' }, + { link: 'https://blog.lyc8503.site/', label: 'Blog' }, + { link: 'mailto:me@lyc8503.site', label: 'Email Me', highlight: true }, + ], +} + +const workerConfig = { // Define all your monitors here monitors: [ // Example HTTP Monitor @@ -50,7 +39,7 @@ const config = { responseKeyword: 'success', // [OPTIONAL] if specified, the check will run in your specified region, // refer to docs https://github.com/lyc8503/UptimeFlare/wiki/Geo-specific-checks-setup before setting this value - checkLocationWorkerRoute: 'https://xxx.example.com' + checkLocationWorkerRoute: 'https://xxx.example.com', }, // Example TCP Monitor { @@ -64,7 +53,43 @@ const config = { timeout: 5000, }, ], + callbacks: { + onStatusChange: async ( + id: string, + name: string, + isUp: boolean, + timeIncidentStart: number, + timeNow: number, + reason: string + ) => { + // This callback will be called when any monitor's status changed + // Write any Typescript code here + // Example implementation: + // const timeString = new Date(timeNow * 1000).toLocaleString('zh-CN', { + // timeZone: 'Asia/Shanghai', + // }) + // let statusChangeMsg + // if (isUp) { + // statusChangeMsg = `✔️${name} came back up at ${timeString} after ${Math.round( + // (timeNow - timeIncidentStart) / 60 + // )} minutes of downtime` + // } else { + // statusChangeMsg = `❌${name} was down at ${timeString} with error ${reason}` + // } + // await fetch('https://api.example.com/callback?msg=' + statusChangeMsg) + }, + onIncident: async ( + id: string, + name: string, + timeIncidentStart: number, + timeNow: number, + currentError: string + ) => { + // This callback will be called EVERY 2 MINTUES if there's an on-going incident for any monitor + // Write any Typescript code here + }, + }, } // Don't forget this, otherwise compilation fails. -export default config +export { pageConfig, workerConfig } diff --git a/worker/src/index.ts b/worker/src/index.ts index e8f38ea..48fdc8d 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -1,4 +1,4 @@ -import config from '../../uptime.config' +import { workerConfig } from '../../uptime.config' import { getWorkerLocation } from './util' import { MonitorState } from '../../uptime.types' import { getStatus } from './monitor' @@ -17,7 +17,7 @@ export default { } const targetId = (await request.json<{ target: string }>())['target'] - const target = config.monitors.find((m) => m.id === targetId) + const target = workerConfig.monitors.find((m) => m.id === targetId) if (target === undefined) { return new Response('Target Not Found', { status: 404 }) @@ -59,9 +59,8 @@ export default { state.overallUp = 0 // Check each monitor - // TODO: callback exception handler // TODO: concurrent status check - for (const monitor of config.monitors) { + for (const monitor of workerConfig.monitors) { console.log(`[${workerLocation}] Checking ${monitor.name}...`) let checkLocation = workerLocation @@ -97,7 +96,6 @@ export default { status.up ? state.overallUp++ : state.overallDown++ // Update incidents - // Create a dummy incident to store the start time of the monitoring and simplify logic state.incident[monitor.id] = state.incident[monitor.id] || [ { @@ -108,20 +106,26 @@ export default { ] // Then lastIncident here must not be undefined const lastIncident = state.incident[monitor.id].slice(-1)[0] - const timeString = new Date().toLocaleString(config.dateLocale, { - timeZone: config.timezone, - }) if (status.up) { // Current status is up // close existing incident if any if (lastIncident.end === undefined) { lastIncident.end = currentTimeSecond - await config.callback( - `✔️${monitor.name} came back up at ${timeString} after ${Math.round( - (lastIncident.end - lastIncident.start[0]) / 60 - )} minutes of downtime` - ) + + try { + await workerConfig.callbacks.onStatusChange( + monitor.id, + monitor.name, + true, + lastIncident.start[0], + currentTimeSecond, + 'OK' + ) + } catch (e) { + console.log('Error calling callback: ') + console.log(e) + } } } else { // Current status is down @@ -132,9 +136,20 @@ export default { end: undefined, error: [status.err], }) - await config.callback( - `❌${monitor.name} went down at ${timeString} with error ${status.err}` - ) + + try { + await workerConfig.callbacks.onStatusChange( + monitor.id, + monitor.name, + false, + currentTimeSecond, + currentTimeSecond, + status.err + ) + } catch (e) { + console.log('Error calling callback: ') + console.log(e) + } } else if ( lastIncident.end === undefined && lastIncident.error.slice(-1)[0] !== status.err @@ -142,9 +157,33 @@ export default { // append if the error message changes lastIncident.start.push(currentTimeSecond) lastIncident.error.push(status.err) - await config.callback( - `❌${monitor.name} is still down at ${timeString} with error ${status.err}` + + try { + await workerConfig.callbacks.onStatusChange( + monitor.id, + monitor.name, + false, + lastIncident.start[0], + currentTimeSecond, + status.err + ) + } catch (e) { + console.log('Error calling callback: ') + console.log(e) + } + } + + try { + await workerConfig.callbacks.onIncident( + monitor.id, + monitor.name, + lastIncident.start[0], + currentTimeSecond, + status.err ) + } catch (e) { + console.log('Error calling callback: ') + console.log(e) } }