made statuspage universal

updated angular to v10
added renovate.json
updated .drone.yml
added arm image
This commit is contained in:
Samuel Philipp 2020-07-05 20:19:52 +02:00
parent 8b89e10cc3
commit a580a2ee6b
15 changed files with 261 additions and 106 deletions

View file

@ -1,18 +1,92 @@
kind: pipeline
name: default
type: docker
name: linux-amd64
platform:
os: linux
arch: amd64
steps:
- name: build
image: node
image: node:alpine
commands:
- npm install
- npm run build:ssr
- name: docker
image: plugins/docker
settings:
repo: samuelph/grafana-statuspage
auto_tag: true
auto_tag_suffix: linux-amd64
repo: samuelph/universal-statuspage
username:
from_secret: USERNAME
password:
from_secret: PASSWORD
trigger:
branch:
- master
---
kind: pipeline
type: docker
name: linux-arm
platform:
os: linux
arch: arm
steps:
- name: build
image: node:alpine
commands:
- npm install
- npm run build:ssr
- name: docker
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm
repo: samuelph/universal-statuspage
username:
from_secret: USERNAME
password:
from_secret: PASSWORD
when:
branch:
- master
event:
- push
---
kind: pipeline
type: docker
name: manifest
platform:
os: linux
arch: arm
steps:
- name: publish
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
target: samuelph/universal-statuspage
template: samuelph/universal-statuspage:OS-ARCH
platforms:
- linux/amd64
- linux/arm
username:
from_secret: USERNAME
password:
from_secret: PASSWORD
depends_on:
- linux-amd64
- linux-arm
trigger:
branch:
- master

1
.gitignore vendored
View file

@ -47,6 +47,5 @@ testem.log
Thumbs.db
# custom
config.json
cache.json

View file

@ -2,6 +2,11 @@
"authToken": "test",
"title": "sp-status",
"description": "Services hosted by sp-codes",
"statePath": "$.status",
"stateValues": {
"operational": ["OK"],
"maintenance": ["PAUSED"]
},
"groups": [
{
"id": "test",
@ -9,8 +14,13 @@
"services": [
{
"id": "test",
"name": "Test",
"url": "http://test.de"
"name": "test",
"url": "http://sp-codes.de",
"statePath": "$.state",
"stateValues": {
"operational": ["ok"],
"maintenance": ["paused"]
}
}
]
}

View file

@ -1,9 +1,9 @@
{
"extends": "../tsconfig.json",
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",

View file

@ -15,47 +15,48 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.4",
"@angular/cdk": "^9.2.2",
"@angular/common": "~9.1.4",
"@angular/compiler": "~9.1.4",
"@angular/core": "~9.1.4",
"@angular/forms": "~9.1.4",
"@angular/material": "^9.2.2",
"@angular/platform-browser": "~9.1.4",
"@angular/platform-browser-dynamic": "~9.1.4",
"@angular/platform-server": "~9.1.4",
"@angular/router": "~9.1.4",
"@fortawesome/fontawesome-free": "^5.13.0",
"@nguniversal/express-engine": "^9.1.0",
"bootstrap": "^4.4.1",
"@angular/animations": "~10.0.2",
"@angular/cdk": "^10.0.1",
"@angular/common": "~10.0.2",
"@angular/compiler": "~10.0.2",
"@angular/core": "~10.0.2",
"@angular/forms": "~10.0.2",
"@angular/material": "^10.0.1",
"@angular/platform-browser": "~10.0.2",
"@angular/platform-browser-dynamic": "~10.0.2",
"@angular/platform-server": "~10.0.2",
"@angular/router": "~10.0.2",
"@fortawesome/fontawesome-free": "^5.13.1",
"@nguniversal/express-engine": "^10.0.1",
"bootstrap": "^4.5.0",
"express": "^4.15.2",
"jsonpath": "^1.0.2",
"roboto-fontface": "^0.10.0",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.4",
"@angular/cli": "~9.1.4",
"@angular/compiler-cli": "~9.1.4",
"@angular/language-service": "~9.1.4",
"@nguniversal/builders": "^9.1.0",
"@angular-devkit/build-angular": "~0.1000.1",
"@angular/cli": "~10.0.1",
"@angular/compiler-cli": "~10.0.2",
"@angular/language-service": "~10.0.2",
"@nguniversal/builders": "^10.0.1",
"@types/express": "^4.17.0",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/node": "^14.0.14",
"@types/jasmine": "~3.5.11",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.1.2",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0",
"karma-jasmine": "~3.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "~5.4.3",
"ts-node": "~8.3.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~3.3.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.10.2",
"tslint": "~6.1.0",
"typescript": "~3.8.3"
"typescript": "~3.9.6"
}
}

35
renovate.json Normal file
View file

@ -0,0 +1,35 @@
{
"assignees": [
"samuel-p"
],
"baseBranches": [
"develop"
],
"rangeStrategy": "bump",
"packageRules": [
{
"managers": [
"npm"
],
"packageNames": [
"@types/node",
"@types/jasmine",
"@types/jasminewd2",
"codelyzer",
"protractor",
"rxjs",
"ts-node",
"tslib",
"tslint",
"typescript",
"zone.js"
],
"packagePatterns": [
"^angular",
"^karma",
"^jasmine"
],
"enabled": false
}
]
}

View file

@ -10,7 +10,14 @@
<footer>
<div class="container">
Made with <span class="fas fa-heart"></span> by <a href="https://sp-codes.de">sp-codes</a>
<div class="row">
<div class="col-12 col-sm-6">
Powered by <a href="https://git.sp-codes.de/samuel-p/universal-statuspage">universal-statuspage</a>
</div>
<div class="col-12 col-sm-6 text-sm-right">
Made with <span class="fas fa-heart"></span> by <a href="https://sp-codes.de">sp-codes</a>
</div>
</div>
</div>
</footer>
</div>

View file

@ -11,7 +11,7 @@
<a *ngFor="let service of group.services; last as last" mat-list-item [href]="service.url" target="_blank">
<div matLine class="d-flex">
<i [class]="stateClasses[service.state]"></i>
<span>{{service.name}}</span>
<span class="text-truncate">{{service.name}}</span>
<span class="flex-grow-1"></span>
<span class="text-capitalize {{service.state}}">{{service.state}}</span>
</div>

View file

@ -1,15 +1,21 @@
import {json, Router} from 'express';
import {CurrentStatus, State} from "./app/_data/data";
import {existsSync, readFileSync, writeFileSync} from "fs";
import {CurrentStatus, State} from './app/_data/data';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import * as jp from 'jsonpath';
interface Cache {
[id: string]: State
[id: string]: State;
}
interface Config {
authToken: string;
title: string;
description: string;
statePath: string;
stateValues: {
operational: string[];
maintenance: string[];
};
groups: {
id: string;
name: string;
@ -17,34 +23,40 @@ interface Config {
id: string;
name: string;
url: string;
statePath?: string;
stateValues?: {
operational?: string[];
maintenance?: 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
interface StateKey {
statePath: string;
stateValues: {
operational: string[];
maintenance: 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;
const config = JSON.parse(readFileSync('config.json', {encoding: 'utf-8'})) as Config;
const stateKeys: { [service: string]: StateKey } = config.groups
.map(g => g.services).reduce((x, y) => x.concat(y), [])
.reduce((services, service) => {
services[service.id] = {
statePath: service.statePath || config.statePath,
stateValues: {
operational: service.stateValues ? service.stateValues.operational || config.stateValues.operational : config.stateValues.operational,
maintenance: service.stateValues ? service.stateValues.maintenance || config.stateValues.maintenance : config.stateValues.maintenance,
}
};
return services;
}, {});
const serviceStates = existsSync('cache.json') ? JSON.parse(readFileSync('cache.json', {encoding: 'utf-8'})) : {} as Cache;
let cache: CurrentStatus;
updateCache();
@ -55,23 +67,20 @@ api.post('/update/health', (req, res) => {
return res.status(401).send('invalid token');
}
const serviceId = req.query.service as string;
const message = req.body as GrafanaWebhookBody;
const keys = stateKeys[serviceId];
const state = jp.value(req.body, keys.statePath);
switch (message.state) {
case "no_data":
case "alerting":
serviceStates[serviceId] = "outage";
break;
case "paused":
serviceStates[serviceId] = "maintenance";
break;
default:
serviceStates[serviceId] = "operational"
if (keys.stateValues.operational.includes(state)) {
serviceStates[serviceId] = 'operational';
} else if (keys.stateValues.maintenance.includes(state)) {
serviceStates[serviceId] = 'maintenance';
} else {
serviceStates[serviceId] = 'outage';
}
updateCache();
writeFileSync('cache.json', JSON.stringify(serviceStates), {encoding: 'UTF-8'});
writeFileSync('cache.json', JSON.stringify(serviceStates), {encoding: 'utf-8'});
return res.send('OK');
});
@ -94,15 +103,15 @@ function updateCache(): void {
id: service.id,
name: service.name,
url: service.url,
state: serviceStates[service.id] || "operational"
}
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)),
@ -111,7 +120,7 @@ function updateCache(): void {
}
function calculateOverallState(states: State[]): State {
return states.includes("outage") ? "outage" : states.includes("maintenance") ? "maintenance" : "operational"
return states.includes('outage') ? 'outage' : states.includes('maintenance') ? 'maintenance' : 'operational';
}
export {api};

View file

@ -1,5 +1,5 @@
{
"extends": "./tsconfig.json",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []

23
tsconfig.base.json Normal file
View file

@ -0,0 +1,23 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "es2020",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}

View file

@ -1,23 +1,20 @@
/*
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScripts language server to improve development experience.
It is not intended to be used to perform a compilation.
To learn more about this file see: https://angular.io/config/solution-tsconfig.
*/
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./tsconfig.server.json"
}
]
}

View file

@ -2,11 +2,11 @@
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/app-server",
"module": "commonjs",
"types": [
"node"
]
},
, "target": "es2016"
},
"files": [
"src/main.server.ts",
"server.ts"

View file

@ -1,5 +1,5 @@
{
"extends": "./tsconfig.json",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [