feat: melhorar inventário e gestão de máquinas

This commit is contained in:
Esdras Renan 2025-10-10 23:20:21 -03:00
parent b1d334045d
commit 3f0702d80b
5 changed files with 584 additions and 59 deletions

View file

@ -112,9 +112,39 @@ async function getActiveToken(
return { token, machine }
}
function isObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value)
}
function mergeInventory(current: unknown, patch: unknown): unknown {
if (!isObject(patch)) {
return patch
}
const base: Record<string, unknown> = isObject(current) ? { ...(current as Record<string, unknown>) } : {}
for (const [key, value] of Object.entries(patch)) {
if (value === undefined) continue
if (isObject(value) && isObject(base[key])) {
base[key] = mergeInventory(base[key], value)
} else {
base[key] = value
}
}
return base
}
function mergeMetadata(current: unknown, patch: Record<string, unknown>) {
if (!current || typeof current !== "object") return patch
return { ...(current as Record<string, unknown>), ...patch }
const base: Record<string, unknown> = isObject(current) ? { ...(current as Record<string, unknown>) } : {}
for (const [key, value] of Object.entries(patch)) {
if (value === undefined) continue
if (key === "inventory") {
base[key] = mergeInventory(base[key], value)
} else if (isObject(value) && isObject(base[key])) {
base[key] = mergeInventory(base[key], value)
} else {
base[key] = value
}
}
return base
}
type PostureFinding = {
@ -272,6 +302,7 @@ export const register = mutation({
const fingerprint = computeFingerprint(tenantId, args.companySlug, args.hostname, identifiers)
const { companyId, companySlug } = await ensureCompany(ctx, tenantId, args.companySlug)
const now = Date.now()
const metadataPatch = args.metadata && typeof args.metadata === "object" ? (args.metadata as Record<string, unknown>) : undefined
const existing = await ctx.db
.query("machines")
@ -291,7 +322,7 @@ export const register = mutation({
architecture: args.os.architecture,
macAddresses: identifiers.macs,
serialNumbers: identifiers.serials,
metadata: args.metadata ? mergeMetadata(existing.metadata, { inventory: args.metadata }) : existing.metadata,
metadata: metadataPatch ? mergeMetadata(existing.metadata, metadataPatch) : existing.metadata,
lastHeartbeatAt: now,
updatedAt: now,
status: "online",
@ -310,7 +341,7 @@ export const register = mutation({
macAddresses: identifiers.macs,
serialNumbers: identifiers.serials,
fingerprint,
metadata: args.metadata ? { inventory: args.metadata } : undefined,
metadata: metadataPatch ? mergeMetadata(undefined, metadataPatch) : undefined,
lastHeartbeatAt: now,
status: "online",
createdAt: now,
@ -385,10 +416,13 @@ export const upsertInventory = mutation({
const { companyId, companySlug } = await ensureCompany(ctx, tenantId, args.companySlug)
const now = Date.now()
const metadataPatch = mergeMetadata({}, {
...(args.inventory ? { inventory: args.inventory } : {}),
...(args.metrics ? { metrics: args.metrics } : {}),
})
const metadataPatch: Record<string, unknown> = {}
if (args.inventory && typeof args.inventory === "object") {
metadataPatch.inventory = args.inventory as Record<string, unknown>
}
if (args.metrics && typeof args.metrics === "object") {
metadataPatch.metrics = args.metrics as Record<string, unknown>
}
const existing = await ctx.db
.query("machines")
@ -408,7 +442,7 @@ export const upsertInventory = mutation({
architecture: args.os.architecture,
macAddresses: identifiers.macs,
serialNumbers: identifiers.serials,
metadata: mergeMetadata(existing.metadata, metadataPatch),
metadata: Object.keys(metadataPatch).length ? mergeMetadata(existing.metadata, metadataPatch) : existing.metadata,
lastHeartbeatAt: now,
updatedAt: now,
status: args.metrics ? "online" : existing.status ?? "unknown",
@ -427,7 +461,7 @@ export const upsertInventory = mutation({
macAddresses: identifiers.macs,
serialNumbers: identifiers.serials,
fingerprint,
metadata: metadataPatch,
metadata: Object.keys(metadataPatch).length ? mergeMetadata(undefined, metadataPatch) : undefined,
lastHeartbeatAt: now,
status: args.metrics ? "online" : "unknown",
createdAt: now,
@ -470,11 +504,17 @@ export const heartbeat = mutation({
const { machine, token } = await getActiveToken(ctx, args.machineToken)
const now = Date.now()
const mergedMetadata = mergeMetadata(machine.metadata, {
...(args.metadata ?? {}),
...(args.metrics ? { metrics: args.metrics } : {}),
...(args.inventory ? { inventory: args.inventory } : {}),
})
const metadataPatch: Record<string, unknown> = {}
if (args.metadata && typeof args.metadata === "object") {
Object.assign(metadataPatch, args.metadata as Record<string, unknown>)
}
if (args.inventory && typeof args.inventory === "object") {
metadataPatch.inventory = mergeInventory(metadataPatch.inventory, args.inventory as Record<string, unknown>)
}
if (args.metrics && typeof args.metrics === "object") {
metadataPatch.metrics = args.metrics as Record<string, unknown>
}
const mergedMetadata = Object.keys(metadataPatch).length ? mergeMetadata(machine.metadata, metadataPatch) : machine.metadata
await ctx.db.patch(machine._id, {
hostname: args.hostname ?? machine.hostname,
@ -684,3 +724,35 @@ export const rename = mutation({
return { ok: true }
},
})
export const remove = mutation({
args: {
machineId: v.id("machines"),
actorId: v.id("users"),
},
handler: async (ctx, { machineId, actorId }) => {
const machine = await ctx.db.get(machineId)
if (!machine) {
throw new ConvexError("Máquina não encontrada")
}
const actor = await ctx.db.get(actorId)
if (!actor || actor.tenantId !== machine.tenantId) {
throw new ConvexError("Acesso negado ao tenant da máquina")
}
const role = (actor.role ?? "AGENT").toUpperCase()
const STAFF = new Set(["ADMIN", "MANAGER", "AGENT", "COLLABORATOR"])
if (!STAFF.has(role)) {
throw new ConvexError("Apenas equipe interna pode excluir máquinas")
}
const tokens = await ctx.db
.query("machineTokens")
.withIndex("by_machine", (q) => q.eq("machineId", machineId))
.collect()
await Promise.all(tokens.map((token) => ctx.db.delete(token._id)))
await ctx.db.delete(machineId)
return { ok: true }
},
})