Exibe status de reprovisionamento quando token é revogado
All checks were successful
CI/CD Web + Desktop / Detect changes (push) Successful in 6s
CI/CD Web + Desktop / Deploy (VPS Linux) (push) Successful in 3m53s
CI/CD Web + Desktop / Deploy Convex functions (push) Has been skipped
Quality Checks / Lint, Test and Build (push) Successful in 4m13s

This commit is contained in:
rever-tecnologia 2025-12-18 16:12:48 -03:00
parent 89f756e088
commit b7e2c4cc98
2 changed files with 60 additions and 19 deletions

View file

@ -66,6 +66,7 @@ import {
TableRow,
} from "@/components/ui/table"
import { Separator } from "@/components/ui/separator"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { ChartContainer } from "@/components/ui/chart"
import { cn } from "@/lib/utils"
import { DEVICE_INVENTORY_COLUMN_METADATA, type DeviceInventoryColumnConfig } from "@/lib/device-inventory-columns"
@ -1050,6 +1051,7 @@ const statusClasses: Record<string, string> = {
online: "border-emerald-200 text-emerald-600",
offline: "border-rose-200 text-rose-600",
stale: "border-amber-200 text-amber-600",
reprovision: "border-orange-200 text-orange-700",
maintenance: "border-amber-300 text-amber-700",
blocked: "border-orange-200 text-orange-600",
deactivated: "border-slate-200 bg-slate-50 text-slate-500",
@ -1314,6 +1316,16 @@ function getStatusVariant(status?: string | null) {
}
}
function resolveAdminDeviceStatus(device: DevicesQueryItem): string {
return resolveDeviceStatus({
status: device.status,
lastHeartbeatAt: device.lastHeartbeatAt,
isActive: device.isActive,
managementMode: device.managementMode,
hasValidToken: Boolean(device.token),
})
}
function OsIcon({ osName }: { osName?: string | null }) {
const name = (osName ?? "").toLowerCase()
if (name.includes("mac") || name.includes("darwin") || name.includes("macos")) return <Apple className="size-4 text-neutral-500" />
@ -1698,7 +1710,7 @@ export function AdminDevicesOverview({
return devices.filter((m) => {
if (onlyAlerts && !(Array.isArray(m.postureAlerts) && m.postureAlerts.length > 0)) return false
if (statusFilter !== "all") {
const s = resolveDeviceStatus(m).toLowerCase()
const s = resolveAdminDeviceStatus(m).toLowerCase()
if (s !== statusFilter) return false
}
if (deviceTypeFilter !== "all") {
@ -1971,6 +1983,7 @@ export function AdminDevicesOverview({
<SelectItem value="online">Online</SelectItem>
<SelectItem value="offline">Offline</SelectItem>
<SelectItem value="stale">Sem sinal</SelectItem>
<SelectItem value="reprovision">Reprovisionar</SelectItem>
<SelectItem value="unknown">Desconhecido</SelectItem>
</SelectContent>
</Select>
@ -2106,7 +2119,7 @@ export function AdminDevicesOverview({
<div className="max-h-80 overflow-y-auto rounded-md border border-slate-200">
<ul className="divide-y divide-slate-100">
{filteredDevices.map((device) => {
const statusKey = resolveDeviceStatus(device)
const statusKey = resolveAdminDeviceStatus(device)
const statusLabel = DEVICE_STATUS_LABELS[statusKey] ?? statusKey
const isChecked = exportSelection.includes(device.id)
const osParts = [device.osName ?? "", device.osVersion ?? ""].filter(Boolean)
@ -2773,7 +2786,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
const normalizedViewerRole = (viewerRole ?? "").toLowerCase()
const canManageRemoteAccess = normalizedViewerRole === "admin" || normalizedViewerRole === "agent"
const canManageFieldCatalog = normalizedViewerRole === "admin"
const effectiveStatus = device ? resolveDeviceStatus(device) : "unknown"
const effectiveStatus = device ? resolveAdminDeviceStatus(device) : "unknown"
const [isActiveLocal, setIsActiveLocal] = useState<boolean>(device?.isActive ?? true)
const isDeactivated = !isActiveLocal || effectiveStatus === "deactivated"
const isManualMobile =
@ -3195,6 +3208,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
const lastHeartbeatDate = device?.lastHeartbeatAt ? new Date(device.lastHeartbeatAt) : null
const tokenExpiry = device?.token?.expiresAt ? new Date(device.token.expiresAt) : null
const tokenLastUsed = device?.token?.lastUsedAt ? new Date(device.token.lastUsedAt) : null
const hasActiveToken = Boolean(device?.token)
const copyEmail = async () => {
if (!device?.authEmail) return
@ -4024,9 +4038,14 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
<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}>
<Tooltip>
<TooltipTrigger asChild>
<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">
{companyName}
</div>
</TooltipTrigger>
<TooltipContent>{companyName}</TooltipContent>
</Tooltip>
) : null}
{!isDeactivated ? <DeviceStatusBadge status={effectiveStatus} /> : null}
{!isActiveLocal ? (
@ -4700,18 +4719,20 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
<div className="flex justify-between gap-4">
<span>Token expira</span>
<span className="text-right font-medium text-foreground">
{tokenExpiry ? formatRelativeTime(tokenExpiry) : ""}
{hasActiveToken ? (tokenExpiry ? formatRelativeTime(tokenExpiry) : "-") : "Sem token ativo"}
</span>
</div>
<div className="flex justify-between gap-4">
<span>Token usado por último</span>
<span className="text-right font-medium text-foreground">
{tokenLastUsed ? formatRelativeTime(tokenLastUsed) : ""}
{hasActiveToken ? (tokenLastUsed ? formatRelativeTime(tokenLastUsed) : "-") : "Sem token ativo"}
</span>
</div>
<div className="flex justify-between gap-4">
<span>Uso do token</span>
<span className="text-right font-medium text-foreground">{device.token?.usageCount ?? 0} trocas</span>
<span className="text-right font-medium text-foreground">
{hasActiveToken ? `${device.token?.usageCount ?? 0} trocas` : "Sem token ativo"}
</span>
</div>
</div>
</section>
@ -6322,7 +6343,7 @@ function DevicesGrid({ devices, companyNameBySlug }: { devices: DevicesQueryItem
function DeviceCard({ device, companyName }: { device: DevicesQueryItem; companyName?: string | null }) {
const router = useRouter()
const effectiveStatus = resolveDeviceStatus(device)
const effectiveStatus = resolveAdminDeviceStatus(device)
const statusIndicator = getDeviceStatusIndicator(effectiveStatus)
const isActive = device.isActive
const lastHeartbeat = device.lastHeartbeatAt ? new Date(device.lastHeartbeatAt) : null
@ -6406,15 +6427,27 @@ function DeviceCard({ device, companyName }: { device: DevicesQueryItem; company
</div>
{collaborator?.email ? (
<div className="mt-3 flex flex-col gap-2 text-xs text-slate-600">
<Tooltip>
<TooltipTrigger asChild>
<div className="flex flex-col rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5">
<span className="text-[11px] uppercase text-slate-400">Usuário vinculado</span>
<span className="line-clamp-1 text-sm font-medium text-slate-800" title={collaborator.name ?? collaborator.email}>
<span className="line-clamp-1 text-sm font-medium text-slate-800">
{collaborator.name ?? "Usuário"}
</span>
<span className="text-[11px] text-slate-500 line-clamp-1" title={collaborator.email}>
<span className="text-[11px] text-slate-500 line-clamp-1">
{collaborator.email}
</span>
</div>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<div className="grid gap-1">
<span className="font-semibold">
{collaborator.name ?? collaborator.email}
</span>
<span>{collaborator.email}</span>
</div>
</TooltipContent>
</Tooltip>
<button
type="button"
className="self-start text-[11px] font-semibold text-slate-500 underline underline-offset-4 hover:text-slate-800"

View file

@ -21,6 +21,7 @@ export const DEVICE_STATUS_LABELS: Record<string, string> = {
online: "Online",
offline: "Offline",
stale: "Sem sinal",
reprovision: "Reprovisionar",
maintenance: "Em manutenção",
blocked: "Bloqueado",
deactivated: "Desativado",
@ -31,6 +32,8 @@ export type DeviceStatusSource = {
status?: string | null
lastHeartbeatAt?: number | null
isActive?: boolean | null
hasValidToken?: boolean | null
managementMode?: string | null
}
export function resolveDeviceStatus(source?: DeviceStatusSource | null): string {
@ -40,6 +43,10 @@ export function resolveDeviceStatus(source?: DeviceStatusSource | null): string
if (manualStatus === "maintenance" || manualStatus === "blocked") {
return manualStatus
}
const managementMode = (source.managementMode ?? "").toLowerCase()
if (managementMode === "agent" && source.hasValidToken === false) {
return "reprovision"
}
const heartbeat = source.lastHeartbeatAt
if (typeof heartbeat === "number" && Number.isFinite(heartbeat) && heartbeat > 0) {
const age = Date.now() - heartbeat
@ -57,6 +64,7 @@ const STATUS_INDICATORS: Record<
online: { dotClass: "bg-emerald-500", ringClass: "bg-emerald-400/30", isPinging: true },
offline: { dotClass: "bg-rose-500", ringClass: "bg-rose-400/30", isPinging: false },
stale: { dotClass: "bg-amber-500", ringClass: "bg-amber-400/30", isPinging: false },
reprovision: { dotClass: "bg-orange-500", ringClass: "bg-orange-400/30", isPinging: false },
maintenance: { dotClass: "bg-amber-500", ringClass: "bg-amber-400/30", isPinging: false },
blocked: { dotClass: "bg-orange-500", ringClass: "bg-orange-400/30", isPinging: false },
deactivated: { dotClass: "bg-slate-500", ringClass: "bg-slate-400/40", isPinging: false },