Some checks failed
Build Frontend / build (push) Failing after 7s
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
156 lines
5.7 KiB
TypeScript
156 lines
5.7 KiB
TypeScript
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import { fetchClient } from "@/lib/api/api-client";
|
|
import { Button } from "@/components/common/button";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/common/card";
|
|
|
|
interface Status {
|
|
uptimeSeconds: number;
|
|
memory: Record<string, number>;
|
|
versions: Record<string, string>;
|
|
counts: { vendors: number; orders: number; products: number; chats: number };
|
|
}
|
|
|
|
function formatDuration(seconds: number) {
|
|
const h = Math.floor(seconds / 3600);
|
|
const m = Math.floor((seconds % 3600) / 60);
|
|
const s = seconds % 60;
|
|
return `${h}h ${m}m ${s}s`;
|
|
}
|
|
|
|
function formatBytes(bytes: number): string {
|
|
if (bytes === 0) return '0 Bytes';
|
|
if (bytes < 0) return '0 Bytes'; // Handle negative values
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.max(0, Math.floor(Math.log(bytes) / Math.log(k))); // Ensure i is at least 0
|
|
// Clamp i to valid array index
|
|
const clampedI = Math.min(i, sizes.length - 1);
|
|
return Math.round(bytes / Math.pow(k, clampedI) * 100) / 100 + ' ' + sizes[clampedI];
|
|
}
|
|
|
|
export default function SystemStatusCard() {
|
|
const [data, setData] = useState<Status | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [showDebug, setShowDebug] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
(async () => {
|
|
try {
|
|
const res = await fetchClient<Status>("/admin/system-status");
|
|
if (mounted) setData(res);
|
|
} catch (e: any) {
|
|
if (mounted) setError(e?.message || "Failed to load status");
|
|
}
|
|
})();
|
|
return () => { mounted = false; };
|
|
}, []);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="rounded-lg border border-border/60 bg-background p-4 h-full min-h-[200px]">
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<h2 className="font-medium">System status</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">Uptime, versions, environment</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400">OK</span>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setShowDebug(!showDebug)}
|
|
>
|
|
{showDebug ? 'Hide' : 'Show'} Debug
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{error && <p className="text-sm text-muted-foreground mt-3">{error}</p>}
|
|
|
|
{data && (
|
|
<div className="mt-3 grid grid-cols-2 gap-3 text-sm">
|
|
<div className="space-y-1">
|
|
<div className="text-muted-foreground">Uptime</div>
|
|
<div>{formatDuration(data.uptimeSeconds)}</div>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="text-muted-foreground">Node</div>
|
|
<div>{data.versions?.node}</div>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="text-muted-foreground">Vendors</div>
|
|
<div>{data.counts?.vendors}</div>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="text-muted-foreground">Orders</div>
|
|
<div>{data.counts?.orders}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{showDebug && data && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Debug: Raw System Status Data</CardTitle>
|
|
<CardDescription>Complete system status response from backend</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4 text-xs font-mono">
|
|
<div>
|
|
<div className="font-semibold mb-2">Memory Usage:</div>
|
|
<div className="pl-4 space-y-1">
|
|
<div>RSS (Resident Set Size): {formatBytes(data.memory?.rss || 0)}</div>
|
|
<div>Heap Total: {formatBytes(data.memory?.heapTotal || 0)}</div>
|
|
<div>Heap Used: {formatBytes(data.memory?.heapUsed || 0)}</div>
|
|
<div>External: {formatBytes(data.memory?.external || 0)}</div>
|
|
<div>Array Buffers: {formatBytes(data.memory?.arrayBuffers || 0)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="font-semibold mb-2">Versions:</div>
|
|
<div className="pl-4 space-y-1">
|
|
{Object.entries(data.versions || {}).map(([key, value]) => (
|
|
<div key={key}>{key}: {value}</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="font-semibold mb-2">Counts:</div>
|
|
<div className="pl-4 space-y-1">
|
|
<div>Vendors: {data.counts?.vendors || 0}</div>
|
|
<div>Orders: {data.counts?.orders || 0}</div>
|
|
<div>Products: {data.counts?.products || 0}</div>
|
|
<div>Chats: {data.counts?.chats || 0}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="font-semibold mb-2">Uptime:</div>
|
|
<div className="pl-4">
|
|
{data.uptimeSeconds} seconds ({formatDuration(data.uptimeSeconds)})
|
|
</div>
|
|
</div>
|
|
|
|
<details className="mt-4">
|
|
<summary className="font-semibold cursor-pointer">Full JSON Response</summary>
|
|
<pre className="mt-2 bg-muted p-4 rounded overflow-auto max-h-96 text-[10px]">
|
|
{JSON.stringify(data, null, 2)}
|
|
</pre>
|
|
</details>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
|
|
|