Attempts to a broken dynamic service loading.
This commit is contained in:
@@ -5,5 +5,6 @@
|
|||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"pluginSearchDirs": ["."],
|
"pluginSearchDirs": ["."],
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
|
||||||
|
"tabWidth": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ services:
|
|||||||
icon: 'fas fa-hard-drive'
|
icon: 'fas fa-hard-drive'
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
url: '/NAS'
|
url: '/NAS'
|
||||||
|
type: 'hello'
|
||||||
keywords: 'cloud storage files'
|
keywords: 'cloud storage files'
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,33 @@
|
|||||||
import configData from '../config.yml';
|
import configData from '../config.yml';
|
||||||
|
|
||||||
export interface Brand {
|
export interface BrandConfig {
|
||||||
logo?: string;
|
logo?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
usemask?: boolean;
|
usemask?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Section extends Brand {
|
export interface SectionConfig extends BrandConfig {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Service extends Section {
|
export interface ServiceConfig extends SectionConfig {
|
||||||
url: string;
|
url: string;
|
||||||
target?: string;
|
target?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
|
||||||
[x: string]: unknown;
|
[x: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceGroup extends Section {
|
export interface ServiceGroupConfig extends SectionConfig {
|
||||||
items: Service[];
|
items: ServiceConfig[];
|
||||||
|
|
||||||
[x: string]: unknown;
|
[x: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config extends Section {
|
export interface Config extends SectionConfig {
|
||||||
services: ServiceGroup[];
|
services: ServiceGroupConfig[];
|
||||||
|
|
||||||
[x: string]: unknown;
|
[x: string]: unknown;
|
||||||
}
|
}
|
||||||
@@ -46,9 +47,9 @@ const requiredConfig: DeepRequired<Config> = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
url: ''
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -63,47 +64,44 @@ export const defaultConfig: Config = {
|
|||||||
services: []
|
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 {
|
for (const [key, value] of Object.entries(res)) {
|
||||||
const res: Type = {...toStrip}
|
if (referenceNames.includes(key) == false) {
|
||||||
const referenceNames = Object.entries(reference).map(([key,value]) => key)
|
|
||||||
|
|
||||||
for ( const [key,value] of Object.entries(res) ) {
|
|
||||||
if ( referenceNames.includes(key) == false ) {
|
|
||||||
// remove the object
|
// remove the object
|
||||||
delete res[key];
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( typeof value != "object") {
|
// strips further arrays
|
||||||
continue
|
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
|
// it is a child object, we strip it further
|
||||||
const stripped : SPOJO = {};
|
const stripped: SPOJO = {};
|
||||||
stripped[key] = strip(value as SPOJO,reference[key] as SPOJO);
|
stripped[key] = strip(value as SPOJO, reference[key] as SPOJO);
|
||||||
Object.assign(res,stripped);
|
Object.assign(res, stripped);
|
||||||
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripPrivateFields(config: Config): Config {
|
export function stripPrivateFields(config: Config): Config {
|
||||||
return strip(config,requiredConfig);
|
return strip(config, requiredConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const config: Config = mergeConfig(defaultConfig, configData);
|
export const config: Config = mergeConfig(defaultConfig, configData);
|
||||||
|
|
||||||
export const clientConfig :Config = stripPrivateFields(config)
|
export const clientConfig: Config = stripPrivateFields(config);
|
||||||
|
|||||||
9
src/lib/services/generic/GenericServiceCard.svelte
Normal file
9
src/lib/services/generic/GenericServiceCard.svelte
Normal 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>
|
||||||
27
src/lib/services/service.ts
Normal file
27
src/lib/services/service.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,12 +1,39 @@
|
|||||||
import { dev } from '$app/environment';
|
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 type { PageServerLoad } from './$types';
|
||||||
|
import * as yml from 'js-yaml';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
import { getService } from '$lib/services/service';
|
||||||
|
|
||||||
|
async function reloadConfig(): Promise<Config> {
|
||||||
export const load: PageServerLoad = ({locals}) => {
|
|
||||||
if (dev) {
|
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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { invalidate } from '$app/navigation';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
onMount(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
invalidate('app:state');
|
||||||
|
}, 10000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>{data.config.title}</h1>
|
<h1>{data.config.title}</h1>
|
||||||
{#if data.config.subtitle}
|
{#if data.config.subtitle}
|
||||||
<h2>{data.config.subtitle}</h2>
|
<h2>{data.config.subtitle}</h2>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<pre> {JSON.stringify(data.config, null, 4)} </pre>
|
||||||
|
|||||||
15
src/routes/+page.ts
Normal file
15
src/routes/+page.ts
Normal 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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user