A simple, universal public Statuspage. https://status.sp-codes.de
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
universal-statuspage/src/main.status.ts

118 lines
2.6 KiB

import {json, Router} from 'express';
import {CurrentStatus, State} from "./app/_data/data";
import {existsSync, readFileSync, writeFileSync} from "fs";
interface Cache {
[id: string]: State
}
interface Config {
authToken: string;
title: string;
description: string;
groups: {
id: string;
name: string;
services: {
id: string;
name: string;
url: string;
}[];
}[];
}
interface GrafanaWebhookBody {
dashboardId: number;
evalMatches: {
value: number,
metric: string,
tags: any
}[];
imageUrl: string,
message: string,
orgId: number,
panelId: number,
ruleId: number,
ruleName: string,
ruleUrl: string,
state: "ok" | "paused" | "alerting" | "pending" | "no_data";
tags: { [key: string]: string },
title: string
}
const api = Router();
api.use(json());
const config = JSON.parse(readFileSync('config.json', {encoding: 'UTF-8'})) as Config;
const serviceStates = existsSync('cache.json') ? JSON.parse(readFileSync('cache.json', {encoding: 'UTF-8'})) : {} as Cache;
let cache: CurrentStatus;
updateCache();
api.post('/update/health', (req, res) => {
const token = req.query.token;
if (token !== config.authToken) {
return res.status(401).send('invalid token');
}
const serviceId = req.query.service as string;
const message = req.body as GrafanaWebhookBody;
switch (message.state) {
case "no_data":
case "alerting":
serviceStates[serviceId] = "outage";
break;
case "paused":
serviceStates[serviceId] = "maintenance";
break;
default:
serviceStates[serviceId] = "operational"
}
updateCache();
writeFileSync('cache.json', JSON.stringify(serviceStates), {encoding: 'UTF-8'});
return res.send('OK');
});
api.get('/status', (req, res) => {
return res.json(cache);
});
api.get('/info', (req, res) => {
return res.json({
title: config.title,
description: config.description
});
});
function updateCache(): void {
const groups = config.groups.map(group => {
const services = group.services.map(service => {
return {
id: service.id,
name: service.name,
url: service.url,
state: serviceStates[service.id] || "operational"
}
});
return {
id: group.id,
name: group.name,
state: calculateOverallState(services.map(s => s.state)),
services: services
}
});
cache = {
state: calculateOverallState(groups.map(g => g.state)),
groups: groups
};
}
function calculateOverallState(states: State[]): State {
return states.includes("outage") ? "outage" : states.includes("maintenance") ? "maintenance" : "operational"
}
export {api};