better callbacks, fix config leak

pull/5/head
lyc8503 2023-12-09 14:59:59 +08:00
parent dfd78b9ca4
commit 9bdeec90f2
5 changed files with 133 additions and 54 deletions

View File

@ -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() {
</div>
<Group gap={5} visibleFrom="sm">
{config.page.links.map(linkToElement)}
{pageConfig.links.map(linkToElement)}
</Group>
<Group gap={5} hiddenFrom="sm">
{config.page.links.filter((link) => (link as any).highlight).map(linkToElement)}
{pageConfig.links.filter((link) => (link as any).highlight).map(linkToElement)}
</Group>
</Container>
</header>

View File

@ -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 (
<Center>
<Card
@ -15,7 +15,7 @@ export default function MonitorList({ config, state }: { config: any; state: Mon
withBorder
style={{ width: '865px' }}
>
{config.monitors.map((monitor: MonitorTarget) => (
{monitors.map((monitor: MonitorTarget) => (
<div key={monitor.id}>
<Card.Section ml="xs" mr="xs">
<MonitorDetail monitor={monitor} state={state} />

View File

@ -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 (
<>
<Head>
<title>{config.page.title}</title>
<title>{pageConfig.title}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
@ -33,7 +39,7 @@ export default function Home({ state }: { state: MonitorState }) {
) : (
<div>
<OverallStatus state={state} />
<MonitorList config={config} state={state} />
<MonitorList monitors={monitors} state={state} />
</div>
)}
@ -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 } }
}

View File

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

View File

@ -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)
}
}