Move USB policy control to modal dialog
- Add USB modal state and clickable InfoChip for USB policy chip - Create Dialog with UsbPolicyControl component for USB management - Add variant prop to UsbPolicyControl (card/inline) for flexible rendering - Remove inline UsbPolicyControl from bottom of device page - USB control now accessible by clicking USB chip in device summary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6007cf6740
commit
06ebad930c
2 changed files with 183 additions and 140 deletions
|
|
@ -99,6 +99,7 @@ interface UsbPolicyControlProps {
|
|||
actorName?: string
|
||||
actorId?: string
|
||||
disabled?: boolean
|
||||
variant?: "card" | "inline"
|
||||
}
|
||||
|
||||
export function UsbPolicyControl({
|
||||
|
|
@ -108,6 +109,7 @@ export function UsbPolicyControl({
|
|||
actorName,
|
||||
actorId,
|
||||
disabled = false,
|
||||
variant = "card",
|
||||
}: UsbPolicyControlProps) {
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<UsbPolicyValue>("ALLOW")
|
||||
const [isApplying, setIsApplying] = useState(false)
|
||||
|
|
@ -135,7 +137,7 @@ export function UsbPolicyControl({
|
|||
|
||||
const handleApplyPolicy = async () => {
|
||||
if (selectedPolicy === usbPolicy?.policy) {
|
||||
toast.info("A política selecionada já está aplicada.")
|
||||
toast.info("A politica selecionada ja esta aplicada.")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -148,10 +150,10 @@ export function UsbPolicyControl({
|
|||
actorEmail,
|
||||
actorName,
|
||||
})
|
||||
toast.success("Política USB enviada para aplicação.")
|
||||
toast.success("Politica USB enviada para aplicacao.")
|
||||
} catch (error) {
|
||||
console.error("[usb-policy] Falha ao aplicar política", error)
|
||||
toast.error("Falha ao aplicar política USB. Tente novamente.")
|
||||
console.error("[usb-policy] Falha ao aplicar politica", error)
|
||||
toast.error("Falha ao aplicar politica USB. Tente novamente.")
|
||||
} finally {
|
||||
setIsApplying(false)
|
||||
}
|
||||
|
|
@ -164,22 +166,10 @@ export function UsbPolicyControl({
|
|||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Usb className="size-5 text-neutral-500" />
|
||||
<CardTitle className="text-base">Controle USB</CardTitle>
|
||||
</div>
|
||||
{usbPolicy?.status && getStatusBadge(usbPolicy.status)}
|
||||
</div>
|
||||
<CardDescription>
|
||||
Gerencie o acesso a dispositivos de armazenamento USB neste dispositivo.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center gap-3 rounded-lg border bg-neutral-50 p-3">
|
||||
const content = (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-3 rounded-lg border bg-neutral-50 p-3 flex-1">
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-white shadow-sm">
|
||||
<CurrentIcon className="size-5 text-neutral-600" />
|
||||
</div>
|
||||
|
|
@ -188,120 +178,144 @@ export function UsbPolicyControl({
|
|||
<p className="text-xs text-muted-foreground">{currentConfig.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
{usbPolicy?.status && getStatusBadge(usbPolicy.status)}
|
||||
</div>
|
||||
|
||||
{usbPolicy?.error && (
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||||
<p className="text-sm font-medium text-red-700">Erro na aplicação</p>
|
||||
<p className="text-xs text-red-600">{usbPolicy.error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">Alterar política</label>
|
||||
<Select
|
||||
value={selectedPolicy}
|
||||
onValueChange={(value) => setSelectedPolicy(value as UsbPolicyValue)}
|
||||
disabled={disabled || isApplying}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione uma política" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{POLICY_OPTIONS.map((option) => {
|
||||
const Icon = option.icon
|
||||
return (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="size-4" />
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={handleApplyPolicy}
|
||||
disabled={disabled || isApplying || selectedPolicy === usbPolicy?.policy}
|
||||
size="default"
|
||||
>
|
||||
{isApplying ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
Aplicando...
|
||||
</>
|
||||
) : (
|
||||
"Aplicar"
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{selectedPolicy === usbPolicy?.policy
|
||||
? "A política já está aplicada"
|
||||
: `Aplicar política "${getPolicyConfig(selectedPolicy).label}"`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{usbPolicy?.error && (
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||||
<p className="text-sm font-medium text-red-700">Erro na aplicacao</p>
|
||||
<p className="text-xs text-red-600">{usbPolicy.error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t pt-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full justify-start gap-2 text-muted-foreground"
|
||||
onClick={() => setShowHistory(!showHistory)}
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">Alterar politica</label>
|
||||
<Select
|
||||
value={selectedPolicy}
|
||||
onValueChange={(value) => setSelectedPolicy(value as UsbPolicyValue)}
|
||||
disabled={disabled || isApplying}
|
||||
>
|
||||
<History className="size-4" />
|
||||
{showHistory ? "Ocultar histórico" : "Ver histórico de alterações"}
|
||||
</Button>
|
||||
|
||||
{showHistory && policyEvents && (
|
||||
<div className="mt-3 space-y-2">
|
||||
{policyEvents.length === 0 ? (
|
||||
<p className="text-center text-xs text-muted-foreground py-2">
|
||||
Nenhuma alteração registrada
|
||||
</p>
|
||||
) : (
|
||||
policyEvents.map((event: UsbPolicyEvent) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="flex items-start justify-between rounded-md border bg-white p-2 text-xs"
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-medium">
|
||||
{getPolicyConfig(event.oldPolicy).label}
|
||||
</span>
|
||||
<span className="text-muted-foreground">→</span>
|
||||
<span className="font-medium">
|
||||
{getPolicyConfig(event.newPolicy).label}
|
||||
</span>
|
||||
{getStatusBadge(event.status)}
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
{event.actorName ?? event.actorEmail ?? "Sistema"} · {formatEventDate(event.createdAt)}
|
||||
</p>
|
||||
{event.error && (
|
||||
<p className="text-red-600">{event.error}</p>
|
||||
)}
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione uma politica" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{POLICY_OPTIONS.map((option) => {
|
||||
const Icon = option.icon
|
||||
return (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="size-4" />
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={handleApplyPolicy}
|
||||
disabled={disabled || isApplying || selectedPolicy === usbPolicy?.policy}
|
||||
size="default"
|
||||
>
|
||||
{isApplying ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
Aplicando...
|
||||
</>
|
||||
) : (
|
||||
"Aplicar"
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{selectedPolicy === usbPolicy?.policy
|
||||
? "A politica ja esta aplicada"
|
||||
: `Aplicar politica "${getPolicyConfig(selectedPolicy).label}"`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
{usbPolicy?.reportedAt && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Último relato do agente: {formatEventDate(usbPolicy.reportedAt)}
|
||||
</p>
|
||||
<div className="border-t pt-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full justify-start gap-2 text-muted-foreground"
|
||||
onClick={() => setShowHistory(!showHistory)}
|
||||
>
|
||||
<History className="size-4" />
|
||||
{showHistory ? "Ocultar historico" : "Ver historico de alteracoes"}
|
||||
</Button>
|
||||
|
||||
{showHistory && policyEvents && (
|
||||
<div className="mt-3 space-y-2">
|
||||
{policyEvents.length === 0 ? (
|
||||
<p className="text-center text-xs text-muted-foreground py-2">
|
||||
Nenhuma alteracao registrada
|
||||
</p>
|
||||
) : (
|
||||
policyEvents.map((event: UsbPolicyEvent) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="flex items-start justify-between rounded-md border bg-white p-2 text-xs"
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-medium">
|
||||
{getPolicyConfig(event.oldPolicy).label}
|
||||
</span>
|
||||
<span className="text-muted-foreground">-></span>
|
||||
<span className="font-medium">
|
||||
{getPolicyConfig(event.newPolicy).label}
|
||||
</span>
|
||||
{getStatusBadge(event.status)}
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
{event.actorName ?? event.actorEmail ?? "Sistema"} - {formatEventDate(event.createdAt)}
|
||||
</p>
|
||||
{event.error && (
|
||||
<p className="text-red-600">{event.error}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{usbPolicy?.reportedAt && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Ultimo relato do agente: {formatEventDate(usbPolicy.reportedAt)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (variant === "inline") {
|
||||
return content
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Usb className="size-5 text-neutral-500" />
|
||||
<CardTitle className="text-base">Controle USB</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Gerencie o acesso a dispositivos de armazenamento USB neste dispositivo.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{content}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue