mirror of
https://github.com/csehviktor/status-monitor.git
synced 2025-08-08 18:06:14 +02:00
197 lines
8.1 KiB
TypeScript
197 lines
8.1 KiB
TypeScript
import { ConnectionStatus } from "@/components/ConnectionStatus";
|
|
import { Header } from "@/components/Header";
|
|
import { DonutChart } from "@/components/DonutChart";
|
|
import { LineChartCard } from "@/components/LineChartCard";
|
|
import { MetricCard } from "@/components/MetricCard";
|
|
import { SysinfoCard } from "@/components/SysinfoCard";
|
|
import { useChartData } from "@/hooks/useChartData";
|
|
import {
|
|
formatBits,
|
|
formatPercentage,
|
|
calcPercentage,
|
|
formatBytes,
|
|
} from "@/services/utils";
|
|
import { useParams } from "react-router-dom";
|
|
import { useWebsocket } from "@/hooks/useWebsocket";
|
|
import { getHistoricalData, TimePeriod } from "@/services/store";
|
|
import { useEffect, useState } from "react";
|
|
import { StatusMessage } from "@/services/types";
|
|
import { TimePeriodSelector } from "@/components/TimePeriodSelector";
|
|
|
|
export function AgentPage() {
|
|
const { agent } = useParams();
|
|
const { status, message } = useWebsocket(`ws://localhost:3000/ws/${agent}`);
|
|
|
|
const [period, setPeriod] = useState<TimePeriod | "realtime">("all");
|
|
const [history, setHistory] = useState<StatusMessage[] | null>(null);
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
const data = await getHistoricalData(
|
|
`http://localhost:3000/history/${agent}/${period}`,
|
|
);
|
|
setHistory(data);
|
|
};
|
|
|
|
fetchData();
|
|
}, [agent, period]);
|
|
|
|
const { metrics } = message ?? {};
|
|
const { cpuData, memoryData, networkData } = useChartData(
|
|
period === "realtime" ? message! : history,
|
|
);
|
|
|
|
const getMetricStatus = (percentage: number | undefined) => {
|
|
if (!percentage) return "nil";
|
|
|
|
if (percentage < 50) return "good";
|
|
if (percentage < 80) return "warning";
|
|
return "critical";
|
|
};
|
|
|
|
const cpuUsage = metrics?.cpu.usage ?? 0;
|
|
|
|
const memoryUsed = metrics?.memory.used ?? 0;
|
|
const memoryTotal = metrics?.memory.total ?? 0;
|
|
const memoryUsage = calcPercentage(memoryUsed, memoryTotal);
|
|
|
|
const diskFree = (metrics?.disk.total ?? 0) - (metrics?.disk.free ?? 0);
|
|
const diskTotal = metrics?.disk.total ?? 0;
|
|
const diskUsage = calcPercentage(diskFree, diskTotal);
|
|
|
|
const networkUp = metrics?.network.up ?? 0;
|
|
const networkDown = metrics?.network.down ?? 0;
|
|
const networkUsage = networkUp + networkDown;
|
|
|
|
return (
|
|
<div>
|
|
<Header
|
|
props={{
|
|
title: "Status Monitor",
|
|
subtitle: agent!,
|
|
hasBackButton: true,
|
|
rightComponent: (
|
|
<ConnectionStatus
|
|
status={status ?? "disconnected"}
|
|
timestamp={message?.timestamp}
|
|
/>
|
|
),
|
|
}}
|
|
/>
|
|
<main className="max-w-7xl mx-auto py-8">
|
|
<div className="mx-2">
|
|
<h2 className="text-xl font-semibold mb-4">Overview</h2>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
<MetricCard
|
|
props={{
|
|
title: "CPU USAGE",
|
|
value: formatPercentage(cpuUsage),
|
|
status: getMetricStatus(cpuUsage),
|
|
subtitle: `${metrics?.cpu.threads ?? 0} threads`,
|
|
}}
|
|
>
|
|
<DonutChart
|
|
data={{
|
|
labels: ["Usage", "Total"],
|
|
values: [cpuUsage, 100 - cpuUsage],
|
|
colors: ["#3b82f6", "#262626"],
|
|
}}
|
|
centerText={formatPercentage(cpuUsage)}
|
|
size="sm"
|
|
/>
|
|
</MetricCard>
|
|
|
|
<MetricCard
|
|
props={{
|
|
title: "MEMORY USAGE",
|
|
value: formatPercentage(memoryUsage),
|
|
status: getMetricStatus(memoryUsage),
|
|
subtitle: `${formatBytes(memoryUsed)} / ${formatBytes(memoryTotal)}`,
|
|
}}
|
|
>
|
|
<DonutChart
|
|
data={{
|
|
labels: ["Used", "Free"],
|
|
values: [memoryUsage, 100 - memoryUsage],
|
|
colors: ["#3b82f6", "#262626"],
|
|
}}
|
|
centerText={formatPercentage(memoryUsage)}
|
|
size="sm"
|
|
/>
|
|
</MetricCard>
|
|
|
|
<MetricCard
|
|
props={{
|
|
title: "NETWORK ACTIVITY",
|
|
value: formatBits(networkUsage),
|
|
status: "nil",
|
|
subtitle: `↑ ${formatBits(networkUp)}/s ↓ ${formatBits(networkDown)}/s`,
|
|
}}
|
|
>
|
|
<DonutChart
|
|
data={{
|
|
labels: ["Upload", "Download"],
|
|
values: [
|
|
message ? networkUp : 1,
|
|
message ? networkDown : 1,
|
|
],
|
|
colors: ["#ef4444", "#3b82f6"],
|
|
}}
|
|
size="sm"
|
|
/>
|
|
</MetricCard>
|
|
</div>
|
|
|
|
<MetricCard
|
|
props={{
|
|
title: "DISK USAGE",
|
|
value: formatPercentage(diskUsage),
|
|
status: getMetricStatus(diskUsage),
|
|
subtitle: `${formatBytes(diskFree)} free of ${formatBytes(diskTotal)}`,
|
|
}}
|
|
>
|
|
<div className="w-full bg-[#141414] rounded-full h-4">
|
|
<div
|
|
className={`h-4 rounded-full transition-all duration-500 ${
|
|
getMetricStatus(diskUsage) == "good"
|
|
? "bg-green-500"
|
|
: getMetricStatus(diskUsage) ==
|
|
"warning"
|
|
? "bg-yellow-500"
|
|
: "bg-red-500"
|
|
}`}
|
|
style={{ width: `${diskUsage}%` }}
|
|
/>
|
|
</div>
|
|
</MetricCard>
|
|
|
|
<div className="flex flex-col my-10">
|
|
<h2 className="text-xl font-semibold mb-4">Details</h2>
|
|
<div className="mt-3">
|
|
<TimePeriodSelector
|
|
currentPeriod={period}
|
|
onChange={setPeriod}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 my-8">
|
|
<LineChartCard title="CPU Usage" data={cpuData} />
|
|
<LineChartCard
|
|
title="Memory Usage"
|
|
data={memoryData}
|
|
/>
|
|
<LineChartCard
|
|
title="Network Activity"
|
|
data={networkData}
|
|
/>
|
|
|
|
<SysinfoCard sysinfo={metrics?.system_info} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|