From f57d87e1c0a9390bdbe2913ec68a1d67db537415 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Mon, 17 Nov 2025 21:49:05 +0000 Subject: [PATCH] fixing host route of version checking for other architectures --- backend/src/routes/dashboardRoutes.js | 57 +++++++++++++-------- backend/src/routes/hostRoutes.js | 74 ++++++++++++++++++++------- frontend/src/pages/Hosts.jsx | 52 ++++++++++++++++--- 3 files changed, 138 insertions(+), 45 deletions(-) diff --git a/backend/src/routes/dashboardRoutes.js b/backend/src/routes/dashboardRoutes.js index 6bcb211..e994f45 100644 --- a/backend/src/routes/dashboardRoutes.js +++ b/backend/src/routes/dashboardRoutes.js @@ -242,33 +242,48 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => { orderBy: { last_update: "desc" }, }); - // OPTIMIZATION: Get all package counts in 2 batch queries instead of N*2 queries + // OPTIMIZATION: Get all package counts in 3 batch queries instead of N*3 queries const hostIds = hosts.map((h) => h.id); - const [updateCounts, totalCounts] = await Promise.all([ - // Get update counts for all hosts at once - prisma.host_packages.groupBy({ - by: ["host_id"], - where: { - host_id: { in: hostIds }, - needs_update: true, - }, - _count: { id: true }, - }), - // Get total counts for all hosts at once - prisma.host_packages.groupBy({ - by: ["host_id"], - where: { - host_id: { in: hostIds }, - }, - _count: { id: true }, - }), - ]); + const [updateCounts, securityUpdateCounts, totalCounts] = await Promise.all( + [ + // Get update counts for all hosts at once + prisma.host_packages.groupBy({ + by: ["host_id"], + where: { + host_id: { in: hostIds }, + needs_update: true, + }, + _count: { id: true }, + }), + // Get security update counts for all hosts at once + prisma.host_packages.groupBy({ + by: ["host_id"], + where: { + host_id: { in: hostIds }, + needs_update: true, + is_security_update: true, + }, + _count: { id: true }, + }), + // Get total counts for all hosts at once + prisma.host_packages.groupBy({ + by: ["host_id"], + where: { + host_id: { in: hostIds }, + }, + _count: { id: true }, + }), + ], + ); // Create lookup maps for O(1) access const updateCountMap = new Map( updateCounts.map((item) => [item.host_id, item._count.id]), ); + const securityUpdateCountMap = new Map( + securityUpdateCounts.map((item) => [item.host_id, item._count.id]), + ); const totalCountMap = new Map( totalCounts.map((item) => [item.host_id, item._count.id]), ); @@ -276,6 +291,7 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => { // Process hosts with counts from maps (no more DB queries!) const hostsWithUpdateInfo = hosts.map((host) => { const updatesCount = updateCountMap.get(host.id) || 0; + const securityUpdatesCount = securityUpdateCountMap.get(host.id) || 0; const totalPackagesCount = totalCountMap.get(host.id) || 0; // Calculate effective status based on reporting interval @@ -292,6 +308,7 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => { return { ...host, updatesCount, + securityUpdatesCount, totalPackagesCount, isStale, effectiveStatus, diff --git a/backend/src/routes/hostRoutes.js b/backend/src/routes/hostRoutes.js index 2e6eebc..f1730c4 100644 --- a/backend/src/routes/hostRoutes.js +++ b/backend/src/routes/hostRoutes.js @@ -181,6 +181,9 @@ router.get("/agent/version", async (req, res) => { if (fs.existsSync(binaryPath)) { // Binary exists in server's agents folder - use its version + let serverVersion = null; + + // Try method 1: Execute binary (works for same architecture) try { const { stdout } = await execAsync(`${binaryPath} --help`, { timeout: 10000, @@ -192,30 +195,63 @@ router.get("/agent/version", async (req, res) => { ); if (versionMatch) { - const serverVersion = versionMatch[1]; - const agentVersion = req.query.currentVersion || serverVersion; - - // Proper semantic version comparison: only update if server version is NEWER - const hasUpdate = compareVersions(serverVersion, agentVersion) > 0; - - return res.json({ - currentVersion: agentVersion, - latestVersion: serverVersion, - hasUpdate: hasUpdate, - downloadUrl: `/api/v1/hosts/agent/download?arch=${architecture}`, - releaseNotes: `PatchMon Agent v${serverVersion}`, - minServerVersion: null, - architecture: architecture, - agentType: "go", - }); + serverVersion = versionMatch[1]; } } catch (execError) { - // Execution failed, but binary exists - try to get version another way + // Execution failed (likely cross-architecture) - try alternative method console.warn( - `Failed to execute binary ${binaryName} to get version: ${execError.message}`, + `Failed to execute binary ${binaryName} to get version (may be cross-architecture): ${execError.message}`, ); - // Fall through to error response + + // Try method 2: Extract version using strings command (works for cross-architecture) + try { + const { stdout: stringsOutput } = await execAsync( + `strings "${binaryPath}" | grep -E "PatchMon Agent v[0-9]+\\.[0-9]+\\.[0-9]+" | head -1`, + { + timeout: 10000, + }, + ); + + const versionMatch = stringsOutput.match( + /PatchMon Agent v([0-9]+\.[0-9]+\.[0-9]+)/i, + ); + + if (versionMatch) { + serverVersion = versionMatch[1]; + console.log( + `✅ Extracted version ${serverVersion} from binary using strings command`, + ); + } + } catch (stringsError) { + console.warn( + `Failed to extract version using strings command: ${stringsError.message}`, + ); + } } + + // If we successfully got the version, return it + if (serverVersion) { + const agentVersion = req.query.currentVersion || serverVersion; + + // Proper semantic version comparison: only update if server version is NEWER + const hasUpdate = compareVersions(serverVersion, agentVersion) > 0; + + return res.json({ + currentVersion: agentVersion, + latestVersion: serverVersion, + hasUpdate: hasUpdate, + downloadUrl: `/api/v1/hosts/agent/download?arch=${architecture}`, + releaseNotes: `PatchMon Agent v${serverVersion}`, + minServerVersion: null, + architecture: architecture, + agentType: "go", + }); + } + + // If we couldn't get version, fall through to error response + console.warn( + `Could not determine version for binary ${binaryName} using any method`, + ); } // Binary doesn't exist or couldn't get version - return error diff --git a/frontend/src/pages/Hosts.jsx b/frontend/src/pages/Hosts.jsx index 6c02d97..69b031b 100644 --- a/frontend/src/pages/Hosts.jsx +++ b/frontend/src/pages/Hosts.jsx @@ -335,9 +335,15 @@ const Hosts = () => { { id: "status", label: "Status", visible: true, order: 10 }, { id: "needs_reboot", label: "Reboot", visible: true, order: 11 }, { id: "updates", label: "Updates", visible: true, order: 12 }, - { id: "notes", label: "Notes", visible: false, order: 13 }, - { id: "last_update", label: "Last Update", visible: true, order: 14 }, - { id: "actions", label: "Actions", visible: true, order: 15 }, + { + id: "security_updates", + label: "Security Updates", + visible: true, + order: 13, + }, + { id: "notes", label: "Notes", visible: false, order: 14 }, + { id: "last_update", label: "Last Update", visible: true, order: 15 }, + { id: "actions", label: "Actions", visible: true, order: 16 }, ]; const saved = localStorage.getItem("hosts-column-config"); @@ -781,6 +787,10 @@ const Hosts = () => { aValue = a.updatesCount || 0; bValue = b.updatesCount || 0; break; + case "security_updates": + aValue = a.securityUpdatesCount || 0; + bValue = b.securityUpdatesCount || 0; + break; case "needs_reboot": // Sort by boolean: false (0) comes before true (1) aValue = a.needs_reboot ? 1 : 0; @@ -947,9 +957,15 @@ const Hosts = () => { { id: "status", label: "Status", visible: true, order: 10 }, { id: "needs_reboot", label: "Reboot", visible: true, order: 11 }, { id: "updates", label: "Updates", visible: true, order: 12 }, - { id: "notes", label: "Notes", visible: false, order: 13 }, - { id: "last_update", label: "Last Update", visible: true, order: 14 }, - { id: "actions", label: "Actions", visible: true, order: 15 }, + { + id: "security_updates", + label: "Security Updates", + visible: true, + order: 13, + }, + { id: "notes", label: "Notes", visible: false, order: 14 }, + { id: "last_update", label: "Last Update", visible: true, order: 15 }, + { id: "actions", label: "Actions", visible: true, order: 16 }, ]; updateColumnConfig(defaultConfig); }; @@ -1135,6 +1151,19 @@ const Hosts = () => { {host.updatesCount || 0} ); + case "security_updates": + return ( + + ); case "last_update": return (
@@ -1731,6 +1760,17 @@ const Hosts = () => { {column.label} {getSortIcon("updates")} + ) : column.id === "security_updates" ? ( + ) : column.id === "needs_reboot" ? (