250 líneas
8.4 KiB
TypeScript
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>
|
|
)
|
|
} |