alert config/style changes, worker code improve
parent
1dc02feeca
commit
2acff525e9
|
|
@ -1,4 +1,4 @@
|
|||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||
import { getColor } from '@/util/color'
|
||||
import { Box, Tooltip, Modal } from '@mantine/core'
|
||||
import { useResizeObserver } from '@mantine/hooks'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
TimeScale,
|
||||
} from 'chart.js'
|
||||
import 'chartjs-adapter-moment'
|
||||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||
import { iataToCountry } from '@/util/iata'
|
||||
|
||||
ChartJS.register(
|
||||
|
|
|
|||
|
|
@ -1,55 +1,42 @@
|
|||
import { Alert, List, Text } from '@mantine/core'
|
||||
import { IconAlertTriangle } from '@tabler/icons-react'
|
||||
import { Maintenances, Monitor } from '@/types/config'
|
||||
import { pageConfig } from '@/uptime.config'
|
||||
import { MaintenanceConfig, MonitorTarget } from '@/types/config'
|
||||
|
||||
export default function MaintenanceAlert({
|
||||
maintenance,
|
||||
groupedMonitor,
|
||||
style,
|
||||
}: {
|
||||
maintenance: Omit<Maintenances, 'monitors'> & { monitors: Monitor[] | undefined }
|
||||
groupedMonitor: boolean
|
||||
maintenance: Omit<MaintenanceConfig, 'monitors'> & { monitors: MonitorTarget[] }
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
return (
|
||||
<Alert
|
||||
icon={<IconAlertTriangle />}
|
||||
title={maintenance.title}
|
||||
color={maintenance.color || 'primary'}
|
||||
mt="md"
|
||||
ml="md"
|
||||
mr="md"
|
||||
title={maintenance.title || 'Scheduled Maintenance'}
|
||||
color={maintenance.color || 'yellow'}
|
||||
withCloseButton={false}
|
||||
style={{ maxWidth: groupedMonitor ? '897px' : '865px', position: 'relative' }}
|
||||
style={{ position: 'relative', margin: '16px auto 0 auto', ...style }}
|
||||
>
|
||||
{/* Date range in top right */}
|
||||
{(maintenance.start || maintenance.end) && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
fontSize: '0.85rem',
|
||||
textAlign: 'right',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 6,
|
||||
}}
|
||||
>
|
||||
{maintenance.start && (
|
||||
<>
|
||||
<b>From:</b> {maintenance.start.toLocaleString()}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{maintenance.end && (
|
||||
<>
|
||||
<b>To:</b> {maintenance.end.toLocaleString()}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
fontSize: '0.85rem',
|
||||
textAlign: 'right',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 6,
|
||||
}}
|
||||
>
|
||||
<b>From:</b> {new Date(maintenance.start).toLocaleString()}
|
||||
<br />
|
||||
<b>To:</b>{' '}
|
||||
{maintenance.end ? new Date(maintenance.end).toLocaleString() : 'Until further notice'}
|
||||
</div>
|
||||
|
||||
{maintenance.body && <Text mt="xs">{maintenance.body}</Text>}
|
||||
{maintenance.monitors && maintenance.monitors.length > 0 && (
|
||||
<Text>{maintenance.body}</Text>
|
||||
{maintenance.monitors.length > 0 && (
|
||||
<>
|
||||
<Text mt="xs">
|
||||
<b>Affected components:</b>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Text, Tooltip } from '@mantine/core'
|
||||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||
import { IconAlertCircle, IconAlertTriangle, IconCircleCheck } from '@tabler/icons-react'
|
||||
import DetailChart from './DetailChart'
|
||||
import DetailBar from './DetailBar'
|
||||
|
|
@ -39,8 +39,8 @@ export default function MonitorDetail({
|
|||
|
||||
// Hide real status icon if monitor is in maintenance
|
||||
const now = new Date()
|
||||
const hasMaintenance = (maintenances || [])
|
||||
.filter((m) => (!m.start && !m.end) || (m.start && m.end && now >= m.start && now <= m.end))
|
||||
const hasMaintenance = maintenances
|
||||
.filter((m) => now >= new Date(m.start) && (!m.end || now <= new Date(m.end)))
|
||||
.find((maintenance) => maintenance.monitors?.includes(monitor.id))
|
||||
if (hasMaintenance)
|
||||
statusIcon = (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||
import { Accordion, Card, Center, Text } from '@mantine/core'
|
||||
import MonitorDetail from './MonitorDetail'
|
||||
import { pageConfig } from '@/uptime.config'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Maintenances, Monitor } from '@/types/config'
|
||||
import { MaintenanceConfig, MonitorTarget } from '@/types/config'
|
||||
import { Center, Container, Title } from '@mantine/core'
|
||||
import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
|
@ -21,8 +21,8 @@ export default function OverallStatus({
|
|||
monitors,
|
||||
}: {
|
||||
state: { overallUp: number; overallDown: number; lastUpdate: number }
|
||||
maintenances: Maintenances[]
|
||||
monitors: Monitor[]
|
||||
maintenances: MaintenanceConfig[]
|
||||
monitors: MonitorTarget[]
|
||||
}) {
|
||||
let group = pageConfig.group
|
||||
let groupedMonitor = (group && Object.keys(group).length > 0) || false
|
||||
|
|
@ -58,16 +58,15 @@ export default function OverallStatus({
|
|||
})
|
||||
|
||||
const now = new Date()
|
||||
let filteredMaintenances: (Omit<Maintenances, 'monitors'> & {
|
||||
monitors: Monitor[] | undefined
|
||||
})[] = (maintenances || [])
|
||||
.filter((m) => (!m.start && !m.end) || (m.start && m.end && now >= m.start && now <= m.end))
|
||||
.map((maintenance) => ({
|
||||
...maintenance,
|
||||
monitors: maintenance.monitors?.map(
|
||||
(monitorId) => monitors.find((mon) => monitorId === mon.id)!
|
||||
),
|
||||
}))
|
||||
let filteredMaintenances: (Omit<MaintenanceConfig, 'monitors'> & { monitors: MonitorTarget[] })[] =
|
||||
maintenances
|
||||
.filter((m) => now >= new Date(m.start) && (!m.end || now <= new Date(m.end)))
|
||||
.map((maintenance) => ({
|
||||
...maintenance,
|
||||
monitors: maintenance.monitors?.map(
|
||||
(monitorId) => monitors.find((mon) => monitorId === mon.id)!
|
||||
),
|
||||
}))
|
||||
|
||||
return (
|
||||
<Container size="md" mt="xl">
|
||||
|
|
@ -83,7 +82,11 @@ export default function OverallStatus({
|
|||
</Title>
|
||||
|
||||
{filteredMaintenances.map((maintenance, idx) => (
|
||||
<MaintenanceAlert groupedMonitor={groupedMonitor} key={idx} maintenance={maintenance} />
|
||||
<MaintenanceAlert
|
||||
key={idx}
|
||||
maintenance={maintenance}
|
||||
style={{ maxWidth: groupedMonitor ? '897px' : '865px' }}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { workerConfig } from '@/uptime.config'
|
||||
import { MonitorState } from '@/types/uptime.types'
|
||||
import { MonitorState } from '@/types/config'
|
||||
import { NextRequest } from 'next/server'
|
||||
|
||||
export const runtime = 'edge'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Head from 'next/head'
|
||||
|
||||
import { Inter } from 'next/font/google'
|
||||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||
import { KVNamespace } from '@cloudflare/workers-types'
|
||||
import { maintenances, pageConfig, workerConfig } from '@/uptime.config'
|
||||
import OverallStatus from '@/components/OverallStatus'
|
||||
|
|
|
|||
|
|
@ -4,18 +4,12 @@ export type PageConfig = {
|
|||
group?: PageConfigGroup
|
||||
}
|
||||
|
||||
export type Maintenances = {
|
||||
// title to display
|
||||
title: string
|
||||
// array of monitor ids
|
||||
monitors?: string[]
|
||||
// body message
|
||||
body?: string
|
||||
// start date
|
||||
start?: Date
|
||||
// end Date
|
||||
end?: Date
|
||||
// display color
|
||||
export type MaintenanceConfig = {
|
||||
monitors: string[]
|
||||
title?: string
|
||||
body: string
|
||||
start: number | string
|
||||
end?: number | string
|
||||
color?: string
|
||||
}
|
||||
|
||||
|
|
@ -27,17 +21,10 @@ export type PageConfigLink = {
|
|||
highlight?: boolean
|
||||
}
|
||||
|
||||
export type WorkerConfig = {
|
||||
kvWriteCooldownMinutes: number
|
||||
monitors: Monitor[]
|
||||
notification?: Notification
|
||||
callbacks?: Callbacks
|
||||
}
|
||||
|
||||
export type Monitor = {
|
||||
export type MonitorTarget = {
|
||||
id: string
|
||||
name: string
|
||||
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'TCP_PING'
|
||||
method: string
|
||||
target: string
|
||||
tooltip?: string
|
||||
statusPageLink?: string
|
||||
|
|
@ -52,6 +39,13 @@ export type Monitor = {
|
|||
checkProxyFallback?: boolean
|
||||
}
|
||||
|
||||
export type WorkerConfig = {
|
||||
kvWriteCooldownMinutes: number
|
||||
monitors: MonitorTarget[]
|
||||
notification?: Notification
|
||||
callbacks?: Callbacks
|
||||
}
|
||||
|
||||
export type Notification = {
|
||||
appriseApiServer?: string
|
||||
recipientUrl?: string
|
||||
|
|
@ -77,3 +71,33 @@ export type Callbacks = {
|
|||
reason: string
|
||||
) => Promise<any>
|
||||
}
|
||||
|
||||
export type MonitorState = {
|
||||
lastUpdate: number
|
||||
overallUp: number
|
||||
overallDown: number
|
||||
incident: Record<
|
||||
string,
|
||||
{
|
||||
start: number[]
|
||||
end: number | undefined // undefined if it's still open
|
||||
error: string[]
|
||||
}[]
|
||||
>
|
||||
|
||||
latency: Record<
|
||||
string,
|
||||
{
|
||||
recent: {
|
||||
loc: string
|
||||
ping: number
|
||||
time: number
|
||||
}[] // recent 12 hour data, 2 min interval
|
||||
all: {
|
||||
loc: string
|
||||
ping: number
|
||||
time: number
|
||||
}[] // all data in 90 days, 1 hour interval
|
||||
}
|
||||
>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import { Monitor } from './config'
|
||||
|
||||
type MonitorState = {
|
||||
lastUpdate: number
|
||||
overallUp: number
|
||||
overallDown: number
|
||||
incident: Record<
|
||||
string,
|
||||
{
|
||||
start: number[]
|
||||
end: number | undefined // undefined if it's still open
|
||||
error: string[]
|
||||
}[]
|
||||
>
|
||||
|
||||
latency: Record<
|
||||
string,
|
||||
{
|
||||
recent: {
|
||||
loc: string
|
||||
ping: number
|
||||
time: number
|
||||
}[] // recent 12 hour data, 2 min interval
|
||||
all: {
|
||||
loc: string
|
||||
ping: number
|
||||
time: number
|
||||
}[] // all data in 90 days, 1 hour interval
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
type MonitorTarget = Monitor & {
|
||||
body?: BodyInit
|
||||
}
|
||||
|
||||
export type { MonitorState, MonitorTarget }
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { workerConfig, maintenances } from '../../uptime.config'
|
||||
import { formatStatusChangeNotification, getWorkerLocation, notifyWithApprise } from './util'
|
||||
import { MonitorState, MonitorTarget } from '../../types/uptime.types'
|
||||
import { MonitorState, MonitorTarget } from '../../types/config'
|
||||
import { getStatus } from './monitor'
|
||||
import { DurableObject } from 'cloudflare:workers'
|
||||
|
||||
|
|
@ -22,23 +22,30 @@ const Worker = {
|
|||
timeNow: number,
|
||||
reason: string
|
||||
) => {
|
||||
const skipList: Set<string> = new Set(workerConfig.notification?.skipNotificationIds)
|
||||
const now = new Date()
|
||||
// build list of ids that are currently in maintenance (based on start & end OR if they are missing) and merge with the skipList setting
|
||||
maintenances
|
||||
.filter((m) => (!m.start && !m.end) || (m.start && m.end && now >= m.start && now <= m.end))
|
||||
.map((e) => e.monitors || [])
|
||||
.flat()
|
||||
.map((monitor) => skipList.add(monitor))
|
||||
|
||||
// Skip notification if monitor is in the skip list
|
||||
if (skipList && skipList.has(monitor.id)) {
|
||||
const skipList = workerConfig.notification?.skipNotificationIds
|
||||
if (skipList && skipList.includes(monitor.id)) {
|
||||
console.log(
|
||||
`Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Skip notification if monitor is in maintenance
|
||||
const maintenanceList = maintenances
|
||||
.filter(
|
||||
(m) =>
|
||||
new Date(timeNow * 1000) >= new Date(m.start) &&
|
||||
(!m.end || new Date(timeNow * 1000) <= new Date(m.end))
|
||||
)
|
||||
.map((e) => e.monitors)
|
||||
.flat()
|
||||
|
||||
if (maintenanceList.includes(monitor.id)) {
|
||||
console.log(`Skipping notification for ${monitor.name} (in maintenance)`)
|
||||
return
|
||||
}
|
||||
|
||||
if (workerConfig.notification?.appriseApiServer && workerConfig.notification?.recipientUrl) {
|
||||
const notification = formatStatusChangeNotification(
|
||||
monitor,
|
||||
|
|
|
|||
Loading…
Reference in New Issue