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 = {