Attempts to a broken dynamic service loading.

This commit is contained in:
2023-08-11 16:08:10 +02:00
parent 22545e46bb
commit 09304b9a95
9 changed files with 128 additions and 64 deletions

View File

@@ -5,5 +5,6 @@
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
"tabWidth": 4
}

View File

@@ -11,4 +11,5 @@ services:
icon: 'fas fa-hard-drive'
target: '_blank'
url: '/NAS'
type: 'hello'
keywords: 'cloud storage files'

View File

@@ -1,24 +0,0 @@
import { dev } from "$app/environment";
import { clientConfig, stripPrivateFields, type Config } from "$lib/config";
import type { Handle } from "@sveltejs/kit";
import { readFile } from 'fs';
import * as yml from 'js-yaml';
async function reloadConfig(): Promise<Config> {
try {
const dynamic = yml.load(await readFile('/dynamic/config.yml', 'utf8'));
return stripPrivateFields({ ...clientConfig, ...dynamic });
} catch (err) {
return clientConfig;
}
}
export const handle: Handle = async ({ event, resolve }) => {
if ( dev == false ) {
Object.assign(event.locals,{config: await reloadConfig()});
}
const response = await resolve(event);
return response;
}

View File

@@ -1,32 +1,33 @@
import configData from '../config.yml';
export interface Brand {
export interface BrandConfig {
logo?: string;
icon?: string;
usemask?: boolean;
}
export interface Section extends Brand {
export interface SectionConfig extends BrandConfig {
title: string;
subtitle?: string;
}
export interface Service extends Section {
export interface ServiceConfig extends SectionConfig {
url: string;
target?: string;
type?: string;
data?: Record<string, unknown>;
[x: string]: unknown;
}
export interface ServiceGroup extends Section {
items: Service[];
export interface ServiceGroupConfig extends SectionConfig {
items: ServiceConfig[];
[x: string]: unknown;
}
export interface Config extends Section {
services: ServiceGroup[];
export interface Config extends SectionConfig {
services: ServiceGroupConfig[];
[x: string]: unknown;
}
@@ -46,9 +47,9 @@ const requiredConfig: DeepRequired<Config> = {
items: [
{
title: '',
url: '',
url: ''
}
],
]
}
]
};
@@ -63,47 +64,44 @@ export const defaultConfig: Config = {
services: []
};
type SPOJO = Record<string, unknown>;
type SPOJO = Record<string,unknown>
function strip<Type extends SPOJO>(toStrip: Type, reference: Type): Type {
const res: Type = { ...toStrip };
const referenceNames = Object.entries(reference).map(([key, value]) => key);
function strip<Type extends SPOJO> (toStrip: Type, reference: Type): Type {
const res: Type = {...toStrip}
const referenceNames = Object.entries(reference).map(([key,value]) => key)
for ( const [key,value] of Object.entries(res) ) {
if ( referenceNames.includes(key) == false ) {
for (const [key, value] of Object.entries(res)) {
if (referenceNames.includes(key) == false) {
// remove the object
delete res[key];
continue
}
// strips further arrays
if ( value instanceof Array ) {
const stripped : SPOJO = {};
const childRef = (reference[key] as Array<SPOJO>)[0];
stripped[key] = value.map((v: SPOJO) => strip(v,childRef));
Object.assign(res,stripped);
continue;
}
if ( typeof value != "object") {
continue
// strips further arrays
if (value instanceof Array) {
const stripped: SPOJO = {};
const childRef = (reference[key] as Array<SPOJO>)[0];
stripped[key] = value.map((v: SPOJO) => strip(v, childRef));
Object.assign(res, stripped);
continue;
}
if (typeof value != 'object') {
continue;
}
// it is a child object, we strip it further
const stripped : SPOJO = {};
stripped[key] = strip(value as SPOJO,reference[key] as SPOJO);
Object.assign(res,stripped);
const stripped: SPOJO = {};
stripped[key] = strip(value as SPOJO, reference[key] as SPOJO);
Object.assign(res, stripped);
}
return res;
}
export function stripPrivateFields(config: Config): Config {
return strip(config,requiredConfig);
return strip(config, requiredConfig);
}
export const config: Config = mergeConfig(defaultConfig, configData);
export const clientConfig :Config = stripPrivateFields(config)
export const clientConfig: Config = stripPrivateFields(config);

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import type { ServiceConfig } from '$lib/config';
export let data: ServiceConfig;
</script>
<div class="service-card">
<title>{data.title}</title>
</div>

View File

@@ -0,0 +1,27 @@
import type { ServiceConfig } from '$lib/config';
interface ServiceHandlerArgs {
fetch: typeof fetch;
config: ServiceConfig;
}
export type ServiceHandler = (input: Partial<ServiceHandlerArgs>) => {
data: Record<string, unknown>;
componentPath: string;
};
const services: Record<string, ServiceHandler> = {};
export function registerService(type: string, handler: ServiceHandler) {
services[type] = handler;
}
export function getService(type: string): ServiceHandler {
const handler = services[type];
if (handler == undefined) {
return () => {
return { data: {}, componentPath: 'generic/GenericServiceCard.svelte' };
};
}
return handler;
}

View File

@@ -1,12 +1,39 @@
import { dev } from '$app/environment';
import { clientConfig, type Config } from '$lib/config';
import { config, stripPrivateFields, type Config } from '$lib/config';
import type { PageServerLoad } from './$types';
import * as yml from 'js-yaml';
import { readFile } from 'fs/promises';
import { getService } from '$lib/services/service';
export const load: PageServerLoad = ({locals}) => {
async function reloadConfig(): Promise<Config> {
if (dev) {
return { config: clientConfig };
return config;
}
try {
const dynamic = yml.load(await readFile('/dynamic/config.yml', 'utf8'));
return { ...config, ...dynamic };
} catch (err) {
return config;
}
}
return {config: (locals as Record<string,Config>).config || clientConfig};
function zip<T>(a: Array<T>, b: Array<T>): Array<Array<T>> {
const size = Math.min(a.length, b.length);
return a.slice(0, size).map((v, i) => [v, b[i]]);
}
export const load: PageServerLoad = async ({ fetch, depends }) => {
depends('app:state');
const serverConfig = await reloadConfig();
const clientConfig = stripPrivateFields(serverConfig);
for (const [serverGroup, clientGroup] of zip(serverConfig.services, clientConfig.services)) {
for (const [serverService, clientService] of zip(serverGroup.items, clientGroup.items)) {
const handler = getService(serverService.type || '');
Object.assign(clientService, handler({ fetch, config: serverService }));
}
}
clientConfig.timestamp = new Date();
return { config: clientConfig };
};

View File

@@ -1,10 +1,20 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { invalidate } from '$app/navigation';
export let data: PageData;
onMount(() => {
const interval = setInterval(() => {
invalidate('app:state');
}, 10000);
return () => clearInterval(interval);
});
</script>
<h1>{data.config.title}</h1>
{#if data.config.subtitle}
<h2>{data.config.subtitle}</h2>
{/if}
<pre> {JSON.stringify(data.config, null, 4)} </pre>

15
src/routes/+page.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data }) => {
for (const group of data.config.services) {
for (const service of group.items) {
const path =
'../lib/services/' + (service.componentPath || 'generic/GenericServiceCard.svelte');
const module = await import(/* @vite-ignore */ path);
service.component = module.default;
delete service.componentPath;
}
}
return { config: data.config };
};