← Назад
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> ); }