feat: devices page, DeviceCard and equipos list filter by OS

This commit is contained in:
2026-02-12 15:26:14 -06:00
parent 20982aa077
commit 9a8815d4f5
4 changed files with 379 additions and 85 deletions

View File

@@ -20,7 +20,7 @@ import {
Terminal,
FolderOpen,
} from 'lucide-react'
import { cn, formatRelativeTime, getStatusColor, getStatusBgColor } from '@/lib/utils'
import { cn, formatRelativeTime, getStatusColor, getStatusBgColor, getStatusBorderColor } from '@/lib/utils'
interface Device {
id: string
@@ -80,7 +80,7 @@ function DeviceCard({
const getDeviceUrl = () => {
const type = device.tipo
if (['PC', 'LAPTOP', 'SERVIDOR'].includes(type)) return `/equipos/${device.id}`
if (['PC', 'LAPTOP', 'SERVIDOR'].includes(type)) return `/devices/${device.id}`
if (['CELULAR', 'TABLET'].includes(type)) return `/celulares/${device.id}`
return `/red/${device.id}`
}
@@ -88,88 +88,75 @@ function DeviceCard({
return (
<div
className={cn(
'card p-4 transition-all hover:border-primary-500/50 relative group',
device.estado === 'ALERTA' && 'border-danger/50'
'card p-4 transition-all hover:border-primary-500/50 relative group border',
getStatusBorderColor(device.estado)
)}
>
{/* Status indicator */}
<div className="absolute top-3 right-3 flex items-center gap-2">
<span
className={cn(
'status-dot',
device.estado === 'ONLINE' && 'status-dot-online',
device.estado === 'OFFLINE' && 'status-dot-offline',
device.estado === 'ALERTA' && 'status-dot-alert',
device.estado === 'MANTENIMIENTO' && 'status-dot-maintenance'
)}
/>
<div className="relative">
<button
onClick={() => setShowMenu(!showMenu)}
className="p-1 rounded hover:bg-dark-100 opacity-0 group-hover:opacity-100 transition-opacity"
>
<MoreVertical className="w-4 h-4 text-gray-500" />
</button>
<div className="absolute top-3 right-3 z-10">
<button
onClick={() => setShowMenu(!showMenu)}
className="p-1.5 rounded hover:bg-dark-100 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity touch-manipulation"
>
<MoreVertical className="w-4 h-4 text-gray-500" />
</button>
{showMenu && (
<>
<div className="fixed inset-0 z-40" onClick={() => setShowMenu(false)} />
<div className="dropdown right-0 z-50">
{['PC', 'LAPTOP', 'SERVIDOR'].includes(device.tipo) && device.estado === 'ONLINE' && (
<>
<button
onClick={() => {
onAction?.(device.id, 'desktop')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<ExternalLink className="w-4 h-4" />
Escritorio remoto
</button>
<button
onClick={() => {
onAction?.(device.id, 'terminal')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<Terminal className="w-4 h-4" />
Terminal
</button>
<button
onClick={() => {
onAction?.(device.id, 'files')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<FolderOpen className="w-4 h-4" />
Archivos
</button>
<div className="h-px bg-dark-100 my-1" />
</>
)}
<button
onClick={() => {
onAction?.(device.id, 'restart')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2 text-warning"
>
<Power className="w-4 h-4" />
Reiniciar
</button>
</div>
</>
)}
</div>
{showMenu && (
<>
<div className="fixed inset-0 z-40" onClick={() => setShowMenu(false)} />
<div className="dropdown right-0 z-50">
{['PC', 'LAPTOP', 'SERVIDOR'].includes(device.tipo) && device.estado === 'ONLINE' && (
<>
<button
onClick={() => {
onAction?.(device.id, 'desktop')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<ExternalLink className="w-4 h-4" />
Escritorio remoto
</button>
<button
onClick={() => {
onAction?.(device.id, 'terminal')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<Terminal className="w-4 h-4" />
Terminal
</button>
<button
onClick={() => {
onAction?.(device.id, 'files')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2"
>
<FolderOpen className="w-4 h-4" />
Archivos
</button>
<div className="h-px bg-dark-100 my-1" />
</>
)}
<button
onClick={() => {
onAction?.(device.id, 'restart')
setShowMenu(false)
}}
className="dropdown-item flex items-center gap-2 text-warning"
>
<Power className="w-4 h-4" />
Reiniciar
</button>
</div>
</>
)}
</div>
{/* Icon and name */}
<Link href={getDeviceUrl()} className="block">
<div className="flex items-center gap-4 mb-3">
<div className={cn('p-3 rounded-lg', getStatusBgColor(device.estado))}>
<div className={cn('p-3 rounded-lg shrink-0', getStatusBgColor(device.estado))}>
<span className={getStatusColor(device.estado)}>
{deviceIcons[device.tipo] || deviceIcons.OTRO}
</span>
@@ -204,9 +191,9 @@ function DeviceCard({
</div>
{/* Metrics bar */}
{device.estado === 'ONLINE' && (device.cpuUsage !== null || device.ramUsage !== null) && (
{device.estado === 'ONLINE' && (device.cpuUsage != null || device.ramUsage != null) && (
<div className="mt-3 pt-3 border-t border-dark-100 grid grid-cols-2 gap-2">
{device.cpuUsage !== null && (
{device.cpuUsage != null && (
<div>
<div className="flex justify-between text-xs mb-1">
<span className="text-gray-500">CPU</span>
@@ -225,7 +212,7 @@ function DeviceCard({
</div>
</div>
)}
{device.ramUsage !== null && (
{device.ramUsage != null && (
<div>
<div className="flex justify-between text-xs mb-1">
<span className="text-gray-500">RAM</span>
@@ -304,7 +291,7 @@ function DeviceList({
</span>
</td>
<td>
{device.cpuUsage !== null ? (
{device.cpuUsage != null ? (
<span className={cn(device.cpuUsage > 80 ? 'text-danger' : 'text-gray-400')}>
{Math.round(device.cpuUsage)}%
</span>
@@ -313,7 +300,7 @@ function DeviceList({
)}
</td>
<td>
{device.ramUsage !== null ? (
{device.ramUsage != null ? (
<span className={cn(device.ramUsage > 80 ? 'text-danger' : 'text-gray-400')}>
{Math.round(device.ramUsage)}%
</span>
@@ -326,7 +313,7 @@ function DeviceList({
</td>
<td>
<Link
href={`/equipos/${device.id}`}
href={`/devices/${device.id}`}
className="btn btn-ghost btn-sm"
>
Ver