mirror of
https://github.com/smartyellow/status.git
synced 2025-01-18 05:27:58 +00:00
Concept refactoring; accept tiles and props from other plugins through hooks.
This commit is contained in:
parent
59d36ed208
commit
086da49c67
@ -5,6 +5,7 @@ html, body {
|
||||
--grey: grey;
|
||||
--red: #f00;
|
||||
--green: #1d641d;
|
||||
--orange: #bf5900;
|
||||
--dark: #0a0a0a;
|
||||
--radius: 10px;
|
||||
--cols: 4;
|
||||
@ -22,6 +23,7 @@ html, body {
|
||||
--tile-bg: #dedede;
|
||||
--dark: #c2c2c2;
|
||||
--green: #377137;
|
||||
--orange: #bf5900;
|
||||
}
|
||||
|
||||
.mb {
|
||||
|
@ -1,92 +1,114 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import TileRawValue from './tile-rawvalue.svelte';
|
||||
import Tile from './tile.svelte';
|
||||
import Settings from './settings.svelte';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { settings, shuffle } from './lib';
|
||||
import { connect } from './apiclient';
|
||||
|
||||
const [ send, receive ] = shuffle;
|
||||
let size = ($settings.cols || 4) * ($settings.rows || 3);
|
||||
let placesLeft = size;
|
||||
let maxNumberOfTilesOnPage = ($settings.cols || 4) * ($settings.rows || 3);
|
||||
let pageNum = -1;
|
||||
let pageCount = 1;
|
||||
let tiles = [];
|
||||
let allTiles = [];
|
||||
let tilesOnPage = [];
|
||||
let time = '';
|
||||
let globalData = {};
|
||||
let hasData = false;
|
||||
|
||||
function tileProps(service) {
|
||||
const props = {
|
||||
title: service.name.en,
|
||||
subtitle: service.cluster,
|
||||
date: service.lastBeat?.date ? new Date(service.lastBeat.date) : undefined,
|
||||
since: service.checked ? new Date(service.checked) : undefined,
|
||||
};
|
||||
|
||||
if (!service.lastBeat?.date) {
|
||||
props.value = 'no data';
|
||||
props.color = 'grey';
|
||||
props.sort = 20;
|
||||
function previousPage() {
|
||||
pageNum--;
|
||||
if (pageNum < 0) {
|
||||
pageNum = pageCount - 1;
|
||||
}
|
||||
else if (service.lastBeat.down) {
|
||||
props.value = 'down';
|
||||
props.color = 'red';
|
||||
props.sort = 0;
|
||||
}
|
||||
else {
|
||||
props.value = 'up';
|
||||
props.color = 'green';
|
||||
props.sort = 10;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
function organiseGrid() {
|
||||
let servicesTemp = [];
|
||||
const { servicesUp, servicesDown, servicesUnknown, total } = globalData;
|
||||
const upOrUnknown = [ ...servicesUp, ...servicesUnknown ];
|
||||
servicesTemp = servicesDown.slice(0, size);
|
||||
pageCount = Math.ceil(upOrUnknown.length / size);
|
||||
placesLeft = size - servicesTemp.length;
|
||||
pageCount = Math.ceil(upOrUnknown.length / placesLeft);
|
||||
|
||||
if (pageNum === -1 || total >= size) {
|
||||
function nextPage() {
|
||||
pageNum++;
|
||||
|
||||
if (pageNum >= pageCount) {
|
||||
pageNum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const offset = placesLeft * pageNum;
|
||||
if (placesLeft > 0) {
|
||||
servicesTemp.push(
|
||||
...upOrUnknown.slice(offset, placesLeft + offset)
|
||||
);
|
||||
function organiseGrid(goToNextPage = true) {
|
||||
const newTiles = allTiles.sort((a, b) => b.prio - a.prio);
|
||||
const pinnedTilesCount = allTiles.filter(t => t.prio > 0).length;
|
||||
const placesLeft = maxNumberOfTilesOnPage - pinnedTilesCount;
|
||||
pageCount = Math.ceil(newTiles.length / placesLeft);
|
||||
|
||||
if (((newTiles.length >= placesLeft) && goToNextPage) || (pageNum === -1)) {
|
||||
nextPage();
|
||||
}
|
||||
|
||||
tiles = servicesTemp;
|
||||
tilesOnPage = [
|
||||
...newTiles.slice(0, pinnedTilesCount),
|
||||
...newTiles.slice(pinnedTilesCount + (pageNum * placesLeft)),
|
||||
].slice(0, maxNumberOfTilesOnPage);
|
||||
}
|
||||
|
||||
function keydown(event) {
|
||||
switch (event.code) {
|
||||
case 'ArrowLeft':
|
||||
case 'PageUp':
|
||||
event.preventDefault();
|
||||
previousPage();
|
||||
organiseGrid(false);
|
||||
break;
|
||||
|
||||
case 'ArrowRight':
|
||||
case 'PageDown':
|
||||
event.preventDefault();
|
||||
nextPage();
|
||||
organiseGrid(false);
|
||||
break;
|
||||
|
||||
case 'Home':
|
||||
event.preventDefault();
|
||||
pageNum = 0;
|
||||
organiseGrid(false);
|
||||
break;
|
||||
|
||||
case 'End':
|
||||
event.preventDefault();
|
||||
pageNum = pageCount - 1;
|
||||
organiseGrid(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (event.code.startsWith('Digit')) {
|
||||
event.preventDefault();
|
||||
let num = parseInt(event.code.slice(5));
|
||||
if (!isNaN(num)) {
|
||||
if (num > pageCount) {
|
||||
num = pageCount;
|
||||
}
|
||||
pageNum = num - 1;
|
||||
organiseGrid(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await connect({
|
||||
onData: data => {
|
||||
globalData = data;
|
||||
allTiles = data.tiles?.map(tile => {
|
||||
if (tile?.service?.checked) {
|
||||
tile.service.checked = new Date(tile.service.checked);
|
||||
}
|
||||
return tile;
|
||||
});
|
||||
organiseGrid();
|
||||
hasData = true;
|
||||
},
|
||||
});
|
||||
|
||||
const clockInterval = setInterval(() => {
|
||||
time = new Date().toLocaleTimeString('en-GB', {
|
||||
timeStyle: 'medium',
|
||||
});
|
||||
time = new Date().toLocaleTimeString('en-GB', { timeStyle: 'medium' });
|
||||
}, 100);
|
||||
|
||||
settings.subscribe(s => {
|
||||
size = (s.cols || 4) * (s.rows || 3);
|
||||
maxNumberOfTilesOnPage = (s.cols || 4) * (s.rows || 3);
|
||||
if (hasData) {
|
||||
organiseGrid();
|
||||
}
|
||||
@ -96,6 +118,8 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
|
||||
<div
|
||||
class="center theme-{$settings.theme}"
|
||||
style="
|
||||
@ -106,13 +130,13 @@
|
||||
>
|
||||
<div class="ratio">
|
||||
<div class="tiles">
|
||||
{#each tiles as tile (tile.id)}
|
||||
{#each tilesOnPage || [] as tile (tile.serviceId)}
|
||||
<div
|
||||
in:receive={{ key: tile.id }}
|
||||
out:send={{ key: tile.id }}
|
||||
in:receive={{ key: tile.serviceId }}
|
||||
out:send={{ key: tile.serviceId }}
|
||||
animate:flip={{ duration: $settings.animate ? (d => Math.sqrt(d) * 120) : 0 }}
|
||||
>
|
||||
<TileRawValue {...tileProps(tile)} />
|
||||
<Tile {tile} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -1,24 +0,0 @@
|
||||
<script>
|
||||
import Tile from './tile.svelte';
|
||||
|
||||
export let title;
|
||||
export let subtitle;
|
||||
export let color;
|
||||
export let value;
|
||||
export let date;
|
||||
export let since;
|
||||
</script>
|
||||
|
||||
<Tile {title} {subtitle} {color} {date} {since}>
|
||||
<div class="value {color}">
|
||||
{value}
|
||||
</div>
|
||||
</Tile>
|
||||
|
||||
<style>
|
||||
.value {
|
||||
font-size: 3em;
|
||||
font-weight: 600;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
@ -2,25 +2,21 @@
|
||||
import { formatDuration } from './lib';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let title;
|
||||
export let subtitle;
|
||||
export let color;
|
||||
export let date;
|
||||
export let since;
|
||||
export let tile;
|
||||
|
||||
let formattedDuration = '';
|
||||
$: formattedDate = date ? date.toLocaleTimeString('en-GB', {
|
||||
$: formattedDate = tile.service?.checked ? tile.service.checked.toLocaleTimeString('en-GB', {
|
||||
timeStyle: 'short',
|
||||
}) : '';
|
||||
|
||||
onMount(() => {
|
||||
function updateDuration() {
|
||||
formattedDuration = formatDuration(
|
||||
new Date().getTime() - since.getTime()
|
||||
new Date().getTime() - tile.since.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
if (since) {
|
||||
if (tile.since) {
|
||||
updateDuration();
|
||||
const interval = setInterval(updateDuration, 100);
|
||||
return () => clearInterval(interval);
|
||||
@ -28,22 +24,33 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="tile {color}">
|
||||
{#if title || subtitle}
|
||||
<div class="tile prio{tile.prio}">
|
||||
<div class="desc">
|
||||
{#if title}
|
||||
<div class="title">{title}</div>
|
||||
{#if tile.service?.name?.en}
|
||||
<div class="title">{tile.service.name.en}</div>
|
||||
{/if}
|
||||
|
||||
{#if subtitle}
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
{#if tile.service?.cluster}
|
||||
<div class="subtitle">{tile.service.cluster}</div>
|
||||
{/if}
|
||||
|
||||
{#if tile.badges?.length}
|
||||
<div class="badges">
|
||||
{#each tile.badges as badge}
|
||||
<span class="badge">{badge}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<div class="content"><slot /></div>
|
||||
{#if date || since}
|
||||
<div class="content">
|
||||
<div class="statustext">
|
||||
{tile.statusText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if tile.date || tile.since}
|
||||
<div class="time">
|
||||
{#if formattedDate}<div>{formattedDate}</div>{/if}
|
||||
{#if formattedDuration}<div>{formattedDuration}</div>{/if}
|
||||
@ -62,19 +69,25 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tile.red {
|
||||
.tile.prio2 {
|
||||
background-color: var(--red);
|
||||
border-color: var(--red);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tile.green {
|
||||
.tile.prio1 {
|
||||
background-color: var(--orange);
|
||||
border-color: var(--orange);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tile.prio0 {
|
||||
border-color: var(--green);
|
||||
background-color: var(--green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tile.grey {
|
||||
.tile.prio-1 {
|
||||
opacity: 0.5;
|
||||
border-color: var(--grey);
|
||||
}
|
||||
@ -98,6 +111,18 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.badges {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
.badges .badge {
|
||||
display: inline-block;
|
||||
margin-right: 0.3em;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.4em;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
margin-top: auto;
|
||||
@ -117,4 +142,10 @@
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.statustext {
|
||||
font-size: 3em;
|
||||
font-weight: 600;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
|
44
index.js
44
index.js
@ -375,32 +375,38 @@ module.exports = {
|
||||
.sort({ date: -1 })
|
||||
.toArray();
|
||||
|
||||
const servicesUp = [];
|
||||
const servicesDown = [];
|
||||
const servicesUnknown = [];
|
||||
const tiles = [];
|
||||
downIdsAfter = [];
|
||||
|
||||
for (let service of services) {
|
||||
const beat = heartbeats.find(b => b.webservice === service.id);
|
||||
service = mapService(service, beat);
|
||||
const tile = {
|
||||
service: service,
|
||||
serviceId: service.id,
|
||||
badges: [],
|
||||
prio: -1,
|
||||
};
|
||||
|
||||
if (!beat) {
|
||||
servicesUnknown.push(service);
|
||||
tile.prio = -1; // no data (grey)
|
||||
tile.statusText = 'no data';
|
||||
}
|
||||
else if (beat.down) {
|
||||
servicesDown.push(service);
|
||||
downIdsAfter.push(service.id);
|
||||
tile.prio = 2; // down (red)
|
||||
tile.statusText = 'down';
|
||||
downIdsAfter.push(tile.serviceId);
|
||||
}
|
||||
else {
|
||||
servicesUp.push(service);
|
||||
}
|
||||
tile.prio = 0; // ok (green)
|
||||
tile.statusText = 'ok';
|
||||
}
|
||||
|
||||
const total = [
|
||||
...servicesUp,
|
||||
...servicesDown,
|
||||
...servicesUnknown,
|
||||
].length;
|
||||
tiles.push(tile);
|
||||
}
|
||||
|
||||
// Let other plugins enrich dashboard tiles with custom badges and priorities.
|
||||
await server.executePostHooks('pupulateDashboardTiles', { tiles });
|
||||
|
||||
let newOutage = false;
|
||||
for (const id of downIdsAfter) {
|
||||
@ -408,20 +414,14 @@ module.exports = {
|
||||
newOutage = true;
|
||||
}
|
||||
}
|
||||
downIdsBefore = JSON.parse(JSON.stringify(downIdsAfter));
|
||||
downIdsBefore = [ ...downIdsAfter ];
|
||||
|
||||
try {
|
||||
if (newOutage) {
|
||||
ws.send(JSON.stringify({ cmd: 'bell' }));
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
cmd: 'data',
|
||||
servicesUp,
|
||||
servicesDown,
|
||||
servicesUnknown,
|
||||
total,
|
||||
}));
|
||||
ws.send(JSON.stringify({ cmd: 'data', tiles: tiles }));
|
||||
}
|
||||
catch {
|
||||
return;
|
||||
@ -804,7 +804,7 @@ module.exports = {
|
||||
method: 'get',
|
||||
handler: async (req, res) => {
|
||||
// const cacheValid = !!renderedDashboard;
|
||||
const cacheValid = true;
|
||||
const cacheValid = false;
|
||||
if (!cacheValid) {
|
||||
// Build dashboard
|
||||
let cssOutput = '';
|
||||
|
Loading…
Reference in New Issue
Block a user