Enrich Windows diagnostics and admin UI
This commit is contained in:
parent
49496f3663
commit
037891485d
2 changed files with 838 additions and 53 deletions
|
|
@ -792,6 +792,106 @@ fn collect_windows_extended() -> serde_json::Value {
|
||||||
ps("@(Get-Service | Select-Object Name,Status,DisplayName)").unwrap_or_else(|| json!([]));
|
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 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 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
|
// Informações de build/edição e ativação
|
||||||
let os_info = ps(r#"
|
let os_info = ps(r#"
|
||||||
|
|
@ -847,6 +947,14 @@ fn collect_windows_extended() -> serde_json::Value {
|
||||||
"memoryModules": memory,
|
"memoryModules": memory,
|
||||||
"videoControllers": video,
|
"videoControllers": video,
|
||||||
"disks": disks,
|
"disks": disks,
|
||||||
|
"bitLocker": bitlocker,
|
||||||
|
"tpm": tpm,
|
||||||
|
"secureBoot": secure_boot,
|
||||||
|
"deviceGuard": device_guard,
|
||||||
|
"firewallProfiles": firewall_profiles,
|
||||||
|
"windowsUpdate": windows_update,
|
||||||
|
"computerSystem": computer_system,
|
||||||
|
"azureAdStatus": device_join,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,31 @@ import { useQuery } from "convex/react"
|
||||||
import { format, formatDistanceToNowStrict } from "date-fns"
|
import { format, formatDistanceToNowStrict } from "date-fns"
|
||||||
import { ptBR } from "date-fns/locale"
|
import { ptBR } from "date-fns/locale"
|
||||||
import { toast } from "sonner"
|
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 { api } from "@/convex/_generated/api"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
|
@ -147,6 +171,14 @@ type WindowsExtended = {
|
||||||
videoControllers?: WindowsVideoController[]
|
videoControllers?: WindowsVideoController[]
|
||||||
disks?: WindowsDiskEntry[]
|
disks?: WindowsDiskEntry[]
|
||||||
osInfo?: WindowsOsInfo
|
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 = {
|
type MacExtended = {
|
||||||
|
|
@ -742,10 +774,126 @@ function formatPercent(value?: number | null) {
|
||||||
return `${normalized.toFixed(0)}%`
|
return `${normalized.toFixed(0)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function readBool(source: unknown, key: string): boolean | undefined {
|
const BADGE_POSITIVE = "gap-1 border-emerald-500/20 bg-emerald-500/15 text-emerald-700"
|
||||||
if (!source || typeof source !== "object") return undefined
|
const BADGE_WARNING = "gap-1 border-amber-500/20 bg-amber-100 text-amber-700"
|
||||||
const value = (source as Record<string, unknown>)[key]
|
const BADGE_NEUTRAL = "gap-1 border-slate-300 bg-slate-100 text-slate-600"
|
||||||
return typeof value === "boolean" ? value : undefined
|
|
||||||
|
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) {
|
function getStatusVariant(status?: string | null) {
|
||||||
|
|
@ -1092,6 +1240,217 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
||||||
return null
|
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 osNameDisplay = useMemo(() => {
|
||||||
const base = machine?.osName?.trim()
|
const base = machine?.osName?.trim()
|
||||||
const edition = windowsEditionLabel?.trim()
|
const edition = windowsEditionLabel?.trim()
|
||||||
|
|
@ -1806,7 +2165,7 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
||||||
|
|
||||||
{/* Windows */}
|
{/* Windows */}
|
||||||
{windowsExt ? (
|
{windowsExt ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
{/* Cards resumidos: CPU / RAM / GPU / Discos */}
|
{/* Cards resumidos: CPU / RAM / GPU / Discos */}
|
||||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||||
<Card className="border-slate-200">
|
<Card className="border-slate-200">
|
||||||
|
|
@ -1862,28 +2221,371 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
{windowsOsInfo ? (
|
<div className="grid gap-3 lg:grid-cols-2">
|
||||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
<Card className="border-slate-200">
|
||||||
<p className="text-xs font-semibold uppercase text-slate-500">Informações do Windows</p>
|
<CardHeader className="pb-2">
|
||||||
<div className="mt-2 grid gap-1 text-sm text-muted-foreground">
|
<CardTitle className="flex items-center gap-2 text-sm font-semibold">
|
||||||
<DetailLine label="Nome do dispositivo" value={windowsComputerName ?? "—"} classNameValue="break-words" />
|
<Monitor className="size-4 text-slate-500" />
|
||||||
<DetailLine label="Edição" value={windowsEditionLabel ?? "—"} classNameValue="break-words" />
|
Sistema operacional
|
||||||
<DetailLine label="Versão" value={windowsVersionLabel ?? "—"} />
|
</CardTitle>
|
||||||
<DetailLine label="Compilação do SO" value={windowsBuildLabel ?? "—"} />
|
<CardDescription>Build, licença e data de instalação</CardDescription>
|
||||||
<DetailLine label="Instalado em" value={windowsInstallDateLabel ?? "—"} />
|
</CardHeader>
|
||||||
<DetailLine label="Experiência" value={windowsExperienceLabel ?? "—"} />
|
<CardContent className="space-y-3 text-sm">
|
||||||
<DetailLine label="Status da licença" value={windowsLicenseStatusLabel ?? "—"} classNameValue="break-words" />
|
<div className="flex flex-wrap gap-2">
|
||||||
<DetailLine
|
{windowsVersionLabel ? (
|
||||||
label="Ativação"
|
<Badge className={BADGE_NEUTRAL}>
|
||||||
value={windowsActivationStatus == null ? "—" : windowsActivationStatus ? "Ativado" : "Não ativado"}
|
<RefreshCcw className="size-3" />
|
||||||
/>
|
{windowsVersionLabel}
|
||||||
<DetailLine label="ID do produto" value={windowsProductId ?? "—"} classNameValue="break-words" />
|
</Badge>
|
||||||
<DetailLine label="Chave parcial" value={windowsPartialProductKey ?? "—"} />
|
) : null}
|
||||||
<DetailLine label="Proprietário registrado" value={windowsRegisteredOwner ?? "—"} classNameValue="break-words" />
|
{windowsBuildLabel ? (
|
||||||
<DetailLine label="Número de série" value={windowsSerialNumber ?? "—"} />
|
<Badge className={BADGE_NEUTRAL}>Build {windowsBuildLabel}</Badge>
|
||||||
</div>
|
) : null}
|
||||||
</div>
|
{windowsLicenseStatusLabel ? (
|
||||||
) : null}
|
<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="Instalação" value={windowsInstallDateLabel ?? "—"} />
|
||||||
|
<DetailLine label="ID do produto" value={windowsProductId ?? "—"} classNameValue="break-words" />
|
||||||
|
<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 ? (
|
{windowsCpuDetails.length > 0 ? (
|
||||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
|
||||||
|
|
@ -2120,31 +2822,6 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue