222 lines
6.6 KiB
TypeScript
222 lines
6.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useMemo, useEffect, useCallback } from 'react'
|
|
import { useSelectedClient } from '@/components/providers/SelectedClientProvider'
|
|
import { trpc } from '@/lib/trpc-client'
|
|
import MetricCard from '@/components/performance/MetricCard'
|
|
import ProcessTable from '@/components/performance/ProcessTable'
|
|
import type { ProcessItem } from '@/components/performance/ProcessRow'
|
|
|
|
const CHART_POINTS = 20
|
|
const POLL_INTERVAL_MS = 2000
|
|
|
|
function randomIn(min: number, max: number) {
|
|
return Math.round(min + Math.random() * (max - min))
|
|
}
|
|
|
|
function generateMockProcesses(): ProcessItem[] {
|
|
const names = [
|
|
'chrome.exe',
|
|
'Code.exe',
|
|
'node.exe',
|
|
'System',
|
|
'svchost.exe',
|
|
'explorer.exe',
|
|
'MsMpEng.exe',
|
|
'SearchHost.exe',
|
|
'RuntimeBroker.exe',
|
|
'dllhost.exe',
|
|
]
|
|
return names.slice(0, 10).map((name, i) => ({
|
|
id: `p-${i}`,
|
|
name,
|
|
pid: 1000 + i * 100 + randomIn(0, 99),
|
|
cpu: randomIn(0, 45),
|
|
memory: `${randomIn(50, 800)} MB`,
|
|
state: i % 3 === 0 ? 'En ejecución' : 'Activo',
|
|
}))
|
|
}
|
|
|
|
export default function RendimientoPage() {
|
|
const { selectedClientId } = useSelectedClient()
|
|
const clienteId = selectedClientId ?? undefined
|
|
|
|
const listQuery = trpc.equipos.list.useQuery(
|
|
{ clienteId, limit: 100 },
|
|
{ refetchOnWindowFocus: false }
|
|
)
|
|
|
|
const devices = useMemo(
|
|
() => (listQuery.data?.dispositivos ?? []).map((d) => ({ id: d.id, nombre: d.nombre })),
|
|
[listQuery.data]
|
|
)
|
|
|
|
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('')
|
|
const [metrics, setMetrics] = useState({
|
|
cpu: 0,
|
|
memory: 0,
|
|
disk: 0,
|
|
network: 0,
|
|
})
|
|
const [chartHistory, setChartHistory] = useState<{
|
|
cpu: number[]
|
|
memory: number[]
|
|
disk: number[]
|
|
network: number[]
|
|
}>({
|
|
cpu: [],
|
|
memory: [],
|
|
disk: [],
|
|
network: [],
|
|
})
|
|
const [processes, setProcesses] = useState<ProcessItem[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const hasDevice = !!selectedDeviceId
|
|
|
|
const tick = useCallback(() => {
|
|
const cpu = randomIn(15, 85)
|
|
const memory = randomIn(40, 90)
|
|
const disk = randomIn(25, 65)
|
|
const network = randomIn(5, 120)
|
|
setMetrics({ cpu, memory, disk, network })
|
|
setChartHistory((prev) => ({
|
|
cpu: [...prev.cpu, cpu].slice(-CHART_POINTS),
|
|
memory: [...prev.memory, memory].slice(-CHART_POINTS),
|
|
disk: [...prev.disk, disk].slice(-CHART_POINTS),
|
|
network: [...prev.network, network].slice(-CHART_POINTS),
|
|
}))
|
|
setProcesses(generateMockProcesses())
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!hasDevice) {
|
|
setMetrics({ cpu: 0, memory: 0, disk: 0, network: 0 })
|
|
setChartHistory({ cpu: [], memory: [], disk: [], network: [] })
|
|
setProcesses([])
|
|
return
|
|
}
|
|
setLoading(true)
|
|
tick()
|
|
const t = setTimeout(() => setLoading(false), 400)
|
|
const interval = setInterval(tick, POLL_INTERVAL_MS)
|
|
return () => {
|
|
clearTimeout(t)
|
|
clearInterval(interval)
|
|
}
|
|
}, [hasDevice, tick])
|
|
|
|
const cpuFooter = hasDevice
|
|
? [
|
|
{ label: 'Procesos:', value: '142' },
|
|
{ label: 'Hilos:', value: '2,840' },
|
|
{ label: 'Velocidad:', value: '2.90 GHz' },
|
|
]
|
|
: [
|
|
{ label: 'Procesos:', value: '—' },
|
|
{ label: 'Hilos:', value: '—' },
|
|
{ label: 'Velocidad:', value: '—' },
|
|
]
|
|
|
|
const ramFooter = hasDevice
|
|
? [
|
|
{ label: 'En uso:', value: `${Math.round((metrics.memory / 100) * 16)} GB` },
|
|
{ label: 'Disponible:', value: `${Math.round(((100 - metrics.memory) / 100) * 16)} GB` },
|
|
{ label: 'Total:', value: '16 GB' },
|
|
]
|
|
: [
|
|
{ label: 'En uso:', value: '—' },
|
|
{ label: 'Disponible:', value: '—' },
|
|
{ label: 'Total:', value: '—' },
|
|
]
|
|
|
|
const diskFooter = hasDevice
|
|
? [
|
|
{ label: 'Lectura:', value: '12.5 MB/s' },
|
|
{ label: 'Escritura:', value: '3.2 MB/s' },
|
|
{ label: 'Activo:', value: 'Sí' },
|
|
]
|
|
: [
|
|
{ label: 'Lectura:', value: '—' },
|
|
{ label: 'Escritura:', value: '—' },
|
|
{ label: 'Activo:', value: '—' },
|
|
]
|
|
|
|
const networkFooter = hasDevice
|
|
? [
|
|
{ label: 'Enviado:', value: '1.2 GB' },
|
|
{ label: 'Recibido:', value: '4.8 GB' },
|
|
{ label: 'Adaptador:', value: 'Ethernet' },
|
|
]
|
|
: [
|
|
{ label: 'Enviado:', value: '—' },
|
|
{ label: 'Recibido:', value: '—' },
|
|
{ label: 'Adaptador:', value: '—' },
|
|
]
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<header className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white sm:text-3xl">
|
|
Rendimiento en Tiempo Real
|
|
</h1>
|
|
<p className="mt-1 text-gray-400">
|
|
Monitorea recursos del sistema
|
|
</p>
|
|
</div>
|
|
<div className="shrink-0">
|
|
<select
|
|
value={selectedDeviceId}
|
|
onChange={(e) => setSelectedDeviceId(e.target.value)}
|
|
className="w-64 rounded-lg border border-white/10 bg-dark-300 px-4 py-2.5 text-sm text-gray-200 transition-colors hover:border-white/20 focus:border-cyan-500/50 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<option value="">-- Seleccionar dispositivo --</option>
|
|
{devices.map((d) => (
|
|
<option key={d.id} value={d.id}>
|
|
{d.nombre}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<MetricCard
|
|
title="CPU"
|
|
value={hasDevice ? `${metrics.cpu}` : '—'}
|
|
valueSuffix="%"
|
|
footerStats={cpuFooter}
|
|
chartData={chartHistory.cpu}
|
|
highUsage={metrics.cpu > 80}
|
|
/>
|
|
<MetricCard
|
|
title="Memoria RAM"
|
|
value={hasDevice ? `${metrics.memory}` : '—'}
|
|
valueSuffix="%"
|
|
footerStats={ramFooter}
|
|
chartData={chartHistory.memory}
|
|
highUsage={metrics.memory > 80}
|
|
/>
|
|
<MetricCard
|
|
title="Disco"
|
|
value={hasDevice ? `${metrics.disk}` : '—'}
|
|
valueSuffix="%"
|
|
footerStats={diskFooter}
|
|
chartData={chartHistory.disk}
|
|
highUsage={metrics.disk > 80}
|
|
/>
|
|
<MetricCard
|
|
title="Red"
|
|
value={hasDevice ? `${metrics.network}` : '—'}
|
|
valueSuffix="Mbps"
|
|
footerStats={networkFooter}
|
|
chartData={chartHistory.network}
|
|
/>
|
|
</div>
|
|
|
|
<ProcessTable processes={processes} noDevice={!hasDevice} />
|
|
</div>
|
|
)
|
|
}
|