feat: improve machines inventory exports
This commit is contained in:
parent
d92c817e7b
commit
38b46f32ce
5 changed files with 858 additions and 222 deletions
661
src/server/machines/inventory-export.ts
Normal file
661
src/server/machines/inventory-export.ts
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
import { buildXlsxWorkbook, type WorksheetConfig } from "@/lib/xlsx"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
|
||||
type LinkedUser = {
|
||||
id: string
|
||||
email: string | null
|
||||
name: string | null
|
||||
}
|
||||
|
||||
export type MachineInventoryRecord = {
|
||||
id: Id<"machines">
|
||||
tenantId: string
|
||||
hostname: string
|
||||
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
|
||||
linkedUsers?: LinkedUser[]
|
||||
}
|
||||
|
||||
type WorkbookOptions = {
|
||||
tenantId: string
|
||||
generatedBy?: string | null
|
||||
companyFilterLabel?: string | null
|
||||
generatedAt?: Date
|
||||
}
|
||||
|
||||
type SoftwareEntry = {
|
||||
hostname: string
|
||||
name: string
|
||||
version: string | null
|
||||
source: string | null
|
||||
publisher: string | null
|
||||
installedOn: string | null
|
||||
}
|
||||
|
||||
type DiskEntry = {
|
||||
hostname: string
|
||||
type: string | null
|
||||
model: string | null
|
||||
name: string | null
|
||||
mountPoint: string | null
|
||||
size: string | null
|
||||
free: string | null
|
||||
serial: string | null
|
||||
smartStatus: string | null
|
||||
}
|
||||
|
||||
const INVENTORY_HEADERS = [
|
||||
"Hostname",
|
||||
"Empresa",
|
||||
"Status",
|
||||
"Persona",
|
||||
"Ativa",
|
||||
"Último heartbeat",
|
||||
"Responsável",
|
||||
"E-mail responsável",
|
||||
"Usuários vinculados",
|
||||
"E-mail autenticado",
|
||||
"Sistema operacional",
|
||||
"Versão SO",
|
||||
"Arquitetura",
|
||||
"Fabricante",
|
||||
"Modelo",
|
||||
"Serial hardware",
|
||||
"Processador",
|
||||
"Cores físicas",
|
||||
"Cores lógicas",
|
||||
"Memória (GiB)",
|
||||
"GPUs",
|
||||
"Labels",
|
||||
"MACs",
|
||||
"Seriais",
|
||||
"IP principal",
|
||||
"IP público",
|
||||
"Registrada via",
|
||||
"Token expira em",
|
||||
"Token último uso",
|
||||
"Uso do token",
|
||||
"Criada em",
|
||||
"Atualizada em",
|
||||
"Softwares instalados",
|
||||
] as const
|
||||
|
||||
const INVENTORY_COLUMN_WIDTHS = [
|
||||
22, 26, 16, 14, 10, 20, 22, 24, 28, 24, 20, 18, 14, 18, 22, 22, 24, 12, 12, 14, 26, 20, 24, 24, 18, 18, 18, 20, 20, 14, 20, 20, 18,
|
||||
] as const
|
||||
|
||||
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 DISK_HEADERS = ["Hostname", "Tipo", "Modelo", "Nome", "Montagem", "Capacidade", "Livre", "Serial", "Status SMART"] as const
|
||||
const DISK_COLUMN_WIDTHS = [22, 14, 24, 18, 18, 16, 16, 22, 18] 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: "Máquina",
|
||||
}
|
||||
|
||||
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 inventoryRows = machines.map((machine) => flattenMachine(machine))
|
||||
const linksRows = buildLinkedUsersRows(machines)
|
||||
const softwareRows = buildSoftwareRows(machines)
|
||||
const diskRows = buildDiskRows(machines)
|
||||
|
||||
const sheets: WorksheetConfig[] = [
|
||||
{
|
||||
name: "Resumo",
|
||||
headers: ["Item", "Valor"],
|
||||
rows: summaryRows,
|
||||
columnWidths: [28, 48],
|
||||
},
|
||||
{
|
||||
name: "Inventário",
|
||||
headers: [...INVENTORY_HEADERS],
|
||||
rows: inventoryRows,
|
||||
columnWidths: [...INVENTORY_COLUMN_WIDTHS],
|
||||
freezePane: { rowSplit: 1 },
|
||||
autoFilter: true,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "Softwares",
|
||||
headers: [...SOFTWARE_HEADERS],
|
||||
rows: softwareRows.length > 0 ? softwareRows : [["—", "—", "—", "—", "—", "—"]],
|
||||
columnWidths: [...SOFTWARE_COLUMN_WIDTHS],
|
||||
freezePane: { rowSplit: 1 },
|
||||
autoFilter: softwareRows.length > 0,
|
||||
},
|
||||
{
|
||||
name: "Discos",
|
||||
headers: [...DISK_HEADERS],
|
||||
rows: diskRows.length > 0 ? diskRows : [Array(DISK_HEADERS.length).fill("—")],
|
||||
columnWidths: [...DISK_COLUMN_WIDTHS],
|
||||
freezePane: { rowSplit: 1 },
|
||||
autoFilter: diskRows.length > 0,
|
||||
},
|
||||
]
|
||||
|
||||
return buildXlsxWorkbook(sheets)
|
||||
}
|
||||
|
||||
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 máquinas", machines.length])
|
||||
|
||||
const activeCount = machines.filter((machine) => machine.isActive).length
|
||||
rows.push(["Máquinas ativas", activeCount])
|
||||
rows.push(["Máquinas inativas", machines.length - activeCount])
|
||||
|
||||
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(", ")])
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
function flattenMachine(machine: MachineInventoryRecord): WorksheetRow {
|
||||
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 softwareCount = countSoftwareEntries(inventory)
|
||||
const linkedUsers = summarizeLinkedUsers(machine.linkedUsers)
|
||||
|
||||
return [
|
||||
machine.hostname,
|
||||
machine.companyName ?? "—",
|
||||
describeStatus(machine.status),
|
||||
describePersona(machine.persona),
|
||||
yesNo(machine.isActive),
|
||||
formatDateTime(machine.lastHeartbeatAt),
|
||||
machine.assignedUserName ?? machine.assignedUserEmail ?? "—",
|
||||
machine.assignedUserEmail ?? "—",
|
||||
linkedUsers ?? "—",
|
||||
machine.authEmail ?? "—",
|
||||
machine.osName,
|
||||
machine.osVersion ?? "—",
|
||||
machine.architecture ?? "—",
|
||||
hardware.vendor ?? "—",
|
||||
hardware.model ?? "—",
|
||||
hardware.serial ?? "—",
|
||||
hardware.cpuType ?? "—",
|
||||
hardware.physicalCores ?? "—",
|
||||
hardware.logicalCores ?? "—",
|
||||
hardware.memoryGiB ?? "—",
|
||||
gpuNames.length > 0 ? gpuNames.join(", ") : "—",
|
||||
labels.length > 0 ? labels.join(", ") : "—",
|
||||
machine.macAddresses.length > 0 ? machine.macAddresses.join(", ") : "—",
|
||||
machine.serialNumbers.length > 0 ? machine.serialNumbers.join(", ") : "—",
|
||||
primaryIp ?? "—",
|
||||
publicIp ?? "—",
|
||||
machine.registeredBy ?? "—",
|
||||
machine.token?.expiresAt ? formatDateTime(machine.token.expiresAt) ?? "—" : "—",
|
||||
machine.token?.lastUsedAt ? formatDateTime(machine.token.lastUsedAt) ?? "—" : "—",
|
||||
machine.token?.usageCount ?? 0,
|
||||
formatDateTime(machine.createdAt) ?? "—",
|
||||
formatDateTime(machine.updatedAt) ?? "—",
|
||||
softwareCount ?? 0,
|
||||
]
|
||||
}
|
||||
|
||||
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: WorksheetRow[] = []
|
||||
machines.forEach((machine) => {
|
||||
const inventory = toRecord(machine.inventory)
|
||||
const entries = extractSoftwareEntries(machine.hostname, inventory)
|
||||
entries.forEach((entry) => {
|
||||
rows.push([
|
||||
entry.hostname,
|
||||
entry.name,
|
||||
entry.version ?? "—",
|
||||
entry.source ?? "—",
|
||||
entry.publisher ?? "—",
|
||||
entry.installedOn ?? "—",
|
||||
])
|
||||
})
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
function buildDiskRows(machines: MachineInventoryRecord[]): WorksheetRow[] {
|
||||
const rows: WorksheetRow[] = []
|
||||
machines.forEach((machine) => {
|
||||
const inventory = toRecord(machine.inventory)
|
||||
const entries = extractDiskEntries(machine.hostname, inventory)
|
||||
entries.forEach((disk) => {
|
||||
rows.push([
|
||||
disk.hostname,
|
||||
disk.type ?? "—",
|
||||
disk.model ?? "—",
|
||||
disk.name ?? "—",
|
||||
disk.mountPoint ?? "—",
|
||||
disk.size ?? "—",
|
||||
disk.free ?? "—",
|
||||
disk.serial ?? "—",
|
||||
disk.smartStatus ?? "—",
|
||||
])
|
||||
})
|
||||
})
|
||||
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 formatBytes(value: number | null | undefined): string | null {
|
||||
if (typeof value !== "number" || Number.isNaN(value) || value <= 0) return null
|
||||
const gib = value / 1024 ** 3
|
||||
return `${gib.toFixed(2)} GiB`
|
||||
}
|
||||
|
||||
function extractHardware(inventory: Record<string, unknown> | null) {
|
||||
if (!inventory) {
|
||||
return {
|
||||
vendor: null,
|
||||
model: null,
|
||||
serial: null,
|
||||
cpuType: null,
|
||||
physicalCores: null,
|
||||
logicalCores: null,
|
||||
memoryGiB: null,
|
||||
}
|
||||
}
|
||||
const hardware = pickRecord(inventory, ["hardware", "Hardware"])
|
||||
const vendor = pickString(hardware, ["vendor", "Vendor"])
|
||||
const model = pickString(hardware, ["model", "Model"])
|
||||
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 memoryGiB = formatBytes(pickNumber(hardware, ["memoryBytes", "MemoryBytes", "totalMemory", "TotalMemory"]))
|
||||
return {
|
||||
vendor,
|
||||
model,
|
||||
serial,
|
||||
cpuType,
|
||||
physicalCores,
|
||||
logicalCores,
|
||||
memoryGiB,
|
||||
}
|
||||
}
|
||||
|
||||
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 countSoftwareEntries(inventory: Record<string, unknown> | null): number | null {
|
||||
if (!inventory) return null
|
||||
const direct = inventory["software"]
|
||||
if (Array.isArray(direct)) return direct.length
|
||||
const extended = pickRecord(inventory, ["extended", "Extended"])
|
||||
const windows = pickRecord(extended, ["windows", "Windows"])
|
||||
const software = windows ? windows["software"] ?? windows["installedPrograms"] ?? windows["InstalledPrograms"] : undefined
|
||||
if (Array.isArray(software)) return software.length
|
||||
return null
|
||||
}
|
||||
|
||||
function extractSoftwareEntries(hostname: string, inventory: Record<string, unknown> | null): SoftwareEntry[] {
|
||||
if (!inventory) return []
|
||||
const entries: SoftwareEntry[] = []
|
||||
|
||||
const pushEntry = (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"])
|
||||
const publisher = pickString(record, ["Publisher", "publisher", "Vendor", "vendor"])
|
||||
const installed = pickString(record, ["InstalledOn", "installedOn", "InstallDate", "installDate", "InstallDateUTC"])
|
||||
entries.push({
|
||||
hostname,
|
||||
name,
|
||||
version,
|
||||
source,
|
||||
publisher,
|
||||
installedOn: installed,
|
||||
})
|
||||
}
|
||||
|
||||
const direct = inventory["software"]
|
||||
if (Array.isArray(direct)) {
|
||||
direct.map(toRecord).forEach(pushEntry)
|
||||
} else if (direct) {
|
||||
pushEntry(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(pushEntry)
|
||||
} else {
|
||||
pushEntry(toRecord(software))
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
function extractDiskEntries(hostname: string, inventory: Record<string, unknown> | null): DiskEntry[] {
|
||||
if (!inventory) return []
|
||||
const entries: DiskEntry[] = []
|
||||
|
||||
const pushDisk = (record: Record<string, unknown> | null) => {
|
||||
if (!record) return
|
||||
const size = formatBytes(pickNumber(record, ["sizeBytes", "Size", "size"]))
|
||||
const free = formatBytes(pickNumber(record, ["freeBytes", "FreeSpace", "free", "Free"]))
|
||||
const type = pickString(record, ["type", "Type", "MediaType"])
|
||||
const model = pickString(record, ["model", "Model"])
|
||||
const name = pickString(record, ["name", "Name", "DeviceID", "Device"])
|
||||
const mountPoint = pickString(record, ["mount", "Mount", "mountpoint", "MountPoint", "path"])
|
||||
const serial = pickString(record, ["serial", "Serial", "SerialNumber"])
|
||||
const smartStatus = pickString(record, ["smartStatus", "SmartStatus", "status", "Status"])
|
||||
entries.push({
|
||||
hostname,
|
||||
type,
|
||||
model,
|
||||
name,
|
||||
mountPoint,
|
||||
size,
|
||||
free,
|
||||
serial,
|
||||
smartStatus,
|
||||
})
|
||||
}
|
||||
|
||||
const direct = inventory["disks"]
|
||||
if (Array.isArray(direct)) {
|
||||
direct.map(toRecord).forEach(pushDisk)
|
||||
} else {
|
||||
pushDisk(toRecord(direct))
|
||||
}
|
||||
|
||||
const extended = pickRecord(inventory, ["extended", "Extended"])
|
||||
const windows = pickRecord(extended, ["windows", "Windows"])
|
||||
if (windows) {
|
||||
const diskDrives = windows["diskDrives"] ?? windows["DiskDrives"]
|
||||
if (Array.isArray(diskDrives)) {
|
||||
diskDrives.map(toRecord).forEach(pushDisk)
|
||||
} else {
|
||||
pushDisk(toRecord(diskDrives))
|
||||
}
|
||||
}
|
||||
|
||||
const linux = pickRecord(extended, ["linux", "Linux"])
|
||||
if (linux) {
|
||||
const lsblk = linux["lsblk"]
|
||||
if (Array.isArray(lsblk)) {
|
||||
lsblk.map(toRecord).forEach(pushDisk)
|
||||
} else {
|
||||
pushDisk(toRecord(lsblk))
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue