mirror of
https://github.com/tormachris/cf-workers-status-page.git
synced 2024-11-23 14:45:41 +01:00
init
This commit is contained in:
commit
e85c5766a7
21
.github/workflows/deploy.yml
vendored
Normal file
21
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Deploy
|
||||
on:
|
||||
- repository_dispatch
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- run: yarn install
|
||||
- run: yarn build
|
||||
- name: Publish
|
||||
uses: cloudflare/wrangler-action@1.2.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
env:
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
IS_WORKER: true
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
out/*
|
||||
!out/.gitkeep
|
||||
|
||||
# production
|
||||
/dist/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
|
||||
/worker
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 80
|
||||
}
|
378
config.yaml
Normal file
378
config.yaml
Normal file
@ -0,0 +1,378 @@
|
||||
settings:
|
||||
title: "Status Page"
|
||||
logo: logo-192x192.png
|
||||
daysInHistory: 90
|
||||
|
||||
allmonitorsOperational: "All Systems Operational"
|
||||
notAllmonitorsOperational: "Not All Systems Operational"
|
||||
monitorLabelOperational: "Operational"
|
||||
monitorLabelNotOperational: "Not great not terrible"
|
||||
monitorLabelNoData: "No data"
|
||||
|
||||
|
||||
monitors:
|
||||
- id: kiwi-com-homepage
|
||||
name: Kiwi.com homepage
|
||||
description: Kiwi.com en homepage
|
||||
url: 'https://www.kiwi.com/en/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
followRedirect: false
|
||||
|
||||
- id: eidam-dev
|
||||
name: Cheesy Status Page
|
||||
description: 'status-page.eidam.dev'
|
||||
url: 'https://status-page.eidam.dev/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: google-com
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: cf-workers-status-page
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: kiwicomapi-cn
|
||||
name: Some other site
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: testy-testy
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: hello-world
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- id: eidam-dev-2
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: google-com-2
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: cf-workers-status-page-2
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: kiwicomapi-cn-2
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: testy-testy-2
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: hello-world-2
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
|
||||
- id: eidam-dev-22
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: google-com-22
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: cf-workers-status-page-22
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: kiwicomapi-cn-22
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: testy-testy-22
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: hello-world-22
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
- id: eidam-dev-333
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: google-com-333
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: cf-workers-status-page-333
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: kiwicomapi-cn-333
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: testy-testy-333
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: hello-world-333
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- id: 25-eidam-dev
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: 25-google-com
|
||||
name: Bing.com
|
||||
description: Bing homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-cf-workers-status-page
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-kiwicomapi-cn
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-testy-testy
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-hello-world
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- id: 25-eidam-dev-2
|
||||
name: Seznam.cz
|
||||
description: 'Just seznam'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: 25-google-com-2
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-cf-workers-status-page-2
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-kiwicomapi-cn-2
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-testy-testy-2
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-hello-world-2
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
|
||||
- id: 25-eidam-dev-22
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: 25-google-com-22
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-cf-workers-status-page-22
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-kiwicomapi-cn-22
|
||||
name: Something totally different
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-testy-testy-22
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-hello-world-22
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
|
||||
- id: 25-eidam-dev-333
|
||||
name: Eidam.dev
|
||||
description: 'Eidam.dev homepage, there is none'
|
||||
url: 'https://eidam.dev'
|
||||
method: GET
|
||||
expectStatus: 403
|
||||
|
||||
- id: 25-google-com-333
|
||||
name: Google.com
|
||||
description: Google homepage
|
||||
url: 'https://www.google.com'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-cf-workers-status-page-333
|
||||
name: This Workers Status Page project made public
|
||||
description: /shrug
|
||||
url: 'https://github.com/adam-janis/cf-workers-status-page'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-kiwicomapi-cn-333
|
||||
name: Kiwi.com API CN
|
||||
description: Is this done yet?
|
||||
url: 'http://kiwicomapi.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-testy-testy-333
|
||||
name: Testy testy
|
||||
description: Something /shrug
|
||||
url: 'http://kiwicomapiiii.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-hello-world-333
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
||||
- id: 25-hello-world-333
|
||||
name: Hello World
|
||||
url: 'http://cnn.cn/'
|
||||
method: GET
|
||||
expectStatus: 200
|
||||
|
11
flareact.config.js
Normal file
11
flareact.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
webpack: (config, options) => {
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: 'yaml-loader',
|
||||
})
|
||||
|
||||
return config
|
||||
},
|
||||
}
|
32
index.js
Normal file
32
index.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { handleEvent } from 'flareact'
|
||||
import { processCronTrigger } from './src/functions/cronTrigger'
|
||||
|
||||
/**
|
||||
* The DEBUG flag will do two things that help during development:
|
||||
* 1. we will skip caching on the edge, which makes it easier to
|
||||
* debug.
|
||||
* 2. we will return an error message on exception in your Response rather
|
||||
* than the default 404.html page.
|
||||
*/
|
||||
const DEBUG = false
|
||||
|
||||
addEventListener('fetch', event => {
|
||||
try {
|
||||
event.respondWith(
|
||||
handleEvent(event, require.context('./pages/', true, /\.js$/), DEBUG),
|
||||
)
|
||||
} catch (e) {
|
||||
if (DEBUG) {
|
||||
return event.respondWith(
|
||||
new Response(e.message || e.toString(), {
|
||||
status: 500,
|
||||
}),
|
||||
)
|
||||
}
|
||||
event.respondWith(new Response('Internal Error', { status: 500 }))
|
||||
}
|
||||
})
|
||||
|
||||
addEventListener('scheduled', event => {
|
||||
event.waitUntil(processCronTrigger(event))
|
||||
})
|
0
out/.gitkeep
Normal file
0
out/.gitkeep
Normal file
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "cf-workers-status-page",
|
||||
"version": "1.0.0",
|
||||
"author": "Adam Janiš <adam.janis@gmail.com>",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "flareact dev",
|
||||
"build": "flareact build",
|
||||
"deploy": "flareact publish",
|
||||
"format": "prettier --write '**/*.{js,css,json,md}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"flareact": "^0.7.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^1.18.2",
|
||||
"yaml-loader": "^0.6.0"
|
||||
}
|
||||
}
|
5
pages/api/triggerCron.js
Normal file
5
pages/api/triggerCron.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { processCronTrigger } from '../../src/functions/cronTrigger'
|
||||
|
||||
export default async event => {
|
||||
return processCronTrigger()
|
||||
}
|
141
pages/index.js
Normal file
141
pages/index.js
Normal file
@ -0,0 +1,141 @@
|
||||
import Head from 'flareact/head'
|
||||
import MonitorHistogram from '../src/components/monitorHistogram'
|
||||
|
||||
import {
|
||||
getLastUpdate,
|
||||
getMonitors,
|
||||
getMonitorsHistory,
|
||||
} from '../src/functions/helpers'
|
||||
|
||||
import config from '../config.yaml'
|
||||
import MonitorStatusLabel from '../src/components/monitorStatusLabel'
|
||||
|
||||
export async function getEdgeProps() {
|
||||
// get KV data
|
||||
const kvMonitors = await getMonitors()
|
||||
const kvMonitorsDays = await getMonitorsHistory()
|
||||
const kvLastUpdate = await getLastUpdate()
|
||||
|
||||
// prepare data maps for components
|
||||
let monitorsOperational = true
|
||||
let kvMonitorsMap = {}
|
||||
kvMonitors.forEach(x => {
|
||||
kvMonitorsMap[x.metadata.id] = x.metadata
|
||||
if (x.metadata.operational === false) monitorsOperational = false
|
||||
})
|
||||
|
||||
let kvMonitorsDaysMap = {}
|
||||
kvMonitorsDays.forEach(x => {
|
||||
kvMonitorsDaysMap[x.name] = x.metadata.operational
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
config,
|
||||
kvMonitorsMap,
|
||||
kvMonitorsDaysMap,
|
||||
monitorsOperational,
|
||||
kvLastUpdate,
|
||||
},
|
||||
// Revalidate these props once every x seconds
|
||||
revalidate: 5,
|
||||
}
|
||||
}
|
||||
|
||||
export default function Index({
|
||||
config,
|
||||
kvMonitorsMap,
|
||||
kvMonitorsDaysMap,
|
||||
monitorsOperational,
|
||||
kvLastUpdate,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>{config.settings.title}</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.css"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link rel="stylesheet" href="./main.css" />
|
||||
</Head>
|
||||
<div className="ui basic segment container">
|
||||
<h1 className="ui huge header">
|
||||
<img
|
||||
className="ui middle aligned tiny image"
|
||||
src={config.settings.logo}
|
||||
/>
|
||||
{config.settings.title}
|
||||
</h1>
|
||||
<div
|
||||
className={`ui inverted segment ${
|
||||
monitorsOperational ? 'green' : 'yellow'
|
||||
}`}
|
||||
>
|
||||
<div className="horizontal flex between">
|
||||
<div className="ui marginless header black-text">
|
||||
{monitorsOperational
|
||||
? config.settings.allmonitorsOperational
|
||||
: config.settings.notAllmonitorsOperational}
|
||||
</div>
|
||||
<div className="black-text">
|
||||
checked {Math.round((Date.now() - kvLastUpdate) / 1000)} sec ago
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{config.monitors.map((monitor, key) => {
|
||||
return (
|
||||
<div key={key} className="ui segment">
|
||||
<div
|
||||
className="ui horizontal flex between"
|
||||
style={{ marginBottom: '8px' }}
|
||||
>
|
||||
<div className="ui marginless header">
|
||||
<span data-tooltip={monitor.description}>
|
||||
<i className="blue small info circle icon" />
|
||||
</span>
|
||||
<div className="content">{monitor.name}</div>
|
||||
</div>
|
||||
<MonitorStatusLabel
|
||||
kvMonitorsMap={kvMonitorsMap}
|
||||
monitor={monitor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MonitorHistogram
|
||||
kvMonitorsDaysMap={kvMonitorsDaysMap}
|
||||
monitor={monitor}
|
||||
/>
|
||||
|
||||
<div className="horizontal flex between grey-text">
|
||||
<div>{config.settings.daysInHistory} days ago</div>
|
||||
<div>Today</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="horizontal flex between grey-text">
|
||||
<div>
|
||||
Powered by{' '}
|
||||
<a href="https://workers.cloudflare.com/" target="_blank">
|
||||
Cloudflare Workers{' '}
|
||||
</a>
|
||||
&{' '}
|
||||
<a href="https://flareact.com/" target="_blank">
|
||||
Flareact{' '}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://github.com/adam-janis/cf-workers-status-page"
|
||||
target="_blank"
|
||||
>
|
||||
Get Your Status Page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
public/logo-192x192.png
Normal file
BIN
public/logo-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
66
public/main.css
Normal file
66
public/main.css
Normal file
@ -0,0 +1,66 @@
|
||||
body {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.flex.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
.flex.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex.between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.marginless {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.paddingless {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.black-text {
|
||||
color: #000 !important;
|
||||
}
|
||||
.grey-text {
|
||||
color: #a0a0a0 !important;
|
||||
}
|
||||
.white-text {
|
||||
color: #fff !important;
|
||||
}
|
||||
.histogram {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.hitbox {
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1px;
|
||||
border-radius: 3.75px;
|
||||
}
|
||||
.bar {
|
||||
background: #dcddde;
|
||||
padding-bottom: 1px;
|
||||
height: 100%;
|
||||
width: 85%;
|
||||
border-radius: 100px;
|
||||
}
|
||||
.bar.green {
|
||||
background: #21ba45;
|
||||
}
|
||||
.bar.red {
|
||||
background: #db2828;
|
||||
}
|
||||
.bar.orange {
|
||||
background: #f2711c;
|
||||
}
|
||||
span i.icon {
|
||||
margin: 0 !important;
|
||||
}
|
49
src/components/monitorHistogram.js
Normal file
49
src/components/monitorHistogram.js
Normal file
@ -0,0 +1,49 @@
|
||||
import config from '../../config.yaml'
|
||||
|
||||
export default function MonitorHistogram({ kvMonitorsDaysMap, monitor }) {
|
||||
let date = new Date()
|
||||
date.setDate(date.getDate() - config.settings.daysInHistory)
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
return (
|
||||
<div
|
||||
key={`${monitor.id}-histogram`}
|
||||
className="horizontal flex histogram"
|
||||
>
|
||||
{Array.from(Array(config.settings.daysInHistory).keys()).map(key => {
|
||||
date.setDate(date.getDate() + 1)
|
||||
const dayInHistory = date.toISOString().split('T')[0]
|
||||
const dayInHistoryKey = 'h_' + monitor.id + '_' + dayInHistory
|
||||
|
||||
let bg = ''
|
||||
let dayInHistoryStatus = 'No data'
|
||||
|
||||
if (typeof kvMonitorsDaysMap[dayInHistoryKey] !== 'undefined') {
|
||||
bg = kvMonitorsDaysMap[dayInHistoryKey] ? 'green' : 'orange'
|
||||
dayInHistoryStatus = kvMonitorsDaysMap[dayInHistoryKey]
|
||||
? 'No outages'
|
||||
: 'Some outages'
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className="hitbox">
|
||||
<div
|
||||
className={`${bg} bar`}
|
||||
data-tooltip={`${dayInHistory} - ${dayInHistoryStatus}`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
key={`${monitor.id}-histogram`}
|
||||
className="horizontal flex histogram"
|
||||
>
|
||||
<div className="grey-text">Loading histogram ...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
16
src/components/monitorStatusLabel.js
Normal file
16
src/components/monitorStatusLabel.js
Normal file
@ -0,0 +1,16 @@
|
||||
export default function MonitorStatusLabel({ kvMonitorsMap, monitor }) {
|
||||
let labelColor = 'grey'
|
||||
let labelText = 'No data'
|
||||
|
||||
if (typeof kvMonitorsMap[monitor.id] !== 'undefined') {
|
||||
if (kvMonitorsMap[monitor.id].operational) {
|
||||
labelColor = 'green'
|
||||
labelText = 'Operational'
|
||||
} else {
|
||||
labelColor = 'orange'
|
||||
labelText = 'Not great not terrible'
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={`ui ${labelColor} horizontal label`}>{labelText}</div>
|
||||
}
|
66
src/css/index.css
Normal file
66
src/css/index.css
Normal file
@ -0,0 +1,66 @@
|
||||
body {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.flex.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
.flex.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex.between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.marginless {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.paddingless {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.black-text {
|
||||
color: #000 !important;
|
||||
}
|
||||
.grey-text {
|
||||
color: #a0a0a0 !important;
|
||||
}
|
||||
.white-text {
|
||||
color: #fff !important;
|
||||
}
|
||||
.histogram {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.hitbox {
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1px;
|
||||
border-radius: 3.75px;
|
||||
}
|
||||
.bar {
|
||||
background: #dcddde;
|
||||
padding-bottom: 1px;
|
||||
height: 100%;
|
||||
width: 85%;
|
||||
border-radius: 100px;
|
||||
}
|
||||
.bar.green {
|
||||
background: #21ba45;
|
||||
}
|
||||
.bar.red {
|
||||
background: #db2828;
|
||||
}
|
||||
.bar.orange {
|
||||
background: #f2711c;
|
||||
}
|
||||
span i.icon {
|
||||
margin: 0 !important;
|
||||
}
|
57
src/functions/cronTrigger.js
Normal file
57
src/functions/cronTrigger.js
Normal file
@ -0,0 +1,57 @@
|
||||
import config from '../../config.yaml'
|
||||
|
||||
import { setKV, getKV, getKVWithMetadata, gcMonitors } from './helpers'
|
||||
|
||||
export async function processCronTrigger(event) {
|
||||
for (const monitor of config.monitors) {
|
||||
console.log(`Checking ${monitor.name} ...`)
|
||||
|
||||
const init = {
|
||||
method: monitor.method || 'GET',
|
||||
redirect: monitor.followRedirect ? 'follow' : 'manual',
|
||||
headers: {
|
||||
'User-Agent': 'cf-worker-status-page',
|
||||
},
|
||||
}
|
||||
|
||||
const response = await fetch(monitor.url, init)
|
||||
const monitorOperational = response.status === (monitor.expectStatus || 200)
|
||||
const kvMonitor = await getKVWithMetadata('s_' + monitor.id)
|
||||
|
||||
// metadata from monitor settings
|
||||
const metadata = {
|
||||
operational: monitorOperational,
|
||||
statusCode: response.status,
|
||||
id: monitor.id,
|
||||
}
|
||||
|
||||
// write current status if status changed or for first time
|
||||
if (
|
||||
!kvMonitor.metadata ||
|
||||
kvMonitor.metadata.operational !== monitorOperational
|
||||
) {
|
||||
console.log('saving new results..')
|
||||
|
||||
if (typeof SECRET_SLACK_WEBHOOK !== 'undefined') {
|
||||
await notifySlack(metadata)
|
||||
}
|
||||
|
||||
await setKV('s_' + monitor.id, null, metadata)
|
||||
}
|
||||
|
||||
// check day status, write only on not operational or for first time
|
||||
const kvDayStatusKey =
|
||||
'h_' + monitor.id + '_' + new Date().toISOString().split('T')[0]
|
||||
//console.log(kvDayStatusKey)
|
||||
const kvDayStatus = await getKV(kvDayStatusKey)
|
||||
|
||||
if (!kvDayStatus || (kvDayStatus && !monitorOperational)) {
|
||||
await setKV(kvDayStatusKey, null, metadata)
|
||||
}
|
||||
|
||||
await setKV('lastUpdate', Date.now())
|
||||
}
|
||||
await gcMonitors(config)
|
||||
|
||||
return new Response('OK')
|
||||
}
|
89
src/functions/helpers.js
Normal file
89
src/functions/helpers.js
Normal file
@ -0,0 +1,89 @@
|
||||
export async function getMonitors() {
|
||||
const monitors = await listKV('s_')
|
||||
return monitors.keys
|
||||
}
|
||||
|
||||
export async function getMonitorsHistory() {
|
||||
const monitorsHistory = await listKV('h_', 600)
|
||||
return monitorsHistory.keys
|
||||
}
|
||||
|
||||
export async function getLastUpdate() {
|
||||
return await getKV('lastUpdate')
|
||||
}
|
||||
|
||||
export async function listKV(prefix = '', cacheTtl = false) {
|
||||
const cacheKey = 'list_' + prefix + '_' + process.env.BUILD_ID
|
||||
const cachedResponse = await getKV(cacheKey)
|
||||
|
||||
if (cacheTtl && cachedResponse) {
|
||||
return JSON.parse(cachedResponse)
|
||||
}
|
||||
|
||||
let list = []
|
||||
let cursor = null
|
||||
let res = {}
|
||||
do {
|
||||
res = await KV_STATUS_PAGE.list({ prefix: prefix, cursor })
|
||||
list = list.concat(res.keys)
|
||||
cursor = res.cursor
|
||||
} while (!res.list_complete)
|
||||
|
||||
if (cacheTtl) {
|
||||
await setKV(cacheKey, JSON.stringify({ keys: list }), null, 600)
|
||||
}
|
||||
return { keys: list }
|
||||
}
|
||||
|
||||
export async function setKV(key, value, metadata, expirationTtl) {
|
||||
return KV_STATUS_PAGE.put(key, value, { metadata, expirationTtl })
|
||||
}
|
||||
|
||||
export async function getKV(key, type = 'text') {
|
||||
return KV_STATUS_PAGE.get(key, type)
|
||||
}
|
||||
|
||||
export async function getKVWithMetadata(key) {
|
||||
return KV_STATUS_PAGE.getWithMetadata(key)
|
||||
}
|
||||
|
||||
export async function deleteKV(key) {
|
||||
return KV_STATUS_PAGE.delete(key)
|
||||
}
|
||||
|
||||
export async function gcMonitors(config) {
|
||||
const checkKvPrefix = 's_'
|
||||
|
||||
const monitors = config.monitors.map(key => {
|
||||
return key.id
|
||||
})
|
||||
|
||||
const kvMonitors = await listKV(checkKvPrefix)
|
||||
const kvState = kvMonitors.keys.map(key => {
|
||||
return key.metadata.id
|
||||
})
|
||||
|
||||
const keysForRemoval = kvState.filter(x => !monitors.includes(x))
|
||||
|
||||
keysForRemoval.forEach(key => {
|
||||
console.log('gc: deleting ' + checkKvPrefix + key)
|
||||
deleteKV(checkKvPrefix + key)
|
||||
})
|
||||
}
|
||||
|
||||
async function notifySlack(monitor, metadata) {
|
||||
const blocks = [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `Some monitor is now in :this: status`,
|
||||
},
|
||||
},
|
||||
]
|
||||
return fetch(SECRET_SLACK_WEBHOOK_URL, {
|
||||
body: JSON.stringify({ blocks }),
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
23
wrangler.toml
Normal file
23
wrangler.toml
Normal file
@ -0,0 +1,23 @@
|
||||
name = "cf-workers-status-page"
|
||||
type = "webpack"
|
||||
account_id = ""
|
||||
workers_dev = true
|
||||
route = ""
|
||||
zone_id = ""
|
||||
webpack_config = "node_modules/flareact/webpack"
|
||||
|
||||
# uncomment and adjust following if you are not using GitHub Actions
|
||||
# kv_namespaces = [{binding="KV_GITHUB_RELEASES", id="xxxx"}]
|
||||
# preview_id = "9581809385634861ae93b0e01677b44d"
|
||||
|
||||
# delete afterwards
|
||||
kv-namespaces = [
|
||||
{ binding = "KV_STATUS_PAGE", id = "c27344947ebb476880fa2ba0ef9bbd10", preview_id = "c27344947ebb476880fa2ba0ef9bbd10" }
|
||||
]
|
||||
|
||||
[triggers]
|
||||
crons = ["* * * * *"]
|
||||
|
||||
[site]
|
||||
bucket = "out"
|
||||
entry-point = "./"
|
Loading…
Reference in New Issue
Block a user