mirror of
https://github.com/fccview/cronmaster.git
synced 2025-12-23 22:18:20 -05:00
303 lines
7.9 KiB
TypeScript
303 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import { MetricCard } from "@/app/_components/GlobalComponents/Cards/MetricCard";
|
|
import { SystemStatus } from "@/app/_components/FeatureComponents/System/SystemStatus";
|
|
import { PerformanceSummary } from "@/app/_components/FeatureComponents/System/PerformanceSummary";
|
|
import { Sidebar } from "@/app/_components/FeatureComponents/Layout/Sidebar";
|
|
import { Clock, HardDrive, Cpu, Monitor, Wifi } from "lucide-react";
|
|
|
|
interface SystemInfoType {
|
|
hostname: string;
|
|
platform: string;
|
|
ip?: string;
|
|
uptime: string;
|
|
memory: {
|
|
total: string;
|
|
used: string;
|
|
free: string;
|
|
usage: number;
|
|
status: string;
|
|
};
|
|
cpu: {
|
|
model: string;
|
|
cores: number;
|
|
usage: number;
|
|
status: string;
|
|
};
|
|
gpu: {
|
|
model: string;
|
|
memory?: string;
|
|
status: string;
|
|
};
|
|
network?: {
|
|
speed: string;
|
|
latency: number;
|
|
downloadSpeed: number;
|
|
uploadSpeed: number;
|
|
status: string;
|
|
};
|
|
disk: {
|
|
total: string;
|
|
used: string;
|
|
free: string;
|
|
usage: number;
|
|
status: string;
|
|
};
|
|
systemStatus: {
|
|
overall: string;
|
|
details: string;
|
|
};
|
|
}
|
|
import { useState, useEffect, useRef } from "react";
|
|
import { useTranslations } from "next-intl";
|
|
import { useSSEContext } from "@/app/_contexts/SSEContext";
|
|
import { SSEEvent } from "@/app/_utils/sse-events";
|
|
import { usePageVisibility } from "@/app/_hooks/usePageVisibility";
|
|
|
|
interface SystemInfoCardProps {
|
|
systemInfo: SystemInfoType;
|
|
}
|
|
|
|
export const SystemInfoCard = ({
|
|
systemInfo: initialSystemInfo,
|
|
}: SystemInfoCardProps) => {
|
|
const [currentTime, setCurrentTime] = useState<string>("");
|
|
const [systemInfo, setSystemInfo] =
|
|
useState<SystemInfoType>(initialSystemInfo);
|
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
const t = useTranslations();
|
|
const { subscribe } = useSSEContext();
|
|
const isPageVisible = usePageVisibility();
|
|
|
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
|
|
const updateSystemInfo = async () => {
|
|
if (abortControllerRef.current) {
|
|
abortControllerRef.current.abort();
|
|
}
|
|
|
|
const abortController = new AbortController();
|
|
abortControllerRef.current = abortController;
|
|
|
|
try {
|
|
setIsUpdating(true);
|
|
const response = await fetch("/api/system-stats", {
|
|
signal: abortController.signal,
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch system stats");
|
|
}
|
|
const freshData = await response.json();
|
|
setSystemInfo(freshData);
|
|
} catch (error: any) {
|
|
if (error.name !== "AbortError") {
|
|
console.error("Failed to update system info:", error);
|
|
}
|
|
} finally {
|
|
if (!abortController.signal.aborted) {
|
|
setIsUpdating(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const unsubscribe = subscribe((event: SSEEvent) => {
|
|
if (event.type === "system-stats") {
|
|
setSystemInfo(event.data);
|
|
}
|
|
});
|
|
|
|
return unsubscribe;
|
|
}, [subscribe]);
|
|
|
|
useEffect(() => {
|
|
const updateTime = () => {
|
|
setCurrentTime(new Date().toLocaleTimeString());
|
|
};
|
|
|
|
updateTime();
|
|
|
|
if (isPageVisible) {
|
|
updateSystemInfo();
|
|
}
|
|
|
|
const updateInterval = parseInt(
|
|
process.env.NEXT_PUBLIC_CLOCK_UPDATE_INTERVAL || "30000"
|
|
);
|
|
|
|
let mounted = true;
|
|
let timeoutId: NodeJS.Timeout | null = null;
|
|
|
|
const doUpdate = () => {
|
|
if (!mounted || !isPageVisible) return;
|
|
updateTime();
|
|
updateSystemInfo().finally(() => {
|
|
if (mounted && isPageVisible) {
|
|
timeoutId = setTimeout(doUpdate, updateInterval);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (isPageVisible) {
|
|
timeoutId = setTimeout(doUpdate, updateInterval);
|
|
}
|
|
|
|
return () => {
|
|
mounted = false;
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
if (abortControllerRef.current) {
|
|
abortControllerRef.current.abort();
|
|
}
|
|
};
|
|
}, [isPageVisible]);
|
|
|
|
const quickStats = {
|
|
cpu: systemInfo.cpu.usage,
|
|
memory: systemInfo.memory.usage,
|
|
network: systemInfo.network ? `${systemInfo.network.latency}ms` : "N/A",
|
|
};
|
|
|
|
const basicInfoItems = [
|
|
{
|
|
icon: Clock,
|
|
label: t("sidebar.uptime"),
|
|
value: systemInfo.uptime,
|
|
color: "text-orange-500",
|
|
},
|
|
];
|
|
|
|
const performanceItems = [
|
|
{
|
|
icon: HardDrive,
|
|
label: t("sidebar.memory"),
|
|
value: `${systemInfo.memory.used} / ${systemInfo.memory.total}`,
|
|
detail: `${systemInfo.memory.free} free`,
|
|
status: systemInfo.memory.status,
|
|
color: "text-cyan-500",
|
|
showProgress: true,
|
|
progressValue: systemInfo.memory.usage,
|
|
},
|
|
{
|
|
icon: Cpu,
|
|
label: t("sidebar.cpu"),
|
|
value: systemInfo.cpu.model,
|
|
detail: `${systemInfo.cpu.cores} cores`,
|
|
status: systemInfo.cpu.status,
|
|
color: "text-pink-500",
|
|
showProgress: true,
|
|
progressValue: systemInfo.cpu.usage,
|
|
},
|
|
{
|
|
icon: Monitor,
|
|
label: t("sidebar.gpu"),
|
|
value: systemInfo.gpu.model,
|
|
detail: systemInfo.gpu.memory
|
|
? `${systemInfo.gpu.memory} VRAM`
|
|
: systemInfo.gpu.status,
|
|
status: systemInfo.gpu.status,
|
|
color: "text-indigo-500",
|
|
},
|
|
...(systemInfo.network
|
|
? [
|
|
{
|
|
icon: Wifi,
|
|
label: t("sidebar.network"),
|
|
value: `${systemInfo.network.latency}ms`,
|
|
detail: `${systemInfo.network.latency}ms latency • ${systemInfo.network.speed}`,
|
|
status: systemInfo.network.status,
|
|
color: "text-teal-500",
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
|
|
const performanceMetrics = [
|
|
{
|
|
label: t("sidebar.cpuUsage"),
|
|
value: `${systemInfo.cpu.usage}%`,
|
|
status: systemInfo.cpu.status,
|
|
},
|
|
{
|
|
label: t("sidebar.memoryUsage"),
|
|
value: `${systemInfo.memory.usage}%`,
|
|
status: systemInfo.memory.status,
|
|
},
|
|
...(systemInfo.network
|
|
? [
|
|
{
|
|
label: t("sidebar.networkLatency"),
|
|
value: `${systemInfo.network.latency}ms`,
|
|
status: systemInfo.network.status,
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
|
|
return (
|
|
<Sidebar defaultCollapsed={false} quickStats={quickStats}>
|
|
<SystemStatus
|
|
status={systemInfo.systemStatus.overall}
|
|
details={systemInfo.systemStatus.details}
|
|
timestamp={currentTime}
|
|
isUpdating={isUpdating}
|
|
/>
|
|
|
|
<div>
|
|
<h3 className="text-xs font-semibold text-foreground mb-2 uppercase tracking-wide">
|
|
{t("sidebar.systemInformation")}
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{basicInfoItems.map((item) => (
|
|
<MetricCard
|
|
key={item.label}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
value={item.value}
|
|
color={item.color}
|
|
variant="basic"
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-xs font-semibold text-foreground mb-2 uppercase tracking-wide">
|
|
{t("sidebar.performanceMetrics")}
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{performanceItems.map((item) => (
|
|
<MetricCard
|
|
key={item.label}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
value={item.value}
|
|
detail={item.detail}
|
|
status={item.status}
|
|
color={item.color}
|
|
variant="performance"
|
|
showProgress={item.showProgress}
|
|
progressValue={item.progressValue}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<PerformanceSummary metrics={performanceMetrics} />
|
|
|
|
<div className="text-xs text-muted-foreground text-center p-2 bg-muted/20 rounded-lg">
|
|
{t("sidebar.statsUpdateEvery")}{" "}
|
|
{Math.round(
|
|
parseInt(process.env.NEXT_PUBLIC_CLOCK_UPDATE_INTERVAL || "30000") /
|
|
1000
|
|
)}
|
|
s • {t("sidebar.networkSpeedEstimatedFromLatency")}
|
|
{isUpdating && (
|
|
<span className="ml-2 animate-pulse">{t("sidebar.updating")}...</span>
|
|
)}
|
|
</div>
|
|
</Sidebar>
|
|
);
|
|
};
|