Files
Termix/src/ui/features/server-stats/widgets/MemoryWidget.tsx
T

110 lines
3.5 KiB
TypeScript
Raw Normal View History

2026-05-11 01:23:11 -05:00
import { MemoryStick } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { ServerMetrics } from "@/main-axios.ts";
import { SectionCard } from "@/components/section-card";
interface MemoryWidgetProps {
metrics: ServerMetrics | null;
metricsHistory: ServerMetrics[];
}
function Sparkline({
history,
current,
}: {
history: ServerMetrics[];
current: number | null;
}) {
const points = [
...history.map((m) => m?.memory?.percent ?? 0),
current ?? 0,
].slice(-20);
if (points.length < 2) return null;
const w = 300;
const h = 48;
const max = Math.max(...points, 1);
const coords = points.map((v, i) => {
const x = (i / (points.length - 1)) * w;
const y = h - (v / max) * h;
return `${x},${y}`;
});
const d = `M ${coords.join(" L ")}`;
const fill = `M 0,${h} L ${coords.join(" L ")} L ${w},${h} Z`;
return (
<div className="h-12 md:h-16 w-full mt-2 bg-muted/20 border border-border/50 relative overflow-hidden">
<svg
className="absolute inset-0 h-full w-full"
viewBox={`0 0 ${w} ${h}`}
preserveAspectRatio="none"
>
<path d={fill} fill="currentColor" className="text-accent-brand/10" />
<path
d={d}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
className="text-accent-brand/60"
/>
</svg>
</div>
);
}
export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
const { t } = useTranslation();
const percent = metrics?.memory?.percent ?? null;
const usedGiB = metrics?.memory?.usedGiB ?? null;
const totalGiB = metrics?.memory?.totalGiB ?? null;
return (
<SectionCard
title={t("serverStats.memoryUsage")}
icon={<MemoryStick className="size-3.5" />}
>
<div className="flex flex-col gap-4 py-2">
<div className="flex items-end justify-between">
<div className="flex flex-col">
<span className="text-xl md:text-3xl font-bold text-accent-brand">
{percent !== null ? `${percent.toFixed(1)}%` : "N/A"}
</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-widest font-bold">
{usedGiB !== null && totalGiB !== null
? `${usedGiB.toFixed(1)} / ${totalGiB.toFixed(1)} GiB`
: "N/A"}
</span>
</div>
</div>
<div className="h-2 bg-muted w-full overflow-hidden">
<div
className="h-full bg-accent-brand transition-all duration-500"
style={{ width: `${percent ?? 0}%` }}
/>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="flex flex-col p-2 bg-muted/30 border border-border">
<span className="text-[10px] text-muted-foreground uppercase font-bold">
{t("serverStats.swap")}
</span>
<span className="text-xs font-semibold">N/A</span>
</div>
<div className="flex flex-col p-2 bg-muted/30 border border-border">
<span className="text-[10px] text-muted-foreground uppercase font-bold">
{t("serverStats.free")}
</span>
<span className="text-xs font-semibold">
{usedGiB !== null && totalGiB !== null
? `${(totalGiB - usedGiB).toFixed(1)} GiB`
: "N/A"}
</span>
</div>
</div>
<Sparkline history={metricsHistory} current={percent} />
</div>
</SectionCard>
);
}