From ee0b141ecb67d48491ffb2fdef8a3de33f65c70b Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 13:42:30 +0000 Subject: [PATCH 01/15] fix(frontend): refresh clusters after starting, stopping, or restarting --- packages/frontend/src/lib/remotes/clusters.remote.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index fa2d2e3..c5baab5 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -36,7 +36,7 @@ export const startCluster = command("unchecked", async (id: number) => { switch (response.status) { case 204: - return; + return await getClusters().refresh(); case 401: return error(401, "Unauthorized"); case 404: @@ -59,7 +59,7 @@ export const stopCluster = command("unchecked", async (id: number) => { switch (response.status) { case 204: - return; + return await getClusters().refresh(); case 401: return error(401, "Unauthorized"); case 404: @@ -82,7 +82,7 @@ export const restartCluster = command("unchecked", async (id: number) => { switch (response.status) { case 204: - return; + return await getClusters().refresh(); case 401: return error(401, "Unauthorized"); case 404: From 0ebaca2c70964218de4bb1236603d1148ca9545c Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 13:42:48 +0000 Subject: [PATCH 02/15] fix(frontend): sort clusters by ID after fetching --- packages/frontend/src/lib/remotes/clusters.remote.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index c5baab5..3a9a00f 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -15,7 +15,8 @@ export const getClusters = query(async (): Promise => { switch (response.status) { case 200: - return await response.json(); + const clusters: GetClusterDto[] = await response.json(); + return clusters.sort((a, b) => a.id.localeCompare(b.id)); case 401: return error(401, "Unauthorized"); From 4e2eecc1af8f26d7dba83c7fe7bb256d843de8f7 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 13:43:49 +0000 Subject: [PATCH 03/15] feat(frontend): add `getClusterLogs` remote to fetch logs for a specific cluster --- .../src/lib/remotes/clusters.remote.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index 3a9a00f..80f764a 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -1,6 +1,6 @@ import { command, getRequestEvent, query } from "$app/server"; import { env } from "$env/dynamic/private"; -import type { GetClusterDto } from "@hallmaster/backend/dto"; +import { type GetClusterDto, type GetClusterLogsDto } from "@hallmaster/backend/dto"; import { error } from "@sveltejs/kit"; export const getClusters = query(async (): Promise => { @@ -93,3 +93,26 @@ export const restartCluster = command("unchecked", async (id: number) => { return error(500, "An error occured"); } }); + +export const getClusterLogs = query( + "unchecked", + async (id: GetClusterDto["id"]): Promise => { + const token = getRequestEvent().cookies.get("token"); + + const response = await fetch(`${env.API_URL}/clusters/${id}/logs`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + switch (response.status) { + case 200: + return await response.json(); + case 401: + return error(401, "Unauthorized"); + case 404: + return error(404, "Cluster not found"); + + default: + return error(500, "An error occured"); + } + }, +); From 3c47b9c4fa726807b0016045dbaf602581f3cdbc Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 13:45:30 +0000 Subject: [PATCH 04/15] refactor(frontend): move body background styles to layout component --- packages/frontend/src/app.css | 5 ----- packages/frontend/src/routes/(form)/+layout.svelte | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/app.css b/packages/frontend/src/app.css index 421515d..e7733b3 100644 --- a/packages/frontend/src/app.css +++ b/packages/frontend/src/app.css @@ -9,8 +9,3 @@ input:focus { outline: none; } - -body { - background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px); - background-size: 15px 15px; -} diff --git a/packages/frontend/src/routes/(form)/+layout.svelte b/packages/frontend/src/routes/(form)/+layout.svelte index 04731ec..3433a61 100644 --- a/packages/frontend/src/routes/(form)/+layout.svelte +++ b/packages/frontend/src/routes/(form)/+layout.svelte @@ -7,3 +7,11 @@ let { children }: LayoutProps = $props();
{@render children()}
+ + From 0a53b2a77303454c321a08aa70b16cdbc59d25b0 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 13:45:59 +0000 Subject: [PATCH 05/15] feat(frontend): add `text-tiny` theme variable for smaller text size --- packages/frontend/src/lib/styles/theme.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend/src/lib/styles/theme.css b/packages/frontend/src/lib/styles/theme.css index feeecab..e9cfecd 100644 --- a/packages/frontend/src/lib/styles/theme.css +++ b/packages/frontend/src/lib/styles/theme.css @@ -205,3 +205,7 @@ --color-surface-contrast-900: var(--color-surface-contrast-light); --color-surface-contrast-950: var(--color-surface-contrast-light); } + +@theme text-tiny { + --text-tiny: 0.625rem; +} From c7ab0338a7e701aad91f99eab095217c7564e175 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Wed, 13 May 2026 14:04:00 +0000 Subject: [PATCH 06/15] build(frontend): upgraded `@sveltejs/kit` to `^2.59.1` --- packages/frontend/package.json | 2 +- pnpm-lock.yaml | 89 ++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a90bb13..5f5108b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -21,7 +21,7 @@ "@skeletonlabs/skeleton": "^4.12.0", "@skeletonlabs/skeleton-svelte": "^4.12.0", "@sveltejs/adapter-node": "^5.5.4", - "@sveltejs/kit": "^2.53.0", + "@sveltejs/kit": "^2.59.1", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.2.0", "@types/node": "^22.19.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bc1305..19e8056 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,10 +206,10 @@ importers: version: 4.12.0(svelte@5.53.0) '@sveltejs/adapter-node': specifier: ^5.5.4 - version: 5.5.4(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0))) + version: 5.5.4(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0))) '@sveltejs/kit': - specifier: ^2.53.0 - version: 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) + specifier: ^2.59.1 + version: 2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) @@ -2199,15 +2199,15 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.53.0': - resolution: {integrity: sha512-Brh/9h8QEg7rWIj+Nnz/2sC49NUeS8g3Qd9H5dTO3EbWG8vCEUl06jE+r5jQVDMHdr1swmCkwZkONFsWelGTpQ==} + '@sveltejs/kit@2.59.1': + resolution: {integrity: sha512-d8OON70AphLdDesuTIl//M2O6fRTIicX8aYv8vhCiYEhTTI2OboKqey0Hu1A4VFhqwgqtq0vKDmPFGkw8kKmgw==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: ^5.3.3 + typescript: ^5.3.3 || ^6.0.0 vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 peerDependenciesMeta: '@opentelemetry/api': @@ -3176,6 +3176,10 @@ packages: resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} engines: {node: 20 || >=22} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -3221,6 +3225,10 @@ packages: resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} engines: {node: 20 || >=22} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3456,8 +3464,8 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: @@ -3664,6 +3672,9 @@ packages: devalue@5.6.3: resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} + devalue@5.8.0: + resolution: {integrity: sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -4924,8 +4935,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libphonenumber-js@1.12.37: - resolution: {integrity: sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==} + libphonenumber-js@1.13.1: + resolution: {integrity: sha512-GEw0GLL7YUUA6nv21IsCvVjtI5Ejn84sjbdfQ9KxdbqEVOk1PZh7xejn01EEiniKw+dBeCfim+8MGeuvVuE2BA==} light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} @@ -5208,6 +5219,10 @@ packages: resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5708,6 +5723,10 @@ packages: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -6485,8 +6504,8 @@ packages: typescript: optional: true - validator@13.15.26: - resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} vary@1.1.2: @@ -8650,15 +8669,15 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))': + '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))': dependencies: '@rollup/plugin-commonjs': 29.0.2(rollup@4.60.0) '@rollup/plugin-json': 6.1.0(rollup@4.60.0) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.0) - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) + '@sveltejs/kit': 2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) rollup: 4.60.0 - '@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0))': + '@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) @@ -8666,7 +8685,7 @@ snapshots: '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 - devalue: 5.6.3 + devalue: 5.8.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -9904,6 +9923,9 @@ snapshots: balanced-match@4.0.3: {} + balanced-match@4.0.4: + optional: true + bare-events@2.8.2: {} base64-js@1.5.1: {} @@ -9939,7 +9961,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.1 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -9959,6 +9981,11 @@ snapshots: dependencies: balanced-match: 4.0.3 + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + optional: true + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -10097,8 +10124,8 @@ snapshots: class-validator@0.14.3: dependencies: '@types/validator': 13.15.10 - libphonenumber-js: 1.12.37 - validator: 13.15.26 + libphonenumber-js: 1.13.1 + validator: 13.15.35 optional: true cli-cursor@3.1.0: @@ -10195,7 +10222,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.1: + content-disposition@1.1.0: optional: true content-type@1.0.5: @@ -10370,6 +10397,8 @@ snapshots: devalue@5.6.3: {} + devalue@5.8.0: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -10829,7 +10858,7 @@ snapshots: dependencies: accepts: 2.0.0 body-parser: 2.2.2 - content-disposition: 1.0.1 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -10847,7 +10876,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.0 + qs: 6.15.1 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -11181,7 +11210,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.2.3 - minimatch: 10.2.2 + minimatch: 10.2.5 minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 2.0.2 @@ -12069,7 +12098,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.12.37: + libphonenumber-js@1.13.1: optional: true light-my-request@6.6.0: @@ -12281,6 +12310,11 @@ snapshots: dependencies: brace-expansion: 5.0.2 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + optional: true + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -12812,6 +12846,11 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + optional: true + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -13750,7 +13789,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - validator@13.15.26: + validator@13.15.35: optional: true vary@1.1.2: From 0e106a8df2ad9a885fa81bf2386e53b4ce500eb0 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Thu, 14 May 2026 19:57:57 +0000 Subject: [PATCH 07/15] build(frontend): added `eventsource-parser` package --- packages/frontend/package.json | 1 + pnpm-lock.yaml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 5f5108b..08afe28 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -25,6 +25,7 @@ "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.2.0", "@types/node": "^22.19.11", + "eventsource-parser": "^3.0.8", "globals": "^16.5.0", "oxfmt": "^0.41.0", "oxlint": "^1.56.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19e8056..b406cc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,9 @@ importers: '@types/node': specifier: ^22.19.11 version: 22.19.11 + eventsource-parser: + specifier: ^3.0.8 + version: 3.0.8 globals: specifier: ^16.5.0 version: 16.5.0 @@ -4006,6 +4009,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -10823,6 +10830,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.8: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 From c2e084cf1520d6ac777b3b27b5ea4b1120b759f0 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Thu, 14 May 2026 19:59:03 +0000 Subject: [PATCH 08/15] feat(frontend): add `getClustersLive` & `getClusterLogsLive` remote functions --- .../src/lib/remotes/clusters.remote.ts | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index 80f764a..d695fa4 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -2,6 +2,7 @@ import { command, getRequestEvent, query } from "$app/server"; import { env } from "$env/dynamic/private"; import { type GetClusterDto, type GetClusterLogsDto } from "@hallmaster/backend/dto"; import { error } from "@sveltejs/kit"; +import { EventSourceParserStream } from "eventsource-parser/stream"; export const getClusters = query(async (): Promise => { const token = getRequestEvent().cookies.get("token"); @@ -16,7 +17,38 @@ export const getClusters = query(async (): Promise => { switch (response.status) { case 200: const clusters: GetClusterDto[] = await response.json(); - return clusters.sort((a, b) => a.id.localeCompare(b.id)); + return clusters.sort((a, b) => a.id - b.id); + case 401: + return error(401, "Unauthorized"); + + default: + return error(500, "An error occured"); + } +}); + +export const getClustersLive = query.live(async function* () { + const token = getRequestEvent().cookies.get("token"); + + const response = await fetch(`${env.API_URL}/clusters/stream`, { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${token}`, + }, + }); + + switch (response.status) { + case 200: + if (!response.body) return error(500, "An error occured"); + + const chunks = response.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new EventSourceParserStream()) + .values(); + + for await (const clusters of chunks) + yield (JSON.parse(clusters.data) as GetClusterDto[]).sort((a, b) => a.id - b.id); + return; + case 401: return error(401, "Unauthorized"); @@ -116,3 +148,36 @@ export const getClusterLogs = query( } }, ); + +export const getClusterLogsLive = query.live( + "unchecked", + async function* (id: GetClusterDto["id"]) { + const token = getRequestEvent().cookies.get("token"); + + const response = await fetch(`${env.API_URL}/clusters/${id}/logs/stream`, { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${token}`, + }, + }); + + switch (response.status) { + case 200: + if (!response.body) return error(500, "An error occured"); + + const chunks = response.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new EventSourceParserStream()) + .values(); + + for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterLogsDto[number]; + return; + + case 401: + return error(401, "Unauthorized"); + + default: + return error(500, "An error occured"); + } + }, +); From 3de6894c0eb067a8a5bebee7f096f0f58392bcda Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Thu, 14 May 2026 20:04:02 +0000 Subject: [PATCH 09/15] feat(frontend): add `/` dashboard page --- .../frontend/src/routes/(app)/+layout.svelte | 9 +++ .../frontend/src/routes/(app)/+page.svelte | 74 ++++++++++------- .../(app)/components/ClusterActions.svelte | 81 +++++++++++++++++++ .../(app)/components/ClusterLogs.svelte | 51 ++++++++++++ .../(app)/components/ClusterShards.svelte | 43 ++++++++++ .../(app)/components/ClusterStatus.svelte | 21 +++++ 6 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 packages/frontend/src/routes/(app)/+layout.svelte create mode 100644 packages/frontend/src/routes/(app)/components/ClusterActions.svelte create mode 100644 packages/frontend/src/routes/(app)/components/ClusterLogs.svelte create mode 100644 packages/frontend/src/routes/(app)/components/ClusterShards.svelte create mode 100644 packages/frontend/src/routes/(app)/components/ClusterStatus.svelte diff --git a/packages/frontend/src/routes/(app)/+layout.svelte b/packages/frontend/src/routes/(app)/+layout.svelte new file mode 100644 index 0000000..9be7c29 --- /dev/null +++ b/packages/frontend/src/routes/(app)/+layout.svelte @@ -0,0 +1,9 @@ + + +
+ {@render children()} +
diff --git a/packages/frontend/src/routes/(app)/+page.svelte b/packages/frontend/src/routes/(app)/+page.svelte index d184d20..f5b2d19 100644 --- a/packages/frontend/src/routes/(app)/+page.svelte +++ b/packages/frontend/src/routes/(app)/+page.svelte @@ -1,34 +1,54 @@ -
-
- {#each await getClusters() as { id, shardIds, status }} -
-
-
- {#each shardIds as id} +
+ {#each await clusters as { id, status, shardIds } (id)} + +
+ + + + + +

#{id.split("-")[0]}

+
+ +
+ +
+
+ + + {#snippet element(attributes)} + {#if !attributes.hidden}
- #{id} + +
- {/each} -
-
- {/each} -
-
+ {/if} + {/snippet} + + + {/each} + diff --git a/packages/frontend/src/routes/(app)/components/ClusterActions.svelte b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte new file mode 100644 index 0000000..9b46674 --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte @@ -0,0 +1,81 @@ + + +{#if id} + {#each actions as { require, remote, icon, label }} + {#if require === status} + {@const Icon = icon} + + + {/if} + {/each} +{/if} + + diff --git a/packages/frontend/src/routes/(app)/components/ClusterLogs.svelte b/packages/frontend/src/routes/(app)/components/ClusterLogs.svelte new file mode 100644 index 0000000..776c927 --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterLogs.svelte @@ -0,0 +1,51 @@ + + +
+
+ +

Logs

+
+ + {#await getClusterLogs(id) then logs} +
+ {#each logs.concat(live).toReversed() as { content, stream }} +

+ {content} +

+ {/each} +
+ {/await} +
diff --git a/packages/frontend/src/routes/(app)/components/ClusterShards.svelte b/packages/frontend/src/routes/(app)/components/ClusterShards.svelte new file mode 100644 index 0000000..b02c3ca --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterShards.svelte @@ -0,0 +1,43 @@ + + +
+
+ +

+ Shards ({shards.length}) +

+
+ +
+ {#each shards as id} +
+

+ {String(id).padStart(2, "0")} +

+
+ {/each} +
+
diff --git a/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte b/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte new file mode 100644 index 0000000..d5a76cc --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte @@ -0,0 +1,21 @@ + + +
+ {status.toLocaleLowerCase()} +
From f32b7b139c74db1d1a99bbb8f94c5ed3f2b2c369 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Fri, 15 May 2026 13:25:40 +0000 Subject: [PATCH 10/15] feat(frontend): add memory & cpu usage --- packages/frontend/package.json | 1 + .../src/lib/remotes/clusters.remote.ts | 42 +- .../frontend/src/lib/utils/formatBytes.ts | 5 + .../frontend/src/routes/(app)/+page.svelte | 4 + .../routes/(app)/components/ClusterCPU.svelte | 62 +++ .../(app)/components/ClusterMemory.svelte | 61 +++ pnpm-lock.yaml | 417 ++++++++++++++++++ 7 files changed, 591 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/src/lib/utils/formatBytes.ts create mode 100644 packages/frontend/src/routes/(app)/components/ClusterCPU.svelte create mode 100644 packages/frontend/src/routes/(app)/components/ClusterMemory.svelte diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 08afe28..759032a 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -27,6 +27,7 @@ "@types/node": "^22.19.11", "eventsource-parser": "^3.0.8", "globals": "^16.5.0", + "layerchart": "2.0.0-next.63", "oxfmt": "^0.41.0", "oxlint": "^1.56.0", "oxlint-tsgolint": "^0.17.1", diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index d695fa4..1bcce7f 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -1,6 +1,10 @@ import { command, getRequestEvent, query } from "$app/server"; import { env } from "$env/dynamic/private"; -import { type GetClusterDto, type GetClusterLogsDto } from "@hallmaster/backend/dto"; +import { + type GetClusterDto, + type GetClusterLogsDto, + type GetClusterStatsDto, +} from "@hallmaster/backend/dto"; import { error } from "@sveltejs/kit"; import { EventSourceParserStream } from "eventsource-parser/stream"; @@ -181,3 +185,39 @@ export const getClusterLogsLive = query.live( } }, ); + +export const getClusterStatsLive = query.live( + "unchecked", + async function* (id: GetClusterDto["id"]) { + const token = getRequestEvent().cookies.get("token"); + + const response = await fetch(new URL(`/clusters/${id}/stats/stream`, env.API_URL), { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${token}`, + }, + }); + + switch (response.status) { + case 200: + if (!response.body) return error(500, "An error occured"); + + const chunks = response.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new EventSourceParserStream()) + .values(); + + for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterStatsDto; + break; + case 400: + return error(400, "Cluster has no container or is not running"); + case 401: + return error(401, "Unauthorized"); + case 404: + return error(404, "Cluster not found"); + + default: + return error(500, "An error occured"); + } + }, +); diff --git a/packages/frontend/src/lib/utils/formatBytes.ts b/packages/frontend/src/lib/utils/formatBytes.ts new file mode 100644 index 0000000..e868698 --- /dev/null +++ b/packages/frontend/src/lib/utils/formatBytes.ts @@ -0,0 +1,5 @@ +export default function formatBytes(bytes: number) { + const suffixes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return (!bytes && "0 Bytes") || (bytes / Math.pow(1024, i)).toFixed(2) + " " + suffixes[i]; +} diff --git a/packages/frontend/src/routes/(app)/+page.svelte b/packages/frontend/src/routes/(app)/+page.svelte index f5b2d19..e733bf0 100644 --- a/packages/frontend/src/routes/(app)/+page.svelte +++ b/packages/frontend/src/routes/(app)/+page.svelte @@ -7,6 +7,8 @@ import ClusterLogs from "./components/ClusterLogs.svelte"; import ClusterShards from "./components/ClusterShards.svelte"; import ClusterStatus from "./components/ClusterStatus.svelte"; + import ClusterMemory from "./components/ClusterMemory.svelte"; + import ClusterCPU from "./components/ClusterCPU.svelte"; let clusters = getClusters(); $effect(() => { @@ -45,6 +47,8 @@ > + + {/if} {/snippet} diff --git a/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte b/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte new file mode 100644 index 0000000..aba3e54 --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte @@ -0,0 +1,62 @@ + + +
+
+ +

+ CPU + + ({cpu.at(-1)?.percentage.toPrecision(2) ?? 0} %) + +

+
+ +
+ y.toPrecision(2) + "%", + }, + header: { format: (x) => new Date(x).toLocaleTimeString() }, + }, + }} + /> +
+
diff --git a/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte b/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte new file mode 100644 index 0000000..50d4725 --- /dev/null +++ b/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte @@ -0,0 +1,61 @@ + + +
+
+ +

+ Memory + + ({formatBytes(memory.at(-1)?.usage ?? 0)}) + +

+
+ +
+ new Date(x).toLocaleTimeString() }, + }, + }} + /> +
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b406cc5..3918b1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: globals: specifier: ^16.5.0 version: 16.5.0 + layerchart: + specifier: 2.0.0-next.63 + version: 2.0.0-next.63(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(zod@4.3.6) oxfmt: specifier: ^0.41.0 version: 0.41.0 @@ -618,6 +621,12 @@ packages: '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + '@dagrejs/dagre@2.0.4': + resolution: {integrity: sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==} + + '@dagrejs/graphlib@3.0.4': + resolution: {integrity: sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg==} + '@electric-sql/pglite-socket@0.0.20': resolution: {integrity: sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==} hasBin: true @@ -1202,6 +1211,18 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@layerstack/svelte-actions@1.0.1-next.18': + resolution: {integrity: sha512-gxPzCnJ1c9LTfWtRqLUzefCx+k59ZpxDUQ2XB+LokveZQPe7IDSOwHaBOEMlaGoGrtwc3Ft8dSZq+2WT2o9u/g==} + + '@layerstack/svelte-state@0.1.0-next.23': + resolution: {integrity: sha512-7O4umv+gXwFfs3/vjzFWYHNXGwYnnjBapWJ5Y+9u99F4eVk6rh4ocNwqkqQNkpMZ5tUJBlRTWjPE1So6+hEzIg==} + + '@layerstack/tailwind@2.0.0-next.21': + resolution: {integrity: sha512-Qgp2EpmEHmjtura8MQzWicR6ztBRSsRvddakFtx9ShrLMz6jWzd6bCMVVRu44Q3ZOrtXmSu4QxjCZWu1ytvuPg==} + + '@layerstack/utils@2.0.0-next.18': + resolution: {integrity: sha512-EYILHpfBRYMMEahajInu9C2AXQom5IcAEdtCeucD3QIl/fdDgRbtzn6/8QW9ewumfyNZetdUvitOksmI1+gZYQ==} + '@lucide/svelte@0.553.0': resolution: {integrity: sha512-uudJd5NF1zrsO0C2dmCo5lzctqGWNxQKUxM+HErUKyG2oCI2rSGllcsSjn6JfLaHUVy+sPESKa5Dsctm4mZHOQ==} peerDependencies: @@ -2468,6 +2489,12 @@ packages: '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -2477,6 +2504,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -3432,6 +3462,10 @@ packages: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -3563,6 +3597,117 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo-voronoi@2.1.0: + resolution: {integrity: sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate-path@2.3.0: + resolution: {integrity: sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-tile@1.0.0: + resolution: {integrity: sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-tricontour@1.1.0: + resolution: {integrity: sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ==} + engines: {node: '>=12'} + dargs@8.1.0: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} @@ -3641,6 +3786,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4412,6 +4560,10 @@ packages: engines: {node: '>=18'} hasBin: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -4461,6 +4613,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -4934,6 +5093,11 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + layerchart@2.0.0-next.63: + resolution: {integrity: sha512-pX/mShq+cOTcdjLsLk0ILjBYxucOneeQcTuuYTE5fu9e3tUX7PwWvH36jXWiIMzRhn3m5Ojds8EVfeXiv4NXEg==} + peerDependencies: + svelte: ^5.0.0 + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -5125,6 +5289,10 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -5157,6 +5325,10 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memoize@10.2.0: + resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} + engines: {node: '>=18'} + meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -5214,6 +5386,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -5876,6 +6052,9 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup@4.58.0: resolution: {integrity: sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -5893,6 +6072,21 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + runed@0.37.1: + resolution: {integrity: sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA==} + peerDependencies: + '@sveltejs/kit': ^2.21.0 + svelte: ^5.7.0 + zod: ^4.1.0 + peerDependenciesMeta: + '@sveltejs/kit': + optional: true + zod: + optional: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -6221,6 +6415,9 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + tailwindcss@4.2.0: resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} @@ -7234,6 +7431,12 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + '@dagrejs/dagre@2.0.4': + dependencies: + '@dagrejs/graphlib': 3.0.4 + + '@dagrejs/graphlib@3.0.4': {} + '@electric-sql/pglite-socket@0.0.20(@electric-sql/pglite@0.3.15)': dependencies: '@electric-sql/pglite': 0.3.15 @@ -7913,6 +8116,29 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@layerstack/svelte-actions@1.0.1-next.18': + dependencies: + '@floating-ui/dom': 1.7.5 + '@layerstack/utils': 2.0.0-next.18 + d3-scale: 4.0.2 + + '@layerstack/svelte-state@0.1.0-next.23': + dependencies: + '@layerstack/utils': 2.0.0-next.18 + + '@layerstack/tailwind@2.0.0-next.21': + dependencies: + '@layerstack/utils': 2.0.0-next.18 + clsx: 2.1.1 + d3-array: 3.2.4 + tailwind-merge: 3.6.0 + + '@layerstack/utils@2.0.0-next.18': + dependencies: + d3-array: 3.2.4 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + '@lucide/svelte@0.553.0(svelte@5.53.0)': dependencies: svelte: 5.53.0 @@ -8924,6 +9150,13 @@ snapshots: '@types/cookiejar@2.1.5': {} + '@types/d3-array@3.2.2': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -8936,6 +9169,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 22.19.11 @@ -10194,6 +10429,8 @@ snapshots: commander@6.2.1: {} + commander@7.2.0: {} + commander@8.3.0: {} comment-json@4.4.1: @@ -10323,6 +10560,114 @@ snapshots: csstype@3.2.3: {} + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo-voronoi@2.1.0: + dependencies: + d3-array: 3.2.4 + d3-delaunay: 6.0.4 + d3-geo: 3.1.1 + d3-tricontour: 1.1.0 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate-path@2.3.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-tile@1.0.0: {} + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-tricontour@1.1.0: + dependencies: + d3-delaunay: 6.0.4 + d3-scale: 4.0.2 + dargs@8.1.0: {} data-view-buffer@1.0.2: @@ -10385,6 +10730,10 @@ snapshots: defu@6.1.4: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} denque@2.1.0: {} @@ -11343,6 +11692,10 @@ snapshots: husky@9.1.7: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -11386,6 +11739,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + + internmap@2.0.3: {} + ipaddr.js@1.9.1: optional: true @@ -12100,6 +12457,42 @@ snapshots: kuler@2.0.0: {} + layerchart@2.0.0-next.63(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(zod@4.3.6): + dependencies: + '@dagrejs/dagre': 2.0.4 + '@layerstack/svelte-actions': 1.0.1-next.18 + '@layerstack/svelte-state': 0.1.0-next.23 + '@layerstack/tailwind': 2.0.0-next.21 + '@layerstack/utils': 2.0.0-next.18 + '@types/d3-contour': 3.0.6 + d3-array: 3.2.4 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dsv: 3.0.1 + d3-force: 3.0.0 + d3-geo: 3.1.1 + d3-geo-voronoi: 2.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-interpolate-path: 2.3.0 + d3-path: 3.1.0 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-sankey: 0.12.3 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-shape: 3.2.0 + d3-tile: 1.0.0 + d3-time: 3.1.0 + memoize: 10.2.0 + runed: 0.37.1(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(zod@4.3.6) + svelte: 5.53.0 + transitivePeerDependencies: + - '@sveltejs/kit' + - zod + leven@3.1.0: {} levn@0.4.1: @@ -12243,6 +12636,8 @@ snapshots: lru.min@1.1.4: {} + lz-string@1.5.0: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -12273,6 +12668,10 @@ snapshots: dependencies: fs-monkey: 1.1.0 + memoize@10.2.0: + dependencies: + mimic-function: 5.0.1 + meow@12.1.1: {} meow@13.2.0: {} @@ -12311,6 +12710,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -13008,6 +13409,8 @@ snapshots: rfdc@1.4.1: {} + robust-predicates@3.0.3: {} + rollup@4.58.0: dependencies: '@types/estree': 1.0.8 @@ -13085,6 +13488,18 @@ snapshots: dependencies: queue-microtask: 1.2.3 + runed@0.37.1(@sveltejs/kit@2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(zod@4.3.6): + dependencies: + dequal: 2.0.3 + esm-env: 1.2.2 + lz-string: 1.5.0 + svelte: 5.53.0 + optionalDependencies: + '@sveltejs/kit': 2.59.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)))(svelte@5.53.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)) + zod: 4.3.6 + + rw@1.3.3: {} + rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -13480,6 +13895,8 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tailwind-merge@3.6.0: {} + tailwindcss@4.2.0: {} tapable@2.3.0: {} From c317a72aa3950c98230505a42b060813215a7fcc Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Fri, 15 May 2026 21:35:29 +0000 Subject: [PATCH 11/15] feat(frontend): enhance layout styling and update cluster ID display --- packages/frontend/src/routes/(app)/+layout.svelte | 2 +- packages/frontend/src/routes/(app)/+page.svelte | 2 +- .../frontend/src/routes/(app)/components/ClusterActions.svelte | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/routes/(app)/+layout.svelte b/packages/frontend/src/routes/(app)/+layout.svelte index 9be7c29..277e83f 100644 --- a/packages/frontend/src/routes/(app)/+layout.svelte +++ b/packages/frontend/src/routes/(app)/+layout.svelte @@ -4,6 +4,6 @@ let { children }: LayoutProps = $props(); -
+
{@render children()}
diff --git a/packages/frontend/src/routes/(app)/+page.svelte b/packages/frontend/src/routes/(app)/+page.svelte index e733bf0..1c2b182 100644 --- a/packages/frontend/src/routes/(app)/+page.svelte +++ b/packages/frontend/src/routes/(app)/+page.svelte @@ -29,7 +29,7 @@ /> -

#{id.split("-")[0]}

+

{String(id).padStart(2, "0")}

diff --git a/packages/frontend/src/routes/(app)/components/ClusterActions.svelte b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte index 9b46674..f1b3fa3 100644 --- a/packages/frontend/src/routes/(app)/components/ClusterActions.svelte +++ b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte @@ -77,5 +77,3 @@ {/if} {/each} {/if} - - From 3d5dab2fc10ab411d9fcc6a479779b68f992c4b3 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Fri, 15 May 2026 22:00:20 +0000 Subject: [PATCH 12/15] fix(frontend): `svelte-check` errors --- .../src/lib/remotes/clusters.remote.ts | 5 +++- .../src/routes/(form)/login/+page.svelte | 24 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index 1bcce7f..2bd3426 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -47,6 +47,7 @@ export const getClustersLive = query.live(async function* () { const chunks = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()) + // @ts-ignore svelte-check false positive .values(); for await (const clusters of chunks) @@ -172,6 +173,7 @@ export const getClusterLogsLive = query.live( const chunks = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()) + // @ts-ignore svelte-check false positive .values(); for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterLogsDto[number]; @@ -191,7 +193,7 @@ export const getClusterStatsLive = query.live( async function* (id: GetClusterDto["id"]) { const token = getRequestEvent().cookies.get("token"); - const response = await fetch(new URL(`/clusters/${id}/stats/stream`, env.API_URL), { + const response = await fetch(new URL(`/clusters/${id}/stats/stream?interval=2`, env.API_URL), { headers: { Accept: "text/event-stream", Authorization: `Bearer ${token}`, @@ -205,6 +207,7 @@ export const getClusterStatsLive = query.live( const chunks = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()) + // @ts-ignore svelte-check false positive .values(); for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterStatsDto; diff --git a/packages/frontend/src/routes/(form)/login/+page.svelte b/packages/frontend/src/routes/(form)/login/+page.svelte index ad395c8..d1831d2 100644 --- a/packages/frontend/src/routes/(form)/login/+page.svelte +++ b/packages/frontend/src/routes/(form)/login/+page.svelte @@ -1,26 +1,26 @@
+ {...login.enhance(({ submit }) => { submit().catch((error) => { toaster.create({ type: "error", title: "Error", description: JSON.parse(error).message, }); - }), - )} + }); + })} > From 5d1cfed5aae436ffeaef85477f2b632ca51a668b Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Fri, 15 May 2026 22:43:13 +0000 Subject: [PATCH 13/15] build(frontend): update `.oxfmtrc.json` config to support tailwind attributes sorting --- packages/frontend/.oxfmtrc.json | 4 +++- packages/frontend/src/app.css | 6 ++++-- packages/frontend/src/lib/styles/utilities.css | 6 +++--- packages/frontend/src/routes/(app)/+page.svelte | 4 +++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/frontend/.oxfmtrc.json b/packages/frontend/.oxfmtrc.json index a4c1e0b..ac043a7 100644 --- a/packages/frontend/.oxfmtrc.json +++ b/packages/frontend/.oxfmtrc.json @@ -1,5 +1,7 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", "sortImports": {}, - "sortTailwindcss": {} + "sortTailwindcss": { + "stylesheet": "src/app.css" + }, } diff --git a/packages/frontend/src/app.css b/packages/frontend/src/app.css index e7733b3..ba108f1 100644 --- a/packages/frontend/src/app.css +++ b/packages/frontend/src/app.css @@ -3,8 +3,10 @@ @import "@skeletonlabs/skeleton"; @import "@skeletonlabs/skeleton-svelte"; -@import "$lib/styles/theme.css"; -@import "$lib/styles/utilities.css"; +@import "./lib/styles/theme.css"; +@import "./lib/styles/utilities.css"; + +@import "layerchart/skeleton-4.css"; input:focus { outline: none; diff --git a/packages/frontend/src/lib/styles/utilities.css b/packages/frontend/src/lib/styles/utilities.css index 39b33a2..a2977ac 100644 --- a/packages/frontend/src/lib/styles/utilities.css +++ b/packages/frontend/src/lib/styles/utilities.css @@ -1,9 +1,9 @@ @utility form { @apply card; @apply bg-surface-50-950!; - @apply border-surface-200-800 border; - @apply ring-surface-100-900 ring-6; - @apply outline-surface-200-800 outline outline-offset-6; + @apply border border-surface-200-800; + @apply ring-6 ring-surface-100-900; + @apply outline outline-offset-6 outline-surface-200-800; @apply m-1.75; @apply flex flex-col items-center gap-4 p-6 *:w-full; diff --git a/packages/frontend/src/routes/(app)/+page.svelte b/packages/frontend/src/routes/(app)/+page.svelte index 1c2b182..818dda8 100644 --- a/packages/frontend/src/routes/(app)/+page.svelte +++ b/packages/frontend/src/routes/(app)/+page.svelte @@ -29,7 +29,9 @@ /> -

{String(id).padStart(2, "0")}

+

+ {String(id).padStart(2, "0")} +

From 28e2b82e743f74d5d2e076c725077e44c192e629 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Fri, 15 May 2026 23:54:15 +0000 Subject: [PATCH 14/15] fix(frontend): removed optional prop from `ClusterActions` --- packages/frontend/.oxfmtrc.json | 2 +- .../(app)/components/ClusterActions.svelte | 61 +++++++++---------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/packages/frontend/.oxfmtrc.json b/packages/frontend/.oxfmtrc.json index ac043a7..bcf0741 100644 --- a/packages/frontend/.oxfmtrc.json +++ b/packages/frontend/.oxfmtrc.json @@ -3,5 +3,5 @@ "sortImports": {}, "sortTailwindcss": { "stylesheet": "src/app.css" - }, + } } diff --git a/packages/frontend/src/routes/(app)/components/ClusterActions.svelte b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte index f1b3fa3..f6a795d 100644 --- a/packages/frontend/src/routes/(app)/components/ClusterActions.svelte +++ b/packages/frontend/src/routes/(app)/components/ClusterActions.svelte @@ -12,11 +12,10 @@ PlayIcon, RotateCcwIcon, SquareIcon, - TrashIcon, } from "@lucide/svelte"; type Props = { - id?: GetClusterDto["id"]; + id: GetClusterDto["id"]; status: GetClusterDto["status"]; }; @@ -46,34 +45,32 @@ let loading: (typeof actions)[number]["label"] | null = $state(null); -{#if id} - {#each actions as { require, remote, icon, label }} - {#if require === status} - {@const Icon = icon} +{#each actions as { require, remote, icon, label }} + {#if require === status} + {@const Icon = icon} - - {/if} - {/each} -{/if} + + {/if} +{/each} From ee50de676308fc68ccfc27dba11d8c2a743d00e4 Mon Sep 17 00:00:00 2001 From: zowks <67444066+zowks@users.noreply.github.com> Date: Sat, 16 May 2026 00:57:01 +0000 Subject: [PATCH 15/15] fix(frontend): fixed review comments --- .../frontend/src/lib/remotes/auth.remote.ts | 4 +- .../frontend/src/lib/remotes/bot.remote.ts | 3 +- .../src/lib/remotes/clusters.remote.ts | 40 ++++++++++--------- packages/frontend/src/lib/styles/theme.css | 2 +- .../frontend/src/lib/utils/formatBytes.ts | 2 +- .../routes/(app)/components/ClusterCPU.svelte | 4 +- .../(app)/components/ClusterMemory.svelte | 2 +- .../(app)/components/ClusterStatus.svelte | 2 +- 8 files changed, 31 insertions(+), 28 deletions(-) diff --git a/packages/frontend/src/lib/remotes/auth.remote.ts b/packages/frontend/src/lib/remotes/auth.remote.ts index 8796510..3b244c0 100644 --- a/packages/frontend/src/lib/remotes/auth.remote.ts +++ b/packages/frontend/src/lib/remotes/auth.remote.ts @@ -26,7 +26,7 @@ export const register = form(registerSchema, async (data) => { return error(409, "A user is already registered"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -56,6 +56,6 @@ export const login = form(loginSchema, async (data, issue) => { ); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); diff --git a/packages/frontend/src/lib/remotes/bot.remote.ts b/packages/frontend/src/lib/remotes/bot.remote.ts index 6e77731..71d56ed 100644 --- a/packages/frontend/src/lib/remotes/bot.remote.ts +++ b/packages/frontend/src/lib/remotes/bot.remote.ts @@ -22,7 +22,8 @@ export const createBot = form(CreateBotSchema, async (data) => { return error(401, "Unauthorized"); case 409: return error(409, "A bot already exists"); + default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index 2bd3426..bf5ae59 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -19,14 +19,15 @@ export const getClusters = query(async (): Promise => { }); switch (response.status) { - case 200: + case 200: { const clusters: GetClusterDto[] = await response.json(); return clusters.sort((a, b) => a.id - b.id); + } case 401: return error(401, "Unauthorized"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -41,8 +42,8 @@ export const getClustersLive = query.live(async function* () { }); switch (response.status) { - case 200: - if (!response.body) return error(500, "An error occured"); + case 200: { + if (!response.body) return error(500, "An error occurred"); const chunks = response.body .pipeThrough(new TextDecoderStream()) @@ -52,13 +53,13 @@ export const getClustersLive = query.live(async function* () { for await (const clusters of chunks) yield (JSON.parse(clusters.data) as GetClusterDto[]).sort((a, b) => a.id - b.id); - return; - + break; + } case 401: return error(401, "Unauthorized"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -81,7 +82,7 @@ export const startCluster = command("unchecked", async (id: number) => { return error(404, "Cluster not found"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -104,7 +105,7 @@ export const stopCluster = command("unchecked", async (id: number) => { return error(404, "Cluster not found"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -127,7 +128,7 @@ export const restartCluster = command("unchecked", async (id: number) => { return error(404, "Cluster not found"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }); @@ -149,7 +150,7 @@ export const getClusterLogs = query( return error(404, "Cluster not found"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }, ); @@ -167,8 +168,8 @@ export const getClusterLogsLive = query.live( }); switch (response.status) { - case 200: - if (!response.body) return error(500, "An error occured"); + case 200: { + if (!response.body) return error(500, "An error occurred"); const chunks = response.body .pipeThrough(new TextDecoderStream()) @@ -177,13 +178,13 @@ export const getClusterLogsLive = query.live( .values(); for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterLogsDto[number]; - return; - + break; + } case 401: return error(401, "Unauthorized"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }, ); @@ -201,8 +202,8 @@ export const getClusterStatsLive = query.live( }); switch (response.status) { - case 200: - if (!response.body) return error(500, "An error occured"); + case 200: { + if (!response.body) return error(500, "An error occurred"); const chunks = response.body .pipeThrough(new TextDecoderStream()) @@ -212,6 +213,7 @@ export const getClusterStatsLive = query.live( for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterStatsDto; break; + } case 400: return error(400, "Cluster has no container or is not running"); case 401: @@ -220,7 +222,7 @@ export const getClusterStatsLive = query.live( return error(404, "Cluster not found"); default: - return error(500, "An error occured"); + return error(500, "An error occurred"); } }, ); diff --git a/packages/frontend/src/lib/styles/theme.css b/packages/frontend/src/lib/styles/theme.css index e9cfecd..8d00a93 100644 --- a/packages/frontend/src/lib/styles/theme.css +++ b/packages/frontend/src/lib/styles/theme.css @@ -206,6 +206,6 @@ --color-surface-contrast-950: var(--color-surface-contrast-light); } -@theme text-tiny { +@theme { --text-tiny: 0.625rem; } diff --git a/packages/frontend/src/lib/utils/formatBytes.ts b/packages/frontend/src/lib/utils/formatBytes.ts index e868698..cb32466 100644 --- a/packages/frontend/src/lib/utils/formatBytes.ts +++ b/packages/frontend/src/lib/utils/formatBytes.ts @@ -1,5 +1,5 @@ export default function formatBytes(bytes: number) { const suffixes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const i = Math.max(0, Math.floor(Math.log(bytes) / Math.log(1024))); return (!bytes && "0 Bytes") || (bytes / Math.pow(1024, i)).toFixed(2) + " " + suffixes[i]; } diff --git a/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte b/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte index aba3e54..0710678 100644 --- a/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte +++ b/packages/frontend/src/routes/(app)/components/ClusterCPU.svelte @@ -14,7 +14,7 @@ let { id }: Props = $props(); let cpu: { - percentage: GetClusterStatsDto["memory"]["usage"]; + percentage: GetClusterStatsDto["cpuPercentage"]; date: Date; }[] = $state([]); @@ -24,7 +24,7 @@ percentage: stats.cpuPercentage, date: new Date(), }); - if (cpu[0].date.getTime() < Date.now() - 60000) cpu.shift(); + while (cpu.length && cpu[0].date.getTime() <= Date.now() - 60000) cpu.shift(); }); }); diff --git a/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte b/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte index 50d4725..0370fb4 100644 --- a/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte +++ b/packages/frontend/src/routes/(app)/components/ClusterMemory.svelte @@ -25,7 +25,7 @@ usage: stats.memory.usage, date: new Date(), }); - if (memory[0].date.getTime() < Date.now() - 60000) memory.shift(); + while (memory.length && memory[0].date.getTime() <= Date.now() - 60000) memory.shift(); }); }); diff --git a/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte b/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte index d5a76cc..f082798 100644 --- a/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte +++ b/packages/frontend/src/routes/(app)/components/ClusterStatus.svelte @@ -17,5 +17,5 @@ "preset-filled-error-500": status === "ERROR", }} > - {status.toLocaleLowerCase()} + {status.toLowerCase()}