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 { getColor } from '@/util/color'
|
||||||
import { Box, Tooltip, Modal } from '@mantine/core'
|
import { Box, Tooltip, Modal } from '@mantine/core'
|
||||||
import { useResizeObserver } from '@mantine/hooks'
|
import { useResizeObserver } from '@mantine/hooks'
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
TimeScale,
|
TimeScale,
|
||||||
} from 'chart.js'
|
} from 'chart.js'
|
||||||
import 'chartjs-adapter-moment'
|
import 'chartjs-adapter-moment'
|
||||||
import { MonitorState, MonitorTarget } from '@/types/uptime.types'
|
import { MonitorState, MonitorTarget } from '@/types/config'
|
||||||
import { iataToCountry } from '@/util/iata'
|
import { iataToCountry } from '@/util/iata'
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,23 @@
|
||||||
import { Alert, List, Text } from '@mantine/core'
|
import { Alert, List, Text } from '@mantine/core'
|
||||||
import { IconAlertTriangle } from '@tabler/icons-react'
|
import { IconAlertTriangle } from '@tabler/icons-react'
|
||||||
import { Maintenances, Monitor } from '@/types/config'
|
import { MaintenanceConfig, MonitorTarget } from '@/types/config'
|
||||||
import { pageConfig } from '@/uptime.config'
|
|
||||||
|
|
||||||
export default function MaintenanceAlert({
|
export default function MaintenanceAlert({
|
||||||
maintenance,
|
maintenance,
|
||||||
groupedMonitor,
|
style,
|
||||||
}: {
|
}: {
|
||||||
maintenance: Omit<Maintenances, 'monitors'> & { monitors: Monitor[] | undefined }
|
maintenance: Omit<MaintenanceConfig, 'monitors'> & { monitors: MonitorTarget[] }
|
||||||
groupedMonitor: boolean
|
style?: React.CSSProperties
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
icon={<IconAlertTriangle />}
|
icon={<IconAlertTriangle />}
|
||||||
title={maintenance.title}
|
title={maintenance.title || 'Scheduled Maintenance'}
|
||||||
color={maintenance.color || 'primary'}
|
color={maintenance.color || 'yellow'}
|
||||||
mt="md"
|
|
||||||
ml="md"
|
|
||||||
mr="md"
|
|
||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
style={{ maxWidth: groupedMonitor ? '897px' : '865px', position: 'relative' }}
|
style={{ position: 'relative', margin: '16px auto 0 auto', ...style }}
|
||||||
>
|
>
|
||||||
{/* Date range in top right */}
|
{/* Date range in top right */}
|
||||||
{(maintenance.start || maintenance.end) && (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -34,22 +29,14 @@ export default function MaintenanceAlert({
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{maintenance.start && (
|
<b>From:</b> {new Date(maintenance.start).toLocaleString()}
|
||||||
<>
|
|
||||||
<b>From:</b> {maintenance.start.toLocaleString()}
|
|
||||||
<br />
|
<br />
|
||||||
</>
|
<b>To:</b>{' '}
|
||||||
)}
|
{maintenance.end ? new Date(maintenance.end).toLocaleString() : 'Until further notice'}
|
||||||
{maintenance.end && (
|
|
||||||
<>
|
|
||||||
<b>To:</b> {maintenance.end.toLocaleString()}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{maintenance.body && <Text mt="xs">{maintenance.body}</Text>}
|
<Text>{maintenance.body}</Text>
|
||||||
{maintenance.monitors && maintenance.monitors.length > 0 && (
|
{maintenance.monitors.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Text mt="xs">
|
<Text mt="xs">
|
||||||
<b>Affected components:</b>
|
<b>Affected components:</b>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Text, Tooltip } from '@mantine/core'
|
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 { IconAlertCircle, IconAlertTriangle, IconCircleCheck } from '@tabler/icons-react'
|
||||||
import DetailChart from './DetailChart'
|
import DetailChart from './DetailChart'
|
||||||
import DetailBar from './DetailBar'
|
import DetailBar from './DetailBar'
|
||||||
|
|
@ -39,8 +39,8 @@ export default function MonitorDetail({
|
||||||
|
|
||||||
// Hide real status icon if monitor is in maintenance
|
// Hide real status icon if monitor is in maintenance
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const hasMaintenance = (maintenances || [])
|
const hasMaintenance = maintenances
|
||||||
.filter((m) => (!m.start && !m.end) || (m.start && m.end && now >= m.start && now <= m.end))
|
.filter((m) => now >= new Date(m.start) && (!m.end || now <= new Date(m.end)))
|
||||||
.find((maintenance) => maintenance.monitors?.includes(monitor.id))
|
.find((maintenance) => maintenance.monitors?.includes(monitor.id))
|
||||||
if (hasMaintenance)
|
if (hasMaintenance)
|
||||||
statusIcon = (
|
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 { Accordion, Card, Center, Text } from '@mantine/core'
|
||||||
import MonitorDetail from './MonitorDetail'
|
import MonitorDetail from './MonitorDetail'
|
||||||
import { pageConfig } from '@/uptime.config'
|
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 { Center, Container, Title } from '@mantine/core'
|
||||||
import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react'
|
import { IconCircleCheck, IconAlertCircle } from '@tabler/icons-react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
@ -21,8 +21,8 @@ export default function OverallStatus({
|
||||||
monitors,
|
monitors,
|
||||||
}: {
|
}: {
|
||||||
state: { overallUp: number; overallDown: number; lastUpdate: number }
|
state: { overallUp: number; overallDown: number; lastUpdate: number }
|
||||||
maintenances: Maintenances[]
|
maintenances: MaintenanceConfig[]
|
||||||
monitors: Monitor[]
|
monitors: MonitorTarget[]
|
||||||
}) {
|
}) {
|
||||||
let group = pageConfig.group
|
let group = pageConfig.group
|
||||||
let groupedMonitor = (group && Object.keys(group).length > 0) || false
|
let groupedMonitor = (group && Object.keys(group).length > 0) || false
|
||||||
|
|
@ -58,10 +58,9 @@ export default function OverallStatus({
|
||||||
})
|
})
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
let filteredMaintenances: (Omit<Maintenances, 'monitors'> & {
|
let filteredMaintenances: (Omit<MaintenanceConfig, 'monitors'> & { monitors: MonitorTarget[] })[] =
|
||||||
monitors: Monitor[] | undefined
|
maintenances
|
||||||
})[] = (maintenances || [])
|
.filter((m) => now >= new Date(m.start) && (!m.end || now <= new Date(m.end)))
|
||||||
.filter((m) => (!m.start && !m.end) || (m.start && m.end && now >= m.start && now <= m.end))
|
|
||||||
.map((maintenance) => ({
|
.map((maintenance) => ({
|
||||||
...maintenance,
|
...maintenance,
|
||||||
monitors: maintenance.monitors?.map(
|
monitors: maintenance.monitors?.map(
|
||||||
|
|
@ -83,7 +82,11 @@ export default function OverallStatus({
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
{filteredMaintenances.map((maintenance, idx) => (
|
{filteredMaintenances.map((maintenance, idx) => (
|
||||||
<MaintenanceAlert groupedMonitor={groupedMonitor} key={idx} maintenance={maintenance} />
|
<MaintenanceAlert
|
||||||
|
key={idx}
|
||||||
|
maintenance={maintenance}
|
||||||
|
style={{ maxWidth: groupedMonitor ? '897px' : '865px' }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { workerConfig } from '@/uptime.config'
|
import { workerConfig } from '@/uptime.config'
|
||||||
import { MonitorState } from '@/types/uptime.types'
|
import { MonitorState } from '@/types/config'
|
||||||
import { NextRequest } from 'next/server'
|
import { NextRequest } from 'next/server'
|
||||||
|
|
||||||
export const runtime = 'edge'
|
export const runtime = 'edge'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
|
||||||
import { Inter } from 'next/font/google'
|
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 { KVNamespace } from '@cloudflare/workers-types'
|
||||||
import { maintenances, pageConfig, workerConfig } from '@/uptime.config'
|
import { maintenances, pageConfig, workerConfig } from '@/uptime.config'
|
||||||
import OverallStatus from '@/components/OverallStatus'
|
import OverallStatus from '@/components/OverallStatus'
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,12 @@ export type PageConfig = {
|
||||||
group?: PageConfigGroup
|
group?: PageConfigGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Maintenances = {
|
export type MaintenanceConfig = {
|
||||||
// title to display
|
monitors: string[]
|
||||||
title: string
|
title?: string
|
||||||
// array of monitor ids
|
body: string
|
||||||
monitors?: string[]
|
start: number | string
|
||||||
// body message
|
end?: number | string
|
||||||
body?: string
|
|
||||||
// start date
|
|
||||||
start?: Date
|
|
||||||
// end Date
|
|
||||||
end?: Date
|
|
||||||
// display color
|
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,17 +21,10 @@ export type PageConfigLink = {
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkerConfig = {
|
export type MonitorTarget = {
|
||||||
kvWriteCooldownMinutes: number
|
|
||||||
monitors: Monitor[]
|
|
||||||
notification?: Notification
|
|
||||||
callbacks?: Callbacks
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Monitor = {
|
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'TCP_PING'
|
method: string
|
||||||
target: string
|
target: string
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
statusPageLink?: string
|
statusPageLink?: string
|
||||||
|
|
@ -52,6 +39,13 @@ export type Monitor = {
|
||||||
checkProxyFallback?: boolean
|
checkProxyFallback?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkerConfig = {
|
||||||
|
kvWriteCooldownMinutes: number
|
||||||
|
monitors: MonitorTarget[]
|
||||||
|
notification?: Notification
|
||||||
|
callbacks?: Callbacks
|
||||||
|
}
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
appriseApiServer?: string
|
appriseApiServer?: string
|
||||||
recipientUrl?: string
|
recipientUrl?: string
|
||||||
|
|
@ -77,3 +71,33 @@ export type Callbacks = {
|
||||||
reason: string
|
reason: string
|
||||||
) => Promise<any>
|
) => 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 { workerConfig, maintenances } from '../../uptime.config'
|
||||||
import { formatStatusChangeNotification, getWorkerLocation, notifyWithApprise } from './util'
|
import { formatStatusChangeNotification, getWorkerLocation, notifyWithApprise } from './util'
|
||||||
import { MonitorState, MonitorTarget } from '../../types/uptime.types'
|
import { MonitorState, MonitorTarget } from '../../types/config'
|
||||||
import { getStatus } from './monitor'
|
import { getStatus } from './monitor'
|
||||||
import { DurableObject } from 'cloudflare:workers'
|
import { DurableObject } from 'cloudflare:workers'
|
||||||
|
|
||||||
|
|
@ -22,23 +22,30 @@ const Worker = {
|
||||||
timeNow: number,
|
timeNow: number,
|
||||||
reason: string
|
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
|
// 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(
|
console.log(
|
||||||
`Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)`
|
`Skipping notification for ${monitor.name} (${monitor.id} in skipNotificationIds)`
|
||||||
)
|
)
|
||||||
return
|
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) {
|
if (workerConfig.notification?.appriseApiServer && workerConfig.notification?.recipientUrl) {
|
||||||
const notification = formatStatusChangeNotification(
|
const notification = formatStatusChangeNotification(
|
||||||
monitor,
|
monitor,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue