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 kind: pipeline
name: default type: docker
name: linux-amd64
platform:
os: linux
arch: amd64
steps: steps:
- name: build - name: build
image: node image: node:alpine
commands: commands:
- npm install - npm install
- npm run build:ssr - npm run build:ssr
- name: docker - name: docker
image: plugins/docker image: plugins/docker
settings: settings:
repo: samuelph/grafana-statuspage auto_tag: true
auto_tag_suffix: linux-amd64
repo: samuelph/universal-statuspage
username: username:
from_secret: USERNAME from_secret: USERNAME
password: password:
from_secret: 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 Thumbs.db
# custom # custom
config.json config.json
cache.json cache.json

View file

@ -2,6 +2,11 @@
"authToken": "test", "authToken": "test",
"title": "sp-status", "title": "sp-status",
"description": "Services hosted by sp-codes", "description": "Services hosted by sp-codes",
"statePath": "$.status",
"stateValues": {
"operational": ["OK"],
"maintenance": ["PAUSED"]
},
"groups": [ "groups": [
{ {
"id": "test", "id": "test",
@ -9,8 +14,13 @@
"services": [ "services": [
{ {
"id": "test", "id": "test",
"name": "Test", "name": "test",
"url": "http://test.de" "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": { "compilerOptions": {
"outDir": "../out-tsc/e2e", "outDir": "../out-tsc/e2e",
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es2018",
"types": [ "types": [
"jasmine", "jasmine",
"jasminewd2", "jasminewd2",

View file

@ -15,47 +15,48 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~9.1.4", "@angular/animations": "~10.0.2",
"@angular/cdk": "^9.2.2", "@angular/cdk": "^10.0.1",
"@angular/common": "~9.1.4", "@angular/common": "~10.0.2",
"@angular/compiler": "~9.1.4", "@angular/compiler": "~10.0.2",
"@angular/core": "~9.1.4", "@angular/core": "~10.0.2",
"@angular/forms": "~9.1.4", "@angular/forms": "~10.0.2",
"@angular/material": "^9.2.2", "@angular/material": "^10.0.1",
"@angular/platform-browser": "~9.1.4", "@angular/platform-browser": "~10.0.2",
"@angular/platform-browser-dynamic": "~9.1.4", "@angular/platform-browser-dynamic": "~10.0.2",
"@angular/platform-server": "~9.1.4", "@angular/platform-server": "~10.0.2",
"@angular/router": "~9.1.4", "@angular/router": "~10.0.2",
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.1",
"@nguniversal/express-engine": "^9.1.0", "@nguniversal/express-engine": "^10.0.1",
"bootstrap": "^4.4.1", "bootstrap": "^4.5.0",
"express": "^4.15.2", "express": "^4.15.2",
"jsonpath": "^1.0.2",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"rxjs": "~6.5.4", "rxjs": "~6.6.0",
"tslib": "^1.10.0", "tslib": "^2.0.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.901.4", "@angular-devkit/build-angular": "~0.1000.1",
"@angular/cli": "~9.1.4", "@angular/cli": "~10.0.1",
"@angular/compiler-cli": "~9.1.4", "@angular/compiler-cli": "~10.0.2",
"@angular/language-service": "~9.1.4", "@angular/language-service": "~10.0.2",
"@nguniversal/builders": "^9.1.0", "@nguniversal/builders": "^10.0.1",
"@types/express": "^4.17.0", "@types/express": "^4.17.0",
"@types/node": "^12.11.1", "@types/node": "^14.0.14",
"@types/jasmine": "~3.5.0", "@types/jasmine": "~3.5.11",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.1.2", "codelyzer": "^6.0.0",
"jasmine-core": "~3.5.0", "jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0", "karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~3.0.1", "karma-jasmine": "~3.3.0",
"karma-jasmine-html-reporter": "^1.4.2", "karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~5.4.3", "protractor": "~7.0.0",
"ts-node": "~8.3.0", "ts-node": "~8.10.2",
"tslint": "~6.1.0", "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> <footer>
<div class="container"> <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> </div>
</footer> </footer>
</div> </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"> <a *ngFor="let service of group.services; last as last" mat-list-item [href]="service.url" target="_blank">
<div matLine class="d-flex"> <div matLine class="d-flex">
<i [class]="stateClasses[service.state]"></i> <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="flex-grow-1"></span>
<span class="text-capitalize {{service.state}}">{{service.state}}</span> <span class="text-capitalize {{service.state}}">{{service.state}}</span>
</div> </div>

View file

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

View file

@ -1,5 +1,5 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "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, "files": [],
"compilerOptions": { "references": [
"baseUrl": "./", {
"outDir": "./dist/out-tsc", "path": "./tsconfig.app.json"
"sourceMap": true, },
"declaration": false, {
"downlevelIteration": true, "path": "./tsconfig.spec.json"
"experimentalDecorators": true, },
"module": "esnext", {
"moduleResolution": "node", "path": "./tsconfig.server.json"
"importHelpers": true, }
"target": "es2015", ]
"lib": [ }
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}

View file

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

View file

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