feat(admin): Windows hardware cards (CPU/RAM/GPU/Disks) with Lucide icons; feat(desktop): inventory summary cards; feat(agent/windows): extended hardware collectors (CPU/board/BIOS/memory/video/disks); fix(agent): memory units in bytes
This commit is contained in:
parent
fcd45ff034
commit
c70691bce8
6 changed files with 6200 additions and 126 deletions
|
|
@ -5,7 +5,7 @@ import { useQuery } from "convex/react"
|
|||
import { format, formatDistanceToNowStrict } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { toast } from "sonner"
|
||||
import { ClipboardCopy, ServerCog } from "lucide-react"
|
||||
import { ClipboardCopy, ServerCog, Cpu, MemoryStick, Monitor, HardDrive } from "lucide-react"
|
||||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
|
@ -53,6 +53,21 @@ type WindowsExtended = {
|
|||
services?: Array<Record<string, unknown>>
|
||||
defender?: Record<string, unknown>
|
||||
hotfix?: Array<Record<string, unknown>>
|
||||
cpu?: Record<string, unknown> | Array<Record<string, unknown>>
|
||||
baseboard?: Record<string, unknown> | Array<Record<string, unknown>>
|
||||
bios?: Record<string, unknown> | Array<Record<string, unknown>>
|
||||
memoryModules?: Array<{
|
||||
BankLabel?: string
|
||||
Capacity?: number
|
||||
Manufacturer?: string
|
||||
PartNumber?: string
|
||||
SerialNumber?: string
|
||||
ConfiguredClockSpeed?: number
|
||||
Speed?: number
|
||||
ConfiguredVoltage?: number
|
||||
}>
|
||||
videoControllers?: Array<{ Name?: string; AdapterRAM?: number; DriverVersion?: string; PNPDeviceID?: string }>
|
||||
disks?: Array<{ Model?: string; SerialNumber?: string; Size?: number; InterfaceType?: string; MediaType?: string }>
|
||||
}
|
||||
|
||||
type MacExtended = {
|
||||
|
|
@ -409,6 +424,22 @@ function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
const windowsExt = extended?.windows ?? null
|
||||
const macosExt = extended?.macos ?? null
|
||||
|
||||
const winCpu = Array.isArray(windowsExt?.cpu)
|
||||
? (windowsExt?.cpu as Array<Record<string, unknown>>)[0] ?? null
|
||||
: (windowsExt?.cpu as Record<string, unknown> | null)
|
||||
const winMemTotal = Array.isArray(windowsExt?.memoryModules)
|
||||
? (windowsExt?.memoryModules as Array<{ Capacity?: number }>).reduce((acc, m) => acc + Number(m?.Capacity ?? 0), 0)
|
||||
: 0
|
||||
const winGpu = Array.isArray(windowsExt?.videoControllers)
|
||||
? (windowsExt?.videoControllers as Array<Record<string, unknown>>)[0] ?? null
|
||||
: null
|
||||
const winDiskStats = Array.isArray(windowsExt?.disks)
|
||||
? {
|
||||
count: (windowsExt?.disks as Array<Record<string, unknown>>).length,
|
||||
total: (windowsExt?.disks as Array<{ Size?: number }>).reduce((acc, d) => acc + Number(d?.Size ?? 0), 0),
|
||||
}
|
||||
: { count: 0, total: 0 }
|
||||
|
||||
const lastHeartbeatDate = machine?.lastHeartbeatAt ? new Date(machine.lastHeartbeatAt) : null
|
||||
const tokenExpiry = machine?.token?.expiresAt ? new Date(machine.token.expiresAt) : null
|
||||
const tokenLastUsed = machine?.token?.lastUsedAt ? new Date(machine.token.lastUsedAt) : null
|
||||
|
|
@ -746,6 +777,83 @@ function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
{/* Windows */}
|
||||
{windowsExt ? (
|
||||
<div className="space-y-3">
|
||||
{/* Cards resumidos: CPU / RAM / GPU / Discos */}
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="flex items-center gap-3 py-3">
|
||||
<Cpu className="size-5 text-slate-500" />
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-xs text-muted-foreground">CPU</p>
|
||||
<p className="truncate text-sm font-semibold text-foreground">{String((winCpu as any)?.Name ?? "—")}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="flex items-center gap-3 py-3">
|
||||
<MemoryStick className="size-5 text-slate-500" />
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Memória total</p>
|
||||
<p className="text-sm font-semibold text-foreground">{formatBytes(winMemTotal)}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="flex items-center gap-3 py-3">
|
||||
<Monitor className="size-5 text-slate-500" />
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-xs text-muted-foreground">GPU</p>
|
||||
<p className="truncate text-sm font-semibold text-foreground">{String((winGpu as any)?.Name ?? "—")}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="flex items-center gap-3 py-3">
|
||||
<HardDrive className="size-5 text-slate-500" />
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Discos</p>
|
||||
<p className="text-sm font-semibold text-foreground">{winDiskStats.count} · {formatBytes(winDiskStats.total)}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
{windowsExt.cpu ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">CPU</p>
|
||||
{Array.isArray(windowsExt.cpu) ? (
|
||||
(windowsExt.cpu as Array<Record<string, unknown>>).slice(0,1).map((c, i) => (
|
||||
<div key={`cpu-${i}`} className="mt-2 grid gap-1 text-sm text-muted-foreground">
|
||||
<DetailLine label="Modelo" value={String(c?.["Name"] ?? "—")} />
|
||||
<DetailLine label="Fabricante" value={String(c?.["Manufacturer"] ?? "—")} />
|
||||
<DetailLine label="Socket" value={String(c?.["SocketDesignation"] ?? "—")} />
|
||||
<DetailLine label="Núcleos" value={String(c?.["NumberOfCores"] ?? "—")} />
|
||||
<DetailLine label="Threads" value={String(c?.["NumberOfLogicalProcessors"] ?? "—")} />
|
||||
<DetailLine label="L2" value={c?.["L2CacheSize"] ? `${c["L2CacheSize"]} KB` : "—"} />
|
||||
<DetailLine label="L3" value={c?.["L3CacheSize"] ? `${c["L3CacheSize"]} KB` : "—"} />
|
||||
<DetailLine label="Clock máx" value={c?.["MaxClockSpeed"] ? `${c["MaxClockSpeed"]} MHz` : "—"} />
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{windowsExt.baseboard || windowsExt.bios ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Placa-mãe / BIOS</p>
|
||||
<div className="mt-2 grid gap-1 text-sm text-muted-foreground">
|
||||
{(() => {
|
||||
const b = Array.isArray(windowsExt.baseboard) ? windowsExt.baseboard[0] : windowsExt.baseboard
|
||||
const bios = Array.isArray(windowsExt.bios) ? windowsExt.bios[0] : windowsExt.bios
|
||||
return (
|
||||
<>
|
||||
<DetailLine label="Board" value={b ? `${String(b?.["Manufacturer"] ?? "")} ${String(b?.["Product"] ?? "")}`.trim() : "—"} />
|
||||
<DetailLine label="Board SN" value={b ? String(b?.["SerialNumber"] ?? "—") : "—"} />
|
||||
<DetailLine label="BIOS" value={bios ? `${String(bios?.["Manufacturer"] ?? "")} ${String(bios?.["SMBIOSBIOSVersion"] ?? "")}`.trim() : "—"} />
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{Array.isArray(windowsExt.services) ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Serviços</p>
|
||||
|
|
@ -787,6 +895,81 @@ function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{Array.isArray(windowsExt.memoryModules) && windowsExt.memoryModules.length > 0 ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Módulos de memória</p>
|
||||
<div className="mt-2 overflow-hidden rounded-md border border-slate-200">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-200 bg-slate-100/80">
|
||||
<TableHead className="text-xs text-slate-500">Banco</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Capacidade</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Fabricante</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">PartNumber</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Clock</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(windowsExt.memoryModules as Array<any>).map((m, idx) => (
|
||||
<TableRow key={`mem-${idx}`} className="border-slate-100">
|
||||
<TableCell className="text-sm">{m?.BankLabel ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{formatBytes(Number(m?.Capacity ?? 0))}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{m?.Manufacturer ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{m?.PartNumber ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{m?.ConfiguredClockSpeed ?? m?.Speed ? `${m?.ConfiguredClockSpeed ?? m?.Speed} MHz` : "—"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{Array.isArray(windowsExt.videoControllers) && windowsExt.videoControllers.length > 0 ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Adaptadores de vídeo</p>
|
||||
<ul className="mt-2 grid gap-1 text-xs text-muted-foreground">
|
||||
{(windowsExt.videoControllers as Array<any>).map((v, idx) => (
|
||||
<li key={`vid-${idx}`}>
|
||||
<span className="font-medium text-foreground">{v?.Name ?? "—"}</span>
|
||||
{v?.AdapterRAM ? <span className="ml-1">{formatBytes(Number(v.AdapterRAM))}</span> : null}
|
||||
{v?.DriverVersion ? <span className="ml-1">· Driver {v.DriverVersion}</span> : null}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{Array.isArray(windowsExt.disks) && windowsExt.disks.length > 0 ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Discos físicos</p>
|
||||
<div className="mt-2 overflow-hidden rounded-md border border-slate-200">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-200 bg-slate-100/80">
|
||||
<TableHead className="text-xs text-slate-500">Modelo</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Tamanho</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Interface</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Tipo</TableHead>
|
||||
<TableHead className="text-xs text-slate-500">Serial</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(windowsExt.disks as Array<any>).map((d, idx) => (
|
||||
<TableRow key={`diskp-${idx}`} className="border-slate-100">
|
||||
<TableCell className="text-sm">{d?.Model ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{formatBytes(Number(d?.Size ?? 0))}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{d?.InterfaceType ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{d?.MediaType ?? "—"}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">{d?.SerialNumber ?? "—"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{windowsExt.defender ? (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||
<p className="text-xs font-semibold uppercase text-slate-500">Defender</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue