mirror of
https://github.com/smartyellow/status.git
synced 2025-01-18 13:37:59 +00:00
Basic dashboard
Signed-off-by: Romein van Buren <romein@vburen.nl>
This commit is contained in:
parent
b3fd94b02a
commit
c561a00954
@ -4,16 +4,20 @@ const { minify: minifyCSS } = require('csso');
|
||||
const { rollup } = require('rollup');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const css = require('rollup-plugin-css-only');
|
||||
const replace = require('@rollup/plugin-replace');
|
||||
const { default: resolve } = require('@rollup/plugin-node-resolve');
|
||||
const svelte = require('rollup-plugin-svelte');
|
||||
const { terser } = require('rollup-plugin-terser');
|
||||
|
||||
async function build() {
|
||||
async function build(server) {
|
||||
const serverDomain = server.settings.domain || 'localhost';
|
||||
const serverPort = server.settings.port || 80;
|
||||
const serverBase = `${serverDomain}:${serverPort}`;
|
||||
let cssOutput = '';
|
||||
|
||||
try {
|
||||
const bundle = await rollup({
|
||||
input: __dirname + '/gui/dashboard/index.js',
|
||||
input: __dirname + '/../gui/dashboard/index.js',
|
||||
plugins: [
|
||||
// Svelte
|
||||
svelte({
|
||||
@ -39,6 +43,14 @@ async function build() {
|
||||
|
||||
// Minify
|
||||
terser(),
|
||||
|
||||
// Replace env vars
|
||||
replace({
|
||||
preventAssignment: false,
|
||||
values: {
|
||||
'__SERVER__': serverBase,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
72
dashboard/socket.js
Normal file
72
dashboard/socket.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let uws;
|
||||
|
||||
async function createDashboardSocket(server) {
|
||||
uws = server.ws({
|
||||
route: '/statusdashboard/socket',
|
||||
onOpen: async ws => {
|
||||
function sendTime() {
|
||||
ws.send(JSON.stringify({
|
||||
cmd: 'time',
|
||||
time: new Date().getTime(),
|
||||
}));
|
||||
}
|
||||
|
||||
sendTime();
|
||||
setInterval(sendTime, 5000);
|
||||
|
||||
async function sendStatuses() {
|
||||
const services = await server.storage
|
||||
.store('smartyellow/webservice')
|
||||
.find()
|
||||
.toArray();
|
||||
const heartbeats = await server.storage
|
||||
.store('smartyellow/webserviceheartbeat')
|
||||
.find({ webservice: { $in: services.map(s => s.id) } })
|
||||
.sort({ date: -1 })
|
||||
.toArray();
|
||||
const mappedServices = {};
|
||||
|
||||
for (const s of services) {
|
||||
const lastBeat = heartbeats.find(h => h.webservice === s.id);
|
||||
mappedServices[s.id] = {
|
||||
name: s.name,
|
||||
lastBeat: lastBeat,
|
||||
};
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
cmd: 'data',
|
||||
data: mappedServices,
|
||||
}));
|
||||
}
|
||||
|
||||
sendStatuses();
|
||||
setInterval(sendStatuses, 5000);
|
||||
},
|
||||
onUpgrade: async () => ({ id: makeId(10) }),
|
||||
onMessage: async (ws, msg) => {
|
||||
msg = JSON.parse(decoder.decode(msg));
|
||||
console.log('msg', msg);
|
||||
|
||||
if (!msg || !msg.command) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.command) {
|
||||
case 'data':
|
||||
ws.send('data');
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createDashboardSocket;
|
@ -1,19 +1,69 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import TileRawValue from './tile-rawvalue.svelte';
|
||||
|
||||
onMount(() => console.log('test'));
|
||||
let lastUpdated = new Date();
|
||||
let lastUpdatedFormatted = '';
|
||||
let services = {};
|
||||
let loading = true;
|
||||
|
||||
$:console.log(services);
|
||||
|
||||
onMount(() => {
|
||||
const ws = new WebSocket('ws://__SERVER__/statusdashboard/socket');
|
||||
|
||||
ws.onmessage = async evt => {
|
||||
const data = JSON.parse(evt.data || '""');
|
||||
|
||||
switch (data.cmd) {
|
||||
case 'time':
|
||||
lastUpdated = new Date(data.time);
|
||||
lastUpdatedFormatted = lastUpdated.toLocaleTimeString('en-GB', {
|
||||
timeStyle: 'short',
|
||||
});
|
||||
break;
|
||||
|
||||
case 'data':
|
||||
services = data.data;
|
||||
loading = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="center">
|
||||
<div class="ratio">
|
||||
<div class="tiles">
|
||||
<TileRawValue title="Last updated" value={lastUpdatedFormatted} />
|
||||
|
||||
{#if !loading}
|
||||
{#each Object.entries(services) as [ id, service ] (id)}
|
||||
{@const isDown = service.lastBeat.down}
|
||||
<TileRawValue
|
||||
title={service.name.en}
|
||||
value={isDown ? 'down' : 'up'}
|
||||
color={isDown ? 'red' : 'green'}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
loading
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(html), :global(body) {
|
||||
--radius: 10px;
|
||||
--tile-bg: #181818;
|
||||
--red: red;
|
||||
--green: green;
|
||||
--radius: 10px;
|
||||
--cols: 4;
|
||||
--rows: 3;
|
||||
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
@ -40,4 +90,13 @@
|
||||
transform: translate(-50%,-50%);
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
margin: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols), 1fr);
|
||||
grid-template-rows: repeat(var(--rows), 1fr);
|
||||
gap: 1rem;
|
||||
justify-items: stretch;
|
||||
}
|
||||
</style>
|
||||
|
29
gui/dashboard/tile-rawvalue.svelte
Normal file
29
gui/dashboard/tile-rawvalue.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
import Tile from './tile.svelte';
|
||||
|
||||
export let title;
|
||||
export let subtitle;
|
||||
export let color;
|
||||
export let value;
|
||||
</script>
|
||||
|
||||
<Tile {title} {subtitle}>
|
||||
<div class="value {color}">
|
||||
{value}
|
||||
</div>
|
||||
</Tile>
|
||||
|
||||
<style>
|
||||
.value {
|
||||
font-size: 3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--green);
|
||||
}
|
||||
</style>
|
@ -4,27 +4,38 @@
|
||||
</script>
|
||||
|
||||
<div class="tile">
|
||||
{#if title}
|
||||
<div class="title">{title}</div>
|
||||
{#if title || subtitle}
|
||||
<div class="desc">
|
||||
{#if title}
|
||||
<div class="title">{title}</div>
|
||||
{/if}
|
||||
|
||||
{#if subtitle}
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if subtitle}
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.desc {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tile {
|
||||
padding: 1rem;
|
||||
background-color: var(--tile-bg);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.title, .subtitle {
|
||||
font-size: 1.3rem;
|
||||
.title {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
.subtitle {
|
||||
font-weight: 100;
|
||||
}
|
||||
</style>
|
||||
|
51
index.js
51
index.js
@ -2,7 +2,8 @@
|
||||
|
||||
const { fork } = require('child_process');
|
||||
const { processOutage } = require('./lib/processoutage');
|
||||
const buildDashboard = require('./builddashboard');
|
||||
const buildDashboard = require('./dashboard/build');
|
||||
const createDashboardSocket = require('./dashboard/socket');
|
||||
const { minifyHtml } = require('core/strings');
|
||||
|
||||
const guiCluster = 'web service status';
|
||||
@ -210,6 +211,15 @@ module.exports = {
|
||||
],
|
||||
|
||||
hooks: ({ server, settings }) => [
|
||||
{ id: 'startDashboardSocket',
|
||||
event: 'boot',
|
||||
order: 100,
|
||||
purpose: 'Start the websocket for the dashboard after server has booted',
|
||||
handler: async ({ server }) => {
|
||||
createDashboardSocket(server);
|
||||
},
|
||||
},
|
||||
|
||||
{ id: 'autotestOnSave',
|
||||
order: 500,
|
||||
event: 'saveEntity',
|
||||
@ -535,24 +545,29 @@ module.exports = {
|
||||
{ route: '/statusdashboard',
|
||||
method: 'get',
|
||||
handler: async (req, res) => {
|
||||
if (!renderedDashboard) {
|
||||
renderedDashboard = await buildDashboard();
|
||||
try {
|
||||
if (!renderedDashboard) {
|
||||
renderedDashboard = await buildDashboard(server);
|
||||
}
|
||||
const dashboardHtml = minifyHtml(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web service status dashboard</title>
|
||||
<style>${renderedDashboard.css || ''}</style>
|
||||
<script>${renderedDashboard.code || ''}</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
`);
|
||||
res.send(dashboardHtml);
|
||||
}
|
||||
catch (error) {
|
||||
server.error('could not compile web service status dashboard', error);
|
||||
}
|
||||
const dashboardHtml = minifyHtml(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web service status dashboard</title>
|
||||
<style>${renderedDashboard.css || ''}</style>
|
||||
<script>${renderedDashboard.code || ''}</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
`);
|
||||
res.send(dashboardHtml);
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/smartyellow/status#readme",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"csso": "^5.0.3",
|
||||
"rollup-plugin-css-only": "^3.1.0"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user