feat(devices): implementa tabela separada para softwares instalados

- Cria tabela machineSoftware no schema com indices otimizados
- Adiciona mutations para sincronizar softwares do heartbeat
- Atualiza heartbeat para processar e salvar softwares
- Cria componente DeviceSoftwareList com pesquisa e paginacao
- Integra lista de softwares no drawer de detalhes do dispositivo

feat(sla): transforma formulario em modal completo

- Substitui formulario inline por modal guiado
- Adiciona badge "Global" para indicar escopo da politica
- Adiciona seletor de unidade de tempo (minutos, horas, dias)
- Melhora textos e adiciona dica sobre hierarquia de SLAs

fix(reports): ajusta altura do SearchableCombobox

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-18 08:00:40 -03:00
parent ef2545221d
commit 23fe67e7d3
7 changed files with 741 additions and 205 deletions

View file

@ -1,6 +1,6 @@
// ci: trigger convex functions deploy (no-op)
import { mutation, query } from "./_generated/server"
import { api } from "./_generated/api"
import { internal, api } from "./_generated/api"
import { paginationOptsValidator } from "convex/server"
import { ConvexError, v, Infer } from "convex/values"
import { sha256 } from "@noble/hashes/sha2.js"
@ -1010,6 +1010,34 @@ export const heartbeat = mutation({
await upsertRemoteAccessSnapshotFromHeartbeat(ctx, machine, remoteAccessSnapshot, now)
}
// Processar softwares instalados (armazenados em tabela separada)
// Os dados de software sao extraidos ANTES de sanitizar o inventory
const rawInventory = args.inventory ?? args.metadata?.inventory
if (rawInventory && typeof rawInventory === "object") {
const softwareArray = (rawInventory as Record<string, unknown>)["software"]
if (Array.isArray(softwareArray) && softwareArray.length > 0) {
const validSoftware = softwareArray
.filter((item): item is Record<string, unknown> => item !== null && typeof item === "object")
.map((item) => ({
name: typeof item.name === "string" ? item.name : "",
version: typeof item.version === "string" ? item.version : undefined,
publisher: typeof item.publisher === "string" || typeof item.source === "string"
? (item.publisher as string) || (item.source as string)
: undefined,
source: typeof item.source === "string" ? item.source : undefined,
}))
.filter((item) => item.name.length > 0)
if (validSoftware.length > 0) {
await ctx.runMutation(internal.machineSoftware.syncFromHeartbeat, {
tenantId: machine.tenantId,
machineId: machine._id,
software: validSoftware,
})
}
}
}
await ctx.db.patch(token._id, {
lastUsedAt: now,
usageCount: (token.usageCount ?? 0) + 1,