mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
feat: initial ui redesign from demo
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import React from "react";
|
||||
import { Cpu } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { ServerMetrics } from "@/main-axios.ts";
|
||||
import { SectionCard } from "@/components/section-card";
|
||||
|
||||
interface CpuWidgetProps {
|
||||
metrics: ServerMetrics | null;
|
||||
metricsHistory: ServerMetrics[];
|
||||
}
|
||||
|
||||
function Sparkline({
|
||||
history,
|
||||
current,
|
||||
}: {
|
||||
history: ServerMetrics[];
|
||||
current: number | null;
|
||||
}) {
|
||||
const points = [
|
||||
...history.map((m) => m?.cpu?.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 CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
|
||||
const { t } = useTranslation();
|
||||
const percent = metrics?.cpu?.percent ?? null;
|
||||
const cores = metrics?.cpu?.cores ?? null;
|
||||
const load = metrics?.cpu?.load ?? null;
|
||||
|
||||
return (
|
||||
<SectionCard
|
||||
title={t("serverStats.cpuUsage")}
|
||||
icon={<Cpu 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">
|
||||
{cores !== null
|
||||
? t("serverStats.cpuCores", { count: cores })
|
||||
: t("serverStats.naCpus")}
|
||||
</span>
|
||||
</div>
|
||||
{load && (
|
||||
<div className="text-right">
|
||||
<span className="text-[10px] text-muted-foreground uppercase tracking-widest font-bold">
|
||||
{t("serverStats.loadAvg")}
|
||||
</span>
|
||||
<div className="text-xs font-mono">
|
||||
{load[0].toFixed(2)} {load[1].toFixed(2)}
|
||||
{load[2].toFixed(2)}
|
||||
</div>
|
||||
</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>
|
||||
<Sparkline history={metricsHistory} current={percent} />
|
||||
</div>
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user