diff --git a/components/Header.tsx b/components/Header.tsx index 4504e38..2e6a149 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 { pageConfig } from '@/config' +import { pageConfig } from '@/uptime.config' import { PageConfigLink } from '@/types/config' export default function Header() { diff --git a/components/MonitorList.tsx b/components/MonitorList.tsx index 71af365..ac33ea5 100644 --- a/components/MonitorList.tsx +++ b/components/MonitorList.tsx @@ -1,7 +1,7 @@ -import { MonitorState, MonitorTarget } from '@/types/uptime.types' +import { MonitorState, MonitorTarget } from '@/uptime.types' import { Accordion, Card, Center, Text } from '@mantine/core' import MonitorDetail from './MonitorDetail' -import { pageConfig } from '@/config' +import { pageConfig } from '@/uptime.config'; function countDownCount(state: MonitorState, ids: string[]) { let downCount = 0 @@ -28,61 +28,45 @@ function getStatusTextColor(state: MonitorState, ids: string[]) { } } -export default function MonitorList({ - monitors, - state, -}: { - monitors: MonitorTarget[] - state: MonitorState -}) { +export default function MonitorList({ monitors, state }: { monitors: MonitorTarget[]; state: MonitorState }) { // @ts-ignore let group: any = pageConfig.group let groupedMonitor = group && Object.keys(group).length > 0 let content - + if (groupedMonitor) { // Grouped monitors content = ( - - {Object.keys(group).map((groupName) => ( - - -
-
{groupName}
- - {group[groupName].length - countDownCount(state, group[groupName])}/ - {group[groupName].length} Operational - -
-
- - {monitors - .filter((monitor) => group[groupName].includes(monitor.id)) - .sort((a, b) => group[groupName].indexOf(a.id) - group[groupName].indexOf(b.id)) - .map((monitor) => ( -
- - - -
- ))} -
-
- ))} + + { + Object.keys(group).map(groupName => ( + + +
+
{groupName}
+ + {group[groupName].length - countDownCount(state, group[groupName])} + /{group[groupName].length} Operational + +
+
+ + { + monitors + .filter(monitor => group[groupName].includes(monitor.id)) + .sort((a, b) => group[groupName].indexOf(a.id) - group[groupName].indexOf(b.id)) + .map(monitor => ( +
+ + + +
+ )) + } +
+
+ )) + }
) } else { diff --git a/config/index.ts b/config/index.ts deleted file mode 100644 index e108389..0000000 --- a/config/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Maintenances, PageConfig, WorkerConfig } from '@/types/config' -import * as config from '@/uptime.config' - -const pageConfig = config.pageConfig -const workerConfig = config.workerConfig - -let maintenances = [] -// need any because we cannot guarantee that maintenances exists -if ((config as any).maintenances) { - maintenances = (config as any).maintenances -} - -export { pageConfig, workerConfig, maintenances } diff --git a/middleware.ts b/middleware.ts index a38e1cb..78ec4d8 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import { workerConfig } from '@/config' - +import { workerConfig } from './uptime.config' + export async function middleware(request: NextRequest) { // @ts-ignore const passwordProtection = workerConfig.passwordProtection @@ -12,16 +12,15 @@ export async function middleware(request: NextRequest) { if (authHeader && authHeader.length === expected.length) { // a simple timing-safe compare - authenticated = true + authenticated = true; for (let i = 0; i < authHeader.length; i++) { - if (authHeader[i] !== expected[i]) authenticated = false + if (authHeader[i] !== expected[i]) authenticated = false; } } if (!authenticated) { return NextResponse.json( - { code: 401, message: 'Not authenticated' }, - { status: 401, headers: { 'WWW-Authenticate': 'Basic' } } + { code: 401, message: "Not authenticated" }, { status: 401, headers: { 'WWW-Authenticate': 'Basic' } } ) } } diff --git a/pages/api/data.ts b/pages/api/data.ts index 9415cf8..b2d29b5 100644 --- a/pages/api/data.ts +++ b/pages/api/data.ts @@ -1,4 +1,4 @@ -import { workerConfig } from '@/config' +import { workerConfig } from '@/uptime.config' import { MonitorState } from '@/types/uptime.types' import { NextRequest } from 'next/server' diff --git a/pages/index.tsx b/pages/index.tsx index 06e4ef7..afc9ec6 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, MonitorTarget } from '@/types/uptime.types' +import { MonitorState, MonitorTarget } from '@/uptime.types' import { KVNamespace } from '@cloudflare/workers-types' -import { pageConfig, workerConfig } from '@/config' +import { pageConfig, workerConfig } from '@/uptime.config' import OverallStatus from '@/components/OverallStatus' import Header from '@/components/Header' import MonitorList from '@/components/MonitorList' @@ -22,17 +22,21 @@ export default function Home({ tooltip?: string statusPageLink?: string }) { - let state + let state; if (stateStr !== undefined) { state = JSON.parse(stateStr) as MonitorState } // Specify monitorId in URL hash to view a specific monitor (can be used in iframe) - const monitorId = window.location.hash.substring(1) + const monitorId = window.location.hash.substring(1); if (monitorId) { - const monitor = monitors.find((monitor) => monitor.id === monitorId) + const monitor = monitors.find((monitor) => monitor.id === monitorId); if (!monitor || !state) { - return Monitor with id {monitorId} not found! + return ( + + Monitor with id {monitorId} not found! + + ) } return (
@@ -66,14 +70,9 @@ export default function Home({ )} - + Open-source monitoring and status page powered by{' '} Uptimeflare diff --git a/types/config.ts b/types/config.ts index 9893628..01644ea 100644 --- a/types/config.ts +++ b/types/config.ts @@ -2,6 +2,7 @@ export type PageConfig = { title?: string links?: PageConfigLink[] group?: PageConfigGroup + maintenances?: Maintenances[] } export type Maintenances = { diff --git a/uptime.config.ts b/uptime.config.ts index e07a2b5..2ccaeb7 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -1,4 +1,4 @@ -import { Maintenances, PageConfig, WorkerConfig } from '@/types/config' +import { PageConfig, WorkerConfig } from './types/config' const pageConfig: PageConfig = { // Title for your status page @@ -119,16 +119,5 @@ const workerConfig: WorkerConfig = { }, } -const maintenances: Maintenances[] = [ - // { - // title: 'Planned downtime', - // body: 'Please be patient', - // monitors: ['monitor-id-1', 'monitor-id-2'], - // start: new Date('2025-04-19 00:00:00Z'), - // end: new Date('2025-04-19 01:00:00Z'), - // color: 'red', - // }, -] - // Don't forget this, otherwise compilation fails. -export { pageConfig, workerConfig, maintenances } +export { pageConfig, workerConfig } diff --git a/worker/src/index.ts b/worker/src/index.ts index 86fb07e..74fc868 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -1,6 +1,6 @@ -import { workerConfig } from '../../config' +import { workerConfig } from '../../uptime.config' import { formatStatusChangeNotification, getWorkerLocation, notifyWithApprise } from './util' -import { MonitorState, MonitorTarget } from '../../types/uptime.types' +import { MonitorState, MonitorTarget } from '../../uptime.types' import { getStatus } from './monitor' import { DurableObject } from 'cloudflare:workers' @@ -26,9 +26,7 @@ export default { // @ts-ignore const skipList: string[] = workerConfig.notification?.skipNotificationIds if (skipList && skipList.includes(monitor.id)) { - console.log( - `Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)` - ) + console.log(`Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)`) return } @@ -48,9 +46,7 @@ export default { notification.body ) } else { - console.log( - `Apprise API server or recipient URL not set, skipping apprise notification for ${monitor.name}` - ) + console.log(`Apprise API server or recipient URL not set, skipping apprise notification for ${monitor.name}`) } } @@ -87,11 +83,11 @@ export default { try { console.log('Calling check proxy: ' + monitor.checkProxy) let resp - if (monitor.checkProxy.startsWith('worker://')) { - const doLoc = monitor.checkProxy.replace('worker://', '') + if (monitor.checkProxy.startsWith("worker://")) { + const doLoc = monitor.checkProxy.replace("worker://", "") const doId = env.REMOTE_CHECKER_DO.idFromName(doLoc) const doStub = env.REMOTE_CHECKER_DO.get(doId, { - locationHint: doLoc as DurableObjectLocationHint, + locationHint: doLoc as DurableObjectLocationHint }) resp = await doStub.getLocationAndStatus(monitor) } else { @@ -101,7 +97,7 @@ export default { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(monitor), }) - ).json<{ location: string; status: { ping: number; up: boolean; err: string } }>() + ).json<{location: string; status: {ping: number; up: boolean; err: string}}>() } checkLocation = resp.location status = resp.status @@ -148,14 +144,17 @@ export default { // grace period not set OR ... workerConfig.notification?.gracePeriod === undefined || // only when we have sent a notification for DOWN status, we will send a notification for UP status (within 30 seconds of possible drift) - currentTimeSecond - lastIncident.start[0] >= - (workerConfig.notification.gracePeriod + 1) * 60 - 30 + currentTimeSecond - lastIncident.start[0] >= (workerConfig.notification.gracePeriod + 1) * 60 - 30 ) { - await formatAndNotify(monitor, true, lastIncident.start[0], currentTimeSecond, 'OK') - } else { - console.log( - `grace period (${workerConfig.notification?.gracePeriod}m) not met, skipping apprise UP notification for ${monitor.name}` + await formatAndNotify( + monitor, + true, + lastIncident.start[0], + currentTimeSecond, + 'OK' ) + } else { + console.log(`grace period (${workerConfig.notification?.gracePeriod}m) not met, skipping apprise UP notification for ${monitor.name}`) } console.log('Calling config onStatusChange callback...') @@ -196,20 +195,22 @@ export default { try { if ( // monitor status changed AND... - (monitorStatusChanged && + (monitorStatusChanged && ( // grace period not set OR ... - (workerConfig.notification?.gracePeriod === undefined || - // have sent a notification for DOWN status - currentTimeSecond - currentIncident.start[0] >= - (workerConfig.notification.gracePeriod + 1) * 60 - 30)) || - // grace period is set AND... - (workerConfig.notification?.gracePeriod !== undefined && - // grace period is met - currentTimeSecond - currentIncident.start[0] >= - workerConfig.notification.gracePeriod * 60 - 30 && - currentTimeSecond - currentIncident.start[0] < - workerConfig.notification.gracePeriod * 60 + 30) - ) { + workerConfig.notification?.gracePeriod === undefined || + // have sent a notification for DOWN status + currentTimeSecond - currentIncident.start[0] >= (workerConfig.notification.gracePeriod + 1) * 60 - 30 + )) + || + ( + // grace period is set AND... + workerConfig.notification?.gracePeriod !== undefined && + ( + // grace period is met + currentTimeSecond - currentIncident.start[0] >= workerConfig.notification.gracePeriod * 60 - 30 && + currentTimeSecond - currentIncident.start[0] < workerConfig.notification.gracePeriod * 60 + 30 + ) + )) { await formatAndNotify( monitor, false, @@ -218,14 +219,7 @@ export default { status.err ) } else { - console.log( - `Grace period (${workerConfig.notification - ?.gracePeriod}m) not met (currently down for ${ - currentTimeSecond - currentIncident.start[0] - }s, changed ${monitorStatusChanged}), skipping apprise DOWN notification for ${ - monitor.name - }` - ) + console.log(`Grace period (${workerConfig.notification?.gracePeriod}m) not met (currently down for ${currentTimeSecond - currentIncident.start[0]}s, changed ${monitorStatusChanged}), skipping apprise DOWN notification for ${monitor.name}`) } if (monitorStatusChanged) { @@ -280,45 +274,40 @@ export default { // discard old incidents let incidentList = state.incident[monitor.id] - while ( - incidentList.length > 0 && - incidentList[0].end && - incidentList[0].end < currentTimeSecond - 90 * 24 * 60 * 60 - ) { + while (incidentList.length > 0 && incidentList[0].end && incidentList[0].end < currentTimeSecond - 90 * 24 * 60 * 60) { incidentList.shift() } - if ( - incidentList.length == 0 || - (incidentList[0].start[0] > currentTimeSecond - 90 * 24 * 60 * 60 && - incidentList[0].error[0] != 'dummy') - ) { + if (incidentList.length == 0 || ( + incidentList[0].start[0] > currentTimeSecond - 90 * 24 * 60 * 60 && + incidentList[0].error[0] != 'dummy' + )) { // put the dummy incident back - incidentList.unshift({ - start: [currentTimeSecond - 90 * 24 * 60 * 60], - end: currentTimeSecond - 90 * 24 * 60 * 60, - error: ['dummy'], - }) + incidentList.unshift( + { + start: [currentTimeSecond - 90 * 24 * 60 * 60], + end: currentTimeSecond - 90 * 24 * 60 * 60, + error: ['dummy'], + } + ) } state.incident[monitor.id] = incidentList statusChanged ||= monitorStatusChanged } - console.log( - `statusChanged: ${statusChanged}, lastUpdate: ${state.lastUpdate}, currentTime: ${currentTimeSecond}` - ) + console.log(`statusChanged: ${statusChanged}, lastUpdate: ${state.lastUpdate}, currentTime: ${currentTimeSecond}`) // Update state // Allow for a cooldown period before writing to KV if ( statusChanged || - currentTimeSecond - state.lastUpdate >= workerConfig.kvWriteCooldownMinutes * 60 - 10 // Allow for 10 seconds of clock drift + currentTimeSecond - state.lastUpdate >= workerConfig.kvWriteCooldownMinutes * 60 - 10 // Allow for 10 seconds of clock drift ) { - console.log('Updating state...') + console.log("Updating state...") state.lastUpdate = currentTimeSecond await env.UPTIMEFLARE_STATE.put('state', JSON.stringify(state)) } else { - console.log('Skipping state update due to cooldown period.') + console.log("Skipping state update due to cooldown period.") } }, } @@ -328,10 +317,8 @@ export class RemoteChecker extends DurableObject { super(ctx, env) } - async getLocationAndStatus( - monitor: MonitorTarget - ): Promise<{ location: string; status: { ping: number; up: boolean; err: string } }> { - const colo = (await getWorkerLocation()) as string + async getLocationAndStatus(monitor: MonitorTarget): Promise<{location: string; status: {ping: number; up: boolean; err: string}}> { + const colo = await getWorkerLocation() as string console.log(`Running remote checker (DurableObject) at ${colo}...`) const status = await getStatus(monitor) return {