added option to update multiple service states at once
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

minor frontend improvements
This commit is contained in:
Samuel Philipp 2020-07-16 22:08:02 +02:00
parent b6b0084641
commit 6799b64f2a
6 changed files with 48 additions and 46 deletions

View File

@ -1,4 +1,4 @@
FROM node:alpine FROM node:14.5.0-alpine
COPY dist/universal-statuspage /universal-statuspage COPY dist/universal-statuspage /universal-statuspage

View File

@ -2,25 +2,23 @@
"authToken": "test", "authToken": "test",
"title": "sp-status", "title": "sp-status",
"description": "Services hosted by sp-codes", "description": "Services hosted by sp-codes",
"servicesPath": "$.alerts.*",
"idPath": "$.labels.status_service",
"statePath": "$.status", "statePath": "$.status",
"stateValues": { "stateValues": {
"operational": ["OK"], "operational": ["ok", "resolved"],
"maintenance": ["PAUSED"] "maintenance": ["paused"]
}, },
"groups": [ "groups": [
{ {
"id": "test", "id": "test",
"name": "Test", "name": "Test",
"url": "http://sp-codes.de",
"services": [ "services": [
{ {
"id": "test", "id": "test",
"name": "test", "name": "test",
"url": "http://sp-codes.de", "statePath": "$.state"
"statePath": "$.state",
"stateValues": {
"operational": ["ok"],
"maintenance": ["paused"]
}
} }
] ]
} }

View File

@ -8,6 +8,7 @@ export interface CurrentStatus {
export interface Group { export interface Group {
id: string; id: string;
name: string; name: string;
url?: string;
state: State; state: State;
services: Service[]; services: Service[];
} }
@ -15,7 +16,7 @@ export interface Group {
export interface Service { export interface Service {
id: string; id: string;
name: string; name: string;
url: string; url?: string;
state: State; state: State;
} }

View File

@ -1,14 +1,15 @@
<mat-accordion [multi]="true"> <mat-accordion [multi]="true">
<mat-expansion-panel *ngFor="let group of groups" [expanded]="true"> <mat-expansion-panel *ngFor="let group of groups" [expanded]="true">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title><i [class]="stateClasses[group.state]"></i> {{group.name}}</mat-panel-title> <mat-panel-title>
<!-- <mat-panel-description>--> <i [class]="stateClasses[group.state]"></i>
<!-- <span class="text-capitalize">{{getGroupState(group.services)}}</span>--> <a *ngIf="group.url" class="name" [href]="group.url" target="_blank" (click)="$event.stopPropagation()">{{group.name}}</a>
<!-- </mat-panel-description>--> <span *ngIf="!group.url">{{group.name}}</span>
</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<mat-list> <mat-list>
<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 || group.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 class="text-truncate">{{service.name}}</span> <span class="text-truncate">{{service.name}}</span>

View File

@ -13,6 +13,11 @@
a { a {
text-decoration: none; text-decoration: none;
outline: none; outline: none;
color: #ffffff;
&:hover.name, &:hover .name {
text-decoration: underline;
}
} }
mat-panel-title { mat-panel-title {

View File

@ -12,7 +12,9 @@ interface Config {
authToken: string; authToken: string;
title: string; title: string;
description: string; description: string;
statePath: string; servicesPath?: string;
idPath?: string;
statePath?: string;
stateValues: { stateValues: {
operational: string[]; operational: string[];
maintenance: string[]; maintenance: string[];
@ -20,42 +22,26 @@ interface Config {
groups: { groups: {
id: string; id: string;
name: string; name: string;
url?: string;
services: { services: {
id: string; id: string;
name: string; name: string;
url: string; url?: string;
statePath?: string; statePath?: string;
stateValues?: {
operational?: string[];
maintenance?: string[];
};
}[]; }[];
}[]; }[];
} }
interface StateKey {
statePath: string;
stateValues: {
operational: string[];
maintenance: string[];
};
}
const api = Router(); const api = Router();
api.use(json()); api.use(json());
const serviceStates = existsSync(join(process.cwd(), 'cache.json')) ? JSON.parse(readFileSync(join(process.cwd(), 'cache.json'), {encoding: 'utf-8'})) : {} as Cache; const serviceStates = existsSync(join(process.cwd(), 'cache.json')) ? JSON.parse(readFileSync(join(process.cwd(), 'cache.json'), {encoding: 'utf-8'})) : {} as Cache;
const config = JSON.parse(readFileSync(join(process.cwd(), 'config.json'), {encoding: 'utf-8'})) as Config; const config = JSON.parse(readFileSync(join(process.cwd(), 'config.json'), {encoding: 'utf-8'})) as Config;
const stateKeys: { [service: string]: StateKey } = config.groups const serviceStatePaths: { [service: string]: string } = config.groups
.map(g => g.services).reduce((x, y) => x.concat(y), []) .map(g => g.services).reduce((x, y) => x.concat(y), [])
.filter(s => s.statePath)
.reduce((services, service) => { .reduce((services, service) => {
services[service.id] = { services[service.id] = service.statePath;
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; return services;
}, {}); }, {});
@ -68,17 +54,27 @@ 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 keys = stateKeys[serviceId]; let services: { id: string, state: string }[] = [];
const state = JSONPath({path: keys.statePath, json: req.body, wrap: false}); if (serviceId) {
services = [{id: serviceId, state: JSONPath({path: serviceStatePaths[serviceId], json: req.body, wrap: false})}];
if (keys.stateValues.operational.includes(state)) { } else if (config.servicesPath && config.idPath && config.statePath) {
serviceStates[serviceId] = 'operational'; services = JSONPath({path: config.servicesPath, json: req.body})
} else if (keys.stateValues.maintenance.includes(state)) { .map(s => ({
serviceStates[serviceId] = 'maintenance'; id: JSONPath({path: config.idPath, json: s, wrap: false}),
} else { state: JSONPath({path: config.statePath, json: s, wrap: false})
serviceStates[serviceId] = 'outage'; }));
} }
services.forEach(s => {
if (config.stateValues.operational.includes(s.state)) {
serviceStates[s.id] = 'operational';
} else if (config.stateValues.maintenance.includes(s.state)) {
serviceStates[s.id] = 'maintenance';
} else {
serviceStates[s.id] = 'outage';
}
});
updateCache(); updateCache();
writeFileSync('cache.json', JSON.stringify(serviceStates), {encoding: 'utf-8'}); writeFileSync('cache.json', JSON.stringify(serviceStates), {encoding: 'utf-8'});
@ -110,6 +106,7 @@ function updateCache(): void {
return { return {
id: group.id, id: group.id,
name: group.name, name: group.name,
url: group.url,
state: calculateOverallState(services.map(s => s.state)), state: calculateOverallState(services.map(s => s.state)),
services: services services: services
}; };