"use client" import { useState, useEffect, useMemo } from "react" import { useMutation, useQuery } from "convex/react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { Usb, Shield, ShieldOff, ShieldAlert, Clock, CheckCircle2, XCircle, Loader2, History, Filter, ChevronDown, RotateCcw, Info, HardDrive } from "lucide-react" import { toast } from "sonner" import { formatDistanceToNow, startOfDay, endOfDay, parseISO } from "date-fns" import { ptBR } from "date-fns/locale" import { DateRangeButton } from "@/components/date-range-button" type UsbPolicyValue = "ALLOW" | "BLOCK_ALL" | "READONLY" interface UsbPolicyEvent { id: string oldPolicy?: string newPolicy: string status: string error?: string actorEmail?: string actorName?: string createdAt: number appliedAt?: number } const POLICY_OPTIONS: Array<{ value: UsbPolicyValue; label: string; description: string; icon: typeof Shield }> = [ { value: "ALLOW", label: "Permitido", description: "Acesso total a dispositivos USB de armazenamento", icon: Shield, }, { value: "BLOCK_ALL", label: "Bloqueado", description: "Nenhum acesso a dispositivos USB de armazenamento", icon: ShieldOff, }, { value: "READONLY", label: "Somente leitura", description: "Permite leitura, bloqueia escrita em dispositivos USB", icon: ShieldAlert, }, ] function getPolicyConfig(policy: string | undefined | null) { return POLICY_OPTIONS.find((opt) => opt.value === policy) ?? POLICY_OPTIONS[0] } function getStatusBadge(status: string | undefined | null) { switch (status) { case "PENDING": return ( Pendente ) case "APPLYING": return ( Aplicando ) case "APPLIED": return ( Aplicado ) case "FAILED": return ( Falhou ) default: return null } } function getProgressValue(status: string | undefined | null): number { switch (status) { case "PENDING": return 33 case "APPLYING": return 66 case "APPLIED": return 100 case "FAILED": return 100 default: return 0 } } function getProgressColor(status: string | undefined | null): string { switch (status) { case "PENDING": return "bg-amber-500" case "APPLYING": return "bg-blue-500" case "APPLIED": return "bg-emerald-500" case "FAILED": return "bg-red-500" default: return "bg-neutral-300" } } interface UsbPolicyControlProps { machineId: string machineName?: string actorEmail?: string actorName?: string actorId?: string disabled?: boolean variant?: "card" | "inline" } const STATUS_FILTER_OPTIONS = [ { value: "all", label: "Todos os status" }, { value: "PENDING", label: "Pendente" }, { value: "APPLYING", label: "Aplicando" }, { value: "APPLIED", label: "Aplicado" }, { value: "FAILED", label: "Falhou" }, ] export function UsbPolicyControl({ machineId, machineName, actorEmail, actorName, actorId, disabled = false, variant = "card", }: UsbPolicyControlProps) { const [selectedPolicy, setSelectedPolicy] = useState("ALLOW") const [isApplying, setIsApplying] = useState(false) const [showHistory, setShowHistory] = useState(false) // Filtros do historico const [statusFilter, setStatusFilter] = useState("all") const [dateFrom, setDateFrom] = useState(null) const [dateTo, setDateTo] = useState(null) const [allEvents, setAllEvents] = useState([]) const [cursor, setCursor] = useState(undefined) const usbPolicy = useQuery(api.usbPolicy.getUsbPolicy, { machineId: machineId as Id<"machines">, }) // Converte datas para timestamp const dateFromTs = useMemo(() => { if (!dateFrom) return undefined return startOfDay(parseISO(dateFrom)).getTime() }, [dateFrom]) const dateToTs = useMemo(() => { if (!dateTo) return undefined return endOfDay(parseISO(dateTo)).getTime() }, [dateTo]) const policyEventsResult = useQuery( api.usbPolicy.listUsbPolicyEvents, showHistory ? { machineId: machineId as Id<"machines">, limit: 10, cursor, status: statusFilter !== "all" ? statusFilter : undefined, dateFrom: dateFromTs, dateTo: dateToTs, } : "skip" ) // Acumula eventos quando carrega mais useEffect(() => { if (policyEventsResult?.events) { if (cursor === undefined) { // Reset quando filtros mudam setAllEvents(policyEventsResult.events) } else { // Acumula quando carrega mais setAllEvents((prev) => [...prev, ...policyEventsResult.events]) } } }, [policyEventsResult?.events, cursor]) // Reset cursor quando filtros mudam useEffect(() => { setCursor(undefined) setAllEvents([]) }, [statusFilter, dateFrom, dateTo]) const handleLoadMore = () => { if (policyEventsResult?.nextCursor) { setCursor(policyEventsResult.nextCursor) } } const handleResetFilters = () => { setStatusFilter("all") setDateFrom(null) setDateTo(null) setCursor(undefined) setAllEvents([]) } const hasActiveFilters = statusFilter !== "all" || dateFrom !== null || dateTo !== null const setUsbPolicyMutation = useMutation(api.usbPolicy.setUsbPolicy) useEffect(() => { if (usbPolicy?.policy) { setSelectedPolicy(usbPolicy.policy as UsbPolicyValue) } }, [usbPolicy?.policy]) const currentConfig = getPolicyConfig(usbPolicy?.policy) const CurrentIcon = currentConfig.icon const handleApplyPolicy = async () => { if (selectedPolicy === usbPolicy?.policy) { toast.info("A política selecionada já está aplicada.") return } setIsApplying(true) try { await setUsbPolicyMutation({ machineId: machineId as Id<"machines">, policy: selectedPolicy, actorId: actorId ? (actorId as Id<"users">) : undefined, actorEmail, actorName, }) toast.success("Política USB enviada para aplicação.") } catch (error) { console.error("[usb-policy] Falha ao aplicar política", error) toast.error("Falha ao aplicar política USB. Tente novamente.") } finally { setIsApplying(false) } } const formatEventDate = (timestamp: number) => { return formatDistanceToNow(new Date(timestamp), { addSuffix: true, locale: ptBR, }) } const content = (
{/* Status atual com progress bar real */} {usbPolicy?.status && (
Status da política {getStatusBadge(usbPolicy.status)}
{/* Progress bar real baseada no estado */} {(usbPolicy.status === "PENDING" || usbPolicy.status === "APPLYING") && (

{usbPolicy.status === "PENDING" ? "Aguardando agente..." : "Agente aplicando política..."}

)}
)} {usbPolicy?.error && (

Erro na aplicação

{usbPolicy.error}

)} {/* Política atual */}

{currentConfig.label}

{currentConfig.description}

{/* Info sobre dispositivos afetados */}

Dispositivos afetados

Pen drives HDs externos Cartões SD

Não afeta teclados, mouses, impressoras ou outros periféricos USB.

{selectedPolicy === usbPolicy?.policy ? "A política já está aplicada" : `Aplicar política "${getPolicyConfig(selectedPolicy).label}"`}
{showHistory && (
{/* Filtros */}
Filtros {hasActiveFilters && ( )}
{ setDateFrom(from) setDateTo(to) }} className="h-9" clearLabel="Limpar" />
{/* Lista de eventos */} {allEvents.length === 0 ? (

{hasActiveFilters ? "Nenhuma alteração encontrada com os filtros selecionados" : "Nenhuma alteração registrada"}

) : ( <>
{allEvents.map((event: UsbPolicyEvent) => (
{getPolicyConfig(event.oldPolicy).label} {getPolicyConfig(event.newPolicy).label} {getStatusBadge(event.status)}

{event.actorName ?? event.actorEmail ?? "Sistema"} · {formatEventDate(event.createdAt)}

{event.error && (

{event.error}

)}
))}
{/* Paginacao */} {policyEventsResult?.hasMore && ( )} )}
)}
{usbPolicy?.reportedAt && (

Último relato do agente: {formatEventDate(usbPolicy.reportedAt)}

)}
) if (variant === "inline") { return content } return (
Controle USB
Gerencie o acesso a dispositivos de armazenamento USB neste dispositivo.
{content}
) }