feat: add maintenance alert

pull/101/head
Bennet Gallein 2025-04-18 18:13:11 +02:00
parent 5551bb554a
commit be3aa8beda
No known key found for this signature in database
GPG Key ID: 54F2DF6954E8C635
1 changed files with 72 additions and 22 deletions

View File

@ -1,30 +1,33 @@
import { Center, Title } from '@mantine/core' import { Maintenances } from '@/types/config'
import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react' import { Alert, Card, Center, Container, List, ListItem, Text, Title } from '@mantine/core'
import { useEffect, useState } from 'react'; import { IconCircleCheck, IconAlertCircle, IconInfoCircle } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
function useWindowVisibility() { function useWindowVisibility() {
const [isVisible, setIsVisible] = useState(true); const [isVisible, setIsVisible] = useState(true)
useEffect(() => { useEffect(() => {
const handleVisibilityChange = () => { const handleVisibilityChange = () => {
console.log('visibility change', document.visibilityState); console.log('visibility change', document.visibilityState)
setIsVisible(document.visibilityState === 'visible'); setIsVisible(document.visibilityState === 'visible')
}; }
document.addEventListener('visibilitychange', handleVisibilityChange); document.addEventListener('visibilitychange', handleVisibilityChange)
return () => { return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange); document.removeEventListener('visibilitychange', handleVisibilityChange)
}; }
}, []); }, [])
return isVisible; return isVisible
} }
export default function OverallStatus({ export default function OverallStatus({
state, state,
maintenances,
}: { }: {
state: { overallUp: number; overallDown: number; lastUpdate: number } state: { overallUp: number; overallDown: number; lastUpdate: number }
maintenances?: Maintenances[]
}) { }) {
let statusString = '' let statusString = ''
let icon = <IconAlertCircle style={{ width: 64, height: 64, color: '#b91c1c' }} /> let icon = <IconAlertCircle style={{ width: 64, height: 64, color: '#b91c1c' }} />
@ -36,12 +39,14 @@ export default function OverallStatus({
statusString = 'All systems operational' statusString = 'All systems operational'
icon = <IconCircleCheck style={{ width: 64, height: 64, color: '#059669' }} /> icon = <IconCircleCheck style={{ width: 64, height: 64, color: '#059669' }} />
} else { } else {
statusString = `Some systems not operational (${state.overallDown} out of ${state.overallUp + state.overallDown})` statusString = `Some systems not operational (${state.overallDown} out of ${
state.overallUp + state.overallDown
})`
} }
const [openTime] = useState(Math.round(Date.now() / 1000)) const [openTime] = useState(Math.round(Date.now() / 1000))
const [currentTime, setCurrentTime] = useState(Math.round(Date.now() / 1000)) const [currentTime, setCurrentTime] = useState(Math.round(Date.now() / 1000))
const isWindowVisible = useWindowVisibility(); const isWindowVisible = useWindowVisibility()
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
@ -58,16 +63,61 @@ export default function OverallStatus({
return () => clearInterval(interval) return () => clearInterval(interval)
}) })
// @TODO make dynamic
const showMaintenance = maintenances
return ( return (
<> <>
<Center>{icon}</Center> <Container size="md" mt="xl" style={{ width: '897px' }}>
<Title mt="sm" style={{ textAlign: 'center' }} order={1}> <Center>{icon}</Center>
{statusString} <Title mt="sm" style={{ textAlign: 'center' }} order={1}>
</Title> {statusString}
<Title mt="sm" style={{ textAlign: 'center', color: '#70778c' }} order={5}> </Title>
Last updated on:{' '} <Title mt="sm" style={{ textAlign: 'center', color: '#70778c' }} order={5}>
{`${new Date(state.lastUpdate * 1000).toLocaleString()} (${currentTime - state.lastUpdate} sec ago)`} Last updated on:{' '}
</Title> {`${new Date(state.lastUpdate * 1000).toLocaleString()} (${
currentTime - state.lastUpdate
} sec ago)`}
</Title>
{showMaintenance &&
maintenances.map((maintenance, idx) => (
<Alert
key={idx}
icon={<IconInfoCircle />}
title={maintenance.title}
color={maintenance.color || 'primary'}
mt="md"
withCloseButton={false}
>
<Text>
{maintenance.start && (
<>
<b>From:</b> {maintenance.start.toLocaleString()}
<br />
</>
)}
{maintenance.end && (
<>
<b>To:</b> {maintenance.end.toLocaleString()}
</>
)}
</Text>
{maintenance.body && <Text mt="xs">{maintenance.body}</Text>}
{maintenance.monitors && maintenance.monitors.length > 0 && (
<>
<Text mt="xs">
<b>Affected components:</b>
</Text>
<List size="sm" withPadding>
{maintenance.monitors.map((comp, compIdx) => (
<List.Item key={compIdx}>{comp}</List.Item>
))}
</List>
</>
)}
</Alert>
))}
</Container>
</> </>
) )
} }