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