sistema-de-chamados/src/server/machines/inventory-export.ts
esdrasrenan 3b1cde79df Melhora chat ao vivo com anexos e eventos de timeline
- Reestrutura visual do widget de chat (header branco, status emerald)
- Adiciona sistema de anexos com upload e drag-and-drop
- Substitui select nativo por componente Select do shadcn
- Adiciona eventos LIVE_CHAT_STARTED e LIVE_CHAT_ENDED na timeline
- Traduz labels de chat para portugues (Chat iniciado/finalizado)
- Filtra CHAT_MESSAGE_ADDED da timeline (apenas inicio/fim aparecem)
- Restringe inicio de chat a tickets com responsavel atribuido
- Exibe duracao da sessao ao finalizar chat

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 02:20:11 -03:00

2109 lines
71 KiB
TypeScript

import type { Id } from "@/convex/_generated/dataModel"
import { DEVICE_INVENTORY_COLUMN_METADATA, type DeviceInventoryColumnConfig } from "@/lib/device-inventory-columns"
import { buildXlsxWorkbook, type WorksheetConfig } from "@/lib/xlsx"
type DeviceCustomField = {
fieldId: Id<"deviceFields">
fieldKey: string
label: string
type: string
value: unknown
displayValue?: string
}
type LinkedUser = {
id: string
email: string | null
name: string | null
}
export type MachineInventoryRecord = {
id: Id<"machines">
tenantId: string
hostname: string
displayName: string | null
deviceType: string | null
devicePlatform: string | null
deviceProfile?: Record<string, unknown> | null
managementMode: string | null
companyId: Id<"companies"> | null
companySlug: string | null
companyName: string | null
status: string | null
isActive: boolean
lastHeartbeatAt: number | null
persona: string | null
assignedUserName: string | null
assignedUserEmail: string | null
authEmail: string | null
osName: string
osVersion: string | null
architecture: string | null
macAddresses: string[]
serialNumbers: string[]
registeredBy: string | null
createdAt: number
updatedAt: number
token: { expiresAt: number; usageCount: number; lastUsedAt: number | null } | null
inventory: Record<string, unknown> | null
metrics?: Record<string, unknown> | null
postureAlerts?: Array<Record<string, unknown>> | null
lastPostureAt?: number | null
remoteAccess?: unknown
linkedUsers?: LinkedUser[]
customFields?: DeviceCustomField[]
usbPolicy?: string | null
usbPolicyStatus?: string | null
ticketCount?: number | null
}
type WorkbookOptions = {
tenantId: string
generatedBy?: string | null
companyFilterLabel?: string | null
generatedAt?: Date
columns?: DeviceInventoryColumnConfig[]
}
type SoftwareEntry = {
hostname: string
name: string
version: string | null
source: string | null
publisher: string | null
installedOn: string | null
}
type InventoryColumnDefinition = {
key: string
label: string
width: number
getValue: (machine: MachineInventoryRecord, derived: MachineDerivedData) => unknown
}
type MachineDerivedData = {
inventory: Record<string, unknown>
hardware: ReturnType<typeof extractHardware>
gpuNames: string[]
labels: string[]
primaryIp: string | null
publicIp: string | null
softwareEntries: SoftwareEntry[]
linkedUsersLabel: string | null
systemInfo: ReturnType<typeof extractSystemInfo>
collaborator: ReturnType<typeof extractCollaborator>
remoteAccessCount: number
fleetInfo: ReturnType<typeof extractFleetInfo>
customFieldByKey: Record<string, DeviceCustomField>
customFieldById: Record<string, DeviceCustomField>
}
const COLUMN_VALUE_RESOLVERS: Record<string, (machine: MachineInventoryRecord, derived: MachineDerivedData) => unknown> = {
displayName: (machine) => machine.displayName ?? machine.hostname,
hostname: (machine) => machine.hostname,
deviceType: (machine) => describeDeviceType(machine.deviceType),
devicePlatform: (machine) => machine.devicePlatform ?? null,
company: (machine) => machine.companyName ?? null,
status: (machine) => describeStatus(machine.status),
persona: (machine) => describePersona(machine.persona),
active: (machine) => yesNo(machine.isActive),
lastHeartbeat: (machine) => formatDateTime(machine.lastHeartbeatAt),
assignedUser: (machine) => machine.assignedUserName ?? machine.assignedUserEmail ?? null,
assignedEmail: (machine) => machine.assignedUserEmail ?? null,
linkedUsers: (_machine, derived) => derived.linkedUsersLabel,
authEmail: (machine) => machine.authEmail ?? null,
osName: (machine) => machine.osName,
osVersion: (machine) => machine.osVersion ?? null,
architecture: (machine) => machine.architecture ?? null,
hardwareVendor: (_machine, derived) => derived.hardware.vendor ?? derived.systemInfo.systemManufacturer ?? null,
hardwareModel: (_machine, derived) => derived.hardware.model ?? derived.systemInfo.systemModel ?? null,
hardwareSerial: (_machine, derived) => derived.hardware.serial ?? derived.systemInfo.boardSerial ?? null,
cpu: (_machine, derived) => derived.hardware.cpuType ?? null,
physicalCores: (_machine, derived) => derived.hardware.physicalCores ?? null,
logicalCores: (_machine, derived) => derived.hardware.logicalCores ?? null,
memoryGiB: (_machine, derived) => derived.hardware.memoryGiB ?? null,
gpus: (_machine, derived) => (derived.gpuNames.length > 0 ? derived.gpuNames.join(", ") : null),
labels: (_machine, derived) => (derived.labels.length > 0 ? derived.labels.join(", ") : null),
macs: (machine) => (machine.macAddresses.length > 0 ? machine.macAddresses.join(", ") : null),
serials: (machine) => (machine.serialNumbers.length > 0 ? machine.serialNumbers.join(", ") : null),
primaryIp: (_machine, derived) => derived.primaryIp ?? null,
publicIp: (_machine, derived) => derived.publicIp ?? null,
registeredBy: (machine) => machine.registeredBy ?? null,
tokenExpiresAt: (machine) => (machine.token?.expiresAt ? formatDateTime(machine.token.expiresAt) : null),
tokenLastUsedAt: (machine) => (machine.token?.lastUsedAt ? formatDateTime(machine.token.lastUsedAt) : null),
tokenUsageCount: (machine) => machine.token?.usageCount ?? 0,
createdAt: (machine) => formatDateTime(machine.createdAt),
updatedAt: (machine) => formatDateTime(machine.updatedAt),
softwareCount: (_machine, derived) => derived.softwareEntries.length,
osBuild: (_machine, derived) => derived.systemInfo.osBuild ?? null,
osLicense: (_machine, derived) => derived.systemInfo.license ?? null,
osExperience: (_machine, derived) => derived.systemInfo.experience ?? null,
domain: (_machine, derived) => derived.systemInfo.domain ?? null,
workgroup: (_machine, derived) => derived.systemInfo.workgroup ?? null,
deviceName: (_machine, derived) => derived.systemInfo.deviceName ?? null,
boardSerial: (_machine, derived) => derived.systemInfo.boardSerial ?? derived.hardware.serial ?? null,
collaboratorName: (_machine, derived) => derived.collaborator?.name ?? null,
collaboratorEmail: (_machine, derived) => derived.collaborator?.email ?? null,
remoteAccessCount: (_machine, derived) => derived.remoteAccessCount,
fleetId: (_machine, derived) => derived.fleetInfo?.id ?? null,
fleetTeam: (_machine, derived) => derived.fleetInfo?.team ?? null,
fleetUpdatedAt: (_machine, derived) =>
derived.fleetInfo?.updatedAt ? formatDateTime(derived.fleetInfo.updatedAt) : null,
managementMode: (machine) => describeManagementMode(machine.managementMode),
usbPolicy: (machine) => describeUsbPolicy(machine.usbPolicy),
usbPolicyStatus: (machine) => describeUsbPolicyStatus(machine.usbPolicyStatus),
ticketCount: (machine) => machine.ticketCount ?? 0,
}
const DEFAULT_COLUMN_CONFIG: DeviceInventoryColumnConfig[] = DEVICE_INVENTORY_COLUMN_METADATA.filter((meta) => meta.default !== false).map(
(meta) => ({ key: meta.key })
)
const INVENTORY_COLUMN_DEFINITIONS: InventoryColumnDefinition[] = DEVICE_INVENTORY_COLUMN_METADATA.map((meta) => ({
key: meta.key,
label: meta.label,
width: meta.width,
getValue: COLUMN_VALUE_RESOLVERS[meta.key] ?? (() => null),
}))
const INVENTORY_COLUMN_MAP: Record<string, InventoryColumnDefinition> = Object.fromEntries(
INVENTORY_COLUMN_DEFINITIONS.map((column) => [column.key, column]),
)
const CUSTOM_FIELD_KEY_PREFIX = "custom:"
const CUSTOM_FIELD_ID_PREFIX = "custom#"
function deriveMachineData(machine: MachineInventoryRecord): MachineDerivedData {
const inventory = toRecord(machine.inventory) ?? {}
const hardware = extractHardware(inventory)
const gpuNames = extractGpuNames(inventory)
const labels = extractLabels(inventory)
const primaryIp = extractPrimaryIp(inventory)
const publicIp = extractPublicIp(inventory)
const softwareEntries = extractSoftwareEntries(machine.hostname, inventory)
const linkedUsersLabel = summarizeLinkedUsers(machine.linkedUsers)
const systemInfo = extractSystemInfo(inventory)
const collaborator = extractCollaborator(machine, inventory)
const remoteAccessCount = collectRemoteAccessEntries(machine).length
const fleetInfo = extractFleetInfo(inventory)
const customFieldByKey: Record<string, DeviceCustomField> = {}
const customFieldById: Record<string, DeviceCustomField> = {}
for (const field of machine.customFields ?? []) {
customFieldByKey[field.fieldKey] = field
customFieldById[String(field.fieldId)] = field
}
return {
inventory,
hardware,
gpuNames,
labels,
primaryIp,
publicIp,
softwareEntries,
linkedUsersLabel,
systemInfo,
collaborator,
remoteAccessCount,
fleetInfo,
customFieldByKey,
customFieldById,
}
}
export function normalizeInventoryColumnConfig(columns?: DeviceInventoryColumnConfig[]): DeviceInventoryColumnConfig[] {
if (!columns || columns.length === 0) {
return [...DEFAULT_COLUMN_CONFIG]
}
const seen = new Set<string>()
const normalized: DeviceInventoryColumnConfig[] = []
for (const column of columns) {
const key = column.key.trim()
if (!key) continue
if (seen.has(key)) continue
if (!isCustomFieldKey(key) && !INVENTORY_COLUMN_MAP[key]) continue
seen.add(key)
normalized.push({
key,
label: column.label?.trim() || undefined,
})
}
return normalized.length > 0 ? normalized : [...DEFAULT_COLUMN_CONFIG]
}
function resolveColumnLabel(column: DeviceInventoryColumnConfig, derivedList: MachineDerivedData[]): string {
if (column.label) return column.label
const definition = INVENTORY_COLUMN_MAP[column.key]
if (definition) return definition.label
if (isCustomFieldKey(column.key)) {
for (const derived of derivedList) {
const field = lookupCustomField(column.key, derived)
if (field) return field.label
}
return "Campo personalizado"
}
return column.key
}
function resolveColumnWidth(key: string): number {
const definition = INVENTORY_COLUMN_MAP[key]
if (definition) return definition.width
if (isCustomFieldKey(key)) return 28
return 20
}
function isCustomFieldKey(key: string): boolean {
return key.startsWith(CUSTOM_FIELD_KEY_PREFIX) || key.startsWith(CUSTOM_FIELD_ID_PREFIX)
}
function lookupCustomField(key: string, derived: MachineDerivedData): DeviceCustomField | null {
if (key.startsWith(CUSTOM_FIELD_KEY_PREFIX)) {
const fieldKey = key.slice(CUSTOM_FIELD_KEY_PREFIX.length)
return derived.customFieldByKey[fieldKey] ?? null
}
if (key.startsWith(CUSTOM_FIELD_ID_PREFIX)) {
const fieldId = key.slice(CUSTOM_FIELD_ID_PREFIX.length)
return derived.customFieldById[fieldId] ?? null
}
return null
}
function formatCustomFieldValue(field: DeviceCustomField): string {
if (field.value === null || field.value === undefined) {
return "—"
}
if (field.displayValue !== undefined && field.displayValue !== null) {
const text = String(field.displayValue).trim()
if (text.length > 0) return text
}
switch (field.type) {
case "boolean":
return yesNo(Boolean(field.value))
case "number": {
const num = typeof field.value === "number" ? field.value : Number(field.value)
return Number.isFinite(num) ? String(num) : String(field.value)
}
case "date": {
const date = new Date(field.value as string | number)
if (Number.isNaN(date.getTime())) return String(field.value)
return date.toISOString().slice(0, 10)
}
default:
return String(field.value)
}
}
function resolveColumnValue(
key: string,
machine: MachineInventoryRecord,
derived: MachineDerivedData
): unknown {
if (isCustomFieldKey(key)) {
const field = lookupCustomField(key, derived)
if (!field) return "—"
return formatCustomFieldValue(field)
}
const definition = INVENTORY_COLUMN_MAP[key]
if (!definition) return "—"
return definition.getValue(machine, derived)
}
function formatInventoryCell(value: unknown): unknown {
if (value === null || value === undefined) return "—"
if (typeof value === "string") {
const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : "—"
}
return value
}
function describeDeviceType(type: string | null | undefined): string {
const normalized = (type ?? "").toLowerCase()
switch (normalized) {
case "desktop":
return "Desktop"
case "mobile":
return "Celular"
case "tablet":
return "Tablet"
default:
return "Desconhecido"
}
}
function describeManagementMode(mode: string | null | undefined): string {
const normalized = (mode ?? "").toLowerCase()
switch (normalized) {
case "agent":
return "Agente"
case "manual":
return "Manual"
default:
return "—"
}
}
function describeUsbPolicy(policy: string | null | undefined): string {
if (!policy) return "—"
const normalized = policy.toUpperCase()
switch (normalized) {
case "ALLOW":
return "Permitido"
case "BLOCK_ALL":
return "Bloqueado"
case "READONLY":
return "Somente leitura"
default:
return policy
}
}
function describeUsbPolicyStatus(status: string | null | undefined): string {
if (!status) return "—"
const normalized = status.toUpperCase()
switch (normalized) {
case "PENDING":
return "Pendente"
case "APPLYING":
return "Aplicando"
case "APPLIED":
return "Aplicado"
case "FAILED":
return "Falhou"
default:
return status
}
}
const SOFTWARE_HEADERS = ["Hostname", "Aplicativo", "Versão", "Origem", "Publicador", "Instalado em"] as const
const SOFTWARE_COLUMN_WIDTHS = [22, 36, 18, 18, 22, 20] as const
const PARTITION_HEADERS = [
"Hostname",
"Nome",
"Montagem",
"FS",
"Capacidade",
"Livre",
"Utilizado",
"Interface",
"Serial",
"Origem",
] as const
const PARTITION_COLUMN_WIDTHS = [22, 24, 18, 12, 16, 16, 16, 16, 20, 18] as const
const PHYSICAL_DISK_HEADERS = ["Hostname", "Modelo", "Tamanho", "Interface", "Tipo", "Serial", "Origem"] as const
const PHYSICAL_DISK_COLUMN_WIDTHS = [22, 32, 18, 16, 16, 22, 16] as const
const NETWORK_HEADERS = ["Hostname", "Interface", "MAC", "IP", "Origem"] as const
const NETWORK_COLUMN_WIDTHS = [22, 28, 22, 24, 18] as const
const REMOTE_ACCESS_HEADERS = [
"Hostname",
"Provedor",
"Identificador",
"Usuário",
"Senha",
"URL",
"Notas",
"Última verificação",
"Origem",
"Metadados",
] as const
const REMOTE_ACCESS_COLUMN_WIDTHS = [22, 22, 24, 24, 20, 32, 28, 22, 16, 36] as const
const SERVICE_HEADERS = ["Hostname", "Nome", "Exibição", "Status", "Origem"] as const
const SERVICE_COLUMN_WIDTHS = [22, 28, 36, 18, 18] as const
const ALERT_HEADERS = ["Hostname", "Tipo", "Mensagem", "Severidade", "Criado em"] as const
const ALERT_COLUMN_WIDTHS = [22, 18, 44, 14, 22] as const
const METRICS_HEADERS = [
"Hostname",
"Capturado em",
"CPU (%)",
"Memória usada",
"Memória total",
"Memória (%)",
"Disco usado",
"Disco total",
"Disco (%)",
"GPU (%)",
] as const
const METRICS_COLUMN_WIDTHS = [22, 24, 14, 20, 20, 14, 20, 20, 14, 14] as const
const LABEL_HEADERS = ["Hostname", "Label"] as const
const LABEL_COLUMN_WIDTHS = [22, 30] as const
const SYSTEM_HEADERS = ["Hostname", "Categoria", "Campo", "Valor"] as const
const SYSTEM_COLUMN_WIDTHS = [22, 24, 32, 44] as const
const STATUS_LABELS: Record<string, string> = {
online: "Online",
offline: "Offline",
stale: "Sem sinal",
maintenance: "Manutenção",
blocked: "Bloqueada",
deactivated: "Desativada",
unknown: "Desconhecido",
}
const PERSONA_LABELS: Record<string, string> = {
collaborator: "Colaborador",
manager: "Gestor",
machine: "Dispositivo",
}
const SUMMARY_STATUS_ORDER = ["Online", "Sem sinal", "Offline", "Manutenção", "Bloqueada", "Desativada", "Desconhecido"]
type WorksheetRow = Array<unknown>
export function buildMachinesInventoryWorkbook(
machines: MachineInventoryRecord[],
options: WorkbookOptions,
): Buffer {
const generatedAt = options.generatedAt ?? new Date()
const summaryRows = buildSummaryRows(machines, options, generatedAt)
const inventorySheet = buildInventoryWorksheet(machines, options.columns)
const linksRows = buildLinkedUsersRows(machines)
const softwareRows = buildSoftwareRows(machines)
const partitionRows = buildPartitionRows(machines)
const physicalDiskRows = buildPhysicalDiskRows(machines)
const networkRows = buildNetworkRows(machines)
const remoteAccessRows = buildRemoteAccessRows(machines)
const serviceRows = buildServiceRows(machines)
const alertRows = buildAlertRows(machines)
const metricsRows = buildMetricsRows(machines)
const labelRows = buildLabelRows(machines)
const systemRows = buildSystemRows(machines)
const sheets: WorksheetConfig[] = []
sheets.push({
name: "Resumo",
headers: ["Item", "Valor"],
rows: summaryRows,
columnWidths: [28, 48],
})
sheets.push(inventorySheet)
sheets.push({
name: "Vínculos",
headers: ["Hostname", "Empresa", "Usuário", "E-mail"],
rows: linksRows.length > 0 ? linksRows : [["—", "—", "—", "—"]],
columnWidths: [22, 26, 26, 28],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
sheets.push({
name: "Softwares",
headers: [...SOFTWARE_HEADERS],
rows: softwareRows.length > 0 ? softwareRows : [["—", "—", "—", "—", "—", "—"]],
columnWidths: [...SOFTWARE_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: softwareRows.length > 0,
})
if (partitionRows.length > 0) {
sheets.push({
name: "Partições",
headers: [...PARTITION_HEADERS],
rows: partitionRows,
columnWidths: [...PARTITION_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (physicalDiskRows.length > 0) {
sheets.push({
name: "Discos físicos",
headers: [...PHYSICAL_DISK_HEADERS],
rows: physicalDiskRows,
columnWidths: [...PHYSICAL_DISK_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (networkRows.length > 0) {
sheets.push({
name: "Rede",
headers: [...NETWORK_HEADERS],
rows: networkRows,
columnWidths: [...NETWORK_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (remoteAccessRows.length > 0) {
sheets.push({
name: "Acessos remotos",
headers: [...REMOTE_ACCESS_HEADERS],
rows: remoteAccessRows,
columnWidths: [...REMOTE_ACCESS_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (serviceRows.length > 0) {
sheets.push({
name: "Serviços",
headers: [...SERVICE_HEADERS],
rows: serviceRows,
columnWidths: [...SERVICE_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (alertRows.length > 0) {
sheets.push({
name: "Alertas",
headers: [...ALERT_HEADERS],
rows: alertRows,
columnWidths: [...ALERT_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (metricsRows.length > 0) {
sheets.push({
name: "Métricas",
headers: [...METRICS_HEADERS],
rows: metricsRows,
columnWidths: [...METRICS_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (labelRows.length > 0) {
sheets.push({
name: "Labels",
headers: [...LABEL_HEADERS],
rows: labelRows,
columnWidths: [...LABEL_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
if (systemRows.length > 0) {
sheets.push({
name: "Sistema",
headers: [...SYSTEM_HEADERS],
rows: systemRows,
columnWidths: [...SYSTEM_COLUMN_WIDTHS],
freezePane: { rowSplit: 1 },
autoFilter: true,
})
}
return buildXlsxWorkbook(sheets)
}
export function buildInventoryWorksheet(
machines: MachineInventoryRecord[],
columns?: DeviceInventoryColumnConfig[],
sheetName = "Inventário",
): WorksheetConfig {
const columnConfig = normalizeInventoryColumnConfig(columns)
const derivedList = machines.map((machine) => deriveMachineData(machine))
const headers = columnConfig.map((column) => resolveColumnLabel(column, derivedList))
const columnWidths = columnConfig.map((column) => resolveColumnWidth(column.key))
const inventoryRows = machines.map((machine, index) => {
const derived = derivedList[index]
return columnConfig.map((column) => formatInventoryCell(resolveColumnValue(column.key, machine, derived)))
})
const fallbackRows =
inventoryRows.length > 0 ? inventoryRows : [columnConfig.map(() => "—")]
return {
name: sheetName,
headers,
rows: fallbackRows,
columnWidths,
freezePane: { rowSplit: 1 },
autoFilter: inventoryRows.length > 0,
}
}
function buildSummaryRows(
machines: MachineInventoryRecord[],
options: WorkbookOptions,
generatedAt: Date,
): Array<[string, unknown]> {
const rows: Array<[string, unknown]> = [
["Tenant", options.tenantId],
["Gerado em", formatDateTime(generatedAt.getTime()) ?? generatedAt.toISOString()],
]
if (options.generatedBy) {
rows.push(["Solicitado por", options.generatedBy])
}
if (options.companyFilterLabel) {
rows.push(["Filtro de empresa", options.companyFilterLabel])
}
rows.push(["Total de dispositivos", machines.length])
const activeCount = machines.filter((machine) => machine.isActive).length
rows.push(["Dispositivos ativos", activeCount])
rows.push(["Dispositivos inativos", machines.length - activeCount])
const typeCounts = new Map<string, number>()
machines.forEach((machine) => {
const label = describeDeviceType(machine.deviceType)
typeCounts.set(label, (typeCounts.get(label) ?? 0) + 1)
})
const DEVICE_TYPE_ORDER = ["Desktop", "Celular", "Tablet", "Desconhecido"]
Array.from(typeCounts.entries())
.sort((a, b) => {
const idxA = DEVICE_TYPE_ORDER.indexOf(a[0])
const idxB = DEVICE_TYPE_ORDER.indexOf(b[0])
if (idxA === -1 && idxB === -1) return a[0].localeCompare(b[0], "pt-BR")
if (idxA === -1) return 1
if (idxB === -1) return -1
return idxA - idxB
})
.forEach(([label, total]) => rows.push([`Tipo: ${label}`, total]))
const statusCounts = new Map<string, number>()
machines.forEach((machine) => {
const label = describeStatus(machine.status)
statusCounts.set(label, (statusCounts.get(label) ?? 0) + 1)
})
const sortedStatuses = Array.from(statusCounts.entries()).sort((a, b) => {
const indexA = SUMMARY_STATUS_ORDER.indexOf(a[0])
const indexB = SUMMARY_STATUS_ORDER.indexOf(b[0])
if (indexA === -1 && indexB === -1) return a[0].localeCompare(b[0], "pt-BR")
if (indexA === -1) return 1
if (indexB === -1) return -1
return indexA - indexB
})
sortedStatuses.forEach(([status, total]) => {
rows.push([`Status: ${status}`, total])
})
const uniqueCompanies = new Set(machines.map((machine) => machine.companyName).filter(Boolean) as string[])
if (uniqueCompanies.size > 0) {
rows.push(["Empresas no resultado", Array.from(uniqueCompanies).sort((a, b) => a.localeCompare(b, "pt-BR")).join(", ")])
}
const totalRemoteAccess = machines.reduce((acc, machine) => acc + collectRemoteAccessEntries(machine).length, 0)
if (totalRemoteAccess > 0) {
rows.push(["Acessos remotos configurados", totalRemoteAccess])
}
const totalAlerts = machines.reduce((acc, machine) => acc + (machine.postureAlerts?.length ?? 0), 0)
if (totalAlerts > 0) {
rows.push(["Alertas de postura ativos", totalAlerts])
}
return rows
}
function buildLinkedUsersRows(machines: MachineInventoryRecord[]): Array<[string, string | null, string | null, string | null]> {
const rows: Array<[string, string | null, string | null, string | null]> = []
machines.forEach((machine) => {
machine.linkedUsers?.forEach((user) => {
rows.push([machine.hostname, machine.companyName ?? null, user.name ?? user.email ?? null, user.email ?? null])
})
})
return rows
}
function buildSoftwareRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows = new Map<string, WorksheetRow>()
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const entries = extractSoftwareEntries(machine.hostname, inventory)
entries.forEach((entry) => {
const key = [
entry.hostname.toLowerCase(),
(entry.name ?? "").toLowerCase(),
(entry.version ?? "").toLowerCase(),
(entry.source ?? "").toLowerCase(),
(entry.publisher ?? "").toLowerCase(),
(entry.installedOn ?? "").toLowerCase(),
].join("|")
if (!rows.has(key)) {
rows.set(key, [
entry.hostname,
entry.name,
entry.version ?? "—",
entry.source ?? "—",
entry.publisher ?? "—",
entry.installedOn ?? "—",
])
}
})
})
return Array.from(rows.values()).sort((a, b) => {
const hostCompare = String(a[0]).localeCompare(String(b[0]), "pt-BR")
if (hostCompare !== 0) return hostCompare
return String(a[1]).localeCompare(String(b[1]), "pt-BR")
})
}
function buildPartitionRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const partitions = extractPartitionEntries(machine.hostname, inventory)
partitions.forEach((partition) => {
rows.push([
partition.hostname,
partition.name ?? "—",
partition.mount ?? "—",
partition.fs ?? "—",
formatBytesValue(partition.capacityBytes),
formatBytesValue(partition.freeBytes),
partition.capacityBytes !== null && partition.freeBytes !== null
? formatBytesValue(Math.max(0, partition.capacityBytes - partition.freeBytes))
: "—",
partition.interface ?? "—",
partition.serial ?? "—",
partition.origin,
])
})
})
return rows
}
function buildPhysicalDiskRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const disks = extractPhysicalDiskEntries(machine.hostname, inventory)
disks.forEach((disk) => {
rows.push([
disk.hostname,
disk.model ?? disk.serial ?? "—",
formatBytesValue(disk.sizeBytes),
disk.interface ?? "—",
disk.mediaType ?? "—",
disk.serial ?? "—",
disk.origin,
])
})
})
return rows
}
function buildNetworkRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const interfaces = extractNetworkEntries(machine.hostname, inventory)
interfaces.forEach((iface) => {
rows.push([iface.hostname, iface.name ?? "—", iface.mac ?? "—", iface.address ?? "—", iface.origin])
})
})
return rows
}
function buildRemoteAccessRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
collectRemoteAccessEntries(machine).forEach((entry) => {
rows.push([
machine.hostname,
entry.provider ?? "—",
entry.identifier ?? "—",
entry.username ?? "—",
entry.password ?? "—",
entry.url ?? "—",
entry.notes ?? "—",
entry.lastVerifiedAt ? formatDateTime(entry.lastVerifiedAt) ?? "—" : "—",
entry.origin,
stringifyMetadata(entry.metadata),
])
})
})
return rows
}
function buildServiceRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const services = extractServiceEntries(machine.hostname, inventory)
services.forEach((service) => {
rows.push([
service.hostname,
service.name ?? "—",
service.displayName ?? "—",
service.status ?? "—",
service.origin,
])
})
})
return rows
}
function buildAlertRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const alerts = machine.postureAlerts ?? []
alerts.forEach((alert) => {
const record = toRecord(alert)
if (!record) return
rows.push([
machine.hostname,
pickString(record, ["kind", "Kind", "type"]) ?? "—",
pickString(record, ["message", "Message", "description"]) ?? "—",
pickString(record, ["severity", "Severity"]) ?? "—",
formatDateTime(
parseDateish(record["createdAt"]) ??
parseDateish(record["timestamp"]) ??
parseDateish(record["created"]) ??
null,
) ?? "—",
])
})
})
return rows
}
function buildMetricsRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const metricsRow = deriveMachineMetrics(machine, inventory)
if (metricsRow) {
rows.push(metricsRow)
}
})
return rows
}
function buildLabelRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const labels = extractLabels(inventory)
labels.forEach((label) => rows.push([machine.hostname, label]))
})
return rows
}
function buildSystemRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
const rows: WorksheetRow[] = []
machines.forEach((machine) => {
const inventory = toRecord(machine.inventory)
const hardware = extractHardware(inventory)
const systemInfo = extractSystemInfo(inventory)
const collaborator = extractCollaborator(machine, inventory)
const fleetInfo = extractFleetInfo(inventory)
const push = (category: string, field: string, value: unknown) => {
const display = value === null || value === undefined || value === "" ? "—" : value
rows.push([machine.hostname, category, field, display])
}
push("Sistema", "Sistema operacional", machine.osName ?? "—")
push("Sistema", "Versão", machine.osVersion ?? "—")
if (systemInfo.windowsEdition) push("Sistema", "Edição", systemInfo.windowsEdition)
if (systemInfo.osBuild) push("Sistema", "Build", systemInfo.osBuild)
if (systemInfo.experience) push("Sistema", "Experiência", systemInfo.experience)
push("Sistema", "Licença", systemInfo.license ?? "—")
if (systemInfo.installDate) {
push("Sistema", "Instalação", formatDateTime(systemInfo.installDate) ?? "—")
}
push("Dispositivo", "Nome do dispositivo", systemInfo.deviceName ?? machine.hostname ?? "—")
push("Dispositivo", "Domínio", systemInfo.domain ?? "—")
push("Dispositivo", "Grupo de trabalho", systemInfo.workgroup ?? "—")
push("Dispositivo", "Fabricante", hardware.vendor ?? systemInfo.systemManufacturer ?? "—")
push("Dispositivo", "Modelo", hardware.model ?? systemInfo.systemModel ?? "—")
push("Dispositivo", "Serial hardware", hardware.serial ?? systemInfo.boardSerial ?? "—")
push("Dispositivo", "MACs", machine.macAddresses.join(", ") || "—")
push("Dispositivo", "Seriais", machine.serialNumbers.join(", ") || "—")
push("Hardware", "Processador", hardware.cpuType ?? "—")
push("Hardware", "Cores físicas", hardware.physicalCores ?? "—")
push("Hardware", "Cores lógicas", hardware.logicalCores ?? "—")
push("Hardware", "Memória", hardware.memoryGiB ?? "—")
push("Hardware", "GPUs", extractGpuNames(inventory).join(", ") || "—")
push("Acesso", "Persona", describePersona(machine.persona))
push("Acesso", "Responsável", machine.assignedUserName ?? "—")
push("Acesso", "Responsável (e-mail)", machine.assignedUserEmail ?? "—")
if (collaborator) {
push("Acesso", "Colaborador (nome)", collaborator.name ?? "—")
push("Acesso", "Colaborador (e-mail)", collaborator.email ?? "—")
}
push("Acesso", "Autenticação", machine.authEmail ?? "—")
push("Acesso", "Registrada via", machine.registeredBy ?? "—")
push("Token", "Expira em", machine.token?.expiresAt ? formatDateTime(machine.token.expiresAt) ?? "—" : "—")
push("Token", "Último uso", machine.token?.lastUsedAt ? formatDateTime(machine.token.lastUsedAt) ?? "—" : "—")
push("Token", "Uso total", machine.token?.usageCount ?? 0)
push("Status", "Ativa", yesNo(machine.isActive))
push("Status", "Status atual", describeStatus(machine.status))
push("Status", "Último heartbeat", formatDateTime(machine.lastHeartbeatAt) ?? "—")
push("Status", "Criada em", formatDateTime(machine.createdAt) ?? "—")
push("Status", "Atualizada em", formatDateTime(machine.updatedAt) ?? "—")
const remoteCount = collectRemoteAccessEntries(machine).length
push("Acessos remotos", "Total", remoteCount)
if (fleetInfo) {
push("Fleet", "ID", fleetInfo.id ?? "—")
push("Fleet", "Equipe", fleetInfo.team ?? "—")
push("Fleet", "Atualizado em", fleetInfo.updatedAt ? formatDateTime(fleetInfo.updatedAt) ?? "—" : "—")
}
})
return rows
}
function toRecord(value: unknown): Record<string, unknown> | null {
if (value && typeof value === "object" && !Array.isArray(value)) {
return value as Record<string, unknown>
}
return null
}
function toRecordArray(value: unknown): Record<string, unknown>[] {
if (Array.isArray(value)) {
return value.map(toRecord).filter((item): item is Record<string, unknown> => Boolean(item))
}
const record = toRecord(value)
return record ? [record] : []
}
function ensureString(value: unknown): string | null {
if (typeof value === "string") {
const trimmed = value.trim()
return trimmed.length ? trimmed : null
}
if (typeof value === "number" && Number.isFinite(value)) {
return String(value)
}
return null
}
function ensureNumber(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) {
return value
}
if (typeof value === "string") {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}
function pickValue(record: Record<string, unknown> | null | undefined, keys: string[]): unknown {
if (!record) return undefined
for (const key of keys) {
if (key in record) {
const value = record[key]
if (value !== undefined && value !== null && value !== "") {
return value
}
}
}
return undefined
}
function pickString(record: Record<string, unknown> | null | undefined, keys: string[]): string | null {
return ensureString(pickValue(record, keys))
}
function pickNumber(record: Record<string, unknown> | null | undefined, keys: string[]): number | null {
return ensureNumber(pickValue(record, keys))
}
function pickRecord(record: Record<string, unknown> | null | undefined, keys: string[]): Record<string, unknown> | null {
return toRecord(pickValue(record, keys))
}
function pickRecordArray(record: Record<string, unknown> | null | undefined, keys: string[]): Record<string, unknown>[] {
return toRecordArray(pickValue(record, keys))
}
function pickArray(record: Record<string, unknown> | null | undefined, keys: string[]): unknown[] {
const value = pickValue(record, keys)
if (Array.isArray(value)) return value
if (value === undefined || value === null) return []
return [value]
}
function describeStatus(status: string | null | undefined): string {
if (!status) return STATUS_LABELS.unknown
const normalized = status.toLowerCase()
return STATUS_LABELS[normalized] ?? status
}
function describePersona(persona: string | null | undefined): string {
if (!persona) return "—"
const normalized = persona.toLowerCase()
return PERSONA_LABELS[normalized] ?? persona
}
function yesNo(value: boolean | null | undefined): string {
if (value === undefined || value === null) return "—"
return value ? "Sim" : "Não"
}
function formatDateTime(value: number | null | undefined): string | null {
if (typeof value !== "number" || Number.isNaN(value)) return null
const date = new Date(value)
const yyyy = date.getUTCFullYear()
const mm = `${date.getUTCMonth() + 1}`.padStart(2, "0")
const dd = `${date.getUTCDate()}`.padStart(2, "0")
const hh = `${date.getUTCHours()}`.padStart(2, "0")
const min = `${date.getUTCMinutes()}`.padStart(2, "0")
return `${yyyy}-${mm}-${dd} ${hh}:${min} UTC`
}
function extractHardware(inventory: Record<string, unknown> | null) {
if (!inventory) {
return {
vendor: null,
model: null,
serial: null,
cpuType: null,
physicalCores: null,
logicalCores: null,
memoryGiB: null,
memoryBytes: null,
}
}
const hardware = pickRecord(inventory, ["hardware", "Hardware"])
const vendor =
pickString(hardware, ["vendor", "Vendor", "manufacturer", "Manufacturer", "systemManufacturer", "SystemManufacturer"]) ?? null
const model =
pickString(hardware, ["model", "Model", "product", "ProductName", "productName", "SystemProductName"]) ?? null
const serial = pickString(hardware, ["serial", "SerialNumber", "Serial"])
const cpuType = pickString(hardware, ["cpuType", "cpu", "processor", "name", "model"])
const physicalCores = pickNumber(hardware, ["physicalCores", "PhysicalCores", "cores", "Cores"])
const logicalCores = pickNumber(hardware, ["logicalCores", "LogicalCores", "threads", "Threads"])
const memoryBytes =
parseBytesLike(pickValue(hardware, ["memoryBytes", "MemoryBytes", "totalMemory", "TotalMemory"])) ??
parseBytesLike(pickValue(hardware, ["memory"])) ??
parseBytesLike(pickValue(hardware, ["ram"])) ??
null
const memoryGiB = memoryBytes !== null ? formatBytesValue(memoryBytes) : null
return {
vendor,
model,
serial,
cpuType,
physicalCores,
logicalCores,
memoryGiB,
memoryBytes,
}
}
function extractPrimaryIp(inventory: Record<string, unknown> | null): string | null {
if (!inventory) return null
const network = pickRecord(inventory, ["network", "Network"])
const direct = pickString(network, ["primaryIp", "PrimaryIp", "ip", "address", "PrimaryAddress"])
if (direct) return direct
const networkArray = pickRecordArray(inventory, ["network", "Network"])
for (const entry of networkArray) {
const ip = pickString(entry, ["ip", "IP", "address", "primaryIp", "PrimaryIp"])
if (ip) return ip
}
return null
}
function extractPublicIp(inventory: Record<string, unknown> | null): string | null {
if (!inventory) return null
const network = pickRecord(inventory, ["network", "Network"])
return pickString(network, ["publicIp", "PublicIp", "externalIp", "ExternalIp"])
}
function extractGpuNames(inventory: Record<string, unknown> | null): string[] {
if (!inventory) return []
const names = new Set<string>()
const hardware = pickRecord(inventory, ["hardware", "Hardware"])
const primaryGpu = pickRecord(hardware, ["primaryGpu", "primarygpu", "PrimaryGpu"])
if (primaryGpu) {
const name = pickString(primaryGpu, ["name", "Name", "model", "Model", "GPUName", "gpuName"])
if (name) names.add(name)
}
const gpuArray = pickArray(hardware, ["gpus", "GPUs"])
if (gpuArray.length > 0) {
gpuArray.forEach((gpu) => {
const record = toRecord(gpu)
const name = pickString(record, ["name", "Name", "model", "Model", "GPUName", "gpuName"])
if (name) names.add(name)
})
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
const videoControllers = pickRecordArray(windows, ["videoControllers", "VideoControllers"])
videoControllers.forEach((controller) => {
const name = pickString(controller, ["Name", "name", "Caption", "Model"])
if (name) names.add(name)
})
return Array.from(names)
}
function extractLabels(inventory: Record<string, unknown> | null): string[] {
if (!inventory) return []
const labels = inventory["labels"]
if (!labels) return []
if (Array.isArray(labels)) {
return labels
.map((label) => {
if (typeof label === "string") return label.trim()
const record = toRecord(label)
if (!record) return null
return pickString(record, ["name", "value", "label", "Name"])
})
.filter((item): item is string => Boolean(item))
}
const record = toRecord(labels)
const name = pickString(record, ["name", "label", "value"])
return name ? [name] : []
}
function summarizeLinkedUsers(users: LinkedUser[] | undefined): string | null {
if (!users || users.length === 0) return null
const parts = users.map((user) => {
const name = user.name ?? user.email ?? ""
if (user.email && user.email !== name) {
return `${name} <${user.email}>`
}
return name
})
return parts.join("; ")
}
function clampPercent(value: number): number {
if (Number.isNaN(value)) return value
return Math.max(0, Math.min(100, value))
}
function parseBytesLike(raw: unknown): number | null {
if (raw === null || raw === undefined) return null
if (typeof raw === "number" && Number.isFinite(raw)) {
if (raw > 10_000 && raw < 10_000_000) {
return raw * 1024
}
return raw
}
if (typeof raw === "string") {
const trimmed = raw.trim()
if (!trimmed) return null
const numeric = Number(trimmed)
if (Number.isFinite(numeric)) {
return numeric
}
const match = trimmed.match(/^([-+]?[0-9]*\.?[0-9]+)\s*([kmgtp]?i?b)?/i)
if (match) {
const value = Number(match[1])
if (!Number.isFinite(value)) return null
const unit = (match[2] ?? "").toLowerCase()
const multipliers: Record<string, number> = {
b: 1,
kb: 1000,
kib: 1024,
mb: 1000 ** 2,
mib: 1024 ** 2,
gb: 1000 ** 3,
gib: 1024 ** 3,
tb: 1000 ** 4,
tib: 1024 ** 4,
pb: 1000 ** 5,
pib: 1024 ** 5,
}
const multiplier = multipliers[unit] ?? 1
return value * multiplier
}
}
return null
}
function formatBytesValue(bytes: number | null | undefined): string {
if (bytes === null || bytes === undefined || Number.isNaN(bytes) || bytes < 0) return "—"
const units = ["B", "KB", "MB", "GB", "TB", "PB"]
let value = bytes
let unitIndex = 0
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024
unitIndex += 1
}
const fixed = value >= 10 || value % 1 === 0 ? value.toFixed(0) : value.toFixed(1)
return `${fixed} ${units[unitIndex]}`
}
function formatPercentValue(value: number | null | undefined): string {
if (value === null || value === undefined || Number.isNaN(value)) return "—"
return `${clampPercent(value).toFixed(0)}%`
}
function parseDateish(value: unknown): number | null {
if (!value) return null
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.getTime()
if (typeof value === "number" && Number.isFinite(value)) {
if (value > 10_000_000_000) return value
if (value > 1_000_000_000) return value * 1000
if (value > 0) return value * 1000
return null
}
if (typeof value !== "string") return null
const trimmed = value.trim()
if (!trimmed) return null
const numeric = Number(trimmed)
if (Number.isFinite(numeric)) return parseDateish(numeric)
const iso = Date.parse(trimmed)
if (!Number.isNaN(iso)) return iso
const digits = trimmed.replace(/[^0-9]/g, "")
if (digits.length >= 8) {
const yyyy = Number(digits.slice(0, 4))
const mm = Number(digits.slice(4, 6))
const dd = Number(digits.slice(6, 8))
const hh = Number(digits.slice(8, 10) || "0")
const mi = Number(digits.slice(10, 12) || "0")
const ss = Number(digits.slice(12, 14) || "0")
if (yyyy > 1900 && mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31) {
const parsed = Date.UTC(yyyy, mm - 1, dd, hh, mi, ss)
if (!Number.isNaN(parsed)) return parsed
}
}
return null
}
function stringifyMetadata(metadata: Record<string, unknown> | null | undefined): string {
if (!metadata || Object.keys(metadata).length === 0) return "—"
try {
return JSON.stringify(metadata)
} catch {
return String(metadata)
}
}
type RemoteAccessNormalized = {
provider: string | null
identifier: string | null
username: string | null
password: string | null
url: string | null
notes: string | null
lastVerifiedAt: number | null
origin: string
metadata: Record<string, unknown> | null
}
function normalizeRemoteAccessEntry(
value: unknown,
origin: string,
providerHint?: string,
): RemoteAccessNormalized | null {
if (value === null || value === undefined) return null
if (typeof value === "string") {
const trimmed = value.trim()
if (!trimmed) return null
const isUrl = /^https?:\/\//i.test(trimmed)
return {
provider: providerHint ?? null,
identifier: isUrl ? null : trimmed,
username: null,
password: null,
url: isUrl ? trimmed : null,
notes: null,
lastVerifiedAt: null,
origin,
metadata: null,
}
}
const record = toRecord(value)
if (!record) return null
const provider =
ensureString(record["provider"]) ??
ensureString(record["tool"]) ??
ensureString(record["vendor"]) ??
ensureString(record["name"]) ??
providerHint ??
null
const identifier =
ensureString(record["identifier"]) ??
ensureString(record["code"]) ??
ensureString(record["id"]) ??
ensureString(record["accessId"]) ??
ensureString(record["value"]) ??
ensureString(record["label"]) ??
null
const username =
ensureString(record["username"]) ??
ensureString(record["user"]) ??
ensureString(record["login"]) ??
ensureString(record["email"]) ??
ensureString(record["account"]) ??
null
const password =
ensureString(record["password"]) ??
ensureString(record["pass"]) ??
ensureString(record["secret"]) ??
ensureString(record["pin"]) ??
null
const url =
ensureString(record["url"]) ??
ensureString(record["link"]) ??
ensureString(record["remoteUrl"]) ??
ensureString(record["console"]) ??
ensureString(record["viewer"]) ??
null
const notes =
ensureString(record["notes"]) ??
ensureString(record["note"]) ??
ensureString(record["description"]) ??
ensureString(record["obs"]) ??
null
const lastVerifiedRaw =
record["lastVerifiedAt"] ?? record["verifiedAt"] ?? record["checkedAt"] ?? record["updatedAt"] ?? record["timestamp"]
const lastVerifiedAt = parseDateish(lastVerifiedRaw)
const metadata = Object.keys(record).length > 0 ? record : null
if (!identifier && !url && !provider) {
return null
}
return {
provider,
identifier,
username,
password,
url,
notes,
lastVerifiedAt,
origin,
metadata,
}
}
function collectRemoteAccessEntries(machine: MachineInventoryRecord): RemoteAccessNormalized[] {
const raw = machine.remoteAccess
const entries: RemoteAccessNormalized[] = []
const push = (entry: RemoteAccessNormalized | null) => {
if (entry) entries.push(entry)
}
const handleValue = (value: unknown, origin: string, providerHint?: string) => {
if (Array.isArray(value)) {
value.forEach((item) => push(normalizeRemoteAccessEntry(item, origin, providerHint)))
return
}
const record = toRecord(value)
if (!record) {
push(normalizeRemoteAccessEntry(value, origin, providerHint))
return
}
const keys = Object.keys(record)
const looksLikeEntry = ["provider", "identifier", "url", "notes", "id", "value"].some((key) => key in record)
if (looksLikeEntry) {
push(normalizeRemoteAccessEntry(record, origin, providerHint))
return
}
if ("entries" in record && Array.isArray(record.entries)) {
record.entries.forEach((item) => push(normalizeRemoteAccessEntry(item, `${origin}.entries`, providerHint)))
return
}
keys.forEach((key) => {
handleValue(record[key], `${origin}.${key}`, key)
})
}
handleValue(raw, "machine.remoteAccess")
return entries.filter(
(entry, index, array) =>
array.findIndex(
(other) =>
(other.provider ?? "").toLowerCase() === (entry.provider ?? "").toLowerCase() &&
(other.identifier ?? "").toLowerCase() === (entry.identifier ?? "").toLowerCase() &&
(other.url ?? "").toLowerCase() === (entry.url ?? "").toLowerCase(),
) === index,
)
}
type CollaboratorInfo = { name: string | null; email: string | null }
function extractCollaborator(machine: MachineInventoryRecord, inventory: Record<string, unknown> | null): CollaboratorInfo | null {
if (machine.assignedUserEmail || machine.assignedUserName) {
return {
name: machine.assignedUserName ?? null,
email: machine.assignedUserEmail ?? null,
}
}
const collab = pickRecord(inventory, ["collaborator", "Collaborator"])
if (!collab) return null
const email = pickString(collab, ["email", "Email"])
const name = pickString(collab, ["name", "Name"])
if (!email && !name) return null
return {
name: name ?? null,
email: email ?? null,
}
}
type FleetInfo = { id: string | null; team: string | null; updatedAt: number | null }
function extractFleetInfo(inventory: Record<string, unknown> | null): FleetInfo | null {
const fleet = pickRecord(inventory, ["fleet", "Fleet"])
if (!fleet) return null
const id = ensureString(fleet["id"]) ?? ensureString(fleet["fleetId"]) ?? ensureString(fleet["teamId"])
const team = ensureString(fleet["teamId"]) ?? ensureString(fleet["team"]) ?? ensureString(fleet["group"])
const updatedAt = parseDateish(fleet["detailUpdatedAt"] ?? fleet["updatedAt"])
if (!id && !team && !updatedAt) return null
return {
id: id ?? null,
team: team ?? null,
updatedAt: updatedAt ?? null,
}
}
type SystemInfo = {
osBuild: string | null
license: string | null
experience: string | null
domain: string | null
workgroup: string | null
deviceName: string | null
systemManufacturer: string | null
systemModel: string | null
boardSerial: string | null
installDate: number | null
windowsEdition: string | null
}
function extractSystemInfo(inventory: Record<string, unknown> | null): SystemInfo {
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
const osInfo = pickRecord(windows, ["osInfo", "OSInfo", "OsInfo"])
const computerSystem = pickRecord(windows, ["computerSystem", "ComputerSystem"])
const baseboard = pickRecord(windows, ["baseboard", "Baseboard"])
const baseBuild =
pickString(osInfo, ["CurrentBuildNumber", "currentBuildNumber", "OSBuild", "osBuild", "BuildNumber", "buildNumber"]) ??
pickString(osInfo, ["CurrentBuild", "currentBuild"])
const ubr = pickString(osInfo, ["UBR", "ubr"])
const osBuild = baseBuild ? (ubr && /^\d+$/.test(ubr) ? `${baseBuild}.${ubr}` : baseBuild) : null
const licenseStatusDescription =
pickString(osInfo, ["LicenseStatusDescription", "licenseStatusDescription", "StatusDescription", "statusDescription"]) ?? null
const licenseStatusNumber = pickNumber(osInfo, ["LicenseStatus", "licenseStatus"])
const licenseBoolRaw = pickValue(osInfo, ["IsActivated", "isActivated", "IsLicensed", "isLicensed"])
const licenseBool =
typeof licenseBoolRaw === "boolean"
? licenseBoolRaw
: typeof licenseBoolRaw === "number"
? licenseBoolRaw === 1
: typeof licenseBoolRaw === "string"
? ["1", "true", "licensed", "ativado", "activated"].includes(licenseBoolRaw.toLowerCase())
: undefined
const license =
licenseBool === true
? "Ativada"
: licenseBool === false
? "Não ativada"
: licenseStatusNumber === 1
? "Ativada"
: licenseStatusDescription ?? null
const experience =
pickString(osInfo, ["Experience", "experience"]) ??
((() => {
const pack = pickString(osInfo, ["FeatureExperiencePack", "featureExperiencePack"])
if (pack) return `Feature Experience Pack ${pack}`
return null
})())
const domain = pickString(computerSystem, ["Domain", "domain"])
const workgroup = pickString(computerSystem, ["Workgroup", "workgroup"])
const deviceName =
pickString(osInfo, ["ComputerName", "computerName"]) ??
pickString(computerSystem, ["DNSHostName", "dnsHostName", "Name", "name"])
const systemManufacturer =
pickString(computerSystem, ["Manufacturer", "manufacturer", "SystemManufacturer", "systemManufacturer"]) ??
pickString(osInfo, ["Manufacturer", "manufacturer"])
const systemModel =
pickString(computerSystem, ["Model", "model", "SystemProductName", "systemProductName"]) ??
pickString(osInfo, ["Model", "model"])
const boardSerial =
pickString(baseboard, ["SerialNumber", "serialNumber", "Serial"]) ??
pickString(computerSystem, ["SystemSKUNumber", "systemSKUNumber"])
const installDate =
parseDateish(osInfo?.["InstallDate"]) ??
parseDateish(osInfo?.["InstallationDate"]) ??
parseDateish(osInfo?.["InstalledOn"])
const edition =
pickString(osInfo, ["ProductName", "productName"]) ??
pickString(osInfo, ["Caption", "caption"]) ??
pickString(osInfo, ["EditionID", "editionId"])
return {
osBuild,
license,
experience,
domain,
workgroup,
deviceName,
systemManufacturer,
systemModel,
boardSerial,
installDate,
windowsEdition: edition,
}
}
type SoftwareEntryInternal = SoftwareEntry
function extractSoftwareEntries(hostname: string, inventory: Record<string, unknown> | null): SoftwareEntryInternal[] {
if (!inventory) return []
const map = new Map<string, SoftwareEntryInternal>()
const push = (record: Record<string, unknown> | null) => {
if (!record) return
const name = pickString(record, ["DisplayName", "displayName", "Name", "name", "Title", "title"])
if (!name) return
const version = pickString(record, ["DisplayVersion", "displayVersion", "Version", "version"])
const source =
pickString(record, ["ParentDisplayName", "parent", "Source", "source", "SystemComponent", "PublisherURL"]) ?? null
const publisher = pickString(record, ["Publisher", "publisher", "Vendor", "vendor"])
const installedRaw =
pickValue(record, ["InstalledOn", "installedOn", "InstallDateUTC", "InstallDate", "installDate", "InstallTime"]) ?? null
const installedTs = parseDateish(installedRaw)
const installedOn =
installedTs !== null
? formatDateTime(installedTs) ?? null
: typeof installedRaw === "string" && installedRaw.trim().length > 0
? installedRaw.trim()
: null
const entry: SoftwareEntryInternal = {
hostname,
name,
version: version ?? null,
source,
publisher: publisher ?? null,
installedOn,
}
const key = [
hostname.toLowerCase(),
name.toLowerCase(),
(version ?? "").toLowerCase(),
(source ?? "").toLowerCase(),
(publisher ?? "").toLowerCase(),
(installedOn ?? "").toLowerCase(),
].join("|")
if (!map.has(key)) {
map.set(key, entry)
}
}
const direct = inventory["software"]
if (Array.isArray(direct)) {
direct.map(toRecord).forEach(push)
} else if (direct) {
push(toRecord(direct))
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
if (windows) {
const software = windows["software"] ?? windows["installedPrograms"] ?? windows["InstalledPrograms"]
if (Array.isArray(software)) {
software.map(toRecord).forEach(push)
} else {
push(toRecord(software))
}
}
const linux = pickRecord(extended, ["linux", "Linux"])
if (linux) {
const packages = linux["packages"]
if (Array.isArray(packages)) {
packages.forEach((pkg) => {
if (typeof pkg === "string") {
push({ name: pkg } as Record<string, unknown>)
} else {
push(toRecord(pkg))
}
})
}
}
const macos = pickRecord(extended, ["macos", "MacOS", "macOS"])
if (macos) {
const packages = macos["packages"]
if (Array.isArray(packages)) {
packages.forEach((pkg) => push(toRecord(pkg)))
}
}
return Array.from(map.values()).sort((a, b) => {
const nameCompare = a.name.localeCompare(b.name, "pt-BR")
if (nameCompare !== 0) return nameCompare
return (a.version ?? "").localeCompare(b.version ?? "", "pt-BR")
})
}
type PartitionEntry = {
hostname: string
name: string | null
mount: string | null
fs: string | null
capacityBytes: number | null
freeBytes: number | null
interface: string | null
serial: string | null
origin: string
}
function extractPartitionEntries(hostname: string, inventory: Record<string, unknown> | null): PartitionEntry[] {
const entries: PartitionEntry[] = []
if (!inventory) return entries
const push = (value: unknown, origin: string) => {
const record = toRecord(value)
if (!record) return
const name =
pickString(record, ["name", "Name", "VolumeName", "label", "Label"]) ??
pickString(record, ["DeviceID", "deviceId"])
const mount =
pickString(record, ["mountPoint", "MountPoint", "mount", "Mount", "path", "Path"]) ??
pickString(record, ["DeviceID", "deviceId"])
const fs = pickString(record, ["fs", "FS", "filesystem", "FileSystem", "FileSystemType", "type"])
const capacityBytes =
parseBytesLike(record["totalBytes"]) ??
parseBytesLike(record["sizeBytes"]) ??
parseBytesLike(record["Size"]) ??
parseBytesLike(record["TotalSpace"]) ??
parseBytesLike(record["Capacity"])
const freeBytes =
parseBytesLike(record["availableBytes"]) ??
parseBytesLike(record["freeBytes"]) ??
parseBytesLike(record["FreeSpace"]) ??
parseBytesLike(record["available"]) ??
parseBytesLike(record["Free"])
const interfaceType = pickString(record, ["interface", "Interface", "interfaceType", "InterfaceType"])
const serial = pickString(record, ["serial", "Serial", "SerialNumber"])
entries.push({
hostname,
name: name ?? mount ?? null,
mount: mount ?? null,
fs: fs ?? null,
capacityBytes: capacityBytes ?? null,
freeBytes: freeBytes ?? null,
interface: interfaceType ?? null,
serial: serial ?? null,
origin,
})
}
const direct = inventory["disks"]
if (Array.isArray(direct)) {
direct.forEach((item) => push(item, "inventory.disks"))
} else if (direct) {
push(direct, "inventory.disks")
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
if (windows) {
const logical = windows["logicalDisks"] ?? windows["LogicalDisks"] ?? windows["volumes"] ?? windows["Volumes"]
if (Array.isArray(logical)) {
logical.forEach((item) => push(item, "extended.windows.logicalDisks"))
} else if (logical) {
push(logical, "extended.windows.logicalDisks")
}
}
const linux = pickRecord(extended, ["linux", "Linux"])
if (linux) {
const lsblk = linux["lsblk"]
if (Array.isArray(lsblk)) {
lsblk.forEach((item) => push(item, "extended.linux.lsblk"))
} else if (lsblk) {
push(lsblk, "extended.linux.lsblk")
}
}
const macos = pickRecord(extended, ["macos", "MacOS", "macOS"])
if (macos) {
const volumes = macos["volumes"]
if (Array.isArray(volumes)) {
volumes.forEach((item) => push(item, "extended.macos.volumes"))
} else if (volumes) {
push(volumes, "extended.macos.volumes")
}
}
const dedup = new Map<string, PartitionEntry>()
entries.forEach((entry) => {
const key = [
entry.hostname.toLowerCase(),
(entry.name ?? "").toLowerCase(),
(entry.mount ?? "").toLowerCase(),
(entry.fs ?? "").toLowerCase(),
].join("|")
if (!dedup.has(key)) {
dedup.set(key, entry)
}
})
return Array.from(dedup.values())
}
type PhysicalDiskEntry = {
hostname: string
model: string | null
sizeBytes: number | null
interface: string | null
mediaType: string | null
serial: string | null
origin: string
}
function extractPhysicalDiskEntries(hostname: string, inventory: Record<string, unknown> | null): PhysicalDiskEntry[] {
const entries: PhysicalDiskEntry[] = []
if (!inventory) return entries
const push = (value: unknown, origin: string) => {
const record = toRecord(value)
if (!record) return
const model = pickString(record, ["Model", "model", "Caption", "DeviceID"])
const serial =
pickString(record, ["SerialNumber", "serialNumber", "Serial"]) ?? pickString(record, ["DeviceID", "deviceId"])
const interfaceType = pickString(record, ["InterfaceType", "interfaceType", "BusType"])
const mediaType = pickString(record, ["MediaType", "mediaType", "Media"]) ?? pickString(record, ["Type", "type"])
const sizeBytes =
parseBytesLike(record["Size"]) ??
parseBytesLike(record["sizeBytes"]) ??
parseBytesLike(record["TotalSize"]) ??
parseBytesLike(record["Capacity"])
entries.push({
hostname,
model: model ?? null,
sizeBytes: sizeBytes ?? null,
interface: interfaceType ?? null,
mediaType: mediaType ?? null,
serial: serial ?? null,
origin,
})
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
if (windows) {
const disks = windows["disks"] ?? windows["Disks"]
if (Array.isArray(disks)) {
disks.forEach((item) => push(item, "extended.windows.disks"))
} else if (disks) {
push(disks, "extended.windows.disks")
}
const drives = windows["diskDrives"] ?? windows["DiskDrives"]
if (Array.isArray(drives)) {
drives.forEach((item) => push(item, "extended.windows.diskDrives"))
} else if (drives) {
push(drives, "extended.windows.diskDrives")
}
}
const hardware = pickRecord(inventory, ["hardware", "Hardware"])
const storage = pickArray(hardware, ["storage", "Storage"])
storage.forEach((item) => push(item, "inventory.hardware.storage"))
const linux = pickRecord(extended, ["linux", "Linux"])
if (linux) {
const disks = linux["disks"]
if (Array.isArray(disks)) {
disks.forEach((item) => push(item, "extended.linux.disks"))
} else if (disks) {
push(disks, "extended.linux.disks")
}
}
const dedup = new Map<string, PhysicalDiskEntry>()
entries.forEach((entry) => {
const key = [
entry.hostname.toLowerCase(),
(entry.serial ?? "").toLowerCase(),
(entry.model ?? "").toLowerCase(),
].join("|")
if (!dedup.has(key)) {
dedup.set(key, entry)
}
})
return Array.from(dedup.values())
}
type NetworkEntry = { hostname: string; name: string | null; mac: string | null; address: string | null; origin: string }
function extractNetworkEntries(hostname: string, inventory: Record<string, unknown> | null): NetworkEntry[] {
const entries: NetworkEntry[] = []
if (!inventory) return entries
const push = (value: unknown, origin: string, nameHint?: string) => {
const record = toRecord(value)
if (!record) return
const mac =
pickString(record, ["mac", "MAC", "MacAddress", "MACAddress", "addressMac"]) ??
(Array.isArray(record["MACAddress"]) ? record["MACAddress"][0] : null)
const ipValue =
pickString(record, ["ip", "IP", "address", "Address", "ipv4", "IPv4"]) ??
(Array.isArray(record["IPAddress"])
? (record["IPAddress"] as unknown[]).map((addr) => ensureString(addr)).filter(Boolean)?.[0] ?? null
: null)
const name =
pickString(record, ["name", "Name", "Interface", "InterfaceDescription", "AdapterName"]) ??
nameHint ??
null
entries.push({
hostname,
name,
mac: mac ?? null,
address: ipValue ?? null,
origin,
})
}
const network = inventory["network"]
if (Array.isArray(network)) {
network.forEach((item) => push(item, "inventory.network"))
} else if (network && typeof network === "object") {
push(network, "inventory.network.summary")
const macAddresses = (network as Record<string, unknown>)["macAddresses"]
if (Array.isArray(macAddresses)) {
macAddresses
.map((addr) => ensureString(addr))
.filter(Boolean)
.forEach((addr) =>
entries.push({
hostname,
name: null,
mac: addr ?? null,
address: null,
origin: "inventory.network.macAddresses",
}),
)
}
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
if (windows) {
const adapters = windows["networkAdapters"] ?? windows["NetworkAdapters"] ?? windows["networkInterfaces"]
if (Array.isArray(adapters)) {
adapters.forEach((adapter) => push(adapter, "extended.windows.networkAdapters"))
} else if (adapters) {
push(adapters, "extended.windows.networkAdapters")
}
}
const linux = pickRecord(extended, ["linux", "Linux"])
if (linux) {
const interfaces = linux["networkInterfaces"]
if (Array.isArray(interfaces)) {
interfaces.forEach((iface) => push(iface, "extended.linux.networkInterfaces"))
} else if (interfaces) {
push(interfaces, "extended.linux.networkInterfaces")
}
}
const dedup = new Map<string, NetworkEntry>()
entries.forEach((entry) => {
const key = [
entry.hostname.toLowerCase(),
(entry.name ?? "").toLowerCase(),
(entry.mac ?? "").toLowerCase(),
(entry.address ?? "").toLowerCase(),
entry.origin,
].join("|")
if (!dedup.has(key)) {
dedup.set(key, entry)
}
})
return Array.from(dedup.values())
}
type ServiceEntry = { hostname: string; name: string | null; displayName: string | null; status: string | null; origin: string }
function extractServiceEntries(hostname: string, inventory: Record<string, unknown> | null): ServiceEntry[] {
const entries: ServiceEntry[] = []
if (!inventory) return entries
const push = (value: unknown, origin: string) => {
const record = toRecord(value)
if (!record) return
const name = pickString(record, ["Name", "name", "ServiceName", "serviceName"])
const displayName = pickString(record, ["DisplayName", "displayName", "Description"])
const status = pickString(record, ["Status", "status", "State", "state"])
if (!name && !displayName) return
entries.push({
hostname,
name: name ?? displayName ?? null,
displayName: displayName ?? null,
status: status ?? null,
origin,
})
}
const direct = inventory["services"]
if (Array.isArray(direct)) {
direct.forEach((svc) => push(svc, "inventory.services"))
} else if (direct) {
push(direct, "inventory.services")
}
const extended = pickRecord(inventory, ["extended", "Extended"])
const windows = pickRecord(extended, ["windows", "Windows"])
if (windows) {
const services = windows["services"] ?? windows["Services"]
if (Array.isArray(services)) {
services.forEach((svc) => push(svc, "extended.windows.services"))
} else if (services) {
push(services, "extended.windows.services")
}
}
const linux = pickRecord(extended, ["linux", "Linux"])
if (linux) {
const services = linux["services"]
if (Array.isArray(services)) {
services.forEach((svc) => push(svc, "extended.linux.services"))
} else if (services) {
push(services, "extended.linux.services")
}
}
const dedup = new Map<string, ServiceEntry>()
entries.forEach((entry) => {
const key = [
entry.hostname.toLowerCase(),
(entry.name ?? "").toLowerCase(),
(entry.displayName ?? "").toLowerCase(),
].join("|")
if (!dedup.has(key)) dedup.set(key, entry)
})
return Array.from(dedup.values())
}
function deriveMachineMetrics(
machine: MachineInventoryRecord,
inventory: Record<string, unknown> | null,
): WorksheetRow | null {
const metricsRecord = toRecord(machine.metrics ?? null)
if (!metricsRecord) return null
const capturedAt =
parseDateish(metricsRecord["capturedAt"]) ??
parseDateish(metricsRecord["collectedAt"]) ??
parseDateish(metricsRecord["timestamp"]) ??
null
const cpuPercentRaw = Number(
metricsRecord["cpuUsagePercent"] ??
metricsRecord["cpuUsage"] ??
metricsRecord["cpu_percent"] ??
metricsRecord["cpu"],
)
const cpuPercent = Number.isFinite(cpuPercentRaw) ? clampPercent(cpuPercentRaw) : NaN
const memoryTotalCandidates = [
metricsRecord["memoryTotalBytes"],
metricsRecord["memory_total"],
metricsRecord["memory"],
metricsRecord["memoryTotal"],
]
.map(parseBytesLike)
.filter((value): value is number => value !== null)
const hardware = extractHardware(inventory)
if (hardware.memoryBytes && !memoryTotalCandidates.includes(hardware.memoryBytes)) {
memoryTotalCandidates.push(hardware.memoryBytes)
}
const memoryTotalBytes = memoryTotalCandidates.find((value) => Number.isFinite(value)) ?? null
const memoryUsedCandidates = [
metricsRecord["memoryUsedBytes"],
metricsRecord["memory_used"],
metricsRecord["memoryUsed"],
metricsRecord["memoryBytesUsed"],
]
.map(parseBytesLike)
.filter((value): value is number => value !== null)
const memoryUsedBytes = memoryUsedCandidates.find((value) => Number.isFinite(value)) ?? null
const memoryPercentCandidates = [
metricsRecord["memoryUsedPercent"],
metricsRecord["memory_percent"],
metricsRecord["memoryPercent"],
]
.map((value) => (typeof value === "number" ? clampPercent(value) : NaN))
.filter((value) => Number.isFinite(value))
let memoryPercent = memoryPercentCandidates[0] ?? NaN
let resolvedMemoryUsed = memoryUsedBytes
if ((resolvedMemoryUsed === null || Number.isNaN(resolvedMemoryUsed)) && memoryTotalBytes && Number.isFinite(memoryPercent)) {
resolvedMemoryUsed = (memoryPercent / 100) * memoryTotalBytes
} else if (resolvedMemoryUsed !== null && memoryTotalBytes) {
memoryPercent = clampPercent((resolvedMemoryUsed / memoryTotalBytes) * 100)
}
const partitions = extractPartitionEntries(machine.hostname, inventory)
let diskTotal = 0
let diskFree = 0
partitions.forEach((partition) => {
if (partition.capacityBytes) {
diskTotal += partition.capacityBytes
}
if (partition.freeBytes) {
diskFree += partition.freeBytes
}
})
const diskUsed = diskTotal > 0 ? Math.max(0, diskTotal - diskFree) : null
const diskPercentCandidates = [
diskTotal > 0 ? clampPercent((diskUsed ?? 0) / diskTotal * 100) : NaN,
Number(metricsRecord["diskUsagePercent"]),
Number(metricsRecord["diskUsedPercent"]),
Number(metricsRecord["diskUsage"]),
].filter((value) => Number.isFinite(value))
const diskPercent = diskPercentCandidates[0] ?? NaN
const gpuPercentCandidates = [
Number(metricsRecord["gpuUsagePercent"]),
Number(metricsRecord["gpuUsage"]),
Number(metricsRecord["gpu_percent"]),
Number(metricsRecord["gpu"]),
].filter((value) => Number.isFinite(value))
const gpuPercent = gpuPercentCandidates[0] ?? NaN
if (
Number.isNaN(cpuPercent) &&
resolvedMemoryUsed === null &&
memoryTotalBytes === null &&
Number.isNaN(memoryPercent) &&
diskUsed === null &&
diskTotal === 0 &&
Number.isNaN(gpuPercent)
) {
return null
}
return [
machine.hostname,
capturedAt ? formatDateTime(capturedAt) ?? "—" : "—",
Number.isNaN(cpuPercent) ? "—" : `${cpuPercent.toFixed(0)}%`,
formatBytesValue(resolvedMemoryUsed),
formatBytesValue(memoryTotalBytes),
Number.isNaN(memoryPercent) ? "—" : `${memoryPercent.toFixed(0)}%`,
formatBytesValue(diskUsed),
formatBytesValue(diskTotal || null),
Number.isNaN(diskPercent) ? "—" : `${diskPercent.toFixed(0)}%`,
Number.isNaN(gpuPercent) ? "—" : `${gpuPercent.toFixed(0)}%`,
]
}