diff --git a/packages/backend/src/index.dto.ts b/packages/backend/src/index.dto.ts index d41ab7d..04af0ee 100644 --- a/packages/backend/src/index.dto.ts +++ b/packages/backend/src/index.dto.ts @@ -5,6 +5,7 @@ export { CreateBotSchema, type CreateBotDto } from './bot/dto/create-bot.dto.js' export { UpdateBotSchema, type UpdateBotDto } from './bot/dto/update-bot.dto.js'; export { GetBotSchema, type GetBotDto } from './bot/dto/get-bot.dto.js'; +export { SseIntervalQuerySchema } from './clusters/dto/sse-interval-query.dto.js'; export { GetClusterSchema, type GetClusterDto } from './clusters/dto/get-cluster.dto.js'; export { GetClusterLogsQuerySchema, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8e6a563..6d1f348 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,10 +24,12 @@ "@sveltejs/kit": "^2.63.1", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.3.1", + "@types/d3-shape": "^3.1.8", "@types/node": "^22.19.11", + "d3-shape": "^3.2.0", "eventsource-parser": "^3.0.8", "globals": "^16.5.0", - "layerchart": "2.0.0-next.63", + "layerchart": "2.0.0-next.65", "oxfmt": "^0.41.0", "oxlint": "^1.69.0", "oxlint-tsgolint": "^0.17.4", diff --git a/packages/frontend/src/lib/components/LiveAreaChart.svelte b/packages/frontend/src/lib/components/LiveAreaChart.svelte new file mode 100644 index 0000000..329c038 --- /dev/null +++ b/packages/frontend/src/lib/components/LiveAreaChart.svelte @@ -0,0 +1,36 @@ + + + diff --git a/packages/frontend/src/lib/remotes/clusters.remote.ts b/packages/frontend/src/lib/remotes/clusters.remote.ts index b8b1067..313e1ac 100644 --- a/packages/frontend/src/lib/remotes/clusters.remote.ts +++ b/packages/frontend/src/lib/remotes/clusters.remote.ts @@ -3,6 +3,7 @@ import { env } from "$env/dynamic/private"; import { GetClusterLogsQuerySchema, GetClusterSchema, + SseIntervalQuerySchema, type GetAggregateStatsDto, type GetClusterDto, type GetClusterLogsDto, @@ -196,16 +197,21 @@ export const getClusterLogsLive = query.live( ); export const getClusterStatsLive = query.live( - "unchecked", - async function* (id: GetClusterDto["id"]) { + SseIntervalQuerySchema.extend({ + id: GetClusterSchema.shape.id, + }), + async function* ({ id, interval }) { const token = getRequestEvent().cookies.get("token"); - const response = await fetch(new URL(`/clusters/${id}/stats/stream?interval=2`, env.API_URL), { - headers: { - Accept: "text/event-stream", - Authorization: `Bearer ${token}`, + const response = await fetch( + new URL(`/clusters/${id}/stats/stream?interval=${interval}`, env.API_URL), + { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${token}`, + }, }, - }); + ); switch (response.status) { case 200: { @@ -217,7 +223,8 @@ export const getClusterStatsLive = query.live( // @ts-ignore svelte-check false positive .values(); - for await (const chunk of chunks) yield JSON.parse(chunk.data) as GetClusterStatsDto; + for await (const chunk of chunks) + yield { ...(JSON.parse(chunk.data) as GetClusterStatsDto), date: new Date() }; break; } case 400: diff --git a/packages/frontend/src/routes/(app)/clusters/+page.svelte b/packages/frontend/src/routes/(app)/clusters/+page.svelte index bd9a8bc..567d9f1 100644 --- a/packages/frontend/src/routes/(app)/clusters/+page.svelte +++ b/packages/frontend/src/routes/(app)/clusters/+page.svelte @@ -7,8 +7,8 @@ import ClusterLogs from "./components/widgets/ClusterLogs.svelte"; import ClusterShards from "./components/widgets/ClusterShards.svelte"; import ClusterStatus from "./components/ClusterStatus.svelte"; - import ClusterMemory from "./components/ClusterMemory.svelte"; - import ClusterCPU from "./components/ClusterCPU.svelte"; + import ClusterMemory from "./components/widgets/ClusterMemory.svelte"; + import ClusterCPU from "./components/widgets/ClusterCPU.svelte"; let clusters = getClusters(); $effect(() => { @@ -25,7 +25,7 @@ onOpenChange={(details) => (open = details.open)} >
diff --git a/packages/frontend/src/routes/(app)/clusters/components/ClusterCPU.svelte b/packages/frontend/src/routes/(app)/clusters/components/ClusterCPU.svelte deleted file mode 100644 index 0710678..0000000 --- a/packages/frontend/src/routes/(app)/clusters/components/ClusterCPU.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - -
-
- -

- 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)/clusters/components/ClusterMemory.svelte b/packages/frontend/src/routes/(app)/clusters/components/ClusterMemory.svelte deleted file mode 100644 index 0370fb4..0000000 --- a/packages/frontend/src/routes/(app)/clusters/components/ClusterMemory.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - -
-
- -

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

-
- -
- new Date(x).toLocaleTimeString() }, - }, - }} - /> -
-
diff --git a/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterCPU.svelte b/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterCPU.svelte new file mode 100644 index 0000000..69092cb --- /dev/null +++ b/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterCPU.svelte @@ -0,0 +1,50 @@ + + + + y.toPrecision(2) + "%", + }, + header: { format: (x) => new Date(x).toLocaleTimeString() }, + }, + area: { curve: curveCatmullRom }, + }} + /> + diff --git a/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterMemory.svelte b/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterMemory.svelte new file mode 100644 index 0000000..2f224c2 --- /dev/null +++ b/packages/frontend/src/routes/(app)/clusters/components/widgets/ClusterMemory.svelte @@ -0,0 +1,52 @@ + + + + new Date(x).toLocaleTimeString() }, + }, + area: { curve: curveCatmullRom }, + }} + /> + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a782237..d7086c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,9 +219,15 @@ importers: '@tailwindcss/vite': specifier: ^4.3.1 version: 4.3.1(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)) + '@types/d3-shape': + specifier: ^3.1.8 + version: 3.1.8 '@types/node': specifier: ^22.19.11 version: 22.19.11 + d3-shape: + specifier: ^3.2.0 + version: 3.2.0 eventsource-parser: specifier: ^3.0.8 version: 3.0.8 @@ -229,8 +235,8 @@ importers: specifier: ^16.5.0 version: 16.5.0 layerchart: - specifier: 2.0.0-next.63 - version: 2.0.0-next.63(@sveltejs/kit@2.65.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.3(@typescript-eslint/types@8.56.0))(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(zod@4.3.6) + specifier: 2.0.0-next.65 + version: 2.0.0-next.65(@sveltejs/kit@2.65.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.3(@typescript-eslint/types@8.56.0))(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(zod@4.3.6) oxfmt: specifier: ^0.41.0 version: 0.41.0 @@ -886,21 +892,12 @@ packages: '@fastify/static@8.3.0': resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==} - '@floating-ui/core@1.7.4': - resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} - '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/dom@1.7.5': - resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} - '@floating-ui/dom@1.7.6': resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} @@ -2378,6 +2375,12 @@ packages: '@types/d3-contour@3.0.6': resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -4989,8 +4992,8 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - layerchart@2.0.0-next.63: - resolution: {integrity: sha512-pX/mShq+cOTcdjLsLk0ILjBYxucOneeQcTuuYTE5fu9e3tUX7PwWvH36jXWiIMzRhn3m5Ojds8EVfeXiv4NXEg==} + layerchart@2.0.0-next.65: + resolution: {integrity: sha512-pyIpQwSrUDz5de/Es8JwrDuEg6VhpFVMlR1RGSGhOYVnDeOUO04v4ZKPNXEltYXldQt9VM7W7rmpwUuR4l7DOg==} peerDependencies: svelte: ^5.0.0 @@ -7552,26 +7555,15 @@ snapshots: glob: 11.1.0 optional: true - '@floating-ui/core@1.7.4': - dependencies: - '@floating-ui/utils': 0.2.10 - '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 - '@floating-ui/dom@1.7.5': - dependencies: - '@floating-ui/core': 1.7.4 - '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.6': dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/utils': 0.2.11 - '@floating-ui/utils@0.2.10': {} - '@floating-ui/utils@0.2.11': {} '@hallmaster/docker.js@0.0.27': {} @@ -8027,7 +8019,7 @@ snapshots: '@layerstack/svelte-actions@1.0.1-next.18': dependencies: - '@floating-ui/dom': 1.7.5 + '@floating-ui/dom': 1.7.6 '@layerstack/utils': 2.0.0-next.18 d3-scale: 4.0.2 @@ -8997,6 +8989,12 @@ snapshots: '@types/d3-array': 3.2.2 '@types/geojson': 7946.0.16 + '@types/d3-path@3.1.1': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -12308,7 +12306,7 @@ snapshots: kuler@2.0.0: {} - layerchart@2.0.0-next.63(@sveltejs/kit@2.65.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.3(@typescript-eslint/types@8.56.0))(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(zod@4.3.6): + layerchart@2.0.0-next.65(@sveltejs/kit@2.65.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.3(@typescript-eslint/types@8.56.0))(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.11)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.56.3(@typescript-eslint/types@8.56.0))(zod@4.3.6): dependencies: '@dagrejs/dagre': 2.0.4 '@layerstack/svelte-actions': 1.0.1-next.18