Files
csf-web/src/components/ServerStats.tsx
2025-09-20 18:42:04 +02:00

250 líneas
8.4 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { useCSFApi } from '@/hooks/use-csf-api'
function formatBytes(bytes: number): string {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
if (bytes === 0) return '0 Bytes'
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
}
function formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (days > 0) {
return `${days}d ${hours}h ${minutes}m`
} else if (hours > 0) {
return `${hours}h ${minutes}m`
} else {
return `${minutes}m`
}
}
interface StatsCardProps {
title: string
value: string | number
subtitle?: string
color?: 'blue' | 'green' | 'red' | 'yellow' | 'purple'
icon?: React.ReactNode
}
function StatsCard({ title, value, subtitle, color = 'blue', icon }: StatsCardProps) {
const colorClasses = {
blue: 'bg-blue-500',
green: 'bg-green-500',
red: 'bg-red-500',
yellow: 'bg-yellow-500',
purple: 'bg-purple-500'
}
return (
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center">
{icon && (
<div className={`w-12 h-12 ${colorClasses[color]} rounded-lg flex items-center justify-center mr-4`}>
{icon}
</div>
)}
<div className="flex-1">
<h3 className="text-sm font-medium text-gray-500 uppercase">{title}</h3>
<p className="text-2xl font-bold text-gray-900">{value}</p>
{subtitle && <p className="text-sm text-gray-600">{subtitle}</p>}
</div>
</div>
</div>
)
}
interface ProgressBarProps {
percentage: number
color?: 'blue' | 'green' | 'red' | 'yellow'
label: string
}
function ProgressBar({ percentage, color = 'blue', label }: ProgressBarProps) {
const colorClasses = {
blue: 'bg-blue-500',
green: 'bg-green-500',
red: 'bg-red-500',
yellow: 'bg-yellow-500'
}
const getColor = () => {
if (percentage >= 90) return 'red'
if (percentage >= 70) return 'yellow'
return color
}
const currentColor = getColor()
return (
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-700">{label}</span>
<span className="text-sm text-gray-500">{percentage.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${colorClasses[currentColor]}`}
style={{ width: `${Math.min(percentage, 100)}%` }}
></div>
</div>
</div>
)
}
interface ServerStatsProps {
className?: string
}
export function ServerStats({ className = '' }: ServerStatsProps) {
const { stats, getStats, loading } = useCSFApi()
useEffect(() => {
getStats()
const interval = setInterval(getStats, 5000) // Update every 5 seconds
return () => clearInterval(interval)
}, [getStats])
if (!stats) {
return (
<div className={`bg-white rounded-lg shadow-md p-6 ${className}`}>
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded mb-4"></div>
<div className="space-y-3">
<div className="h-8 bg-gray-200 rounded"></div>
<div className="h-8 bg-gray-200 rounded"></div>
<div className="h-8 bg-gray-200 rounded"></div>
</div>
</div>
</div>
)
}
const cpuIcon = (
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
)
const memoryIcon = (
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
</svg>
)
const diskIcon = (
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
)
const networkIcon = (
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
</svg>
)
return (
<div className={`space-y-6 ${className}`}>
<h2 className="text-xl font-bold text-gray-900">Estadísticas del Servidor</h2>
{/* Resource Usage */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ProgressBar
percentage={stats.cpu_usage}
label="Uso de CPU"
color="blue"
/>
<ProgressBar
percentage={stats.memory_usage}
label="Uso de Memoria"
color="green"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ProgressBar
percentage={stats.disk_usage}
label="Uso de Disco"
color="blue"
/>
<StatsCard
title="Conexiones Activas"
value={stats.active_connections}
color="yellow"
icon={networkIcon}
/>
</div>
{/* Network Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<StatsCard
title="Tráfico de Entrada"
value={formatBytes(stats.network_in)}
subtitle="Total recibido"
color="green"
icon={networkIcon}
/>
<StatsCard
title="Tráfico de Salida"
value={formatBytes(stats.network_out)}
subtitle="Total enviado"
color="blue"
icon={networkIcon}
/>
</div>
{/* CSF Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<StatsCard
title="IPs Bloqueadas"
value={stats.blocked_ips}
color="red"
icon={
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
}
/>
<StatsCard
title="IPs Permitidas"
value={stats.allowed_ips}
color="green"
icon={
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
}
/>
<StatsCard
title="Tiempo Activo"
value={formatUptime(stats.uptime)}
subtitle="Sistema en línea"
color="blue"
icon={
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
}
/>
</div>
{loading && (
<div className="text-center py-4">
<div className="inline-flex items-center px-4 py-2 font-semibold leading-6 text-sm shadow rounded-md text-gray-500 bg-white">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Actualizando estadísticas...
</div>
</div>
)}
</div>
)
}