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!([]));
|
||||
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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ?? "—"} />
|
||||
<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="ID do produto" value={windowsProductId ?? "—"} classNameValue="break-words" />
|
||||
<DetailLine label="Chave parcial" value={windowsPartialProductKey ?? "—"} />
|
||||
<DetailLine label="Proprietário registrado" value={windowsRegisteredOwner ?? "—"} classNameValue="break-words" />
|
||||
<DetailLine label="Número de série" value={windowsSerialNumber ?? "—"} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<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="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 ? (
|
||||
<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}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue