Enrich Windows diagnostics and admin UI

This commit is contained in:
Esdras Renan 2025-10-20 22:43:42 -03:00
parent 49496f3663
commit 037891485d
2 changed files with 838 additions and 53 deletions

View file

@ -792,6 +792,106 @@ fn collect_windows_extended() -> serde_json::Value {
ps("@(Get-Service | Select-Object Name,Status,DisplayName)").unwrap_or_else(|| json!([]));
let defender = ps("Get-MpComputerStatus | Select-Object AMRunningMode,AntivirusEnabled,RealTimeProtectionEnabled,AntispywareEnabled").unwrap_or_else(|| json!({}));
let hotfix = ps("Get-HotFix | Select-Object HotFixID,InstalledOn").unwrap_or_else(|| json!([]));
let bitlocker = ps(
"@(if (Get-Command -Name Get-BitLockerVolume -ErrorAction SilentlyContinue) { Get-BitLockerVolume | Select-Object MountPoint,VolumeStatus,ProtectionStatus,LockStatus,EncryptionMethod,EncryptionPercentage,CapacityGB,KeyProtector } else { @() })",
)
.unwrap_or_else(|| json!([]));
let tpm = ps(
"if (Get-Command -Name Get-Tpm -ErrorAction SilentlyContinue) { Get-Tpm | Select-Object TpmPresent,TpmReady,TpmEnabled,TpmActivated,ManagedAuthLevel,OwnerAuth,ManufacturerId,ManufacturerIdTxt,ManufacturerVersion,ManufacturerVersionFull20,SpecVersion } else { $null }",
)
.unwrap_or_else(|| json!({}));
let secure_boot = ps(
r#"
if (-not (Get-Command -Name Confirm-SecureBootUEFI -ErrorAction SilentlyContinue)) {
[PSCustomObject]@{ Supported = $false; Enabled = $null; Error = 'Cmdlet Confirm-SecureBootUEFI indisponível' }
} else {
try {
$enabled = Confirm-SecureBootUEFI
[PSCustomObject]@{ Supported = $true; Enabled = [bool]$enabled; Error = $null }
} catch {
[PSCustomObject]@{ Supported = $true; Enabled = $null; Error = $_.Exception.Message }
}
}
"#,
)
.unwrap_or_else(|| json!({}));
let device_guard = ps(
"@(Get-CimInstance -ClassName Win32_DeviceGuard | Select-Object SecurityServicesConfigured,SecurityServicesRunning,RequiredSecurityProperties,AvailableSecurityProperties,VirtualizationBasedSecurityStatus)",
)
.unwrap_or_else(|| json!([]));
let firewall_profiles = ps(
"@(Get-NetFirewallProfile | Select-Object Name,Enabled,DefaultInboundAction,DefaultOutboundAction,NotifyOnListen)",
)
.unwrap_or_else(|| json!([]));
let windows_update = ps(
r#"
$reg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -ErrorAction SilentlyContinue
if ($null -eq $reg) { return $null }
$last = $null
if ($reg.PSObject.Properties.Name -contains 'LastSuccessTime') {
$raw = $reg.LastSuccessTime
if ($raw) {
try {
if ($raw -is [DateTime]) {
$last = ($raw.ToUniversalTime()).ToString('o')
} elseif ($raw -is [string]) {
$last = $raw
} else {
$last = [DateTime]::FromFileTimeUtc([long]$raw).ToString('o')
}
} catch {
$last = $raw
}
}
}
[PSCustomObject]@{
AUOptions = $reg.AUOptions
NoAutoUpdate = $reg.NoAutoUpdate
ScheduledInstallDay = $reg.ScheduledInstallDay
ScheduledInstallTime = $reg.ScheduledInstallTime
DetectionFrequency = $reg.DetectionFrequencyEnabled
LastSuccessTime = $last
}
"#,
)
.unwrap_or_else(|| json!({}));
let computer_system = ps(
"Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer,Model,Domain,DomainRole,PartOfDomain,Workgroup,TotalPhysicalMemory,HypervisorPresent,PCSystemType,PCSystemTypeEx",
)
.unwrap_or_else(|| json!({}));
let device_join = ps(
r#"
$output = & dsregcmd.exe /status 2>$null
if (-not $output) { return $null }
$map = [ordered]@{}
$current = $null
foreach ($line in $output) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
if ($line -match '^\[(.+)\]$') {
$current = $matches[1].Trim()
if (-not $map.Contains($current)) {
$map[$current] = [ordered]@{}
}
continue
}
if (-not $current) { continue }
$parts = $line.Split(':', 2)
if ($parts.Length -ne 2) { continue }
$key = $parts[0].Trim()
$value = $parts[1].Trim()
if ($key) {
($map[$current])[$key] = $value
}
}
if ($map.Count -eq 0) { return $null }
$obj = [ordered]@{}
foreach ($entry in $map.GetEnumerator()) {
$obj[$entry.Key] = [PSCustomObject]$entry.Value
}
[PSCustomObject]$obj
"#,
)
.unwrap_or_else(|| json!({}));
// Informações de build/edição e ativação
let os_info = ps(r#"
@ -847,6 +947,14 @@ fn collect_windows_extended() -> serde_json::Value {
"memoryModules": memory,
"videoControllers": video,
"disks": disks,
"bitLocker": bitlocker,
"tpm": tpm,
"secureBoot": secure_boot,
"deviceGuard": device_guard,
"firewallProfiles": firewall_profiles,
"windowsUpdate": windows_update,
"computerSystem": computer_system,
"azureAdStatus": device_join,
}
})
}

View file

@ -6,7 +6,31 @@ import { useQuery } from "convex/react"
import { format, formatDistanceToNowStrict } from "date-fns"
import { ptBR } from "date-fns/locale"
import { toast } from "sonner"
import { ClipboardCopy, ServerCog, Cpu, MemoryStick, Monitor, HardDrive, Pencil, ShieldCheck, ShieldAlert, Apple, Terminal, Power, PlayCircle, Download } from "lucide-react"
import {
ClipboardCopy,
ServerCog,
Cpu,
MemoryStick,
Monitor,
HardDrive,
Pencil,
ShieldCheck,
ShieldAlert,
Shield,
ShieldOff,
ShieldQuestion,
Lock,
Cloud,
RefreshCcw,
AlertTriangle,
Key,
Globe,
Apple,
Terminal,
Power,
PlayCircle,
Download,
} from "lucide-react"
import { api } from "@/convex/_generated/api"
import { Badge } from "@/components/ui/badge"
@ -147,6 +171,14 @@ type WindowsExtended = {
videoControllers?: WindowsVideoController[]
disks?: WindowsDiskEntry[]
osInfo?: WindowsOsInfo
bitLocker?: Array<Record<string, unknown>> | Record<string, unknown>
tpm?: Record<string, unknown>
secureBoot?: Record<string, unknown>
deviceGuard?: Array<Record<string, unknown>> | Record<string, unknown>
firewallProfiles?: Array<Record<string, unknown>> | Record<string, unknown>
windowsUpdate?: Record<string, unknown>
computerSystem?: Record<string, unknown>
azureAdStatus?: Record<string, unknown>
}
type MacExtended = {
@ -742,10 +774,126 @@ function formatPercent(value?: number | null) {
return `${normalized.toFixed(0)}%`
}
function readBool(source: unknown, key: string): boolean | undefined {
if (!source || typeof source !== "object") return undefined
const value = (source as Record<string, unknown>)[key]
return typeof value === "boolean" ? value : undefined
const BADGE_POSITIVE = "gap-1 border-emerald-500/20 bg-emerald-500/15 text-emerald-700"
const BADGE_WARNING = "gap-1 border-amber-500/20 bg-amber-100 text-amber-700"
const BADGE_NEUTRAL = "gap-1 border-slate-300 bg-slate-100 text-slate-600"
function parseBooleanLike(value: unknown): boolean | undefined {
if (typeof value === "boolean") return value
if (typeof value === "number") {
if (value === 1) return true
if (value === 0) return false
}
if (typeof value === "string") {
const normalized = value.trim().toLowerCase()
if (["yes", "sim", "true", "enabled", "on", "ativo", "active", "y", "1"].includes(normalized)) return true
if (["no", "não", "nao", "false", "disabled", "off", "inativo", "not joined", "n", "0"].includes(normalized)) return false
}
return undefined
}
function toNumberArray(value: unknown): number[] {
if (!value) return []
if (Array.isArray(value)) {
return value
.map((item) => {
if (typeof item === "number") return item
if (typeof item === "string") {
const parsed = Number(item)
return Number.isNaN(parsed) ? null : parsed
}
return null
})
.filter((item): item is number => item !== null)
}
if (typeof value === "number") return [value]
if (typeof value === "string" && value.trim().length > 0) {
return value
.replace(/[^\d,]/g, " ")
.split(/[,\s]+/)
.map((part) => Number(part))
.filter((num) => !Number.isNaN(num))
}
return []
}
function describeDomainRole(role?: number | null): string | null {
if (role === null || role === undefined) return null
const map: Record<number, string> = {
0: "Estação isolada",
1: "Estação em domínio",
2: "Servidor isolado",
3: "Servidor em domínio",
4: "Controlador de domínio (backup)",
5: "Controlador de domínio (primário)",
}
return map[role] ?? `Função ${role}`
}
function describePcSystemType(code?: number | null): string | null {
if (code === null || code === undefined) return null
const map: Record<number, string> = {
0: "Não especificado",
1: "Desktop",
2: "Portátil / Laptop",
3: "Workstation",
4: "Servidor corporativo",
5: "Servidor SOHO",
8: "Tablet / Slate",
9: "Conversível",
10: "Sistema baseado em detecção",
}
return map[code] ?? `Tipo ${code}`
}
function describeDeviceGuardService(code: number): string {
const map: Record<number, string> = {
1: "Credential Guard",
2: "HVCI (Kernel Integrity)",
3: "Secure Boot com DMA",
4: "Hypervisor com Device Guard",
5: "Aplicação protegida",
}
return map[code] ?? `Serviço ${code}`
}
function describeVbsStatus(code?: number | null): string | null {
switch (code) {
case 0:
return "VBS desabilitado"
case 1:
return "VBS habilitado (inativo)"
case 2:
return "VBS ativo (sem serviços)"
case 3:
return "VBS ativo com proteções"
default:
return code != null ? `VBS status ${code}` : null
}
}
function describeAuOption(value?: number | null): string | null {
switch (value ?? -1) {
case 1:
return "Não configurado"
case 2:
return "Notificar antes de baixar"
case 3:
return "Baixar automático e notificar"
case 4:
return "Baixar e instalar automaticamente"
case 5:
return "Administrador local define"
default:
return value != null ? `Opção ${value}` : null
}
}
function describeScheduledDay(value?: number | null): string | null {
if (value === null || value === undefined) return null
if (value === 0) return "Todos os dias"
const map = ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"]
return map[value - 1] ?? `Dia ${value}`
}
function getStatusVariant(status?: string | null) {
@ -1092,6 +1240,217 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
return null
}
})()
const windowsBitLockerRaw = windowsExt?.bitLocker ?? windowsExt?.bitlocker ?? null
const windowsBitLockerVolumes = useMemo(() => {
if (Array.isArray(windowsBitLockerRaw)) return windowsBitLockerRaw
if (windowsBitLockerRaw && typeof windowsBitLockerRaw === "object") return [windowsBitLockerRaw]
return []
}, [windowsBitLockerRaw])
const windowsBitLockerSummary = useMemo(() => {
if (windowsBitLockerVolumes.length === 0) return null
let protectedCount = 0
let lockedCount = 0
windowsBitLockerVolumes.forEach((volume) => {
const record = toRecord(volume) ?? {}
const protection = readString(record, "ProtectionStatus", "protectionStatus")?.toLowerCase()
if (protection && (protection.includes("on") || protection.includes("ativo"))) {
protectedCount += 1
}
const lockStatus = readString(record, "LockStatus", "lockStatus")?.toLowerCase()
if (lockStatus && (lockStatus.includes("locked") || lockStatus.includes("bloqueado"))) {
lockedCount += 1
}
})
return { total: windowsBitLockerVolumes.length, protectedCount, lockedCount }
}, [windowsBitLockerVolumes])
const windowsTpm = toRecord(windowsExt?.tpm ?? (windowsExt as Record<string, unknown> | undefined)?.["TPM"])
const windowsSecureBoot = toRecord(windowsExt?.secureBoot ?? (windowsExt as Record<string, unknown> | undefined)?.["secureboot"])
const windowsDeviceGuardRaw = windowsExt?.deviceGuard ?? (windowsExt as Record<string, unknown> | undefined)?.["deviceguard"] ?? null
const windowsDeviceGuard = useMemo(() => {
if (Array.isArray(windowsDeviceGuardRaw)) return windowsDeviceGuardRaw
if (windowsDeviceGuardRaw && typeof windowsDeviceGuardRaw === "object") return [windowsDeviceGuardRaw]
return []
}, [windowsDeviceGuardRaw])
const windowsDeviceGuardDetails = useMemo(() => {
if (!windowsDeviceGuard.length) return null
const primary = toRecord(windowsDeviceGuard[0]) ?? {}
const configured = toNumberArray(primary?.["SecurityServicesConfigured"])
const running = toNumberArray(primary?.["SecurityServicesRunning"])
const required = toNumberArray(primary?.["RequiredSecurityProperties"])
const available = toNumberArray(primary?.["AvailableSecurityProperties"])
const vbsRaw = primary?.["VirtualizationBasedSecurityStatus"]
const vbs =
typeof vbsRaw === "number"
? vbsRaw
: typeof vbsRaw === "string" && vbsRaw.trim().length > 0
? Number(vbsRaw)
: undefined
return { primary, configured, running, required, available, vbs }
}, [windowsDeviceGuard])
const deviceGuardConfiguredLabels = windowsDeviceGuardDetails?.configured?.map((code) => describeDeviceGuardService(code)) ?? []
const deviceGuardRunningLabels = windowsDeviceGuardDetails?.running?.map((code) => describeDeviceGuardService(code)) ?? []
const windowsFirewallProfilesRaw =
windowsExt?.firewallProfiles ?? (windowsExt as Record<string, unknown> | undefined)?.["firewallprofiles"] ?? null
const windowsFirewallProfiles = useMemo(() => {
if (Array.isArray(windowsFirewallProfilesRaw)) return windowsFirewallProfilesRaw
if (windowsFirewallProfilesRaw && typeof windowsFirewallProfilesRaw === "object") return [windowsFirewallProfilesRaw]
return []
}, [windowsFirewallProfilesRaw])
const windowsUpdateSettings = toRecord(windowsExt?.windowsUpdate ?? (windowsExt as Record<string, unknown> | undefined)?.["windowsupdate"])
const windowsUpdateLastSuccess = useMemo(() => {
if (!windowsUpdateSettings) return null
const candidates = [
windowsUpdateSettings["LastSuccessTime"],
windowsUpdateSettings["lastSuccessTime"],
windowsUpdateSettings["LastSuccessTimeUtc"],
windowsUpdateSettings["lastSuccessTimeUtc"],
]
for (const candidate of candidates) {
const parsed = parseDateish(candidate)
if (parsed) return parsed
if (typeof candidate === "string" && candidate.trim().length > 0) {
const parsedIso = new Date(candidate)
if (!Number.isNaN(parsedIso.getTime())) return parsedIso
}
}
return null
}, [windowsUpdateSettings])
const windowsUpdateLastSuccessLabel = windowsUpdateLastSuccess ? formatAbsoluteDateTime(windowsUpdateLastSuccess) : null
const windowsComputerSystem = toRecord(
windowsExt?.computerSystem ?? (windowsExt as Record<string, unknown> | undefined)?.["computersystem"]
)
const windowsAzureAdStatusRaw = toRecord(
windowsExt?.azureAdStatus ?? (windowsExt as Record<string, unknown> | undefined)?.["azureadstatus"]
)
const windowsAzureAdStatus = useMemo(() => {
if (!windowsAzureAdStatusRaw) return null
return Object.entries(windowsAzureAdStatusRaw).reduce<Record<string, Record<string, unknown>>>((acc, [section, value]) => {
acc[section] = toRecord(value) ?? {}
return acc
}, {})
}, [windowsAzureAdStatusRaw])
const secureBootSupported = windowsSecureBoot
? parseBooleanLike(windowsSecureBoot["Supported"] ?? windowsSecureBoot["supported"])
: undefined
const secureBootEnabled = windowsSecureBoot
? parseBooleanLike(windowsSecureBoot["Enabled"] ?? windowsSecureBoot["enabled"])
: undefined
const secureBootError = windowsSecureBoot ? readString(windowsSecureBoot, "Error", "error") : undefined
const tpmPresent = windowsTpm ? parseBooleanLike(windowsTpm["TpmPresent"] ?? windowsTpm["tpmPresent"]) : undefined
const tpmReady = windowsTpm ? parseBooleanLike(windowsTpm["TpmReady"] ?? windowsTpm["tpmReady"]) : undefined
const tpmEnabled = windowsTpm ? parseBooleanLike(windowsTpm["TpmEnabled"] ?? windowsTpm["tpmEnabled"]) : undefined
const tpmActivated = windowsTpm ? parseBooleanLike(windowsTpm["TpmActivated"] ?? windowsTpm["tpmActivated"]) : undefined
const tpmManufacturer =
windowsTpm ? readString(windowsTpm, "ManufacturerIdTxt", "manufacturerIdTxt", "ManufacturerId", "manufacturerId") : undefined
const tpmVersion =
windowsTpm
? readString(windowsTpm, "ManufacturerVersionFull20", "manufacturerVersionFull20", "ManufacturerVersion", "manufacturerVersion", "SpecVersion")
: undefined
const windowsFirewallNormalized = useMemo(() => {
return windowsFirewallProfiles.map((profile) => {
const record = toRecord(profile) ?? {}
return {
name: readString(record, "Name", "name") ?? "Perfil",
enabled: parseBooleanLike(record["Enabled"] ?? record["enabled"]),
inboundAction: readString(record, "DefaultInboundAction", "defaultInboundAction"),
outboundAction: readString(record, "DefaultOutboundAction", "defaultOutboundAction"),
notifyOnListen: parseBooleanLike(record["NotifyOnListen"] ?? record["notifyOnListen"]),
}
})
}, [windowsFirewallProfiles])
const firewallEnabledCount = windowsFirewallNormalized.filter((profile) => profile.enabled !== false).length
const windowsUpdateMode = windowsUpdateSettings ? describeAuOption(readNumber(windowsUpdateSettings, "AUOptions", "auOptions")) : null
const windowsUpdateDay = windowsUpdateSettings
? describeScheduledDay(readNumber(windowsUpdateSettings, "ScheduledInstallDay", "scheduledInstallDay"))
: null
const windowsUpdateHourRaw = windowsUpdateSettings
? readNumber(windowsUpdateSettings, "ScheduledInstallTime", "scheduledInstallTime")
: undefined
const windowsUpdateHour =
windowsUpdateHourRaw != null ? `${windowsUpdateHourRaw.toString().padStart(2, "0")}h` : null
const windowsUpdateDisabled = windowsUpdateSettings
? parseBooleanLike(windowsUpdateSettings["NoAutoUpdate"] ?? windowsUpdateSettings["noAutoUpdate"])
: undefined
const windowsUpdateDetectionEnabled = windowsUpdateSettings
? parseBooleanLike(
windowsUpdateSettings["DetectionFrequency"] ??
windowsUpdateSettings["DetectionFrequencyEnabled"] ??
windowsUpdateSettings["detectionFrequencyEnabled"],
)
: undefined
const windowsUpdateScheduleLabel =
windowsUpdateDay || windowsUpdateHour
? `${windowsUpdateDay ?? ""}${windowsUpdateDay && windowsUpdateHour ? ` · ${windowsUpdateHour}` : windowsUpdateHour ?? ""}`.trim()
: null
const computerDomain = windowsComputerSystem ? readString(windowsComputerSystem, "Domain", "domain") : undefined
const computerWorkgroup = windowsComputerSystem ? readString(windowsComputerSystem, "Workgroup", "workgroup") : undefined
const computerPartOfDomain = windowsComputerSystem
? parseBooleanLike(windowsComputerSystem["PartOfDomain"] ?? windowsComputerSystem["partOfDomain"])
: undefined
const computerDomainRole = windowsComputerSystem
? readNumber(windowsComputerSystem, "DomainRole", "domainRole")
: undefined
const computerManufacturer = windowsComputerSystem
? readString(windowsComputerSystem, "Manufacturer", "manufacturer")
: undefined
const computerModel = windowsComputerSystem ? readString(windowsComputerSystem, "Model", "model") : undefined
const computerPcType = windowsComputerSystem
? readNumber(windowsComputerSystem, "PCSystemType", "pcSystemType", "PCSystemTypeEx", "pcSystemTypeEx")
: undefined
const computerTotalMemory = windowsComputerSystem
? readNumber(windowsComputerSystem, "TotalPhysicalMemory", "totalPhysicalMemory")
: undefined
const computerDomainRoleLabel = describeDomainRole(computerDomainRole)
const computerPcTypeLabel = describePcSystemType(computerPcType)
const computerTotalMemoryLabel = typeof computerTotalMemory === "number" ? formatBytes(computerTotalMemory) : null
const azureDeviceState = windowsAzureAdStatus ? windowsAzureAdStatus["Device State"] ?? null : null
const azureTenantDetails = windowsAzureAdStatus ? windowsAzureAdStatus["Tenant Details"] ?? null : null
const azureUserState = windowsAzureAdStatus ? windowsAzureAdStatus["User State"] ?? null : null
const azureAdJoined = azureDeviceState ? parseBooleanLike(azureDeviceState["AzureAdJoined"]) : undefined
const azureDomainJoined = azureDeviceState ? parseBooleanLike(azureDeviceState["DomainJoined"]) : undefined
const azureEnterpriseJoined = azureDeviceState ? parseBooleanLike(azureDeviceState["EnterpriseJoined"]) : undefined
const azureTenantName =
(azureDeviceState ? readString(azureDeviceState, "TenantName") : undefined) ??
(azureTenantDetails ? readString(azureTenantDetails, "TenantName") : undefined)
const azureDeviceId =
(azureDeviceState ? readString(azureDeviceState, "DeviceId") : undefined) ??
(azureTenantDetails ? readString(azureTenantDetails, "DeviceId") : undefined)
const azureUserSso = azureUserState
? readString(azureUserState, "AzureAdPrt") ??
(azureUserState["AzureAdPrt"] ? String(azureUserState["AzureAdPrt"]) : undefined)
: undefined
const defenderAntivirus = windowsExt.defender
? parseBooleanLike(windowsExt.defender["AntivirusEnabled"] ?? windowsExt.defender["antivirusEnabled"])
: undefined
const defenderRealtime = windowsExt.defender
? parseBooleanLike(
windowsExt.defender["RealTimeProtectionEnabled"] ?? windowsExt.defender["realTimeProtectionEnabled"],
)
: undefined
const defenderMode = windowsExt.defender ? readString(windowsExt.defender, "AMRunningMode", "amRunningMode") : undefined
const windowsHotfixes = useMemo(() => {
if (!Array.isArray(windowsExt?.hotfix)) return []
return windowsExt.hotfix
.map((entry) => {
const record = toRecord(entry) ?? {}
const id =
readString(record, "HotFixID", "hotFixId", "HotfixId", "Id", "id") ??
"Atualização"
const installedAt = parseDateish(record["InstalledOn"] ?? record["installedOn"])
const installedLabel = installedAt
? formatAbsoluteDateTime(installedAt)
: readString(record, "InstalledOn", "installedOn") ?? "—"
return { id, installedAt, installedLabel }
})
.sort((a, b) => {
if (a.installedAt && b.installedAt) {
return b.installedAt.getTime() - a.installedAt.getTime()
}
if (a.installedAt) return -1
if (b.installedAt) return 1
return a.id.localeCompare(b.id)
})
}, [windowsExt?.hotfix])
const osNameDisplay = useMemo(() => {
const base = machine?.osName?.trim()
const edition = windowsEditionLabel?.trim()
@ -1806,7 +2165,7 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
{/* Windows */}
{windowsExt ? (
<div className="space-y-3">
<div className="space-y-4">
{/* Cards resumidos: CPU / RAM / GPU / Discos */}
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
<Card className="border-slate-200">
@ -1862,28 +2221,371 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
</CardContent>
</Card>
</div>
{windowsOsInfo ? (
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
<p className="text-xs font-semibold uppercase text-slate-500">Informações do Windows</p>
<div className="mt-2 grid gap-1 text-sm text-muted-foreground">
<DetailLine label="Nome do dispositivo" value={windowsComputerName ?? "—"} classNameValue="break-words" />
<DetailLine label="Edição" value={windowsEditionLabel ?? "—"} classNameValue="break-words" />
<DetailLine label="Versão" value={windowsVersionLabel ?? "—"} />
<DetailLine label="Compilação do SO" value={windowsBuildLabel ?? "—"} />
<DetailLine label="Instalado em" value={windowsInstallDateLabel ?? "—"} />
<div className="grid gap-3 lg:grid-cols-2">
<Card className="border-slate-200">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-semibold">
<Monitor className="size-4 text-slate-500" />
Sistema operacional
</CardTitle>
<CardDescription>Build, licença e data de instalação</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="flex flex-wrap gap-2">
{windowsVersionLabel ? (
<Badge className={BADGE_NEUTRAL}>
<RefreshCcw className="size-3" />
{windowsVersionLabel}
</Badge>
) : null}
{windowsBuildLabel ? (
<Badge className={BADGE_NEUTRAL}>Build {windowsBuildLabel}</Badge>
) : null}
{windowsLicenseStatusLabel ? (
<Badge className={windowsActivationStatus ? BADGE_POSITIVE : BADGE_WARNING}>
{windowsActivationStatus ? <ShieldCheck className="size-3" /> : <ShieldAlert className="size-3" />}
{windowsLicenseStatusLabel}
</Badge>
) : null}
</div>
<div className="grid gap-2">
<DetailLine label="Nome do dispositivo" value={windowsComputerName ?? machine?.hostname ?? "—"} classNameValue="break-words" />
<DetailLine label="Edição" value={windowsEditionLabel ?? windowsOsInfo?.productName ?? "—"} classNameValue="break-words" />
<DetailLine label="Experiência" value={windowsExperienceLabel ?? "—"} />
<DetailLine label="Status da licença" value={windowsLicenseStatusLabel ?? "—"} classNameValue="break-words" />
<DetailLine
label="Ativação"
value={windowsActivationStatus == null ? "—" : windowsActivationStatus ? "Ativado" : "Não ativado"}
/>
<DetailLine label="Instalação" value={windowsInstallDateLabel ?? "—"} />
<DetailLine label="ID do produto" value={windowsProductId ?? "—"} classNameValue="break-words" />
<DetailLine label="Chave parcial" value={windowsPartialProductKey ?? "—"} />
<DetailLine label="Chave parcial" value={windowsPartialProductKey ?? "—"} classNameValue="break-words" />
<DetailLine label="Proprietário registrado" value={windowsRegisteredOwner ?? "—"} classNameValue="break-words" />
<DetailLine label="Número de série" value={windowsSerialNumber ?? "—"} />
</div>
</CardContent>
</Card>
<Card className="border-slate-200">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-semibold">
<Globe className="size-4 text-slate-500" />
Identidade e dispositivo
</CardTitle>
<CardDescription>Associação a domínio, Azure AD e detalhes físicos</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="grid gap-2">
<DetailLine label="Fabricante" value={computerManufacturer ?? "—"} />
<DetailLine label="Modelo" value={computerModel ?? "—"} />
<DetailLine label="Tipo de sistema" value={computerPcTypeLabel ?? "—"} />
<DetailLine label="Domínio" value={computerDomain ?? "—"} />
<DetailLine label="Workgroup" value={computerWorkgroup ?? "—"} />
<DetailLine label="Função" value={computerDomainRoleLabel ?? "—"} />
<DetailLine label="Memória física" value={computerTotalMemoryLabel ?? "—"} />
</div>
<div className="flex flex-wrap gap-2 text-xs">
{computerPartOfDomain !== undefined ? (
<Badge className={computerPartOfDomain ? BADGE_POSITIVE : BADGE_WARNING}>
{computerPartOfDomain ? <ShieldCheck className="size-3" /> : <ShieldQuestion className="size-3" />}
{computerPartOfDomain ? `Domínio ativo${computerDomain ? ` (${computerDomain})` : ""}` : "Fora de domínio"}
</Badge>
) : null}
{azureAdJoined !== undefined ? (
<Badge className={azureAdJoined ? BADGE_POSITIVE : BADGE_NEUTRAL}>
<Cloud className="size-3" />
{azureAdJoined ? "Azure AD conectado" : "Azure AD não conectado"}
</Badge>
) : null}
{azureDomainJoined !== undefined ? (
<Badge className={BADGE_NEUTRAL}>
<ShieldQuestion className="size-3" />
Domínio local: {azureDomainJoined ? "Sim" : "Não"}
</Badge>
) : null}
{azureEnterpriseJoined !== undefined ? (
<Badge className={BADGE_NEUTRAL}>
<ShieldQuestion className="size-3" />
Enterprise: {azureEnterpriseJoined ? "Sim" : "Não"}
</Badge>
) : null}
</div>
{azureTenantName ? (
<p className="text-xs text-muted-foreground">Tenant: {azureTenantName}</p>
) : null}
{azureDeviceId ? (
<p className="break-words text-xs text-muted-foreground">Device ID: {azureDeviceId}</p>
) : null}
{azureUserSso ? (
<p className="text-xs text-muted-foreground">Usuário SSO: {azureUserSso}</p>
) : null}
</CardContent>
</Card>
<Card className="border-slate-200">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-semibold">
<RefreshCcw className="size-4 text-slate-500" />
Atualizações do Windows
</CardTitle>
<CardDescription>Configurações automáticas e histórico recente</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="flex flex-wrap gap-2 text-xs">
{windowsUpdateDisabled !== undefined ? (
<Badge className={windowsUpdateDisabled ? BADGE_WARNING : BADGE_POSITIVE}>
{windowsUpdateDisabled ? <ShieldOff className="size-3" /> : <ShieldCheck className="size-3" />}
{windowsUpdateDisabled ? "Atualizações desativadas" : "Atualizações automáticas"}
</Badge>
) : null}
{windowsUpdateDetectionEnabled === false ? (
<Badge className={BADGE_WARNING}>
<AlertTriangle className="size-3" />
Sem detecção automática
</Badge>
) : null}
</div>
<div className="grid gap-2">
<DetailLine label="Modo" value={windowsUpdateMode ?? "—"} />
<DetailLine label="Agendamento" value={windowsUpdateScheduleLabel ?? "—"} />
<DetailLine label="Último sucesso" value={windowsUpdateLastSuccessLabel ?? "—"} />
</div>
{windowsHotfixes.length > 0 ? (
<div className="space-y-1">
<Separator className="my-2" />
<p className="text-xs font-semibold uppercase text-slate-500">Atualizações recentes</p>
<ul className="space-y-1 text-xs text-muted-foreground">
{windowsHotfixes.slice(0, 3).map((fix) => (
<li key={fix.id} className="flex items-center justify-between gap-3">
<span className="font-medium text-foreground">{fix.id}</span>
<span>{fix.installedLabel}</span>
</li>
))}
</ul>
</div>
) : null}
</CardContent>
</Card>
<Card className="border-slate-200 lg:col-span-2">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-semibold">
<Shield className="size-4 text-slate-500" />
Segurança do dispositivo
</CardTitle>
<CardDescription>Proteções do Windows, criptografia e firewall</CardDescription>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<div className="flex flex-wrap gap-2 text-xs">
{windowsActivationStatus !== null && windowsActivationStatus !== undefined ? (
<Badge className={windowsActivationStatus ? BADGE_POSITIVE : BADGE_WARNING}>
{windowsActivationStatus ? <ShieldCheck className="size-3" /> : <ShieldAlert className="size-3" />}
{windowsActivationStatus ? "Licença ativada" : "Licença pendente"}
</Badge>
) : null}
{defenderAntivirus !== undefined ? (
<Badge className={defenderAntivirus ? BADGE_POSITIVE : BADGE_WARNING}>
{defenderAntivirus ? <ShieldCheck className="size-3" /> : <ShieldAlert className="size-3" />}
Antivírus {defenderAntivirus ? "ativo" : "inativo"}
</Badge>
) : null}
{defenderRealtime !== undefined ? (
<Badge className={defenderRealtime ? BADGE_POSITIVE : BADGE_WARNING}>
{defenderRealtime ? <ShieldCheck className="size-3" /> : <ShieldAlert className="size-3" />}
Tempo real {defenderRealtime ? "ativo" : "inativo"}
</Badge>
) : null}
{secureBootSupported !== undefined ? (
<Badge
className={
secureBootEnabled === true
? BADGE_POSITIVE
: secureBootSupported
? BADGE_WARNING
: BADGE_NEUTRAL
}
>
{secureBootEnabled
? <Shield className="size-3" />
: secureBootSupported
? <ShieldQuestion className="size-3" />
: <ShieldOff className="size-3" />}
{secureBootSupported
? secureBootEnabled === true
? "Secure Boot ativo"
: "Secure Boot desabilitado"
: "Secure Boot não suportado"}
</Badge>
) : null}
{windowsBitLockerSummary ? (
<Badge
className={
windowsBitLockerSummary.protectedCount === windowsBitLockerSummary.total
? BADGE_POSITIVE
: BADGE_WARNING
}
>
<Lock className="size-3" />
{windowsBitLockerSummary.protectedCount}/{windowsBitLockerSummary.total} BitLocker
</Badge>
) : null}
{tpmPresent !== undefined ? (
<Badge className={tpmPresent ? BADGE_POSITIVE : BADGE_WARNING}>
<Key className="size-3" />
{tpmPresent ? (tpmReady ? "TPM pronto" : "TPM detectado") : "TPM ausente"}
</Badge>
) : null}
{windowsDeviceGuardDetails ? (
<Badge className={windowsDeviceGuardDetails.vbs === 3 ? BADGE_POSITIVE : BADGE_NEUTRAL}>
<ShieldQuestion className="size-3" />
{describeVbsStatus(windowsDeviceGuardDetails.vbs) ?? "VBS desconhecido"}
</Badge>
) : null}
{windowsFirewallNormalized.length > 0 ? (
<Badge
className={
firewallEnabledCount === windowsFirewallNormalized.length ? BADGE_POSITIVE : BADGE_WARNING
}
>
<Shield className="size-3" />
Firewall {firewallEnabledCount}/{windowsFirewallNormalized.length}
</Badge>
) : null}
</div>
{windowsExt.defender ? (
<div>
<p className="text-xs font-semibold uppercase text-slate-500">Defender</p>
<div className="mt-1 grid gap-1 text-xs text-muted-foreground">
<span>Modo: {defenderMode ?? "—"}</span>
<span>
Antivírus: {defenderAntivirus === undefined ? "—" : defenderAntivirus ? "Ativo" : "Inativo"}
</span>
<span>
Tempo real: {defenderRealtime === undefined ? "—" : defenderRealtime ? "Ativo" : "Inativo"}
</span>
</div>
</div>
) : null}
{secureBootError ? (
<div className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 p-3 text-xs text-amber-700">
<AlertTriangle className="mt-0.5 size-3 shrink-0" />
<span>{secureBootError}</span>
</div>
) : null}
{windowsBitLockerVolumes.length > 0 ? (
<div>
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-semibold uppercase text-slate-500">BitLocker</p>
<Badge variant="outline" className="text-[11px] text-slate-600">
{windowsBitLockerSummary
? `${windowsBitLockerSummary.protectedCount}/${windowsBitLockerSummary.total} protegidos`
: `${windowsBitLockerVolumes.length} volumes`}
</Badge>
</div>
<div className="mt-2 overflow-hidden rounded-md border border-slate-200">
<Table>
<TableHeader>
<TableRow className="border-slate-200 bg-slate-100/80">
<TableHead className="text-xs text-slate-500">Volume</TableHead>
<TableHead className="text-xs text-slate-500">Proteção</TableHead>
<TableHead className="text-xs text-slate-500">Bloqueio</TableHead>
<TableHead className="text-xs text-slate-500">Método</TableHead>
<TableHead className="text-xs text-slate-500">% Cript.</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{windowsBitLockerVolumes.map((volume, idx) => {
const record = toRecord(volume) ?? {}
const mount = readString(record, "MountPoint", "mountPoint") ?? "—"
const protection = readString(record, "ProtectionStatus", "protectionStatus") ?? "—"
const lock = readString(record, "LockStatus", "lockStatus") ?? "—"
const method = readString(record, "EncryptionMethod", "encryptionMethod") ?? "—"
const percent = readNumber(record, "EncryptionPercentage", "encryptionPercentage")
return (
<TableRow key={`bit-${idx}`} className="border-slate-100">
<TableCell className="text-sm">{mount}</TableCell>
<TableCell className="text-sm text-muted-foreground">{protection}</TableCell>
<TableCell className="text-sm text-muted-foreground">{lock}</TableCell>
<TableCell className="text-sm text-muted-foreground">{method}</TableCell>
<TableCell className="text-sm text-muted-foreground">
{percent != null ? formatPercent(percent) : "—"}
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
</div>
) : null}
{windowsTpm ? (
<div className="grid gap-1 text-xs text-muted-foreground">
<p className="text-xs font-semibold uppercase text-slate-500">TPM</p>
<span>Fabricante: {tpmManufacturer ?? "—"}</span>
<span>Versão: {tpmVersion ?? "—"}</span>
<span>
Status:{" "}
{tpmPresent === undefined
? "—"
: [
tpmPresent ? "Presente" : "Ausente",
tpmEnabled ? "Habilitado" : "Desabilitado",
tpmReady ? "Pronto" : "Não pronto",
tpmActivated ? "Ativado" : "Inativo",
].join(" · ")}
</span>
</div>
) : null}
{windowsDeviceGuardDetails ? (
<div className="grid gap-1 text-xs text-muted-foreground">
<p className="text-xs font-semibold uppercase text-slate-500">Device Guard</p>
<span>{describeVbsStatus(windowsDeviceGuardDetails.vbs) ?? "—"}</span>
{deviceGuardConfiguredLabels.length > 0 ? (
<span>Configurado: {deviceGuardConfiguredLabels.join(", ")}</span>
) : null}
{deviceGuardRunningLabels.length > 0 ? (
<span>Em execução: {deviceGuardRunningLabels.join(", ")}</span>
) : null}
</div>
) : null}
{windowsFirewallNormalized.length > 0 ? (
<div>
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-semibold uppercase text-slate-500">Perfis de firewall</p>
<Badge variant="outline" className="text-[11px] text-slate-600">
{firewallEnabledCount}/{windowsFirewallNormalized.length} ativos
</Badge>
</div>
<div className="mt-2 overflow-hidden rounded-md border border-slate-200">
<Table>
<TableHeader>
<TableRow className="border-slate-200 bg-slate-100/80">
<TableHead className="text-xs text-slate-500">Perfil</TableHead>
<TableHead className="text-xs text-slate-500">Entrada</TableHead>
<TableHead className="text-xs text-slate-500">Saída</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{windowsFirewallNormalized.map((profile, idx) => (
<TableRow key={`fw-${idx}`} className="border-slate-100">
<TableCell className="text-sm">{profile.name}</TableCell>
<TableCell className="text-sm text-muted-foreground">
{profile.inboundAction ?? "—"}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{profile.outboundAction ?? "—"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
) : null}
</CardContent>
</Card>
</div>
{windowsCpuDetails.length > 0 ? (
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
@ -2120,31 +2822,6 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
</div>
) : null}
{windowsExt.defender ? (
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
<p className="text-xs font-semibold uppercase text-slate-500">Defender</p>
<div className="mt-2 flex flex-wrap gap-2 text-xs">
{readBool(windowsExt.defender, "AntivirusEnabled") === true ? (
<Badge className="gap-1 border-emerald-500/20 bg-emerald-500/15 text-emerald-700">
<ShieldCheck className="size-3" /> Antivírus: Ativo
</Badge>
) : (
<Badge className="gap-1 border-rose-500/20 bg-rose-500/15 text-rose-700">
<ShieldAlert className="size-3" /> Antivírus: Inativo
</Badge>
)}
{readBool(windowsExt.defender, "RealTimeProtectionEnabled") === true ? (
<Badge className="gap-1 border-emerald-500/20 bg-emerald-500/15 text-emerald-700">
<ShieldCheck className="size-3" /> Proteção em tempo real: Ativa
</Badge>
) : (
<Badge className="gap-1 border-rose-500/20 bg-rose-500/15 text-rose-700">
<ShieldAlert className="size-3" /> Proteção em tempo real: Inativa
</Badge>
)}
</div>
</div>
) : null}
</div>
) : null}