Concept refactoring; accept tiles and props from other plugins through hooks.

This commit is contained in:
Romein van Buren 2023-02-23 14:47:51 +01:00
parent 59d36ed208
commit 086da49c67
Signed by: romein
GPG Key ID: 0EFF8478ADDF6C49
5 changed files with 160 additions and 127 deletions

View File

@ -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 {

View File

@ -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);
function nextPage() {
pageNum++;
if (pageNum >= pageCount) {
pageNum = 0;
}
}
if (pageNum === -1 || total >= size) {
pageNum++;
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 (pageNum >= pageCount) {
if (((newTiles.length >= placesLeft) && goToNextPage) || (pageNum === -1)) {
nextPage();
}
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;
const offset = placesLeft * pageNum;
if (placesLeft > 0) {
servicesTemp.push(
...upOrUnknown.slice(offset, placesLeft + offset)
);
}
case 'End':
event.preventDefault();
pageNum = pageCount - 1;
organiseGrid(false);
break;
tiles = servicesTemp;
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>

View File

@ -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>

View File

@ -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="desc">
{#if title}
<div class="title">{title}</div>
{/if}
<div class="tile prio{tile.prio}">
<div class="desc">
{#if tile.service?.name?.en}
<div class="title">{tile.service.name.en}</div>
{/if}
{#if subtitle}
<div class="subtitle">{subtitle}</div>
{/if}
</div>
{/if}
{#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>

View File

@ -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';
}
tiles.push(tile);
}
const total = [
...servicesUp,
...servicesDown,
...servicesUnknown,
].length;
// 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 = '';