Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/backend/src/index.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
36 changes: 36 additions & 0 deletions packages/frontend/src/lib/components/LiveAreaChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts" generics="T extends { date: Date }">
import { AreaChart, type AreaChartProps } from "layerchart";
import { untrack } from "svelte";

interface Props extends Omit<AreaChartProps<T>, "data" | "xDomain"> {
value?: T;
}

let { value, ...props }: Props = $props();

let data = $state<NonNullable<AreaChartProps<T>["data"]>>([]);
let xDomain = $state<AreaChartProps<T>["xDomain"]>();

$effect(() => {
if (!value) return;

const oneMinuteAgo = new Date(Date.now() - 60_000);
const index = untrack(() => data).findLastIndex(
({ date }) => date < oneMinuteAgo,
);

data = [...untrack(() => data).slice(index !== -1 ? index : 0), value];
Comment on lines +18 to +22

xDomain = [
new Date(
Math.min(
oneMinuteAgo.getTime(),
untrack(() => data)[0]?.date.getTime(),
),
),
untrack(() => data).at(-1)?.date ?? new Date(),
];
});
</script>

<AreaChart {...props} {data} {xDomain} />
23 changes: 15 additions & 8 deletions packages/frontend/src/lib/remotes/clusters.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { env } from "$env/dynamic/private";
import {
GetClusterLogsQuerySchema,
GetClusterSchema,
SseIntervalQuerySchema,
type GetAggregateStatsDto,
type GetClusterDto,
type GetClusterLogsDto,
Expand Down Expand Up @@ -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: {
Expand All @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/src/routes/(app)/clusters/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -25,7 +25,7 @@
onOpenChange={(details) => (open = details.open)}
>
<div
class="flex items-center gap-2 p-2 preset-filled-surface-100-900 w-full rounded-xl justify-between border border-surface-200-800 shadow-lg sticky top-0"
class="flex items-center gap-2 p-2 preset-filled-surface-100-900 w-full rounded-xl justify-between border border-surface-200-800 shadow-lg sticky top-0 z-10"
>
<Collapsible.Trigger class="btn grow justify-start">
<Collapsible.Indicator class="group">
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
import LiveAreaChart from "$lib/components/LiveAreaChart.svelte";
import { getClusterStatsLive } from "$lib/remotes/clusters.remote";
import type {
GetClusterDto,
GetClusterStatsDto,
} from "@hallmaster/backend/dto";
Comment thread
zowks marked this conversation as resolved.
import { CpuIcon } from "@lucide/svelte";
import { curveCatmullRom } from "d3-shape";
import Widget from "../Widget.svelte";

interface Props {
id: GetClusterDto["id"];
}

let { id }: Props = $props();

let value = $state<{
date: Date;
cpuPercentage: GetClusterStatsDto["cpuPercentage"];
}>();

$effect(() => {
getClusterStatsLive({ id, interval: 2 }).then(
({ date, cpuPercentage }) => (value = { date, cpuPercentage }),
);
});
</script>

<Widget
icon={CpuIcon}
title="CPU"
value={`${value?.cpuPercentage.toPrecision(2) ?? 0} %`}
>
<LiveAreaChart
{value}
x="date"
y="cpuPercentage"
axis={false}
props={{
tooltip: {
item: {
format: (y: number) => y.toPrecision(2) + "%",
},
header: { format: (x) => new Date(x).toLocaleTimeString() },
},
area: { curve: curveCatmullRom },
}}
/>
</Widget>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import LiveAreaChart from "$lib/components/LiveAreaChart.svelte";
import { getClusterStatsLive } from "$lib/remotes/clusters.remote";
import formatBytes from "$lib/utils/formatBytes";
import type {
GetClusterDto,
GetClusterStatsDto,
} from "@hallmaster/backend/dto";
import { MemoryStickIcon } from "@lucide/svelte";
import { curveCatmullRom } from "d3-shape";
import Widget from "../Widget.svelte";

interface Props {
id: GetClusterDto["id"];
}

let { id }: Props = $props();

let value = $state<{
date: Date;
memory: GetClusterStatsDto["memory"]["usage"];
}>();

$effect(() => {
getClusterStatsLive({ id, interval: 2 }).then(({ date, memory }) => {
value = {
memory: memory.usage,
date,
};
});
});
</script>

<Widget
icon={MemoryStickIcon}
title="Memory"
value={formatBytes(value?.memory ?? 0)}
>
<LiveAreaChart
{value}
x="date"
y="memory"
axis={false}
props={{
tooltip: {
item: { format: formatBytes },
header: { format: (x) => new Date(x).toLocaleTimeString() },
},
area: { curve: curveCatmullRom },
}}
/>
</Widget>
Loading
Loading