From 4bf07257f27945f86e0c456bdaba7e225d556342 Mon Sep 17 00:00:00 2001 From: lyc8503 Date: Sat, 27 Sep 2025 00:38:48 +0800 Subject: [PATCH 1/2] misc: add scheduled maintenance example --- README.md | 4 ++++ uptime.config.ts | 29 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4edee64..2c03d3e 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,7 @@ Please refer to [Wiki](https://github.com/lyc8503/UptimeFlare/wiki) - [ ] Update wiki/README and add docs for dev - [ ] Migration to Terraform Cloudflare provider version 5.x - [ ] Cloudflare D1 database +- [x] Scheduled maintenances (via IIFE) +- [ ] Simpler config example +- [ ] Upcoming maintenances +- [ ] Universal Webhook upgrade \ No newline at end of file diff --git a/uptime.config.ts b/uptime.config.ts index 65e9356..ddc06f0 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -102,7 +102,7 @@ const workerConfig: WorkerConfig = { reason: string ) => { // This callback will be called when there's a status change for any monitor - // Write any Typescript code here + // 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 }, @@ -114,7 +114,7 @@ const workerConfig: WorkerConfig = { reason: string ) => { // This callback will be called EVERY 1 MINTUE if there's an on-going incident for any monitor - // Write any Typescript code here + // Write any TypeScript code here }, }, } @@ -140,6 +140,31 @@ const maintenances: MaintenanceConfig[] = [ // [Optional] color of the maintenance alert at status page, default to "yellow" color: 'blue', }, + // As this config file is a TypeScript file, you can even use IIFE to generate scheduled maintenances + // The following example shows a scheduled maintenance from 2 AM to 4 AM on the 15th of every month (UTC+8) + // This COULD BE DANGEROUS, as generating too many maintenance entries can lead to performance problems + // Undeterministic outputs may also lead to bugs or unexpected behavior + // If you don't know how to DEBUG, use this approach WITH CAUTION + ...(function (){ + const schedules = []; + const today = new Date(); + + for (let i = -1; i <= 1; i++) { + // JavaScript's Date object will automatically handle year rollovers + const date = new Date(today.getFullYear(), today.getMonth() + i, 15); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + + schedules.push({ + title: `${year}/${parseInt(month)} - Test scheduled maintenance`, + monitors: ['foo_monitor'], + body: 'Monthly scheduled maintenance', + start: `${year}-${month}-15T02:00:00.000+08:00`, + end: `${year}-${month}-15T04:00:00.000+08:00`, + }); + } + return schedules; + })() ] // Don't forget this, otherwise compilation fails. From f88e286b9c9cc53c64b960549838355408c1c814 Mon Sep 17 00:00:00 2001 From: Mack <45337741+QuackieMackie@users.noreply.github.com> Date: Sat, 27 Sep 2025 14:30:13 +0100 Subject: [PATCH 2/2] Upcoming and active maintenance (#142) * Updated with intial commit - MaintenanceAlert.tsx now handles upcoming and active maintenance alerts. - OverallStatus.tsx updated to include collapsible sections for ongoing and upcoming maintenance alerts. * Updated MaintenanceAlert layout for better responsiveness * Revert the uptime.config.ts * fix: Added type definitions for active and upcoming maintenances * Update components/MaintenanceAlert.tsx Co-authored-by: Bennet Gallein * Update: added `pageConfig.maintenances.upcomingColor` to control upcoming maintenance alert colors. * Feature: Added a config option for the favicon. * keep original styles * remove collapse * add back a different collapse for upcoming events * css grid to align date text * tiny css changes and cleanup --------- Co-authored-by: Bennet Gallein Co-authored-by: lyc8503 --- components/MaintenanceAlert.tsx | 37 ++++++++++++++++++------ components/OverallStatus.tsx | 50 +++++++++++++++++++++++++++++---- pages/incidents.tsx | 2 +- pages/index.tsx | 2 +- types/config.ts | 4 +++ uptime.config.ts | 8 ++++++ 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/components/MaintenanceAlert.tsx b/components/MaintenanceAlert.tsx index da7b2e3..30f1bdf 100644 --- a/components/MaintenanceAlert.tsx +++ b/components/MaintenanceAlert.tsx @@ -2,13 +2,16 @@ import { Alert, List, Text, useMantineTheme } from '@mantine/core' import { useMediaQuery } from '@mantine/hooks' import { IconAlertTriangle } from '@tabler/icons-react' import { MaintenanceConfig, MonitorTarget } from '@/types/config' +import { pageConfig } from '@/uptime.config' export default function MaintenanceAlert({ maintenance, style, + upcoming = false, }: { maintenance: Omit & { monitors?: MonitorTarget[] } style?: React.CSSProperties + upcoming?: boolean }) { const theme = useMantineTheme() const isDesktop = useMediaQuery(`(min-width: ${theme.breakpoints.sm})`) @@ -23,12 +26,14 @@ export default function MaintenanceAlert({ fontWeight: 700, }} > - {maintenance.title || 'Scheduled Maintenance'} + {(upcoming ? '[Upcoming] ' : '') + (maintenance.title || 'Scheduled Maintenance')} } - color={maintenance.color || 'yellow'} + color={ + upcoming ? pageConfig.maintenances?.upcomingColor ?? 'gray' : maintenance.color || 'yellow' + } withCloseButton={false} - style={{ position: 'relative', margin: '16px auto 0 auto', ...style }} + style={{ margin: '16px auto 0 auto', ...style }} > {/* Date range in top right (desktop) or inline (mobile) */}
- From: {new Date(maintenance.start).toLocaleString()} -
- To:{' '} - {maintenance.end ? new Date(maintenance.end).toLocaleString() : 'Until further notice'} +
+
+ {upcoming ? 'Scheduled for:' : 'From:'} +
+
{new Date(maintenance.start).toLocaleString()}
+
+ {upcoming ? 'Expected end:' : 'To:'} +
+
+ {maintenance.end ? new Date(maintenance.end).toLocaleString() : 'Until further notice'} +
+
- {maintenance.body} + {maintenance.body} {maintenance.monitors && maintenance.monitors.length > 0 && ( <> diff --git a/components/OverallStatus.tsx b/components/OverallStatus.tsx index aa1fa78..8aa13b5 100644 --- a/components/OverallStatus.tsx +++ b/components/OverallStatus.tsx @@ -1,6 +1,6 @@ import { MaintenanceConfig, MonitorTarget } from '@/types/config' -import { Center, Container, Title } from '@mantine/core' -import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react' +import { Center, Container, Title, Collapse, Button, Box } from '@mantine/core' +import { IconCircleCheck, IconAlertCircle, IconPlus, IconMinus } from '@tabler/icons-react' import { useEffect, useState } from 'react' import MaintenanceAlert from './MaintenanceAlert' import { pageConfig } from '@/uptime.config' @@ -45,6 +45,7 @@ export default function OverallStatus({ const [openTime] = useState(Math.round(Date.now() / 1000)) const [currentTime, setCurrentTime] = useState(Math.round(Date.now() / 1000)) const isWindowVisible = useWindowVisibility() + const [expandUpcoming, setExpandUpcoming] = useState(false) useEffect(() => { const interval = setInterval(() => { @@ -58,7 +59,8 @@ export default function OverallStatus({ }) const now = new Date() - let filteredMaintenances: (Omit & { + + const activeMaintenances: (Omit & { monitors?: MonitorTarget[] })[] = maintenances .filter((m) => now >= new Date(m.start) && (!m.end || now <= new Date(m.end))) @@ -69,6 +71,17 @@ export default function OverallStatus({ ), })) + const upcomingMaintenances: (Omit & { + monitors?: MonitorTarget[] + })[] = maintenances + .filter((m) => now < new Date(m.start)) + .map((maintenance) => ({ + ...maintenance, + monitors: maintenance.monitors?.map( + (monitorId) => monitors.find((mon) => monitorId === mon.id)! + ), + })) + return (
{icon}
@@ -82,9 +95,36 @@ export default function OverallStatus({ } sec ago)`} - {filteredMaintenances.map((maintenance, idx) => ( + {/* Upcoming Maintenance */} + {upcomingMaintenances.length > 0 && ( + <> + + {`${upcomingMaintenances.length} upcoming maintenances`}{' '} + <span + style={{ textDecoration: 'underline' }} + onClick={() => setExpandUpcoming(!expandUpcoming)} + > + {expandUpcoming ? '[HIDE]' : '[SHOW]'} + </span> + + + + {upcomingMaintenances.map((maintenance, idx) => ( + + ))} + + + )} + + {/* Active Maintenance */} + {activeMaintenances.map((maintenance, idx) => ( diff --git a/pages/incidents.tsx b/pages/incidents.tsx index 98f3fb6..aecfbf4 100644 --- a/pages/incidents.tsx +++ b/pages/incidents.tsx @@ -81,7 +81,7 @@ export default function IncidentsPage() { <> {pageConfig.title} - +
diff --git a/pages/index.tsx b/pages/index.tsx index be84a96..274315b 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -46,7 +46,7 @@ export default function Home({ <> {pageConfig.title} - +
diff --git a/types/config.ts b/types/config.ts index 83c39a6..35f076a 100644 --- a/types/config.ts +++ b/types/config.ts @@ -4,6 +4,10 @@ export type PageConfig = { title?: string links?: PageConfigLink[] group?: PageConfigGroup + favicon?: string + maintenances?: { + upcomingColor?: string + } } export type MaintenanceConfig = { diff --git a/uptime.config.ts b/uptime.config.ts index ddc06f0..ae48df2 100644 --- a/uptime.config.ts +++ b/uptime.config.ts @@ -16,6 +16,14 @@ const pageConfig: PageConfig = { '🌐 Public (example group name)': ['foo_monitor', 'bar_monitor', 'more monitor ids...'], '🔐 Private': ['test_tcp_monitor'], }, + // [OPTIONAL] Set the path to your favicon, default to '/favicon.ico' if not specified + favicon: '/favicon.ico', + // [OPTIONAL] Maintenance related settings + maintenances: { + // [OPTIONAL] The color of upcoming maintenance alerts, default to 'gray' + // Active alerts will always use the color specified in the MaintenanceConfig + upcomingColor: 'gray', + }, } const workerConfig: WorkerConfig = {