Ajusta placeholders, formulários e widgets
This commit is contained in:
parent
343f0c8c64
commit
b94cea2f9a
33 changed files with 2122 additions and 462 deletions
|
|
@ -2441,6 +2441,9 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
|||
const effectiveStatus = device ? resolveDeviceStatus(device) : "unknown"
|
||||
const [isActiveLocal, setIsActiveLocal] = useState<boolean>(device?.isActive ?? true)
|
||||
const isDeactivated = !isActiveLocal || effectiveStatus === "deactivated"
|
||||
const isManualMobile =
|
||||
(device?.managementMode ?? "").toLowerCase() === "manual" &&
|
||||
(device?.deviceType ?? "").toLowerCase() === "mobile"
|
||||
const alertsHistory = useQuery(
|
||||
api.devices.listAlerts,
|
||||
device ? { machineId: device.id as Id<"machines">, limit: 50 } : "skip"
|
||||
|
|
@ -3578,6 +3581,11 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
|||
<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"
|
||||
|
|
@ -3723,29 +3731,33 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
|||
<ShieldCheck className="size-4" />
|
||||
Ajustar acesso
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-2 border-dashed border-amber-300 text-amber-700 hover:border-amber-400 hover:text-amber-800"
|
||||
onClick={handleResetAgent}
|
||||
disabled={isResettingAgent}
|
||||
>
|
||||
<RefreshCcw className={cn("size-4", isResettingAgent && "animate-spin")} />
|
||||
{isResettingAgent ? "Resetando agente..." : "Resetar agente"}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={isActiveLocal ? "outline" : "default"}
|
||||
className={cn(
|
||||
"gap-2 border-dashed",
|
||||
!isActiveLocal && "bg-emerald-600 text-white hover:bg-emerald-600/90"
|
||||
)}
|
||||
onClick={handleToggleActive}
|
||||
disabled={togglingActive}
|
||||
>
|
||||
{isActiveLocal ? <Power className="size-4" /> : <PlayCircle className="size-4" />}
|
||||
{isActiveLocal ? (togglingActive ? "Desativando..." : "Desativar") : togglingActive ? "Reativando..." : "Reativar"}
|
||||
</Button>
|
||||
{!isManualMobile ? (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-2 border-dashed border-amber-300 text-amber-700 hover:border-amber-400 hover:text-amber-800"
|
||||
onClick={handleResetAgent}
|
||||
disabled={isResettingAgent}
|
||||
>
|
||||
<RefreshCcw className={cn("size-4", isResettingAgent && "animate-spin")} />
|
||||
{isResettingAgent ? "Resetando agente..." : "Resetar agente"}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={isActiveLocal ? "outline" : "default"}
|
||||
className={cn(
|
||||
"gap-2 border-dashed",
|
||||
!isActiveLocal && "bg-emerald-600 text-white hover:bg-emerald-600/90"
|
||||
)}
|
||||
onClick={handleToggleActive}
|
||||
disabled={togglingActive}
|
||||
>
|
||||
{isActiveLocal ? <Power className="size-4" /> : <PlayCircle className="size-4" />}
|
||||
{isActiveLocal ? (togglingActive ? "Desativando..." : "Desativar") : togglingActive ? "Reativando..." : "Reativar"}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{device.registeredBy ? (
|
||||
<span
|
||||
className={cn(
|
||||
|
|
@ -4157,55 +4169,59 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h4 className="text-sm font-semibold">Sincronização</h4>
|
||||
<div className="grid gap-2 text-sm text-muted-foreground">
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Último heartbeat</span>
|
||||
<span className="text-right font-medium text-foreground">
|
||||
{formatRelativeTime(lastHeartbeatDate)}
|
||||
</span>
|
||||
{!isManualMobile ? (
|
||||
<section className="space-y-2">
|
||||
<h4 className="text-sm font-semibold">Sincronização</h4>
|
||||
<div className="grid gap-2 text-sm text-muted-foreground">
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Último heartbeat</span>
|
||||
<span className="text-right font-medium text-foreground">
|
||||
{formatRelativeTime(lastHeartbeatDate)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Criada em</span>
|
||||
<span className="text-right font-medium text-foreground">{formatDate(new Date(device.createdAt))}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Atualizada em</span>
|
||||
<span className="text-right font-medium text-foreground">{formatDate(new Date(device.updatedAt))}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Token expira</span>
|
||||
<span className="text-right font-medium text-foreground">
|
||||
{tokenExpiry ? formatRelativeTime(tokenExpiry) : "—"}
|
||||
</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) : "—"}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Criada em</span>
|
||||
<span className="text-right font-medium text-foreground">{formatDate(new Date(device.createdAt))}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Atualizada em</span>
|
||||
<span className="text-right font-medium text-foreground">{formatDate(new Date(device.updatedAt))}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-4">
|
||||
<span>Token expira</span>
|
||||
<span className="text-right font-medium text-foreground">
|
||||
{tokenExpiry ? formatRelativeTime(tokenExpiry) : "—"}
|
||||
</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) : "—"}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h4 className="text-sm font-semibold">Métricas recentes</h4>
|
||||
{lastUpdateRelative ? (
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Última atualização {lastUpdateRelative}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<MetricsGrid metrics={metrics} hardware={hardware} disks={disks} />
|
||||
</section>
|
||||
{!isManualMobile ? (
|
||||
<section className="space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h4 className="text-sm font-semibold">Métricas recentes</h4>
|
||||
{lastUpdateRelative ? (
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Última atualização {lastUpdateRelative}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<MetricsGrid metrics={metrics} hardware={hardware} disks={disks} />
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{hardware || network || (labels && labels.length > 0) ? (
|
||||
{!isManualMobile && (hardware || network || (labels && labels.length > 0)) ? (
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold">Inventário</h4>
|
||||
|
|
@ -5145,39 +5161,41 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
|||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-semibold">Histórico de alertas</h4>
|
||||
{deviceAlertsHistory.length > 0 ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Últimos {deviceAlertsHistory.length} {deviceAlertsHistory.length === 1 ? "evento" : "eventos"}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{deviceAlertsHistory.length > 0 ? (
|
||||
<div className="relative max-h-64 overflow-y-auto pr-2">
|
||||
<div className="absolute left-3 top-3 bottom-3 w-px bg-slate-200" />
|
||||
<ol className="space-y-3 pl-6">
|
||||
{deviceAlertsHistory.map((alert) => {
|
||||
const date = new Date(alert.createdAt)
|
||||
return (
|
||||
<li key={alert.id} className="relative rounded-md border border-slate-200/80 bg-white px-3 py-2 text-xs shadow-sm">
|
||||
<span className="absolute -left-5 top-3 inline-flex size-3 items-center justify-center rounded-full border border-white bg-slate-200 ring-2 ring-white" />
|
||||
<div className={cn("flex items-center justify-between", postureSeverityClass(alert.severity))}>
|
||||
<span className="text-xs font-medium uppercase tracking-wide text-slate-600">{formatPostureAlertKind(alert.kind)}</span>
|
||||
<span className="text-xs text-slate-500">{formatRelativeTime(date)}</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-foreground">{alert.message ?? formatPostureAlertKind(alert.kind)}</p>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">{format(date, "dd/MM/yyyy HH:mm:ss")}</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
{!isManualMobile ? (
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-semibold">Histórico de alertas</h4>
|
||||
{deviceAlertsHistory.length > 0 ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Últimos {deviceAlertsHistory.length} {deviceAlertsHistory.length === 1 ? "evento" : "eventos"}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">Nenhum alerta registrado para este dispositivo.</p>
|
||||
)}
|
||||
</section>
|
||||
{deviceAlertsHistory.length > 0 ? (
|
||||
<div className="relative max-h-64 overflow-y-auto pr-2">
|
||||
<div className="absolute left-3 top-3 bottom-3 w-px bg-slate-200" />
|
||||
<ol className="space-y-3 pl-6">
|
||||
{deviceAlertsHistory.map((alert) => {
|
||||
const date = new Date(alert.createdAt)
|
||||
return (
|
||||
<li key={alert.id} className="relative rounded-md border border-slate-200/80 bg-white px-3 py-2 text-xs shadow-sm">
|
||||
<span className="absolute -left-5 top-3 inline-flex size-3 items-center justify-center rounded-full border border-white bg-slate-200 ring-2 ring-white" />
|
||||
<div className={cn("flex items-center justify-between", postureSeverityClass(alert.severity))}>
|
||||
<span className="text-xs font-medium uppercase tracking-wide text-slate-600">{formatPostureAlertKind(alert.kind)}</span>
|
||||
<span className="text-xs text-slate-500">{formatRelativeTime(date)}</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-foreground">{alert.message ?? formatPostureAlertKind(alert.kind)}</p>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">{format(date, "dd/MM/yyyy HH:mm:ss")}</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">Nenhum alerta registrado para este dispositivo.</p>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
{Array.isArray(software) && software.length > 0 ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue