← Назадimport { Server, Cpu, HardDrive, Activity, FolderOpen, Wifi, WifiOff } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { useSystem } from '../../hooks/useSystem';
interface SystemCardProps {
onViewDetails: () => void;
onViewTasks: () => void;
onViewFiles?: () => void;
}
interface Pm2Proc {
name: string;
status: string;
cpu: number;
memoryMB: string;
uptime: string;
restarts: number;
}
interface ServiceHealth {
id: string;
name: string;
status: string;
responseMs: number;
}
export function SystemCard({ onViewDetails, onViewTasks, onViewFiles }: SystemCardProps) {
const { data, isLoading, error, refetch } = useSystem();
if (isLoading) {
return (
<Card className="animate-pulse">
<div className="h-64 flex items-center justify-center">
<div className="text-gray-400">Loading...</div>
</div>
</Card>
);
}
if (error) {
return (
<Card className="border-danger/30">
<div className="p-6 text-center text-danger">
Failed to load system metrics
</div>
</Card>
);
}
const { kpis } = data || {};
const cpuUsage = parseFloat(kpis?.cpu?.usage || '0');
const memUsage = parseFloat(kpis?.memory?.usedPercent || '0');
const memUsedGB = kpis?.memory?.usedGB || '--';
const memTotalGB = kpis?.memory?.totalGB || '--';
const pm2Procs: Pm2Proc[] = kpis?.pm2 || [];
const services: ServiceHealth[] = kpis?.services || [];
const status = cpuUsage > 80 || memUsage > 90 ? 'warning' : 'success';
const statusColor = status === 'success' ? 'border-l-success' : 'border-l-danger';
const downServices = services.filter(s => s.status === 'down');
return (
<Card className={`border-l-4 ${statusColor} transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-system/10`}>
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="p-3 rounded-xl bg-system/20">
<Server className="w-6 h-6 text-system" />
</div>
<div>
<CardTitle>System Monitor</CardTitle>
<CardDescription>Server resources & services</CardDescription>
</div>
</div>
<div className="flex gap-2">
{downServices.length > 0 && (
<Badge variant="danger">{downServices.length} DOWN</Badge>
)}
<Badge variant={status}>{status === 'success' ? 'HEALTHY' : 'WARNING'}</Badge>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Core metrics */}
<div className="grid grid-cols-3 gap-4">
<div className="bg-white/5 rounded-xl p-4 border border-white/5">
<div className="flex items-center gap-2 text-xs text-gray-400 uppercase mb-2">
<Cpu className="w-4 h-4" />
CPU
</div>
<div className="text-2xl font-bold text-white">{kpis?.cpu?.usage || '--'}</div>
</div>
<div className="bg-white/5 rounded-xl p-4 border border-white/5">
<div className="flex items-center gap-2 text-xs text-gray-400 uppercase mb-2">
<Activity className="w-4 h-4" />
RAM
</div>
<div className="text-2xl font-bold text-white">{kpis?.memory?.usedPercent || '--'}</div>
<div className="text-xs text-gray-500">{memUsedGB}/{memTotalGB} GB</div>
</div>
<div className="bg-white/5 rounded-xl p-4 border border-white/5">
<div className="flex items-center gap-2 text-xs text-gray-400 uppercase mb-2">
<HardDrive className="w-4 h-4" />
Disk
</div>
<div className="text-2xl font-bold text-white">{kpis?.disk?.[0]?.usePercent || '--'}</div>
</div>
</div>
{/* #5: Service health */}
{services.length > 0 && (
<div>
<div className="text-xs text-gray-400 uppercase mb-2 font-semibold">Services</div>
<div className="grid grid-cols-2 gap-2">
{services.map((svc: ServiceHealth) => (
<div key={svc.id} className="flex items-center gap-2 bg-white/5 rounded-lg px-3 py-2 border border-white/5">
{svc.status === 'up' ? (
<Wifi className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
) : (
<WifiOff className="w-3.5 h-3.5 text-red-400 shrink-0" />
)}
<span className="text-xs text-gray-300 truncate">{svc.name}</span>
<span className={`text-xs ml-auto ${svc.status === 'up' ? 'text-gray-500' : 'text-red-400 font-bold'}`}>
{svc.status === 'up' ? `${svc.responseMs}ms` : 'DOWN'}
</span>
</div>
))}
</div>
</div>
)}
{/* #6: PM2 processes */}
{pm2Procs.length > 0 && (
<div>
<div className="text-xs text-gray-400 uppercase mb-2 font-semibold">PM2 Processes</div>
<div className="space-y-1">
{pm2Procs.map((p: Pm2Proc) => (
<div key={p.name} className="flex items-center gap-2 text-xs bg-white/5 rounded-lg px-3 py-1.5 border border-white/5">
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${p.status === 'online' ? 'bg-emerald-400' : 'bg-red-400'}`} />
<span className="text-gray-300 font-medium w-28 truncate">{p.name}</span>
<span className="text-gray-500 w-12 text-right">{p.memoryMB}M</span>
<span className="text-gray-500 w-10 text-right">{p.cpu}%</span>
<span className="text-gray-500 ml-auto">{p.uptime}</span>
{p.restarts > 0 && (
<span className={`text-xs ${p.restarts > 5 ? 'text-amber-400' : 'text-gray-500'}`}>
{p.restarts}x
</span>
)}
</div>
))}
</div>
</div>
)}
</CardContent>
<CardFooter className="gap-2">
<Button variant="primary" size="sm" onClick={() => refetch()}>
<Activity className="w-4 h-4" />
Обновить
</Button>
<Button variant="ghost" size="sm" onClick={onViewDetails}>
Детали
</Button>
<Button variant="ghost" size="sm" onClick={onViewTasks}>
Задачи
</Button>
{onViewFiles && (
<Button variant="ghost" size="sm" onClick={onViewFiles}>
<FolderOpen className="w-4 h-4" />
Файлы
</Button>
)}
</CardFooter>
</Card>
);
}