From 47a72c82f468ab4ac4ce72d5349908ecafc1a8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Wed, 25 Jun 2025 22:38:01 +0200 Subject: [PATCH] some improvements --- package-lock.json | 25 ------ package.json | 2 - src/components/Toast.svelte | 33 ++++++++ src/routes/dashboard/+page.server.ts | 55 +++++++++---- src/routes/dashboard/+page.svelte | 7 +- .../dashboard/server/[id]/+page.server.ts | 77 ++++++++++--------- src/routes/dashboard/server/[id]/+page.svelte | 9 ++- 7 files changed, 128 insertions(+), 80 deletions(-) create mode 100644 src/components/Toast.svelte diff --git a/package-lock.json b/package-lock.json index 75ddf7b..5e088bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,7 @@ "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@ethercorps/sveltekit-redis-session": "^1.3.1", - "@sveltejs/adapter-auto": "^4.0.0", "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/postcss": "^4.0.4", @@ -1319,19 +1317,6 @@ "win32" ] }, - "node_modules/@sveltejs/adapter-auto": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-4.0.0.tgz", - "integrity": "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-meta-resolve": "^4.1.0" - }, - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, "node_modules/@sveltejs/adapter-node": { "version": "5.2.12", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", @@ -1348,16 +1333,6 @@ "@sveltejs/kit": "^2.4.0" } }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, "node_modules/@sveltejs/kit": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.17.1.tgz", diff --git a/package.json b/package.json index 99baec2..ebeb1b1 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,7 @@ "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@ethercorps/sveltekit-redis-session": "^1.3.1", - "@sveltejs/adapter-auto": "^4.0.0", "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/postcss": "^4.0.4", diff --git a/src/components/Toast.svelte b/src/components/Toast.svelte new file mode 100644 index 0000000..447c553 --- /dev/null +++ b/src/components/Toast.svelte @@ -0,0 +1,33 @@ + + +{#if visible} + +{/if} diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index 2aa7a59..7a7eedf 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -1,7 +1,6 @@ -import { logout } from '$api/authService'; import { checkAuth } from '$api/authService'; import { getServers, restartService, startService, stopService } from '$api/serverService'; -import { redirect, type Actions, type RequestEvent } from '@sveltejs/kit'; +import { fail, redirect, type Actions, type RequestEvent } from '@sveltejs/kit'; export const load = async (event: RequestEvent) => { const isAuth = await checkAuth(event); @@ -10,17 +9,45 @@ export const load = async (event: RequestEvent) => { return { servers }; }; +// Helper function to create a server action with validation and error handling +const createServerAction = ( + action: (event: RequestEvent, id: number) => Promise, + { success, failure }: { success: string; failure: string } +) => { + return async (event: RequestEvent) => { + const formData = await event.request.formData(); + const id = formData.get('id'); + + if (!id || typeof id !== 'string') { + return fail(400, { message: 'Invalid server ID provided.' }); + } + + const serverId = Number(id); + if (isNaN(serverId)) { + return fail(400, { message: 'Server ID must be a number.' }); + } + + try { + await action(event, serverId); + return { success: true, message: success }; + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + return fail(500, { message: `${failure}: ${message}` }); + } + }; +}; + export const actions = { - start: async (event: RequestEvent) => { - const id = (await event.request.formData()).get('id') as string; - await startService(event, +id); - }, - restart: async (event: RequestEvent) => { - const id = (await event.request.formData()).get('id') as string; - await restartService(event, +id); - }, - stop: async (event: RequestEvent) => { - const id = (await event.request.formData()).get('id') as string; - await stopService(event, +id); - } + start: createServerAction(startService, { + success: 'Server started successfully.', + failure: 'Failed to start server' + }), + restart: createServerAction(restartService, { + success: 'Server restarted successfully.', + failure: 'Failed to restart server' + }), + stop: createServerAction(stopService, { + success: 'Server stopped successfully.', + failure: 'Failed to stop server' + }) } satisfies Actions; diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index d89e5d4..188709b 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -1,11 +1,16 @@ +{#if form?.message} + +{/if} +
diff --git a/src/routes/dashboard/server/[id]/+page.server.ts b/src/routes/dashboard/server/[id]/+page.server.ts index 7669d93..7e4afc4 100644 --- a/src/routes/dashboard/server/[id]/+page.server.ts +++ b/src/routes/dashboard/server/[id]/+page.server.ts @@ -2,15 +2,14 @@ import { updateConfig, getConfigFiles, getServerById, - getStateHistory, getStateHistoryStats } from '$api/serverService'; import type { Actions } from './$types'; import { checkAuth } from '$api/authService'; import { getTracks } from '$api/lookupService'; -import { redirect } from '@sveltejs/kit'; +import { fail, redirect } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit'; -import { configFile, type Config, type Session } from '$models/config'; +import { configFile, type Config } from '$models/config'; import { set } from 'lodash-es'; import { subDays, formatISO } from 'date-fns'; import { UTCDate } from '@date-fns/utc'; @@ -38,20 +37,16 @@ export const load = async (event: RequestEvent) => { }; }; -type SessionField = - | 'sessionDurationMinutes' - | 'sessionType' - | 'timeMultiplier' - | 'dayOfWeekend' - | 'hourOfDay'; - export const actions = { update: async (event: RequestEvent) => { - const { id, restart, file, data } = await destructureFormData(event); - - const sessions: Array> = []; - - await updateConfig(event, id, file, data, true, restart); + try { + const { id, restart, file, data } = await destructureFormData(event); + await updateConfig(event, id, file, data, true, restart); + return { success: true, message: `Configuration file '${file}' updated successfully.` }; + } catch (err) { + const message = err instanceof Error ? err.message : 'An unknown error occurred'; + return fail(500, { message: `Failed to update configuration: ${message}` }); + } } } satisfies Actions; @@ -59,36 +54,46 @@ async function destructureFormData( event: RequestEvent ): Promise<{ id: string; restart: boolean; data: Config; file: configFile }> { const formData = await event.request.formData(); - const id = formData.get('id') as string; + + const id = formData.get('id'); + if (!id || typeof id !== 'string') { + throw new Error('Server ID is missing or invalid.'); + } + + const file = formData.get('file'); + if (!file || typeof file !== 'string') { + throw new Error('Config file name is missing or invalid.'); + } + const restart = formData.get('restart'); - const file = formData.get('file') as configFile; - const object: any = {}; + const object: Record = {}; + formData.forEach((value, key) => { - switch (key) { - case 'id': - case 'restart': - case 'file': - return; - default: - set(object, key, parseFormField(value)); + // Exclude metadata fields from the dynamic object + if (key === 'id' || key === 'restart' || key === 'file') { + return; } + set(object, key, parseFormField(value)); }); + return { id, - restart: restart == 'on' || restart == 'true', - data: object, - file + restart: restart === 'on' || restart === 'true', + data: object as unknown as Config, + file: file as configFile }; } +/** + * Parses a form field value. If the value can be cleanly converted to a number, + * it returns a number; otherwise, it returns the original string. + */ function parseFormField(value: FormDataEntryValue): string | number { - return value !== '' && !Number.isNaN(+value) ? +value : (value as string); -} - -function tryParse(str: string) { - try { - return JSON.parse(str); - } catch { - return str; + // Avoid converting empty strings to 0 + if (value === '' || typeof value !== 'string') { + return value as string; } + // Check if string is a valid number without being too aggressive + const num = Number(value); + return !Number.isNaN(num) && value.trim() !== '' ? num : value; } diff --git a/src/routes/dashboard/server/[id]/+page.svelte b/src/routes/dashboard/server/[id]/+page.svelte index e143e19..721b326 100644 --- a/src/routes/dashboard/server/[id]/+page.svelte +++ b/src/routes/dashboard/server/[id]/+page.svelte @@ -5,10 +5,11 @@ import EditorEventRules from '$components/EditorEventRules.svelte'; import EditorSettings from '$components/EditorSettings.svelte'; import Statistics from '$components/Statistics.svelte'; + import Toast from '$components/Toast.svelte'; import { getStatusColor, serviceStatusToString } from '$lib/types/serviceStatus.js'; import { serverTab } from '$models/config.js'; - let { data } = $props(); + let { data, form } = $props(); const configs = data.configs; const tracks = data.tracks; const id = data.id; @@ -17,6 +18,10 @@ let tab = $state(serverTab.statistics); +{#if form?.message} + +{/if} + {server.name} @@ -30,7 +35,7 @@

{server.name}

- + {serviceStatusToString(server.status)}