format code and no semi
parent
eba65bd654
commit
87611700c3
|
|
@ -2,3 +2,4 @@ trailingComma: 'es5'
|
||||||
tabWidth: 2
|
tabWidth: 2
|
||||||
semi: false
|
semi: false
|
||||||
singleQuote: true
|
singleQuote: true
|
||||||
|
printWidth: 100
|
||||||
|
|
@ -3,11 +3,7 @@ import classes from '@/styles/Header.module.css'
|
||||||
import config from '@/uptime.config'
|
import config from '@/uptime.config'
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const linkToElement = (link: {
|
const linkToElement = (link: { label: string; link: string; highlight?: boolean }) => {
|
||||||
label: string
|
|
||||||
link: string
|
|
||||||
highlight?: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={link.label}
|
key={link.label}
|
||||||
|
|
@ -46,9 +42,7 @@ export default function Header() {
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group gap={5} hiddenFrom="sm">
|
<Group gap={5} hiddenFrom="sm">
|
||||||
{config.page.links
|
{config.page.links.filter((link) => link.highlight).map(linkToElement)}
|
||||||
.filter((link) => link.highlight)
|
|
||||||
.map(linkToElement)}
|
|
||||||
</Group>
|
</Group>
|
||||||
</Container>
|
</Container>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -31,21 +31,17 @@ export default function MonitorDetail({
|
||||||
{monitor.name}
|
{monitor.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text mt="sm" fw={700}>
|
<Text mt="sm" fw={700}>
|
||||||
No data available, please make sure you have deployed your workers
|
No data available, please make sure you have deployed your workers with latest config and
|
||||||
with latest config and check your worker status!
|
check your worker status!
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const statusIcon =
|
const statusIcon =
|
||||||
state.incident[monitor.id].slice(-1)[0].end === undefined ? (
|
state.incident[monitor.id].slice(-1)[0].end === undefined ? (
|
||||||
<IconAlertCircle
|
<IconAlertCircle style={{ width: '1.25em', height: '1.25em', color: '#b91c1c' }} />
|
||||||
style={{ width: '1.25em', height: '1.25em', color: '#b91c1c' }}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<IconCircleCheck
|
<IconCircleCheck style={{ width: '1.25em', height: '1.25em', color: '#059669' }} />
|
||||||
style={{ width: '1.25em', height: '1.25em', color: '#059669' }}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let totalTime = Date.now() / 1000 - state.incident[monitor.id][0].start[0]
|
let totalTime = Date.now() / 1000 - state.incident[monitor.id][0].start[0]
|
||||||
|
|
@ -56,26 +52,15 @@ export default function MonitorDetail({
|
||||||
|
|
||||||
console.log(totalTime)
|
console.log(totalTime)
|
||||||
console.log(downTime)
|
console.log(downTime)
|
||||||
const uptimePercent = (
|
const uptimePercent = (((totalTime - downTime) / totalTime) * 100).toPrecision(4)
|
||||||
((totalTime - downTime) / totalTime) *
|
|
||||||
100
|
|
||||||
).toPrecision(4)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Text
|
<Text mt="sm" fw={700} style={{ display: 'inline-flex', alignItems: 'center' }}>
|
||||||
mt="sm"
|
|
||||||
fw={700}
|
|
||||||
style={{ display: 'inline-flex', alignItems: 'center' }}
|
|
||||||
>
|
|
||||||
{statusIcon} {monitor.name}
|
{statusIcon} {monitor.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text mt="sm" fw={700} style={{ display: 'inline', color: getColor(uptimePercent, true) }}>
|
||||||
mt="sm"
|
|
||||||
fw={700}
|
|
||||||
style={{ display: 'inline', color: getColor(uptimePercent, true) }}
|
|
||||||
>
|
|
||||||
Overall: {uptimePercent}%
|
Overall: {uptimePercent}%
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,7 @@ import { MonitorState, MonitorTarget } from '@/uptime.types'
|
||||||
import { Card, Center, Divider } from '@mantine/core'
|
import { Card, Center, Divider } from '@mantine/core'
|
||||||
import MonitorDetail from './MonitorDetail'
|
import MonitorDetail from './MonitorDetail'
|
||||||
|
|
||||||
export default function MonitorList({
|
export default function MonitorList({ config, state }: { config: any; state: MonitorState }) {
|
||||||
config,
|
|
||||||
state,
|
|
||||||
}: {
|
|
||||||
config: any
|
|
||||||
state: MonitorState
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
<Card
|
<Card
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,14 @@ export default function OverallStatus({
|
||||||
state: { overallUp: number; overallDown: number; lastUpdate: number }
|
state: { overallUp: number; overallDown: number; lastUpdate: number }
|
||||||
}) {
|
}) {
|
||||||
let statusString = ''
|
let statusString = ''
|
||||||
let icon = (
|
let icon = <IconAlertCircle style={{ width: 64, height: 64, color: '#b91c1c' }} />
|
||||||
<IconAlertCircle style={{ width: 64, height: 64, color: '#b91c1c' }} />
|
|
||||||
)
|
|
||||||
if (state.overallUp === 0 && state.overallDown === 0) {
|
if (state.overallUp === 0 && state.overallDown === 0) {
|
||||||
statusString = 'No data yet'
|
statusString = 'No data yet'
|
||||||
} else if (state.overallUp === 0) {
|
} else if (state.overallUp === 0) {
|
||||||
statusString = 'All systems not operational'
|
statusString = 'All systems not operational'
|
||||||
} else if (state.overallDown === 0) {
|
} else if (state.overallDown === 0) {
|
||||||
statusString = 'All systems operational'
|
statusString = 'All systems operational'
|
||||||
icon = (
|
icon = <IconCircleCheck style={{ width: 64, height: 64, color: '#059669' }} />
|
||||||
<IconCircleCheck style={{ width: 64, height: 64, color: '#059669' }} />
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
statusString = `Some systems not operational (${state.overallDown} out of ${
|
statusString = `Some systems not operational (${state.overallDown} out of ${
|
||||||
state.overallUp + state.overallDown
|
state.overallUp + state.overallDown
|
||||||
|
|
@ -31,11 +27,7 @@ export default function OverallStatus({
|
||||||
<Title mt="sm" style={{ textAlign: 'center' }} order={1}>
|
<Title mt="sm" style={{ textAlign: 'center' }} order={1}>
|
||||||
{statusString}
|
{statusString}
|
||||||
</Title>
|
</Title>
|
||||||
<Title
|
<Title mt="sm" style={{ textAlign: 'center', color: '#70778c' }} order={5}>
|
||||||
mt="sm"
|
|
||||||
style={{ textAlign: 'center', color: '#70778c' }}
|
|
||||||
order={5}
|
|
||||||
>
|
|
||||||
Last updated on:{' '}
|
Last updated on:{' '}
|
||||||
{`${new Date(state.lastUpdate * 1000).toLocaleString()} (${
|
{`${new Date(state.lastUpdate * 1000).toLocaleString()} (${
|
||||||
Math.round(Date.now() / 1000) - state.lastUpdate
|
Math.round(Date.now() / 1000) - state.lastUpdate
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ export default function Home({ state }: { state: MonitorState }) {
|
||||||
{state === undefined ? (
|
{state === undefined ? (
|
||||||
<Center>
|
<Center>
|
||||||
<Text fw={700}>
|
<Text fw={700}>
|
||||||
Monitor State is not defined now, please check your worker's
|
Monitor State is not defined now, please check your worker's status and KV
|
||||||
status and KV binding!
|
binding!
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -64,10 +64,7 @@ export async function getServerSideProps() {
|
||||||
const { UPTIMEFLARE_STATE } = process.env as unknown as {
|
const { UPTIMEFLARE_STATE } = process.env as unknown as {
|
||||||
UPTIMEFLARE_STATE: KVNamespace
|
UPTIMEFLARE_STATE: KVNamespace
|
||||||
}
|
}
|
||||||
const state = (await UPTIMEFLARE_STATE?.get(
|
const state = (await UPTIMEFLARE_STATE?.get('state', 'json')) as unknown as MonitorState
|
||||||
'state',
|
|
||||||
'json'
|
|
||||||
)) as unknown as MonitorState
|
|
||||||
|
|
||||||
return { props: { state } }
|
return { props: { state } }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
height: rem(56px);
|
height: rem(56px);
|
||||||
margin-bottom: rem(120px);
|
margin-bottom: rem(120px);
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
border-bottom: rem(1px) solid
|
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
||||||
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
|
|
@ -24,10 +23,7 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
@mixin hover {
|
@mixin hover {
|
||||||
background-color: light-dark(
|
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||||
var(--mantine-color-gray-0),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-mantine-color-scheme] &[data-active] {
|
[data-mantine-color-scheme] &[data-active] {
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ const config = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
callback: async (statusChangeMsg: string) => {
|
callback: async (statusChangeMsg: string) => {
|
||||||
await fetch(
|
await fetch('https://server.lyc8503.site/wepush?key=wepushkey&msg=' + statusChangeMsg)
|
||||||
'https://server.lyc8503.site/wepush?key=wepushkey&msg=' + statusChangeMsg
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
monitors: [
|
monitors: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,47 @@
|
||||||
import { connect } from 'cloudflare:sockets';
|
import { connect } from 'cloudflare:sockets'
|
||||||
import config from '../../uptime.config';
|
import config from '../../uptime.config'
|
||||||
import { fetchTimeout, getWorkerLocation, withTimeout } from './util';
|
import { fetchTimeout, getWorkerLocation, withTimeout } from './util'
|
||||||
import { MonitorState, MonitorTarget } from '../../uptime.types';
|
import { MonitorState, MonitorTarget } from '../../uptime.types'
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
UPTIMEFLARE_STATE: KVNamespace;
|
UPTIMEFLARE_STATE: KVNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getStatus(monitor: MonitorTarget): Promise<{ ping: number; up: boolean; err: string }> {
|
async function getStatus(
|
||||||
|
monitor: MonitorTarget
|
||||||
|
): Promise<{ ping: number; up: boolean; err: string }> {
|
||||||
let status = {
|
let status = {
|
||||||
ping: 0,
|
ping: 0,
|
||||||
up: false,
|
up: false,
|
||||||
err: 'Unknown',
|
err: 'Unknown',
|
||||||
};
|
}
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now()
|
||||||
|
|
||||||
if (monitor.method === 'TCP_PING') {
|
if (monitor.method === 'TCP_PING') {
|
||||||
// TCP port endpoint monitor
|
// TCP port endpoint monitor
|
||||||
// TODO: TCP timeout
|
// TODO: TCP timeout
|
||||||
try {
|
try {
|
||||||
const [hostname, port] = monitor.target.split(':');
|
const [hostname, port] = monitor.target.split(':')
|
||||||
|
|
||||||
// Write "PING\n"
|
// Write "PING\n"
|
||||||
const socket = connect({ hostname: hostname, port: Number(port) });
|
const socket = connect({ hostname: hostname, port: Number(port) })
|
||||||
const writer = socket.writable.getWriter();
|
const writer = socket.writable.getWriter()
|
||||||
await writer.write(new TextEncoder().encode('PING\n'));
|
await writer.write(new TextEncoder().encode('PING\n'))
|
||||||
// Can't do this: await socket.close()
|
// Can't do this: await socket.close()
|
||||||
|
|
||||||
// https://github.com/cloudflare/workerd/issues/1305
|
// https://github.com/cloudflare/workerd/issues/1305
|
||||||
await withTimeout(monitor.timeout || 10000, socket.closed);
|
await withTimeout(monitor.timeout || 10000, socket.closed)
|
||||||
|
|
||||||
console.log(`${monitor.name} connected to ${monitor.target}`);
|
console.log(`${monitor.name} connected to ${monitor.target}`)
|
||||||
|
|
||||||
status.ping = Date.now() - startTime;
|
status.ping = Date.now() - startTime
|
||||||
status.up = true;
|
status.up = true
|
||||||
status.err = '';
|
status.err = ''
|
||||||
} catch (e: Error | any) {
|
} catch (e: Error | any) {
|
||||||
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`);
|
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`)
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = e.name + ': ' + e.message;
|
status.err = e.name + ': ' + e.message
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// HTTP endpoint monitor
|
// HTTP endpoint monitor
|
||||||
|
|
@ -53,82 +55,86 @@ async function getStatus(monitor: MonitorTarget): Promise<{ ping: number; up: bo
|
||||||
'100-599': -1, // Don't cache any status code, from https://developers.cloudflare.com/workers/runtime-apis/request/#requestinitcfproperties
|
'100-599': -1, // Don't cache any status code, from https://developers.cloudflare.com/workers/runtime-apis/request/#requestinitcfproperties
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
console.log(`${monitor.name} responded with ${response.status}`);
|
console.log(`${monitor.name} responded with ${response.status}`)
|
||||||
status.ping = Date.now() - startTime;
|
status.ping = Date.now() - startTime
|
||||||
|
|
||||||
if (monitor.expectedCodes) {
|
if (monitor.expectedCodes) {
|
||||||
if (!monitor.expectedCodes.includes(response.status)) {
|
if (!monitor.expectedCodes.includes(response.status)) {
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${response.status}`;
|
status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${
|
||||||
return status;
|
response.status
|
||||||
|
}`
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (response.status < 200 || response.status > 299) {
|
if (response.status < 200 || response.status > 299) {
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = `Expected codes: 2xx, Got: ${response.status}`;
|
status.err = `Expected codes: 2xx, Got: ${response.status}`
|
||||||
return status;
|
return status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor.responseKeyword) {
|
if (monitor.responseKeyword) {
|
||||||
const responseBody = await response.text();
|
const responseBody = await response.text()
|
||||||
if (!responseBody.includes(monitor.responseKeyword)) {
|
if (!responseBody.includes(monitor.responseKeyword)) {
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = "HTTP response doesn't contain the configured keyword";
|
status.err = "HTTP response doesn't contain the configured keyword"
|
||||||
return status;
|
return status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status.up = true;
|
status.up = true
|
||||||
status.err = '';
|
status.err = ''
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`);
|
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`)
|
||||||
if (e.name === 'AbortError') {
|
if (e.name === 'AbortError') {
|
||||||
status.ping = monitor.timeout || 10000;
|
status.ping = monitor.timeout || 10000
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = `Timeout after ${status.ping}ms`;
|
status.err = `Timeout after ${status.ping}ms`
|
||||||
} else {
|
} else {
|
||||||
status.up = false;
|
status.up = false
|
||||||
status.err = e.name + ': ' + e.message;
|
status.err = e.name + ': ' + e.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
|
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
|
||||||
const workerLocation = (await getWorkerLocation()) || 'ERROR';
|
const workerLocation = (await getWorkerLocation()) || 'ERROR'
|
||||||
console.log(`Running scheduled event on ${workerLocation}...`);
|
console.log(`Running scheduled event on ${workerLocation}...`)
|
||||||
|
|
||||||
// Read state, set init state if it doesn't exist
|
// Read state, set init state if it doesn't exist
|
||||||
let state =
|
let state =
|
||||||
((await env.UPTIMEFLARE_STATE.get('state', { type: 'json' })) as unknown as MonitorState) ||
|
((await env.UPTIMEFLARE_STATE.get('state', {
|
||||||
|
type: 'json',
|
||||||
|
})) as unknown as MonitorState) ||
|
||||||
({
|
({
|
||||||
lastUpdate: 0,
|
lastUpdate: 0,
|
||||||
overallUp: 0,
|
overallUp: 0,
|
||||||
overallDown: 0,
|
overallDown: 0,
|
||||||
incident: {},
|
incident: {},
|
||||||
latency: {},
|
latency: {},
|
||||||
} as MonitorState);
|
} as MonitorState)
|
||||||
state.overallDown = 0;
|
state.overallDown = 0
|
||||||
state.overallUp = 0;
|
state.overallUp = 0
|
||||||
|
|
||||||
// Check each monitor
|
// Check each monitor
|
||||||
// TODO: callback exception handler
|
// TODO: callback exception handler
|
||||||
// TODO: advanced status check
|
// TODO: advanced status check
|
||||||
// TODO: concurrent status check
|
// TODO: concurrent status check
|
||||||
for (const monitor of config.monitors) {
|
for (const monitor of config.monitors) {
|
||||||
console.log(`[${workerLocation}] Checking ${monitor.name}...`);
|
console.log(`[${workerLocation}] Checking ${monitor.name}...`)
|
||||||
|
|
||||||
const status = await getStatus(monitor);
|
const status = await getStatus(monitor)
|
||||||
const currentTimeSecond = Math.round(Date.now() / 1000);
|
const currentTimeSecond = Math.round(Date.now() / 1000)
|
||||||
|
|
||||||
// Update counters
|
// Update counters
|
||||||
status.up ? state.overallUp++ : state.overallDown++;
|
status.up ? state.overallUp++ : state.overallDown++
|
||||||
|
|
||||||
// Update incidents
|
// Update incidents
|
||||||
|
|
||||||
|
|
@ -139,21 +145,23 @@ export default {
|
||||||
end: currentTimeSecond,
|
end: currentTimeSecond,
|
||||||
error: ['dummy'],
|
error: ['dummy'],
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
// Then lastIncident here must not be undefined
|
// Then lastIncident here must not be undefined
|
||||||
const lastIncident = state.incident[monitor.id].slice(-1)[0];
|
const lastIncident = state.incident[monitor.id].slice(-1)[0]
|
||||||
const timeString = new Date().toLocaleString(config.dateLocale, { timeZone: config.timezone });
|
const timeString = new Date().toLocaleString(config.dateLocale, {
|
||||||
|
timeZone: config.timezone,
|
||||||
|
})
|
||||||
|
|
||||||
if (status.up) {
|
if (status.up) {
|
||||||
// Current status is up
|
// Current status is up
|
||||||
// close existing incident if any
|
// close existing incident if any
|
||||||
if (lastIncident.end === undefined) {
|
if (lastIncident.end === undefined) {
|
||||||
lastIncident.end = currentTimeSecond;
|
lastIncident.end = currentTimeSecond
|
||||||
await config.callback(
|
await config.callback(
|
||||||
`✔️${monitor.name} came back up at ${timeString} after ${Math.round(
|
`✔️${monitor.name} came back up at ${timeString} after ${Math.round(
|
||||||
(lastIncident.end - lastIncident.start.slice(-1)[0]) / 60,
|
(lastIncident.end - lastIncident.start.slice(-1)[0]) / 60
|
||||||
)} minutes of downtime`,
|
)} minutes of downtime`
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Current status is down
|
// Current status is down
|
||||||
|
|
@ -163,13 +171,20 @@ export default {
|
||||||
start: [currentTimeSecond],
|
start: [currentTimeSecond],
|
||||||
end: undefined,
|
end: undefined,
|
||||||
error: [status.err],
|
error: [status.err],
|
||||||
});
|
})
|
||||||
await config.callback(`❌${monitor.name} went down at ${timeString} with error ${status.err}`);
|
await config.callback(
|
||||||
} else if (lastIncident.end === undefined && lastIncident.error.slice(-1)[0] !== status.err) {
|
`❌${monitor.name} went down at ${timeString} with error ${status.err}`
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
lastIncident.end === undefined &&
|
||||||
|
lastIncident.error.slice(-1)[0] !== status.err
|
||||||
|
) {
|
||||||
// append if the error message changes
|
// append if the error message changes
|
||||||
lastIncident.start.push(currentTimeSecond);
|
lastIncident.start.push(currentTimeSecond)
|
||||||
lastIncident.error.push(status.err);
|
lastIncident.error.push(status.err)
|
||||||
await config.callback(`❌${monitor.name} is still down at ${timeString} with error ${status.err}`);
|
await config.callback(
|
||||||
|
`❌${monitor.name} is still down at ${timeString} with error ${status.err}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,30 +192,30 @@ export default {
|
||||||
let latencyLists = state.latency[monitor.id] || {
|
let latencyLists = state.latency[monitor.id] || {
|
||||||
recent: [],
|
recent: [],
|
||||||
all: [],
|
all: [],
|
||||||
};
|
}
|
||||||
|
|
||||||
const record = {
|
const record = {
|
||||||
loc: workerLocation,
|
loc: workerLocation,
|
||||||
ping: status.ping,
|
ping: status.ping,
|
||||||
time: currentTimeSecond,
|
time: currentTimeSecond,
|
||||||
};
|
}
|
||||||
latencyLists.recent.push(record);
|
latencyLists.recent.push(record)
|
||||||
if (latencyLists.all.length === 0 || currentTimeSecond - latencyLists.all[0].time > 60 * 60) {
|
if (latencyLists.all.length === 0 || currentTimeSecond - latencyLists.all[0].time > 60 * 60) {
|
||||||
latencyLists.all.push(record);
|
latencyLists.all.push(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
// discard old data
|
// discard old data
|
||||||
while (latencyLists.recent[0]?.time < currentTimeSecond - 12 * 60 * 60) {
|
while (latencyLists.recent[0]?.time < currentTimeSecond - 12 * 60 * 60) {
|
||||||
latencyLists.recent.shift();
|
latencyLists.recent.shift()
|
||||||
}
|
}
|
||||||
while (latencyLists.all[0]?.time < currentTimeSecond - 90 * 24 * 60 * 60) {
|
while (latencyLists.all[0]?.time < currentTimeSecond - 90 * 24 * 60 * 60) {
|
||||||
latencyLists.all.shift();
|
latencyLists.all.shift()
|
||||||
}
|
}
|
||||||
state.latency[monitor.id] = latencyLists;
|
state.latency[monitor.id] = latencyLists
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.lastUpdate = Math.round(Date.now() / 1000);
|
state.lastUpdate = Math.round(Date.now() / 1000)
|
||||||
await env.UPTIMEFLARE_STATE.put('state', JSON.stringify(state));
|
await env.UPTIMEFLARE_STATE.put('state', JSON.stringify(state))
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,29 @@
|
||||||
async function getWorkerLocation() {
|
async function getWorkerLocation() {
|
||||||
const res = await fetch('https://cloudflare.com/cdn-cgi/trace');
|
const res = await fetch('https://cloudflare.com/cdn-cgi/trace')
|
||||||
const text = await res.text();
|
const text = await res.text()
|
||||||
|
|
||||||
const colo = /^colo=(.*)$/m.exec(text)?.[1];
|
const colo = /^colo=(.*)$/m.exec(text)?.[1]
|
||||||
return colo;
|
return colo
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchTimeout = (
|
const fetchTimeout = (
|
||||||
url: string,
|
url: string,
|
||||||
ms: number,
|
ms: number,
|
||||||
{ signal, ...options }: RequestInit<RequestInitCfProperties> | undefined = {},
|
{ signal, ...options }: RequestInit<RequestInitCfProperties> | undefined = {}
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController()
|
||||||
const promise = fetch(url, { signal: controller.signal, ...options });
|
const promise = fetch(url, { signal: controller.signal, ...options })
|
||||||
if (signal) signal.addEventListener('abort', () => controller.abort());
|
if (signal) signal.addEventListener('abort', () => controller.abort())
|
||||||
const timeout = setTimeout(() => controller.abort(), ms);
|
const timeout = setTimeout(() => controller.abort(), ms)
|
||||||
return promise.finally(() => clearTimeout(timeout));
|
return promise.finally(() => clearTimeout(timeout))
|
||||||
};
|
|
||||||
|
|
||||||
function withTimeout<T>(millis: number, promise: Promise<T>): Promise<T> {
|
|
||||||
const timeout = new Promise<T>((resolve, reject) => setTimeout(() => reject(new Error(`Promise timed out after ${millis}ms`)), millis));
|
|
||||||
|
|
||||||
return Promise.race([promise, timeout]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getWorkerLocation, fetchTimeout, withTimeout };
|
function withTimeout<T>(millis: number, promise: Promise<T>): Promise<T> {
|
||||||
|
const timeout = new Promise<T>((resolve, reject) =>
|
||||||
|
setTimeout(() => reject(new Error(`Promise timed out after ${millis}ms`)), millis)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Promise.race([promise, timeout])
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getWorkerLocation, fetchTimeout, withTimeout }
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
"lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
"lib": [
|
||||||
|
"es2021"
|
||||||
|
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||||
"jsx": "react" /* Specify what JSX code is generated. */,
|
"jsx": "react" /* Specify what JSX code is generated. */,
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
|
@ -31,7 +33,9 @@
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
"types": ["@cloudflare/workers-types"] /* Specify type package names to be included without being referenced in a source file. */,
|
"types": [
|
||||||
|
"@cloudflare/workers-types"
|
||||||
|
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
"resolveJsonModule": true /* Enable importing .json files */,
|
"resolveJsonModule": true /* Enable importing .json files */,
|
||||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue