From 3311594f1e316f555041b4f01832c0e90d3cddc9 Mon Sep 17 00:00:00 2001 From: Alexandre Tuleu Date: Thu, 21 Sep 2023 10:57:37 +0200 Subject: [PATCH] Makes healthcheck data a server polling thing. --- src/hooks.server.ts | 2 + src/lib/server/config.ts | 3 ++ src/lib/server/serviceData.ts | 45 ------------------- src/lib/server/serviceDataPolling.ts | 65 ++++++++++++++++++++++++++++ src/lib/services/pihole/+service.ts | 26 +++++++---- src/lib/services/service.ts | 7 +-- src/routes/+page.server.ts | 31 +++---------- 7 files changed, 96 insertions(+), 83 deletions(-) delete mode 100644 src/lib/server/serviceData.ts create mode 100644 src/lib/server/serviceDataPolling.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 4fc24bf..6d3f501 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,5 +1,6 @@ import { dev } from '$app/environment'; import { watchDymamicConfig } from '$lib/server/config'; +import { initServicePolling } from '$lib/server/serviceDataPolling'; import { initComponents, initServices } from '$lib/services/services'; if (!dev) { @@ -8,3 +9,4 @@ if (!dev) { await initServices(); await initComponents(); +initServicePolling(); diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts index 23785f1..604eea3 100644 --- a/src/lib/server/config.ts +++ b/src/lib/server/config.ts @@ -1,6 +1,7 @@ import configData from '../../config.yml'; import { readFile, watch } from 'fs/promises'; import * as yml from 'js-yaml'; + import type { ColorConfig, Config, @@ -10,6 +11,7 @@ import type { } from '$lib/config'; import * as ipRangeCheck from 'ip-range-check'; +import { initializeServiceData } from './serviceDataPolling'; const requiredService: Required = { title: '', @@ -197,6 +199,7 @@ export function watchDymamicConfig() { const dynamicConfig = yml.load(await readFile(__filepath, 'utf8')); _serverConfig = mergeConfig(defaultConfig, dynamicConfig); _clientConfig = stripPrivateFields(_serverConfig); + initializeServiceData(); } catch (err) { console.error('could not read or parse config: ' + err); } diff --git a/src/lib/server/serviceData.ts b/src/lib/server/serviceData.ts deleted file mode 100644 index 9c54465..0000000 --- a/src/lib/server/serviceData.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Config, ServiceConfig } from '$lib/config'; -import type { - ServiceData, - ServiceHandler, - ServiceHandlerArgs, - ServiceStatus -} from '$lib/services/service'; -import { getServiceHandler } from '$lib/services/services'; -import { serverConfig } from './config'; - -export const serviceData: Array> = []; - -function wrapGeneric(handler: ServiceHandler): ServiceHandler { - return handler; -} - -function pollServices() { - const config: Config = serverConfig(); - - for (const [i, group] of config.services.entries()) { - if (serviceData[i] == undefined) { - serviceData[i] = []; - } - for (const [j, service] of group.items.entries()) { - let handler = getServiceHandler(service.type || ''); - if (handler == undefined) { - handler = wrapGeneric(() => { - return {}; - }); - } - - if (handler.constructor.name !== 'AsyncFucntion') { - handler = wrapGeneric(handler); - } - - (handler({ config: service }) as Promise).then((value) => { - serviceData[i][j] = value; - }); - } - } -} - -export function initServicePolling() { - setInterval(pollServices, 30000); -} diff --git a/src/lib/server/serviceDataPolling.ts b/src/lib/server/serviceDataPolling.ts new file mode 100644 index 0000000..c1c4c24 --- /dev/null +++ b/src/lib/server/serviceDataPolling.ts @@ -0,0 +1,65 @@ +import type { Config, ServiceConfig } from '$lib/config'; +import type { AsyncServiceHandler, ServiceData, ServiceHandler } from '$lib/services/service'; +import { getServiceHandler } from '$lib/services/services'; +import { serverConfig } from './config'; + +export const serviceData: Array> = []; + +function isPromise(p: any): boolean { + return typeof p === 'object' && typeof p.then === 'function'; +} + +async function pollGeneric(upstream: ServiceData, url: string): Promise { + upstream.status = undefined; + return fetch(url).then((resp: Response): ServiceData => { + upstream.status = resp.ok ? 'online' : 'offline'; + return upstream; + }); +} + +function wrapHandler(handler: ServiceHandler): AsyncServiceHandler { + return async (config: ServiceConfig): Promise => { + const value = handler(config); + if (isPromise(value) === true) { + return (value as Promise).then((data: ServiceData) => { + if (data.status != undefined) { + return data; + } + return pollGeneric(data, config.url); + }); + } + return pollGeneric(value as ServiceData, config.url); + }; +} + +function pollServices() { + const config: Config = serverConfig(); + + for (const [i, group] of config.services.entries()) { + for (const [j, service] of group.items.entries()) { + const handler: ServiceHandler = + getServiceHandler(service.type || '') || + (() => { + return {} as ServiceData; + }); + + wrapHandler(handler)(service).then((data: ServiceData) => { + serviceData[i][j] = data; + }); + } + } +} + +export function initializeServiceData() { + const config = serverConfig(); + serviceData.length = config.services.length; + for (let i = 0; i < serviceData.length; i++) { + serviceData[i] = []; + } +} + +export function initServicePolling() { + initializeServiceData(); + pollServices(); + setInterval(pollServices, 30000); +} diff --git a/src/lib/services/pihole/+service.ts b/src/lib/services/pihole/+service.ts index 194be11..8aa0cb7 100644 --- a/src/lib/services/pihole/+service.ts +++ b/src/lib/services/pihole/+service.ts @@ -1,11 +1,21 @@ -import { type ServiceHandler } from '../service'; +import type { ServiceConfig } from '$lib/config'; +import { type ServiceData, type ServiceHandler } from '../service'; -export const handle: ServiceHandler = ({ fetch, config }) => { - const url = config.url + '/api.php?summaryRaw&auth=' + config.api_token; - - return { - logo: 'https://cdn.rawgit.com/pi-hole/graphics/master/Vortex/Vortex.svg', - raw: fetch(url).then((raw) => raw.json()), - url: url +export const handle: ServiceHandler = async (config: ServiceConfig) => { + const res: ServiceData = { + logo: 'https://cdn.rawgit.com/pi-hole/graphics/master/Vortex/Vortex.svg' }; + + try { + const resp: Response = await fetch( + config.url + '/api.php?summaryRaw&auth=' + config.api_token + ); + res.status = resp.ok ? 'online' : 'offline'; + res.raw = resp.json(); + } catch (error) { + res.status = undefined; + console.warn('could not fetch pihole status: ' + error); + } + + return res; }; diff --git a/src/lib/services/service.ts b/src/lib/services/service.ts index 037b8fe..6252423 100644 --- a/src/lib/services/service.ts +++ b/src/lib/services/service.ts @@ -1,9 +1,5 @@ import type { ServiceConfig } from '$lib/config'; -export interface ServiceHandlerArgs { - config: ServiceConfig; -} - export type ServiceStatus = 'online' | 'offline'; export interface ServiceData { @@ -12,4 +8,5 @@ export interface ServiceData { [x: string]: unknown; } -export type ServiceHandler = (input: ServiceHandlerArgs) => ServiceData | Promise; +export type ServiceHandler = (input: ServiceConfig) => ServiceData | Promise; +export type AsyncServiceHandler = (input: ServiceConfig) => Promise; diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index aea2769..e80cc64 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,31 +1,12 @@ import { clientAddressIsPrivate, clientConfig, serverConfig } from '$lib/server/config'; -import type { PageServerLoad } from './$types'; -import { getServiceHandler } from '$lib/services/services'; - -export const load: PageServerLoad = async ({ fetch, getClientAddress }) => { - const serviceData: Array> = []; - const config = clientConfig(); - - const privateConfig = serverConfig(); - - for (const [i, group] of privateConfig.services.entries()) { - const groupData: Array = []; - serviceData.push(groupData); - for (const [j, service] of group.items.entries()) { - const handler = getServiceHandler(service.type || ''); - if (handler == undefined) { - config.services[i].items[j].type = undefined; - groupData.push(undefined); - } else { - groupData.push(handler({ fetch, config: service })); - } - } - } +import type { PageServerLoad } from './$types.d'; +import { serviceData } from '$lib/server/serviceDataPolling'; +export const load: PageServerLoad = async ({ getClientAddress }) => { return { - config, - serviceData, + config: clientConfig(), + serviceData: serviceData, location: getClientAddress(), - privateAccess: clientAddressIsPrivate(getClientAddress(), privateConfig) + privateAccess: clientAddressIsPrivate(getClientAddress(), serverConfig()) }; };