Exibe status de reprovisionamento quando token é revogado
All checks were successful
All checks were successful
This commit is contained in:
parent
89f756e088
commit
b7e2c4cc98
2 changed files with 60 additions and 19 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue