"use client" import { useEffect, useMemo, useState } from "react" import { useQuery } from "convex/react" import { format, formatDistanceToNowStrict } from "date-fns" import { ptBR } from "date-fns/locale" import { toast } from "sonner" import { ClipboardCopy, ServerCog, Cpu, MemoryStick, Monitor, HardDrive, Pencil, ShieldCheck, ShieldAlert, Apple, Terminal } from "lucide-react" import { api } from "@/convex/_generated/api" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Checkbox } from "@/components/ui/checkbox" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Separator } from "@/components/ui/separator" import { cn } from "@/lib/utils" import Link from "next/link" import { useRouter } from "next/navigation" import { useAuth } from "@/lib/auth-client" import type { Id } from "@/convex/_generated/dataModel" type MachineMetrics = Record | null type MachineLabel = { id?: number | string name?: string } type MachineSoftware = { name?: string version?: string source?: string } type DetailLineProps = { label: string; value?: string | number | null; classNameValue?: string } type GpuAdapter = { name?: string vendor?: string driver?: string memoryBytes?: number } type LinuxLsblkEntry = { name?: string mountPoint?: string mountpoint?: string fs?: string fstype?: string sizeBytes?: number size?: number } type LinuxSmartEntry = { smart_status?: { passed?: boolean } model_name?: string model_family?: string serial_number?: string device?: { name?: string } } type LinuxExtended = { lsblk?: LinuxLsblkEntry[] lspci?: string lsusb?: string pciList?: Array<{ text: string }> usbList?: Array<{ text: string }> smart?: LinuxSmartEntry[] } type WindowsCpuInfo = { Name?: string Manufacturer?: string SocketDesignation?: string NumberOfCores?: number NumberOfLogicalProcessors?: number L2CacheSize?: number L3CacheSize?: number MaxClockSpeed?: number } type WindowsMemoryModule = { BankLabel?: string Capacity?: number Manufacturer?: string PartNumber?: string SerialNumber?: string ConfiguredClockSpeed?: number Speed?: number ConfiguredVoltage?: number } type WindowsVideoController = { Name?: string AdapterRAM?: number DriverVersion?: string PNPDeviceID?: string } type WindowsDiskEntry = { Model?: string SerialNumber?: string Size?: number InterfaceType?: string MediaType?: string } type WindowsOsInfo = { ProductName?: string CurrentBuild?: string | number CurrentBuildNumber?: string | number DisplayVersion?: string ReleaseId?: string EditionID?: string LicenseStatus?: number IsActivated?: boolean } type WindowsExtended = { software?: MachineSoftware[] services?: Array<{ name?: string; status?: string; displayName?: string }> defender?: Record hotfix?: Array> cpu?: WindowsCpuInfo | WindowsCpuInfo[] baseboard?: Record | Array> bios?: Record | Array> memoryModules?: WindowsMemoryModule[] videoControllers?: WindowsVideoController[] disks?: WindowsDiskEntry[] osInfo?: WindowsOsInfo } type MacExtended = { systemProfiler?: Record packages?: string[] launchctl?: string } type NetworkInterface = { name?: string; mac?: string; ip?: string } type MachineInventory = { hardware?: { vendor?: string model?: string serial?: string cpuType?: string physicalCores?: number logicalCores?: number memoryBytes?: number memory?: number primaryGpu?: GpuAdapter gpus?: GpuAdapter[] } network?: { primaryIp?: string; publicIp?: string; macAddresses?: string[] } | NetworkInterface[] software?: MachineSoftware[] labels?: MachineLabel[] fleet?: { id?: number | string teamId?: number | string detailUpdatedAt?: string osqueryVersion?: string } disks?: Array<{ name?: string; mountPoint?: string; fs?: string; interface?: string | null; serial?: string | null; totalBytes?: number; availableBytes?: number }> extended?: { linux?: LinuxExtended; windows?: WindowsExtended; macos?: MacExtended } services?: Array<{ name?: string; status?: string; displayName?: string }> collaborator?: { email?: string; name?: string; role?: string } } function toRecord(value: unknown): Record | null { if (!value || typeof value !== "object") return null return value as Record } function readString(record: Record, ...keys: string[]): string | undefined { for (const key of keys) { const raw = record[key] if (typeof raw === "string" && raw.trim().length > 0) { return raw } } return undefined } function readNumber(record: Record, ...keys: string[]): number | undefined { for (const key of keys) { const raw = record[key] if (typeof raw === "number" && Number.isFinite(raw)) { return raw } if (typeof raw === "string") { const trimmed = raw.trim() if (!trimmed) continue const parsed = Number(trimmed) if (!Number.isNaN(parsed)) return parsed const digits = trimmed.replace(/[^0-9.]/g, "") if (digits) { const fallback = Number(digits) if (!Number.isNaN(fallback)) return fallback } } } return undefined } function parseBytesLike(value: unknown): number | undefined { if (typeof value === "number" && Number.isFinite(value)) return value if (typeof value === "string") { const trimmed = value.trim() if (!trimmed) return undefined const normalized = trimmed.replace(",", ".") const match = normalized.match(/^([\d.]+)\s*(ti|tb|tib|gb|gib|mb|mib|kb|kib|b)?$/i) if (match) { const amount = Number(match[1]) if (Number.isNaN(amount)) return undefined const unit = match[2]?.toLowerCase() const base = 1024 const unitMap: Record = { b: 1, kb: base, kib: base, mb: base ** 2, mib: base ** 2, gb: base ** 3, gib: base ** 3, tb: base ** 4, tib: base ** 4, ti: base ** 4, } if (unit) { const multiplier = unitMap[unit] if (multiplier) { return amount * multiplier } } return amount } const digits = normalized.replace(/[^0-9.]/g, "") if (digits) { const fallback = Number(digits) if (!Number.isNaN(fallback)) return fallback } } return undefined } function deriveVendor(record: Record): string | undefined { const direct = readString(record, "vendor", "Vendor", "AdapterCompatibility") if (direct) return direct const pnp = readString(record, "PNPDeviceID") if (!pnp) return undefined const match = pnp.match(/VEN_([0-9A-F]{4})/i) if (match) { const vendorCode = match[1].toUpperCase() const vendorMap: Record = { "10DE": "NVIDIA", "1002": "AMD", "1022": "AMD", "8086": "Intel", "8087": "Intel", "1AF4": "Red Hat", } return vendorMap[vendorCode] ?? `VEN_${vendorCode}` } const segments = pnp.split("\\") const last = segments.pop() return last && last.trim().length > 0 ? last : pnp } function normalizeGpuSource(value: unknown): GpuAdapter | null { if (typeof value === "string") { const name = value.trim() return name ? { name } : null } const record = toRecord(value) if (!record) return null const name = readString(record, "name", "Name", "_name", "AdapterCompatibility") const vendor = deriveVendor(record) const driver = readString(record, "driver", "DriverVersion", "driverVersion") const memoryBytes = readNumber(record, "memoryBytes", "MemoryBytes", "AdapterRAM", "VRAM", "vramBytes") ?? parseBytesLike(record["AdapterRAM"] ?? record["VRAM"] ?? record["vram"]) if (!name && !vendor && !driver && memoryBytes === undefined) { return null } return { name, vendor, driver, memoryBytes } } function uniqueBy(items: T[], keyFn: (item: T) => string): T[] { const seen = new Set() const result: T[] = [] items.forEach((item) => { const key = keyFn(item) if (key && !seen.has(key)) { seen.add(key) result.push(item) } }) return result } export type MachinesQueryItem = { id: string tenantId: string hostname: string companyId: string | null companySlug: string | null osName: string | null osVersion: string | null architecture: string | null macAddresses: string[] serialNumbers: string[] authUserId: string | null authEmail: string | null persona: string | null assignedUserId: string | null assignedUserEmail: string | null assignedUserName: string | null assignedUserRole: string | null status: string | null lastHeartbeatAt: number | null heartbeatAgeMs: number | null registeredBy: string | null createdAt: number updatedAt: number token: { expiresAt: number lastUsedAt: number | null usageCount: number } | null metrics: MachineMetrics inventory: MachineInventory | null postureAlerts?: Array> | null lastPostureAt?: number | null } function useMachinesQuery(tenantId: string): MachinesQueryItem[] { return ( (useQuery(api.machines.listByTenant, { tenantId, includeMetadata: true, }) ?? []) as MachinesQueryItem[] ) } const statusLabels: Record = { online: "Online", offline: "Offline", maintenance: "Manutenção", blocked: "Bloqueada", unknown: "Desconhecida", } const statusClasses: Record = { online: "border-emerald-500/20 bg-emerald-500/15 text-emerald-600", offline: "border-rose-500/20 bg-rose-500/15 text-rose-600", maintenance: "border-amber-500/20 bg-amber-500/15 text-amber-600", blocked: "border-orange-500/20 bg-orange-500/15 text-orange-600", unknown: "border-slate-300 bg-slate-200 text-slate-700", } function formatRelativeTime(date?: Date | null) { if (!date) return "Nunca" try { return formatDistanceToNowStrict(date, { addSuffix: true, locale: ptBR }) } catch { return "—" } } function formatDate(date?: Date | null) { if (!date) return "—" return format(date, "dd/MM/yyyy HH:mm") } function formatBytes(bytes?: number | null) { if (!bytes || Number.isNaN(bytes)) return "—" const units = ["B", "KB", "MB", "GB", "TB"] let value = bytes let unitIndex = 0 while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024 unitIndex += 1 } return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}` } function formatPercent(value?: number | null) { if (value === null || value === undefined || Number.isNaN(value)) return "—" const normalized = value > 1 ? value : value * 100 return `${normalized.toFixed(0)}%` } function readBool(source: unknown, key: string): boolean | undefined { if (!source || typeof source !== "object") return undefined const value = (source as Record)[key] return typeof value === "boolean" ? value : undefined } function getStatusVariant(status?: string | null) { if (!status) return { label: statusLabels.unknown, className: statusClasses.unknown } const normalized = status.toLowerCase() return { label: statusLabels[normalized] ?? status, className: statusClasses[normalized] ?? statusClasses.unknown, } } function OsIcon({ osName }: { osName?: string | null }) { const name = (osName ?? "").toLowerCase() if (name.includes("mac") || name.includes("darwin") || name.includes("macos")) return if (name.includes("linux")) return // fallback para Windows/outros como monitor genérico return } export function AdminMachinesOverview({ tenantId }: { tenantId: string }) { const machines = useMachinesQuery(tenantId) const [q, setQ] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [osFilter, setOsFilter] = useState("all") const [companyQuery, setCompanyQuery] = useState("") const [onlyAlerts, setOnlyAlerts] = useState(false) const { convexUserId } = useAuth() const companies = useQuery( convexUserId ? api.companies.list : "skip", convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : ("skip" as const) ) as Array<{ id: string; name: string; slug?: string }> | undefined const companyNameBySlug = useMemo(() => { const map = new Map() ;(companies ?? []).forEach((c) => c.slug && map.set(c.slug, c.name)) return map }, [companies]) const osOptions = useMemo(() => { const set = new Set() machines.forEach((m) => m.osName && set.add(m.osName)) return Array.from(set).sort() }, [machines]) const companyNameOptions = useMemo(() => (companies ?? []).map((c) => c.name).sort((a,b)=>a.localeCompare(b,"pt-BR")), [companies]) const filteredMachines = useMemo(() => { const text = q.trim().toLowerCase() return machines.filter((m) => { if (onlyAlerts && !(Array.isArray(m.postureAlerts) && m.postureAlerts.length > 0)) return false if (statusFilter !== "all") { const s = (m.status ?? "unknown").toLowerCase() if (s !== statusFilter) return false } if (osFilter !== "all" && (m.osName ?? "").toLowerCase() !== osFilter.toLowerCase()) return false if (companyQuery && companyQuery.trim().length > 0) { const name = companyNameBySlug.get(m.companySlug ?? "")?.toLowerCase() ?? "" if (!name.includes(companyQuery.trim().toLowerCase())) return false } if (!text) return true const hay = [ m.hostname, m.authEmail ?? "", (m.macAddresses ?? []).join(" "), (m.serialNumbers ?? []).join(" "), ] .join(" ") .toLowerCase() return hay.includes(text) }) }, [machines, q, statusFilter, osFilter, companyQuery, onlyAlerts, companyNameBySlug]) return (
Máquinas registradas Sincronizadas via agente local ou Fleet. Atualiza em tempo real.
setQ(e.target.value)} placeholder="Buscar hostname, e-mail, MAC, serial..." />
setCompanyQuery(e.target.value)} placeholder="Buscar empresa" className="min-w-[220px]" /> {companyQuery && companyNameOptions.filter((c) => c.toLowerCase().includes(companyQuery.toLowerCase())).slice(0,6).length > 0 ? (
{companyNameOptions .filter((c) => c.toLowerCase().includes(companyQuery.toLowerCase())) .slice(0, 8) .map((c) => ( ))}
) : null}
{machines.length === 0 ? ( ) : ( )}
) } function MachineStatusBadge({ status }: { status?: string | null }) { const { label, className } = getStatusVariant(status) const s = String(status ?? "").toLowerCase() const colorClass = s === "online" ? "bg-emerald-500" : s === "offline" ? "bg-rose-500" : s === "maintenance" ? "bg-amber-500" : s === "blocked" ? "bg-orange-500" : "bg-slate-400" const ringClass = s === "online" ? "bg-emerald-400/30" : s === "offline" ? "bg-rose-400/30" : s === "maintenance" ? "bg-amber-400/30" : s === "blocked" ? "bg-orange-400/30" : "bg-slate-300/30" const isOnline = s === "online" return ( {isOnline ? ( ) : null} {label} ) } function EmptyState() { return (

Nenhuma máquina registrada ainda

Execute o agente local ou o webhook do Fleet para registrar as máquinas do tenant.

) } type MachineDetailsProps = { machine: MachinesQueryItem | null } export function MachineDetails({ machine }: MachineDetailsProps) { const { convexUserId } = useAuth() const router = useRouter() // Company name lookup (by slug) const companyQueryArgs = convexUserId && machine ? { tenantId: machine.tenantId, viewerId: convexUserId as Id<"users"> } : undefined const companies = useQuery( api.companies.list, companyQueryArgs ?? ("skip" as const) ) as Array<{ id: string; name: string; slug?: string }> | undefined const metadata = machine?.inventory ?? null const metrics = machine?.metrics ?? null const hardware = metadata?.hardware const network = metadata?.network ?? null const networkInterfaces = Array.isArray(network) ? network : null const networkSummary = !Array.isArray(network) && network ? network : null const software = metadata?.software ?? null const labels = metadata?.labels ?? null const fleet = metadata?.fleet ?? null const disks = Array.isArray(metadata?.disks) ? metadata.disks : [] const extended = metadata?.extended ?? null const linuxExt = extended?.linux ?? null const windowsExt = extended?.windows ?? null const macosExt = extended?.macos ?? null const windowsMemoryModulesRaw = windowsExt?.memoryModules const windowsVideoControllersRaw = windowsExt?.videoControllers const windowsDiskEntriesRaw = windowsExt?.disks const windowsMemoryModules = Array.isArray(windowsMemoryModulesRaw) ? windowsMemoryModulesRaw : windowsMemoryModulesRaw && typeof windowsMemoryModulesRaw === "object" ? [windowsMemoryModulesRaw] : [] const windowsVideoControllers = Array.isArray(windowsVideoControllersRaw) ? windowsVideoControllersRaw : windowsVideoControllersRaw && typeof windowsVideoControllersRaw === "object" ? [windowsVideoControllersRaw] : [] const windowsDiskEntries = Array.isArray(windowsDiskEntriesRaw) ? windowsDiskEntriesRaw : windowsDiskEntriesRaw && typeof windowsDiskEntriesRaw === "object" ? [windowsDiskEntriesRaw] : [] const linuxLsblk = linuxExt?.lsblk ?? [] const linuxSmartEntries = linuxExt?.smart ?? [] const normalizedHardwareGpus = Array.isArray(hardware?.gpus) ? hardware.gpus.map((gpu) => normalizeGpuSource(gpu)).filter((gpu): gpu is GpuAdapter => Boolean(gpu)) : [] const hardwarePrimaryGpu = hardware?.primaryGpu ? normalizeGpuSource(hardware.primaryGpu) : null const windowsCpuRaw = windowsExt?.cpu const winCpu = windowsCpuRaw ? (Array.isArray(windowsCpuRaw) ? windowsCpuRaw[0] ?? null : windowsCpuRaw) : null const winMemTotal = windowsMemoryModules.reduce((acc, module) => acc + (parseBytesLike(module?.Capacity) ?? 0), 0) const normalizedWindowsGpus = windowsVideoControllers .map((controller) => normalizeGpuSource(controller)) .filter((gpu): gpu is GpuAdapter => Boolean(gpu)) const combinedGpus = uniqueBy( [ ...(hardwarePrimaryGpu ? [hardwarePrimaryGpu] : []), ...normalizedHardwareGpus, ...normalizedWindowsGpus, ], (gpu) => `${gpu.name ?? ""}|${gpu.vendor ?? ""}|${gpu.driver ?? ""}` ) const displayGpus = [...combinedGpus].sort( (a, b) => (b.memoryBytes ?? 0) - (a.memoryBytes ?? 0) ) const primaryGpu = hardwarePrimaryGpu ?? displayGpus[0] ?? null const windowsPrimaryGpu = [...normalizedWindowsGpus].sort( (a, b) => (b.memoryBytes ?? 0) - (a.memoryBytes ?? 0) )[0] ?? null const windowsCpuDetails = windowsCpuRaw ? Array.isArray(windowsCpuRaw) ? windowsCpuRaw : [windowsCpuRaw] : [] const windowsServices = windowsExt?.services ?? [] const windowsSoftware = windowsExt?.software ?? [] const winDiskStats = windowsDiskEntries.length > 0 ? { count: windowsDiskEntries.length, total: windowsDiskEntries.reduce((acc, disk) => acc + (parseBytesLike(disk?.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 const copyEmail = async () => { if (!machine?.authEmail) return try { await navigator.clipboard.writeText(machine.authEmail) toast.success("E-mail da máquina copiado.") } catch { toast.error("Não foi possível copiar o e-mail da máquina.") } } // collaborator (from machine assignment or metadata) type Collaborator = { email?: string; name?: string; role?: string } const collaborator: Collaborator | null = useMemo(() => { if (machine?.assignedUserEmail) { return { email: machine.assignedUserEmail ?? undefined, name: machine.assignedUserName ?? undefined, role: machine.persona ?? machine.assignedUserRole ?? undefined, } } if (!metadata || typeof metadata !== "object") return null const inv = metadata as Record const c = inv["collaborator"] if (c && typeof c === "object") { const base = c as Record return { email: typeof base.email === "string" ? base.email : undefined, name: typeof base.name === "string" ? base.name : undefined, role: typeof base.role === "string" ? (base.role as string) : undefined, } } return null }, [machine?.assignedUserEmail, machine?.assignedUserName, machine?.persona, machine?.assignedUserRole, metadata]) const personaLabel = collaborator?.role === "manager" ? "Gestor" : "Colaborador" const companyName = (() => { if (!companies || !machine?.companySlug) return machine?.companySlug ?? null const found = companies.find((c) => c.slug === machine.companySlug) return found?.name ?? machine.companySlug })() const [renaming, setRenaming] = useState(false) const [newName, setNewName] = useState(machine?.hostname ?? "") const [openDialog, setOpenDialog] = useState(false) const [dialogQuery, setDialogQuery] = useState("") const [deleteDialog, setDeleteDialog] = useState(false) const [deleting, setDeleting] = useState(false) const [accessDialog, setAccessDialog] = useState(false) const [accessEmail, setAccessEmail] = useState(collaborator?.email ?? "") const [accessName, setAccessName] = useState(collaborator?.name ?? "") const [accessRole, setAccessRole] = useState<"collaborator" | "manager">( (machine?.persona === "manager" || collaborator?.role === "manager") ? "manager" : "collaborator" ) const [savingAccess, setSavingAccess] = useState(false) const jsonText = useMemo(() => { const payload = { id: machine?.id, hostname: machine?.hostname, status: machine?.status, lastHeartbeatAt: machine?.lastHeartbeatAt, metrics, inventory: metadata, postureAlerts: machine?.postureAlerts ?? null, lastPostureAt: machine?.lastPostureAt ?? null, } return JSON.stringify(payload, null, 2) }, [machine, metrics, metadata]) const filteredJsonHtml = useMemo(() => { if (!dialogQuery.trim()) return jsonText const q = dialogQuery.trim().toLowerCase() // highlight simples return jsonText.replace(new RegExp(q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"), (m) => `__HIGHLIGHT__${m}__END__`) }, [jsonText, dialogQuery]) // removed copy/export inventory JSON buttons as requested useEffect(() => { setAccessEmail(collaborator?.email ?? "") setAccessName(collaborator?.name ?? "") setAccessRole((machine?.persona === "manager" || collaborator?.role === "manager") ? "manager" : "collaborator") }, [machine?.id, machine?.persona, collaborator?.email, collaborator?.name, collaborator?.role]) const handleSaveAccess = async () => { if (!machine) return if (!accessEmail.trim()) { toast.error("Informe o e-mail do colaborador ou gestor.") return } setSavingAccess(true) try { const response = await fetch("/api/admin/machines/access", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ machineId: machine.id, persona: accessRole, email: accessEmail.trim(), name: accessName.trim() || undefined, }), }) if (!response.ok) { throw new Error(await response.text()) } toast.success("Perfil de acesso atualizado.") setAccessDialog(false) } catch (error) { console.error(error) toast.error("Falha ao atualizar acesso da máquina.") } finally { setSavingAccess(false) } } return ( Detalhes Resumo da máquina selecionada. {!machine ? (

Selecione uma máquina para visualizar detalhes.

) : (

{machine.hostname}

{machine.authEmail ?? "E-mail não definido"}

{machine.companySlug ? (

Empresa vinculada: {companyName ?? machine.companySlug}

) : null}
{/* ping integrado na badge de status */}
{machine.osName ?? "SO desconhecido"} {machine.osVersion ?? ""} {machine.architecture?.toUpperCase() ?? "Arquitetura indefinida"} {windowsExt?.osInfo ? ( Build: {String(windowsExt.osInfo?.CurrentBuildNumber ?? windowsExt.osInfo?.CurrentBuild ?? "—")} ) : null} {windowsExt?.osInfo ? ( Ativado: {windowsExt.osInfo?.IsActivated === true ? "Sim" : "Não"} ) : null} {primaryGpu?.name ? ( GPU: {primaryGpu.name} {typeof primaryGpu.memoryBytes === "number" ? ` · ${formatBytes(primaryGpu.memoryBytes)}` : ""} ) : null} {companyName ? ( Empresa: {companyName} ) : null} {collaborator?.email ? ( {personaLabel}: {collaborator?.name ? `${collaborator.name} · ` : ""}{collaborator.email} ) : null}
{machine.authEmail ? ( ) : null} {machine.registeredBy ? ( Registrada via {machine.registeredBy} ) : null}
{/* Renomear máquina */} Renomear máquina
setNewName(e.target.value)} placeholder="Novo hostname" />
Ajustar acesso da máquina
setAccessEmail(e.target.value)} placeholder="colaborador@empresa.com" />
setAccessName(e.target.value)} placeholder="Nome completo" />

Sincronização

Último heartbeat {formatRelativeTime(lastHeartbeatDate)}
Criada em {formatDate(new Date(machine.createdAt))}
Atualizada em {formatDate(new Date(machine.updatedAt))}
Token expira {tokenExpiry ? formatRelativeTime(tokenExpiry) : "—"}
Token usado por último {tokenLastUsed ? formatRelativeTime(tokenLastUsed) : "—"}
Uso do token {machine.token?.usageCount ?? 0} trocas
{metrics && typeof metrics === "object" ? (

Métricas recentes

) : null} {hardware || network || (labels && labels.length > 0) ? (

Inventário

Dados sincronizados via agente ou Fleet.

{hardware ? (

Hardware

{displayGpus.length > 0 ? (

GPUs

    {displayGpus.slice(0, 3).map((gpu, idx) => { const { name, memoryBytes, driver, vendor } = gpu return (
  • {name ?? "Adaptador de vídeo"} {memoryBytes ? {formatBytes(memoryBytes)} : null} {vendor ? · {vendor} : null} {driver ? · Driver {driver} : null}
  • ) })} {displayGpus.length > 3 ? (
  • +{displayGpus.length - 3} adaptadores adicionais
  • ) : null}
) : null}
) : null} {networkInterfaces ? (

Rede (interfaces)

Interface MAC IP {networkInterfaces.map((iface, idx) => ( {iface?.name ?? "—"} {iface?.mac ?? "—"} {iface?.ip ?? "—"} ))}
) : networkSummary ? (

Rede

) : null} {labels && labels.length > 0 ? (

Labels

{labels.slice(0, 12).map((label, index) => ( {label.name ?? `Label ${index + 1}`} ))} {labels.length > 12 ? ( +{labels.length - 12} outras ) : null}
) : null}
) : null} {/* Discos (agente) */} {disks.length > 0 ? (

Discos e partições

Nome Mount FS Capacidade Livre {disks.map((d, idx) => ( {d.name ?? "—"} {d.mountPoint ?? "—"} {d.fs ?? "—"} {formatBytes(Number(d.totalBytes))} {formatBytes(Number(d.availableBytes))} ))}
) : null} {/* Inventário estendido por SO */} {extended ? (

Inventário estendido

Dados ricos coletados pelo agente, variam por sistema operacional.

{/* Linux */} {linuxExt ? (
{linuxLsblk.length > 0 ? (

Montagens (lsblk)

Nome Ponto de montagem FS Tamanho {linuxLsblk.slice(0, 18).map((entry, idx) => { const name = entry.name ?? "—" const mp = entry.mountPoint ?? entry.mountpoint ?? "—" const fs = entry.fs ?? entry.fstype ?? "—" const sizeRaw = typeof entry.sizeBytes === "number" ? entry.sizeBytes : entry.size return ( {name} {mp || "—"} {fs || "—"} {typeof sizeRaw === "number" ? formatBytes(sizeRaw) : "—"} ) })}
) : null} {linuxSmartEntries.length > 0 ? (

SMART

{linuxSmartEntries.map((smartEntry, idx) => { const ok = smartEntry.smart_status?.passed !== false const model = smartEntry.model_name ?? smartEntry.model_family ?? "Disco" const serial = smartEntry.serial_number ?? smartEntry.device?.name ?? "—" return (
{model} ({serial}) {ok ? "OK" : "ALERTA"}
) })}
) : null} {linuxExt.lspci ? (

PCI

{linuxExt.lspci}
) : null} {linuxExt.lsusb ? (

USB

{linuxExt.lsusb}
) : null}
) : null} {/* Windows */} {windowsExt ? (
{/* Cards resumidos: CPU / RAM / GPU / Discos */}

CPU

{winCpu?.Name ?? "—"}

Memória total

{formatBytes(winMemTotal)}

GPU

{(windowsPrimaryGpu ?? primaryGpu)?.name ?? "—"}

Discos

{winDiskStats.count} · {formatBytes(winDiskStats.total)}

{windowsCpuDetails.length > 0 ? (

CPU

{windowsCpuDetails.slice(0, 1).map((cpuRecord, idx) => (
))}
) : null} {windowsExt.baseboard || windowsExt.bios ? (

Placa-mãe / BIOS

{(() => { const b = Array.isArray(windowsExt.baseboard) ? windowsExt.baseboard[0] : windowsExt.baseboard const bios = Array.isArray(windowsExt.bios) ? windowsExt.bios[0] : windowsExt.bios return ( <> ) })()}
) : null} {windowsServices.length > 0 ? (

Serviços

Nome Exibição Status {windowsServices.slice(0, 10).map((service, index) => { const record = toRecord(service) ?? {} const name = readString(record, "Name", "name") ?? "—" const displayName = readString(record, "DisplayName", "displayName") ?? "—" const status = readString(record, "Status", "status") ?? "—" return ( {name} {displayName} {status} ) })}
) : null} {windowsSoftware.length > 0 ? (

Softwares (amostra)

    {windowsSoftware.slice(0, 8).map((softwareItem, index) => { const record = toRecord(softwareItem) ?? {} const name = readString(record, "DisplayName", "name") ?? "—" const version = readString(record, "DisplayVersion", "version") const publisher = readString(record, "Publisher") return (
  • {name} {version ? {version} : null} {publisher ? · {publisher} : null}
  • ) })}
) : null} {windowsMemoryModules.length > 0 ? (

Módulos de memória

Banco Capacidade Fabricante PartNumber Clock {windowsMemoryModules.map((module, idx) => { const record = toRecord(module) ?? {} const bank = readString(record, "BankLabel", "bankLabel") ?? "—" const capacityBytes = parseBytesLike(record["Capacity"]) ?? parseBytesLike(record["capacity"]) ?? 0 const manufacturer = readString(record, "Manufacturer", "manufacturer") ?? "—" const partNumber = readString(record, "PartNumber", "partNumber") ?? "—" const clockValue = readNumber( record, "ConfiguredClockSpeed", "configuredClockSpeed", "Speed", "speed" ) const clockLabel = typeof clockValue === "number" && Number.isFinite(clockValue) ? `${clockValue} MHz` : "—" return ( {bank} {capacityBytes > 0 ? formatBytes(capacityBytes) : "—"} {manufacturer} {partNumber} {clockLabel} ) })}
) : null} {windowsVideoControllers.length > 0 ? (

Adaptadores de vídeo

    {windowsVideoControllers.map((controller, idx) => { const record = toRecord(controller) ?? {} const normalized = normalizeGpuSource(record) const name = normalized?.name ?? readString(record, "Name", "name") ?? "—" const ram = normalized?.memoryBytes ?? parseBytesLike(record["AdapterRAM"]) ?? undefined const driver = normalized?.driver ?? readString(record, "DriverVersion", "driverVersion") const vendor = normalized?.vendor ?? readString(record, "AdapterCompatibility") return (
  • {name} {typeof ram === "number" && ram > 0 ? {formatBytes(ram)} : null} {vendor ? · {vendor} : null} {driver ? · Driver {driver} : null}
  • ) })}
) : null} {windowsDiskEntries.length > 0 ? (

Discos físicos

Modelo Tamanho Interface Tipo Serial {windowsDiskEntries.map((disk, idx) => { const record = toRecord(disk) ?? {} const model = readString(record, "Model", "model") const serial = readString(record, "SerialNumber", "serialNumber") const size = parseBytesLike(record["Size"]) const iface = readString(record, "InterfaceType", "interfaceType") const media = readString(record, "MediaType", "mediaType") return ( {model ?? serial ?? "—"} {typeof size === "number" && size > 0 ? formatBytes(size) : "—"} {iface ?? "—"} {media ?? "—"} {serial ?? "—"} ) })}
) : null} {windowsExt.defender ? (

Defender

{readBool(windowsExt.defender, "AntivirusEnabled") === true ? ( Antivírus: Ativo ) : ( Antivírus: Inativo )} {readBool(windowsExt.defender, "RealTimeProtectionEnabled") === true ? ( Proteção em tempo real: Ativa ) : ( Proteção em tempo real: Inativa )}
) : null}
) : null} {/* macOS */} {macosExt ? (
{Array.isArray(macosExt.packages) && macosExt.packages.length > 0 ? (

Pacotes

{macosExt.packages.slice(0, 8).join(", ")}

) : null} {macosExt.launchctl ? (

Launchctl

{macosExt.launchctl}
) : null}
) : null}
) : null} {/* Postura/Alertas */} {Array.isArray(machine?.postureAlerts) && machine?.postureAlerts?.length ? (

Alertas de postura

{machine?.postureAlerts?.map((a: { kind?: string; message?: string; severity?: string }, i: number) => (
{a?.message ?? a?.kind ?? "Alerta"} {String(a?.kind ?? "ALERTA")}
))}

Última avaliação: {machine?.lastPostureAt ? formatRelativeTime(new Date(machine.lastPostureAt)) : "—"}

) : null}
{Array.isArray(software) && software.length > 0 ? ( ) : null} {Array.isArray(metadata?.services) && metadata.services.length > 0 ? ( ) : null}
{fleet ? (
ID Fleet {fleet.id ?? "—"}
Team ID {fleet.teamId ?? "—"}
Detalhes atualizados {fleet.detailUpdatedAt ? formatDate(new Date(String(fleet.detailUpdatedAt))) : "—"}
Versão osquery {fleet.osqueryVersion ?? "—"}
) : null} {software && software.length > 0 ? (

Softwares detectados

Nome Versão Fonte {software.slice(0, 6).map((item, index) => ( {item.name ?? "—"} {item.version ?? "—"} {item.source ?? "—"} ))}
{software.length > 6 ? (

+{software.length - 6} softwares adicionais sincronizados via Fleet.

) : null}
) : null} {machine ? (

Zona perigosa

Excluir a máquina revoga o token atual e remove os dados de inventário sincronizados.

) : null}
Inventário completo — {machine.hostname}
setDialogQuery(e.target.value)} />
')
                      .replaceAll("__END__", '')
                    }} />
                  
{ if (!open) setDeleting(false); setDeleteDialog(open) }}> Excluir máquina

Tem certeza que deseja excluir {machine?.hostname}? Esta ação não pode ser desfeita.

Os tokens ativos serão revogados e o inventário deixará de aparecer no painel.

)}
) } function MachinesGrid({ machines, companyNameBySlug }: { machines: MachinesQueryItem[]; companyNameBySlug: Map }) { if (!machines || machines.length === 0) return return (
{machines.map((m) => ( ))}
) } function MachineCard({ machine, companyName }: { machine: MachinesQueryItem; companyName?: string | null }) { const { className } = getStatusVariant(machine.status) const lastHeartbeat = machine.lastHeartbeatAt ? new Date(machine.lastHeartbeatAt) : null type AgentMetrics = { memoryUsedBytes?: number memoryTotalBytes?: number memoryUsedPercent?: number cpuUsagePercent?: number } const mm = (machine.metrics ?? null) as unknown as AgentMetrics | null const memUsed = mm?.memoryUsedBytes ?? NaN const memTotal = mm?.memoryTotalBytes ?? NaN const memPct = mm?.memoryUsedPercent ?? (Number.isFinite(memUsed) && Number.isFinite(memTotal) ? (Number(memUsed) / Number(memTotal)) * 100 : NaN) const cpuPct = mm?.cpuUsagePercent ?? NaN const collaborator = (() => { if (machine.assignedUserEmail) { return { email: machine.assignedUserEmail ?? undefined, name: machine.assignedUserName ?? undefined, role: machine.persona ?? machine.assignedUserRole ?? undefined, } } const inv = machine.inventory as unknown if (!inv || typeof inv !== "object") return null const raw = (inv as Record).collaborator if (!raw || typeof raw !== "object") return null const obj = raw as Record const email = typeof obj.email === "string" ? obj.email : undefined if (!email) return null return { email, name: typeof obj.name === "string" ? obj.name : undefined, role: typeof obj.role === "string" ? (obj.role as string) : undefined, } })() const persona = collaborator?.role === "manager" ? "Gestor" : "Colaborador" const companyLabel = companyName ?? machine.companySlug ?? null return (
{String(machine.status ?? "").toLowerCase() === "online" ? ( ) : null}
{machine.hostname} {machine.authEmail ?? "—"}
{machine.osName ?? "SO"} {machine.osVersion ?? ""} {machine.architecture ? ( {machine.architecture.toUpperCase()} ) : null} {companyLabel ? ( {companyLabel} ) : null}
{collaborator?.email ? (

{persona}: {collaborator.name ? `${collaborator.name} · ` : ""} {collaborator.email}

) : null}
{formatPercent(cpuPct)}
{Number.isFinite(memUsed) && Number.isFinite(memTotal) ? `${formatBytes(memUsed)} / ${formatBytes(memTotal)}` : formatPercent(memPct)}
{Array.isArray(machine.inventory?.disks) ? `${machine.inventory?.disks?.length ?? 0} discos` : "—"} {lastHeartbeat ? formatRelativeTime(lastHeartbeat) : "sem heartbeat"}
) } function DetailLine({ label, value, classNameValue }: DetailLineProps) { if (value === null || value === undefined) return null if (typeof value === "string" && (value.trim() === "" || value === "undefined" || value === "null")) { return null } return (
{label} {value}
) } function MetricsGrid({ metrics }: { metrics: MachineMetrics }) { const data = (metrics ?? {}) as Record // Compat: aceitar chaves do agente desktop (cpuUsagePercent, memoryUsedBytes, memoryTotalBytes) const cpu = (() => { const v = Number( data.cpuUsage ?? data.cpu ?? data.cpu_percent ?? data.cpuUsagePercent ?? NaN ) return v })() const memory = (() => { // valor absoluto em bytes, se disponível const memBytes = Number( data.memoryBytes ?? data.memory ?? data.memory_used ?? data.memoryUsedBytes ?? NaN ) if (Number.isFinite(memBytes)) return memBytes // tentar derivar a partir de percentuais do agente const usedPct = Number(data.memoryUsedPercent ?? NaN) const totalBytes = Number(data.memoryTotalBytes ?? NaN) if (Number.isFinite(usedPct) && Number.isFinite(totalBytes)) { return Math.max(0, Math.min(1, usedPct > 1 ? usedPct / 100 : usedPct)) * totalBytes } return NaN })() const disk = Number(data.diskUsage ?? data.disk ?? NaN) const gpuUsage = Number( data.gpuUsage ?? data.gpu ?? data.gpuUsagePercent ?? data.gpu_percent ?? NaN ) const cards: Array<{ label: string; value: string }> = [ { label: "CPU", value: formatPercent(cpu) }, { label: "Memória", value: formatBytes(memory) }, { label: "Disco", value: Number.isNaN(disk) ? "—" : formatPercent(disk) }, ] if (!Number.isNaN(gpuUsage)) { cards.push({ label: "GPU", value: formatPercent(gpuUsage) }) } return (
{cards.map((card) => (

{card.label}

{card.value}

))}
) } function exportCsv(items: Array>, filename: string) { if (!Array.isArray(items) || items.length === 0) return const headersSet = new Set() items.forEach((it) => Object.keys(it ?? {}).forEach((k) => headersSet.add(k))) const headers = Array.from(headersSet) const csv = [headers.join(",")] for (const it of items) { const row = headers .map((h) => { const v = (it as Record)[h] if (v === undefined || v === null) return "" const s = typeof v === "string" ? v : JSON.stringify(v) return `"${s.replace(/"/g, '""')}"` }) .join(",") csv.push(row) } const blob = new Blob([csv.join("\n")], { type: "text/csv;charset=utf-8;" }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = filename document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(url) }