feat(admin/ui): filters + badges + full inventory dialog with search; CSV export; types tightened; feat(desktop): charts in diagnostics and heartbeat interval settings; feat(agent): normalized software/services; linux lspci/lsusb parsed
This commit is contained in:
parent
e682c6773a
commit
0556502685
4 changed files with 308 additions and 46 deletions
|
|
@ -56,6 +56,7 @@ type AgentConfig = {
|
|||
createdAt: number
|
||||
lastSyncedAt?: number | null
|
||||
expiresAt?: number | null
|
||||
heartbeatIntervalSec?: number | null
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
@ -186,7 +187,7 @@ async function startHeartbeat(config: AgentConfig) {
|
|||
baseUrl: config.apiBaseUrl,
|
||||
token,
|
||||
status: "online",
|
||||
intervalSeconds: 300,
|
||||
intervalSeconds: Math.max(60, Number(config.heartbeatIntervalSec ?? 300)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +320,59 @@ function renderDiagnosticsPanel(profile: MachineProfile) {
|
|||
.map((v) => (v ? v.toFixed(2) : "—"))
|
||||
.join(" / ")}</div>
|
||||
</div>
|
||||
<div class="machine-summary">
|
||||
<div><strong>Histórico (curto)</strong></div>
|
||||
<canvas id="diag-cpu" width="520" height="100" style="background:rgba(148,163,184,0.15); border-radius:8px;"></canvas>
|
||||
<canvas id="diag-mem" width="520" height="100" style="background:rgba(148,163,184,0.15); border-radius:8px;"></canvas>
|
||||
<p class="text-xs">Amostras locais a cada ~3s, mantemos ~60 pontos.</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
const cpuCanvas = panel.querySelector<HTMLCanvasElement>("#diag-cpu")
|
||||
const memCanvas = panel.querySelector<HTMLCanvasElement>("#diag-mem")
|
||||
const cpuData: number[] = []
|
||||
const memData: number[] = []
|
||||
|
||||
function draw(canvas: HTMLCanvasElement, series: number[], maxValue: number, color: string) {
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
const w = canvas.width
|
||||
const h = canvas.height
|
||||
const n = Math.max(1, series.length)
|
||||
ctx.strokeStyle = color
|
||||
ctx.lineWidth = 2
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < n; i++) {
|
||||
const x = (i / (n - 1)) * (w - 6) + 3
|
||||
const v = Math.max(0, Math.min(maxValue, series[i] ?? 0))
|
||||
const y = h - 4 - (v / maxValue) * (h - 8)
|
||||
if (i === 0) ctx.moveTo(x, y)
|
||||
else ctx.lineTo(x, y)
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
let stop = false
|
||||
async function pump() {
|
||||
if (stop) return
|
||||
try {
|
||||
const p = await collectMachineProfile()
|
||||
cpuData.push(Math.max(0, Math.min(100, p.metrics.cpuUsagePercent)))
|
||||
const memPct = (p.metrics.memoryUsedPercent)
|
||||
memData.push(Math.max(0, Math.min(100, memPct)))
|
||||
while (cpuData.length > 60) cpuData.shift()
|
||||
while (memData.length > 60) memData.shift()
|
||||
if (cpuCanvas) draw(cpuCanvas, cpuData, 100, "#2563eb")
|
||||
if (memCanvas) draw(memCanvas, memData, 100, "#10b981")
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setTimeout(pump, 3000)
|
||||
}
|
||||
}
|
||||
pump()
|
||||
panel.addEventListener("DOMNodeRemoved", () => { stop = true })
|
||||
}
|
||||
|
||||
function renderSettingsPanel(config: AgentConfig) {
|
||||
|
|
@ -339,6 +392,14 @@ function renderSettingsPanel(config: AgentConfig) {
|
|||
<div class="actions">
|
||||
<button id="reset-agent-settings" class="secondary">Reprovisionar</button>
|
||||
</div>
|
||||
<div class="machine-summary">
|
||||
<div><strong>Intervalo do heartbeat (segundos)</strong></div>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<input id="hb-interval" type="number" min="60" step="30" value="${String(config.heartbeatIntervalSec ?? 300)}" style="max-width:140px;" />
|
||||
<button id="save-hb-interval">Salvar</button>
|
||||
</div>
|
||||
<p class="text-xs">Mínimo 60s. Salvar reinicia o processo de heartbeat.</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
document.getElementById("open-app-settings")?.addEventListener("click", () => redirectToApp(config))
|
||||
|
|
@ -374,6 +435,21 @@ function renderSettingsPanel(config: AgentConfig) {
|
|||
setAlert("Falha ao enviar inventário agora.", "error")
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById("save-hb-interval")?.addEventListener("click", async () => {
|
||||
try {
|
||||
const input = document.getElementById("hb-interval") as HTMLInputElement | null
|
||||
const value = Math.max(60, Number(input?.value ?? 300))
|
||||
const updated: AgentConfig = { ...config, heartbeatIntervalSec: value, lastSyncedAt: Date.now() }
|
||||
await saveConfig(updated)
|
||||
await stopHeartbeat().catch(() => undefined)
|
||||
await startHeartbeat(updated)
|
||||
setAlert("Intervalo do heartbeat atualizado.", "success")
|
||||
} catch (error) {
|
||||
console.error("[agent] Falha ao salvar intervalo", error)
|
||||
setAlert("Falha ao salvar intervalo do heartbeat.", "error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue