Redesenho da UI de dispositivos e correcao de VRAM

- Reorganiza layout da tela de dispositivos admin
- Renomeia secao "Controles do dispositivo" para "Atalhos"
- Adiciona botao de Tickets com badge de quantidade
- Simplifica textos de botoes (Acesso, Resetar)
- Remove email da maquina do cabecalho
- Move empresa e status para mesma linha
- Remove chip de Build do resumo
- Corrige deteccao de VRAM para GPUs >4GB usando nvidia-smi
- Adiciona prefixo "VRAM" na exibicao de memoria da GPU
- Documenta sincronizacao RustDesk

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-06 17:01:40 -03:00
parent c5150fee8f
commit 23e7cf58ae
11 changed files with 863 additions and 441 deletions

View file

@ -41,6 +41,7 @@ import {
Usb,
Loader2,
X,
TicketCheck,
} from "lucide-react"
import { api } from "@/convex/_generated/api"
@ -3154,14 +3155,6 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
icon: <Cpu className="size-4 text-neutral-500" />,
})
}
if (windowsBuildLabel) {
chips.push({
key: "build",
label: "Build",
value: windowsBuildLabel,
icon: <ServerCog className="size-4 text-neutral-500" />,
})
}
if (windowsActivationStatus !== null && windowsActivationStatus !== undefined) {
chips.push({
key: "activation",
@ -3222,7 +3215,6 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
device?.osVersion,
device?.architecture,
windowsVersionLabel,
windowsBuildLabel,
windowsActivationStatus,
primaryLinkedUser?.email,
primaryLinkedUser?.name,
@ -3867,25 +3859,49 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
return (
<Card className="border-slate-200">
<CardHeader className="gap-1">
<CardTitle>Detalhes</CardTitle>
<CardDescription>Resumo do dispositivo selecionado</CardDescription>
{device ? (
<CardAction>
<div className="flex flex-col items-end gap-2 text-xs sm:text-sm">
{companyName ? (
<div className="rounded-lg border border-slate-200 bg-white px-3 py-1 font-semibold text-neutral-600 shadow-sm">
{companyName}
</div>
<>
<CardTitle className="flex items-center gap-2">
<span className="break-words text-2xl font-semibold text-neutral-900">
{device.displayName ?? device.hostname ?? "Dispositivo"}
</span>
{isManualMobile ? (
<span className="rounded-full border border-slate-200 bg-slate-50 px-2 py-0.5 text-xs font-semibold uppercase tracking-wide text-neutral-600">
Identificação interna
</span>
) : null}
{!isDeactivated ? <DeviceStatusBadge status={effectiveStatus} /> : null}
{!isActiveLocal ? (
<Badge variant="outline" className="h-7 border-rose-200 bg-rose-50 px-3 font-semibold uppercase text-rose-700">
Dispositivo desativada
</Badge>
) : null}
</div>
</CardAction>
) : null}
<Button
size="icon"
variant="ghost"
className="size-7"
onClick={() => {
setNewName(device.displayName ?? device.hostname ?? "")
setRenaming(true)
}}
>
<Pencil className="size-4" />
<span className="sr-only">Renomear dispositivo</span>
</Button>
</CardTitle>
<CardAction>
<div className="flex flex-wrap items-center justify-end gap-2 text-xs sm:text-sm">
{companyName ? (
<div className="max-w-[200px] truncate rounded-lg border border-slate-200 bg-white px-3 py-1 font-semibold text-neutral-600 shadow-sm" title={companyName}>
{companyName}
</div>
) : null}
{!isDeactivated ? <DeviceStatusBadge status={effectiveStatus} /> : null}
{!isActiveLocal ? (
<Badge variant="outline" className="h-7 border-rose-200 bg-rose-50 px-3 font-semibold uppercase text-rose-700">
Dispositivo desativado
</Badge>
) : null}
</div>
</CardAction>
</>
) : (
<CardTitle>Detalhes</CardTitle>
)}
</CardHeader>
<CardContent className="space-y-6">
{!device ? (
@ -3893,47 +3909,6 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
) : (
<div className="space-y-6">
<section className="space-y-3">
<div className="flex flex-wrap items-start gap-2">
<div className="space-y-1">
<div className="flex items-center gap-2">
<h1 className="break-words text-2xl font-semibold text-neutral-900">
{device.displayName ?? device.hostname ?? "Dispositivo"}
</h1>
{isManualMobile ? (
<span className="rounded-full border border-slate-200 bg-slate-50 px-2 py-0.5 text-xs font-semibold uppercase tracking-wide text-neutral-600">
Identificação interna
</span>
) : null}
<Button
size="icon"
variant="ghost"
className="size-7"
onClick={() => {
setNewName(device.displayName ?? device.hostname ?? "")
setRenaming(true)
}}
>
<Pencil className="size-4" />
<span className="sr-only">Renomear dispositivo</span>
</Button>
</div>
<p className="flex items-center gap-2 text-xs text-muted-foreground">
<span>{device.authEmail ?? "E-mail não definido"}</span>
{device.authEmail ? (
<button
type="button"
onClick={copyEmail}
className="inline-flex items-center rounded-md p-1 text-neutral-500 transition hover:bg-[#00d6eb]/15 hover:text-[#0a4760] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#00d6eb]/40 focus-visible:ring-offset-2"
title="Copiar e-mail do dispositivo"
aria-label="Copiar e-mail do dispositivo"
>
<ClipboardCopy className="size-3.5" />
</button>
) : null}
</p>
</div>
</div>
{/* ping integrado na badge de status */}
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
{summaryChips.map((chip) => (
<InfoChip
@ -3975,17 +3950,32 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
<div className="rounded-2xl border border-slate-200 bg-white/80 px-4 py-4 shadow-sm">
<div className="flex flex-wrap items-center justify-between gap-3">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500">Controles do dispositivo</p>
{device.registeredBy ? (
<span className="text-xs font-medium text-slate-500">
Registrada via <span className="text-slate-800">{device.registeredBy}</span>
</span>
) : null}
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500">Atalhos</p>
<span className="text-xs font-medium text-slate-500">
{device.registeredBy === "desktop-agent" ? (
<span className="text-slate-800">Agente na máquina</span>
) : (
<span className="text-slate-800">Manual</span>
)}
</span>
</div>
<div className="mt-3 flex flex-wrap gap-2">
{deviceTicketsHref ? (
<Button size="sm" variant="outline" className="gap-2 border-dashed" asChild>
<Link href={deviceTicketsHref}>
<TicketCheck className="size-4" />
Tickets
{totalOpenTickets > 0 ? (
<Badge variant="secondary" className="ml-1 rounded-full px-2 py-0 text-[10px] font-semibold">
{totalOpenTickets}
</Badge>
) : null}
</Link>
</Button>
) : null}
<Button size="sm" variant="outline" className="gap-2 border-dashed" onClick={() => { setAccessDialog(true) }}>
<ShieldCheck className="size-4" />
Ajustar acesso
Acesso
</Button>
{!isManualMobile ? (
<>
@ -3997,7 +3987,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
disabled={isResettingAgent}
>
<RefreshCcw className={cn("size-4", isResettingAgent && "animate-spin")} />
{isResettingAgent ? "Resetando agente..." : "Resetar agente"}
{isResettingAgent ? "Resetando..." : "Resetar"}
</Button>
<Button
size="sm"
@ -4029,7 +4019,195 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
</div>
</div>
{/* Campos personalizados (posicionado logo após métricas) */}
{/* Acesso remoto */}
<div className="space-y-3 border-t border-slate-100 pt-5">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<h4 className="text-sm font-semibold">Acesso remoto</h4>
{hasRemoteAccess ? (
<Badge variant="outline" className="rounded-full px-2.5 py-0.5 text-[11px] font-semibold">
{remoteAccessEntries.length === 1 ? "1 acesso" : `${remoteAccessEntries.length} acessos`}
</Badge>
) : null}
</div>
{canManageRemoteAccess ? (
<Button
size="sm"
variant="outline"
className="gap-2 border-dashed"
onClick={() => {
setEditingRemoteAccessClientId(null)
setRemoteAccessDialog(true)
}}
>
<Key className="size-4" />
{hasRemoteAccess ? "Adicionar acesso" : "Cadastrar acesso"}
</Button>
) : null}
</div>
{hasRemoteAccess ? (
<div className="space-y-3">
{remoteAccessEntries.map((entry) => {
const lastVerifiedDate =
entry.lastVerifiedAt && Number.isFinite(entry.lastVerifiedAt)
? new Date(entry.lastVerifiedAt)
: null
const isRustDesk = isRustDeskAccess(entry)
const secretVisible = Boolean(visibleRemoteSecrets[entry.clientId])
return (
<div key={entry.clientId} className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-700">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-semibold uppercase tracking-wide text-slate-500">ID</span>
{entry.identifier ? (
<code className="rounded-md border border-slate-200 bg-white px-2.5 py-1 font-mono text-sm font-semibold text-neutral-800">
{entry.identifier}
</code>
) : null}
{entry.identifier ? (
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 transition-colors hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteIdentifier(entry.identifier)}
title="Copiar ID"
aria-label="Copiar ID"
>
<ClipboardCopy className="size-3.5" />
</Button>
) : null}
</div>
{entry.username || entry.password ? (
<div className="flex flex-col gap-1">
{entry.username ? (
<div className="inline-flex flex-wrap items-center gap-2">
<span className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Usuário</span>
<code className="rounded-md border border-slate-200 bg-white px-2 py-0.5 font-mono text-xs text-slate-700">
{entry.username}
</code>
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteCredential(entry.username, "Usuário do acesso remoto")}
title="Copiar usuário"
aria-label="Copiar usuário"
>
<ClipboardCopy className="size-3.5" />
</Button>
</div>
) : null}
{entry.password ? (
<div className="inline-flex flex-wrap items-center gap-2">
<span className="text-xs font-semibold uppercase tracking-wide text-slate-500">Senha</span>
<code className="rounded-md border border-slate-200 bg-white px-2.5 py-1 font-mono text-sm text-slate-700">
{secretVisible ? entry.password : "••••••••"}
</code>
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => toggleRemoteSecret(entry.clientId)}
title={secretVisible ? "Ocultar senha" : "Mostrar senha"}
aria-label={secretVisible ? "Ocultar senha" : "Mostrar senha"}
>
{secretVisible ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
</Button>
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteCredential(entry.password, "Senha do acesso remoto")}
title="Copiar senha"
aria-label="Copiar senha"
>
<ClipboardCopy className="size-3.5" />
</Button>
</div>
) : null}
</div>
) : null}
{entry.url && !isRustDesk ? (
<a
href={entry.url}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 text-slate-600 underline-offset-4 hover:text-slate-900 hover:underline"
>
Abrir console remoto
</a>
) : null}
</div>
<div className="flex flex-col items-end gap-1 text-right">
{entry.notes ? (
<p className="whitespace-pre-wrap text-xs text-slate-600">{entry.notes}</p>
) : null}
{lastVerifiedDate ? (
<p className="text-xs text-slate-500">
Atualizado {formatRelativeTime(lastVerifiedDate)}{" "}
<span className="text-slate-400">({formatAbsoluteDateTime(lastVerifiedDate)})</span>
</p>
) : null}
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-slate-200 pt-3 mt-3">
<div>
{isRustDesk && (entry.identifier || entry.password) ? (
<Button
variant="outline"
size="sm"
className="inline-flex items-center gap-2 border-slate-300 bg-white text-slate-800 shadow-sm transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-900 focus-visible:border-slate-400 focus-visible:ring-slate-200"
onClick={() => handleRustDeskConnect(entry)}
>
<MonitorSmartphone className="size-4 text-[#4b5563]" /> Conectar via RustDesk
</Button>
) : null}
</div>
<div className="flex items-center gap-2">
{entry.provider ? (
<Button variant="outline" size="sm" className="gap-2 border-slate-300 cursor-default hover:bg-white">
{entry.provider}
</Button>
) : null}
{canManageRemoteAccess ? (
<>
<Button
size="sm"
variant="outline"
className="gap-2 border-slate-300"
onClick={() => {
setEditingRemoteAccessClientId(entry.clientId)
setRemoteAccessDialog(true)
}}
>
<Pencil className="size-3.5" /> Editar
</Button>
<Button
size="sm"
variant="ghost"
className="gap-2 text-rose-600 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-700"
onClick={() => void handleRemoveRemoteAccess(entry)}
disabled={remoteAccessSaving}
>
<ShieldOff className="size-3.5" /> Remover
</Button>
</>
) : null}
</div>
</div>
</div>
)
})}
</div>
) : (
<div className="rounded-lg border border-dashed border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-600">
Nenhum identificador de acesso remoto cadastrado. Registre o ID do TeamViewer, AnyDesk ou outra ferramenta para agilizar o suporte.
</div>
)}
</div>
{/* Campos personalizados */}
<div className="space-y-3 border-t border-slate-100 pt-5">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex flex-col gap-1">
@ -4068,248 +4246,6 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
</div>
) : null}
</div>
<div className="rounded-2xl border border-[color:var(--accent)] bg-[color:var(--accent)]/80 px-4 py-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<h4 className="text-sm font-semibold text-accent-foreground">Tickets abertos por este dispositivo</h4>
{totalOpenTickets === 0 ? (
<p className="text-xs text-[color:var(--accent-foreground)]/80">
Nenhum chamado em aberto registrado diretamente por este dispositivo.
</p>
) : hasAdditionalOpenTickets ? (
<p className="text-[10px] font-semibold uppercase tracking-[0.14em] text-[color:var(--accent-foreground)]/70">
Mostrando últimos {Math.min(displayLimit, totalOpenTickets)} de {totalOpenTickets} chamados em aberto
</p>
) : (
<p className="text-xs text-[color:var(--accent-foreground)]/80">
Últimos chamados vinculados a este dispositivo.
</p>
)}
</div>
<div className="flex items-center">
<div className="flex h-10 min-w-[56px] items-center justify-center rounded-xl border border-[color:var(--accent)] bg-white px-3 text-[color:var(--accent-foreground)] shadow-sm">
<span className="text-lg font-semibold leading-none tabular-nums">{totalOpenTickets}</span>
</div>
</div>
</div>
{totalOpenTickets > 0 ? (
<div className="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
{displayedDeviceTickets.map((ticket) => {
const priorityMeta = getTicketPriorityMeta(ticket.priority)
return (
<Link
key={ticket.id}
href={`/tickets/${ticket.id}`}
className="group flex h-full flex-col justify-between gap-3 rounded-xl border border-[color:var(--accent)] bg-white p-3 text-sm shadow-sm transition hover:-translate-y-0.5 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
>
<div className="space-y-1">
<p className="line-clamp-2 font-medium text-neutral-900">
#{ticket.reference} · {ticket.subject}
</p>
<p className="text-xs text-neutral-500">
Atualizado {formatRelativeTime(new Date(ticket.updatedAt))}
</p>
</div>
<div className="flex flex-wrap items-center justify-between gap-2">
<Badge className={cn("rounded-full px-3 py-1 text-xs font-semibold", priorityMeta.badgeClass)}>
{priorityMeta.label}
</Badge>
<TicketStatusBadge status={ticket.status} className="h-7 px-3 text-xs font-semibold" />
</div>
</Link>
)
})}
</div>
) : null}
{deviceTicketsHref ? (
<div className="mt-4">
<Link
href={deviceTicketsHref}
className="text-xs font-semibold text-[color:var(--accent-foreground)] underline-offset-4 transition hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-foreground)] focus-visible:ring-offset-2"
>
Ver todos
</Link>
</div>
) : null}
</div>
<div className="space-y-3 border-t border-slate-100 pt-5">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<h4 className="text-sm font-semibold">Acesso remoto</h4>
{hasRemoteAccess ? (
<Badge variant="outline" className="border-slate-200 bg-slate-100 text-[11px] font-semibold text-slate-700">
{remoteAccessEntries.length === 1
? remoteAccessEntries[0].provider ?? "Configuração única"
: `${remoteAccessEntries.length} acessos`}
</Badge>
) : null}
</div>
{canManageRemoteAccess ? (
<Button
size="sm"
variant="outline"
className="gap-2 border-dashed"
onClick={() => {
setEditingRemoteAccessClientId(null)
setRemoteAccessDialog(true)
}}
>
<Key className="size-4" />
{hasRemoteAccess ? "Adicionar acesso" : "Cadastrar acesso"}
</Button>
) : null}
</div>
{hasRemoteAccess ? (
<div className="space-y-3">
{remoteAccessEntries.map((entry) => {
const lastVerifiedDate =
entry.lastVerifiedAt && Number.isFinite(entry.lastVerifiedAt)
? new Date(entry.lastVerifiedAt)
: null
const isRustDesk = isRustDeskAccess(entry)
const secretVisible = Boolean(visibleRemoteSecrets[entry.clientId])
return (
<div key={entry.clientId} className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-700">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
{entry.provider ? (
<Badge variant="outline" className="border-slate-200 bg-white text-[11px] font-semibold text-slate-700">
{entry.provider}
</Badge>
) : null}
{entry.identifier ? (
<span className="font-semibold text-neutral-800">{entry.identifier}</span>
) : null}
{entry.identifier ? (
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 transition-colors hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteIdentifier(entry.identifier)}
title="Copiar ID"
aria-label="Copiar ID"
>
<ClipboardCopy className="size-3.5" />
</Button>
) : null}
</div>
{entry.username || entry.password ? (
<div className="flex flex-col gap-1">
{entry.username ? (
<div className="inline-flex flex-wrap items-center gap-2">
<span className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Usuário</span>
<code className="rounded-md border border-slate-200 bg-white px-2 py-0.5 font-mono text-xs text-slate-700">
{entry.username}
</code>
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteCredential(entry.username, "Usuário do acesso remoto")}
title="Copiar usuário"
aria-label="Copiar usuário"
>
<ClipboardCopy className="size-3.5" />
</Button>
</div>
) : null}
{entry.password ? (
<div className="inline-flex flex-wrap items-center gap-2">
<span className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Senha</span>
<code className="rounded-md border border-slate-200 bg-white px-2 py-0.5 font-mono text-xs text-slate-700">
{secretVisible ? entry.password : "••••••••"}
</code>
<Button
variant="ghost"
size="sm"
className="h-7 gap-1 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => toggleRemoteSecret(entry.clientId)}
>
{secretVisible ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
{secretVisible ? "Ocultar" : "Mostrar"}
</Button>
<Button
variant="ghost"
size="sm"
className="h-7 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
onClick={() => handleCopyRemoteCredential(entry.password, "Senha do acesso remoto")}
title="Copiar senha"
aria-label="Copiar senha"
>
<ClipboardCopy className="size-3.5" />
</Button>
</div>
) : null}
</div>
) : null}
{entry.url && !isRustDesk ? (
<a
href={entry.url}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 text-slate-600 underline-offset-4 hover:text-slate-900 hover:underline"
>
Abrir console remoto
</a>
) : null}
{isRustDesk && (entry.identifier || entry.password) ? (
<Button
variant="outline"
size="sm"
className="mt-1 inline-flex items-center gap-2 border-slate-300 bg-white text-slate-800 shadow-sm transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-900 focus-visible:border-slate-400 focus-visible:ring-slate-200"
onClick={() => handleRustDeskConnect(entry)}
>
<MonitorSmartphone className="size-4 text-[#4b5563]" /> Conectar via RustDesk
</Button>
) : null}
{entry.notes ? (
<p className="whitespace-pre-wrap text-[11px] text-slate-600">{entry.notes}</p>
) : null}
{lastVerifiedDate ? (
<p className="text-[11px] text-slate-500">
Atualizado {formatRelativeTime(lastVerifiedDate)}{" "}
<span className="text-slate-400">({formatAbsoluteDateTime(lastVerifiedDate)})</span>
</p>
) : null}
</div>
{canManageRemoteAccess ? (
<div className="flex items-start gap-2">
<Button
size="sm"
variant="outline"
className="gap-2 border-slate-300"
onClick={() => {
setEditingRemoteAccessClientId(entry.clientId)
setRemoteAccessDialog(true)
}}
>
<Pencil className="size-3.5" /> Editar
</Button>
<Button
size="sm"
variant="ghost"
className="gap-2 text-rose-600 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-700"
onClick={() => void handleRemoveRemoteAccess(entry)}
disabled={remoteAccessSaving}
>
<ShieldOff className="size-3.5" /> Remover
</Button>
</div>
) : null}
</div>
</div>
)
})}
</div>
) : (
<div className="rounded-lg border border-dashed border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-600">
Nenhum identificador de acesso remoto cadastrado. Registre o ID do TeamViewer, AnyDesk ou outra ferramenta para agilizar o suporte.
</div>
)}
</div>
</section>
<section className="space-y-3 border-t border-slate-100 pt-6">
@ -4686,7 +4622,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
return (
<li key={`gpu-${idx}`}>
<span className="font-medium text-foreground">{name ?? "Adaptador de vídeo"}</span>
{memoryBytes ? <span className="ml-1 text-muted-foreground">{formatBytes(memoryBytes)}</span> : null}
{memoryBytes ? <span className="ml-1 text-muted-foreground">VRAM {formatBytes(memoryBytes)}</span> : null}
{vendor ? <span className="ml-1 text-muted-foreground">· {vendor}</span> : null}
{driver ? <span className="ml-1 text-muted-foreground">· Driver {driver}</span> : null}
</li>