parent
2bea201bb3
commit
a4542f7abd
@ -1,3 +1,51 @@
|
||||
.idea/
|
||||
*.iml
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# custom
|
||||
|
||||
config.json
|
||||
cache.json
|
||||
|
@ -1,4 +1,4 @@
|
||||
# sp-status frontend
|
||||
# GrafanaStatuspage
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.4.
|
||||
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"authToken": "test",
|
||||
"title": "sp-status",
|
||||
"description": "Services hosted by sp-codes",
|
||||
"groups": [
|
||||
{
|
||||
"id": "test",
|
||||
"name": "Test",
|
||||
"services": [
|
||||
{
|
||||
"id": "test",
|
||||
"name": "Test",
|
||||
"url": "http://test.de"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
@ -1,53 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable, of} from "rxjs";
|
||||
import {ApiResponse} from "../_data/data";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public getServiceStates(): Observable<ApiResponse> {
|
||||
return of({
|
||||
state: "maintenance",
|
||||
groups: [{
|
||||
id: 'default',
|
||||
name: 'Some Group',
|
||||
state: "outage",
|
||||
services: [{
|
||||
id: 'nextcloud',
|
||||
name: 'Nextcloud',
|
||||
url: "https://sp-codes.de",
|
||||
state: "operational"
|
||||
}, {
|
||||
id: 'synapse',
|
||||
name: 'Synapse',
|
||||
url: "https://sp-codes.de",
|
||||
state: "outage"
|
||||
}, {
|
||||
id: 'searx',
|
||||
name: 'Searx',
|
||||
url: "https://sp-codes.de",
|
||||
state: "maintenance"
|
||||
}]
|
||||
}, {
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
state: "operational",
|
||||
services: [{
|
||||
id: 'nextcloud',
|
||||
name: 'Nextcloud',
|
||||
url: "https://sp-codes.de",
|
||||
state: "operational"
|
||||
}, {
|
||||
id: 'synapse',
|
||||
name: 'Synapse',
|
||||
url: "https://sp-codes.de",
|
||||
state: "operational"
|
||||
}]
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { StatusComponent } from './status/status.component';
|
||||
import {MatExpansionModule} from "@angular/material/expansion";
|
||||
import {MatListModule} from "@angular/material/list";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
StatusComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
MatExpansionModule,
|
||||
MatListModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -1,3 +0,0 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>sp-status</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link id="favicon" rel="icon" type="image/x-icon" href="favicon-operational.ico">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@
|
||||
import 'zone.js/dist/zone-node';
|
||||
|
||||
import {ngExpressEngine} from '@nguniversal/express-engine';
|
||||
import * as express from 'express';
|
||||
import {join} from 'path';
|
||||
|
||||
import {AppServerModule} from './src/main.server';
|
||||
import {APP_BASE_HREF} from '@angular/common';
|
||||
import {existsSync} from 'fs';
|
||||
import {api} from './src/main.status';
|
||||
|
||||
// The Express app is exported so that it can be used by serverless Functions.
|
||||
export function app() {
|
||||
const server = express();
|
||||
const distFolder = join(process.cwd(), 'dist/grafana-statuspage/browser');
|
||||
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
|
||||
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||
server.engine('html', ngExpressEngine({
|
||||
bootstrap: AppServerModule,
|
||||
}));
|
||||
|
||||
server.set('view engine', 'html');
|
||||
server.set('views', distFolder);
|
||||
|
||||
server.use('/api', api);
|
||||
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(distFolder, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
|
||||
// All regular routes use the Universal engine
|
||||
server.get('*', (req, res) => {
|
||||
res.render(indexHtml, {req, providers: [{provide: APP_BASE_HREF, useValue: req.baseUrl}]});
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function run() {
|
||||
const port = process.env.PORT || 4000;
|
||||
|
||||
// Start up the Node server
|
||||
const server = app();
|
||||
server.listen(port, () => {
|
||||
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Webpack will replace 'require' with '__webpack_require__'
|
||||
// '__non_webpack_require__' is a proxy to Node 'require'
|
||||
// The below code is to ensure that the server is run only when not requiring the bundle.
|
||||
declare const __non_webpack_require__: NodeRequire;
|
||||
const mainModule = __non_webpack_require__.main;
|
||||
const moduleFilename = mainModule && mainModule.filename || '';
|
||||
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
||||
run();
|
||||
}
|
||||
|
||||
export * from './src/main.server';
|
@ -0,0 +1,25 @@
|
||||
import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
|
||||
import {Observable} from "rxjs";
|
||||
import {CurrentStatus, MetaInfo} from "../_data/data";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {isPlatformBrowser} from "@angular/common";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
private readonly api;
|
||||
|
||||
constructor(private http: HttpClient, @Inject(PLATFORM_ID) platformId: Object) {
|
||||
this.api = isPlatformBrowser(platformId) ? '/api' : environment.serverUrl + '/api';
|
||||
}
|
||||
|
||||
public getServiceStates(): Observable<CurrentStatus> {
|
||||
return this.http.get<CurrentStatus>(this.api+ '/status');
|
||||
}
|
||||
|
||||
public getMetaInfo(): Observable<MetaInfo> {
|
||||
return this.http.get<MetaInfo>(this.api+ '/info');
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<div class="box">
|
||||
<header class="container pt-4">
|
||||
<h1>sp-status</h1>
|
||||
<h3>Services hosted by sp-codes</h3>
|
||||
<h1 *ngIf="title && title.length">{{title}}</h1>
|
||||
<h3 *ngIf="description && description.length">{{description}}</h3>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
@ -0,0 +1,26 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from "./_service/api.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {MetaInfo} from "./_data/data";
|
||||
import {Title} from "@angular/platform-browser";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
constructor(private api: ApiService, private htmlTitle: Title) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.api.getMetaInfo().subscribe(info => {
|
||||
this.title = info.title;
|
||||
this.description = info.description;
|
||||
this.htmlTitle.setTitle(this.title);
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {StatusComponent} from './status/status.component';
|
||||
import {MatExpansionModule} from "@angular/material/expansion";
|
||||
import {MatListModule} from "@angular/material/list";
|
||||
import {HttpClientModule} from "@angular/common/http";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
StatusComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: 'serverApp'}),
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
MatExpansionModule,
|
||||
MatListModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ServerModule } from '@angular/platform-server';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
ServerModule,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppServerModule {}
|
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
serverUrl: 'http://localhost:4000'
|
||||
};
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>grafana-statuspage</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link id="favicon" rel="icon" type="image/x-icon" href="">
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,10 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
export { AppServerModule } from './app/app.server.module';
|
||||
export { renderModule, renderModuleFactory } from '@angular/platform-server';
|
@ -0,0 +1,117 @@
|
||||
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};
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app-server",
|
||||
"module": "commonjs",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/main.server.ts",
|
||||
"server.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"entryModule": "./src/app/app.server.module#AppServerModule"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue