Compare commits

...

9 Commits

Author SHA1 Message Date
renovate[bot]
186fb1b4c7 Update dependency @vitejs/plugin-react to v5 2025-11-18 18:17:16 +00:00
9 Technology Group LTD
c6a2163e79 Merge pull request #333 from PatchMon/release/1-3-5
Release/1 3 5
2025-11-18 18:15:41 +00:00
Muhammad Ibrahim
b55ee6a6e0 Fixing proxmox auto-enrolment script 2025-11-18 18:10:38 +00:00
9 Technology Group LTD
e983d39bd6 Merge pull request #331 from PatchMon/release/1-3-5
fixing host route of version checking for other architectures
2025-11-17 22:09:47 +00:00
Muhammad Ibrahim
189de7a593 fixed linting 2025-11-17 21:59:19 +00:00
Muhammad Ibrahim
f57d87e1c0 fixing host route of version checking for other architectures 2025-11-17 21:49:05 +00:00
9 Technology Group LTD
470b204a8c Merge pull request #328 from PatchMon/release/1-3-5
Fixing critical bug on agent version handling causing agents to fill …
2025-11-17 19:38:17 +00:00
Muhammad Ibrahim
fa1f0fd7d7 Fixing critical bug on agent version handling causing agents to fill up deisk 2025-11-17 19:30:29 +00:00
Muhammad Ibrahim
334357a12e Added arm64 support for running PatchMon in Docker
Appended with bash for proxmox auto-enrolment
Fixed rootless docker issues for prisma 6.1 requirements
2025-11-17 18:40:28 +00:00
16 changed files with 398 additions and 185 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -197,6 +197,40 @@ while IFS= read -r line; do
continue continue
fi fi
# Check if agent is already installed and working BEFORE enrollment
info " Checking if agent is already configured..."
config_check=$(timeout 10 pct exec "$vmid" -- bash -c "
if [[ -f /etc/patchmon/config.yml ]] && [[ -f /etc/patchmon/credentials.yml ]]; then
if [[ -f /usr/local/bin/patchmon-agent ]]; then
# Try to ping using existing configuration
if /usr/local/bin/patchmon-agent ping >/dev/null 2>&1; then
echo 'ping_success'
else
echo 'ping_failed'
fi
else
echo 'binary_missing'
fi
else
echo 'not_configured'
fi
" 2>/dev/null </dev/null || echo "error")
if [[ "$config_check" == "ping_success" ]]; then
info " ✓ Host already enrolled and agent ping successful - skipping enrollment"
((skipped_count++)) || true
echo ""
continue
elif [[ "$config_check" == "ping_failed" ]]; then
warn " ⚠ Agent configuration exists but ping failed - will re-enroll and reinstall"
elif [[ "$config_check" == "binary_missing" ]]; then
warn " ⚠ Config exists but agent binary missing - will re-enroll and reinstall"
elif [[ "$config_check" == "not_configured" ]]; then
info " Agent not yet configured - proceeding with enrollment"
else
warn " ⚠ Could not check agent status - proceeding with enrollment"
fi
# Call PatchMon auto-enrollment API # Call PatchMon auto-enrollment API
info " Enrolling $friendly_name in PatchMon..." info " Enrolling $friendly_name in PatchMon..."
@@ -230,40 +264,6 @@ while IFS= read -r line; do
info " ✓ Host enrolled successfully: $api_id" info " ✓ Host enrolled successfully: $api_id"
# Check if agent is already installed and working
info " Checking if agent is already configured..."
config_check=$(timeout 10 pct exec "$vmid" -- bash -c "
if [[ -f /etc/patchmon/config.yml ]] && [[ -f /etc/patchmon/credentials.yml ]]; then
if [[ -f /usr/local/bin/patchmon-agent ]]; then
# Try to ping using existing configuration
if /usr/local/bin/patchmon-agent ping >/dev/null 2>&1; then
echo 'ping_success'
else
echo 'ping_failed'
fi
else
echo 'binary_missing'
fi
else
echo 'not_configured'
fi
" 2>/dev/null </dev/null || echo "error")
if [[ "$config_check" == "ping_success" ]]; then
info " ✓ Host already enrolled and agent ping successful - skipping"
((skipped_count++)) || true
echo ""
continue
elif [[ "$config_check" == "ping_failed" ]]; then
warn " ⚠ Agent configuration exists but ping failed - will reinstall"
elif [[ "$config_check" == "binary_missing" ]]; then
warn " ⚠ Config exists but agent binary missing - will reinstall"
elif [[ "$config_check" == "not_configured" ]]; then
info " Agent not yet configured - proceeding with installation"
else
warn " ⚠ Could not check agent status - proceeding with installation"
fi
# Ensure curl is installed in the container # Ensure curl is installed in the container
info " Checking for curl in container..." info " Checking for curl in container..."
curl_check=$(timeout 10 pct exec "$vmid" -- bash -c "command -v curl >/dev/null 2>&1 && echo 'installed' || echo 'missing'" 2>/dev/null </dev/null || echo "error") curl_check=$(timeout 10 pct exec "$vmid" -- bash -c "command -v curl >/dev/null 2>&1 && echo 'installed' || echo 'missing'" 2>/dev/null </dev/null || echo "error")

View File

@@ -1,6 +1,6 @@
{ {
"name": "patchmon-backend", "name": "patchmon-backend",
"version": "1.3.4", "version": "1.3.5",
"description": "Backend API for Linux Patch Monitoring System", "description": "Backend API for Linux Patch Monitoring System",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "src/server.js", "main": "src/server.js",

View File

@@ -1,6 +1,6 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"] binaryTargets = ["native", "linux-musl-openssl-3.0.x", "linux-musl-arm64-openssl-3.0.x"]
} }
datasource db { datasource db {

View File

@@ -588,8 +588,11 @@ router.get("/script", async (req, res) => {
// Check for --force parameter // Check for --force parameter
const force_install = req.query.force === "true" || req.query.force === "1"; const force_install = req.query.force === "true" || req.query.force === "1";
// Use bash for proxmox-lxc, sh for others
const shebang = script_type === "proxmox-lxc" ? "#!/bin/bash" : "#!/bin/sh";
// Inject the token credentials, server URL, curl flags, and force flag into the script // Inject the token credentials, server URL, curl flags, and force flag into the script
const env_vars = `#!/bin/sh const env_vars = `${shebang}
# PatchMon Auto-Enrollment Configuration (Auto-generated) # PatchMon Auto-Enrollment Configuration (Auto-generated)
export PATCHMON_URL="${server_url}" export PATCHMON_URL="${server_url}"
export AUTO_ENROLLMENT_KEY="${token.token_key}" export AUTO_ENROLLMENT_KEY="${token.token_key}"

View File

@@ -242,10 +242,11 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
orderBy: { last_update: "desc" }, 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 hostIds = hosts.map((h) => h.id);
const [updateCounts, totalCounts] = await Promise.all([ const [updateCounts, securityUpdateCounts, totalCounts] = await Promise.all(
[
// Get update counts for all hosts at once // Get update counts for all hosts at once
prisma.host_packages.groupBy({ prisma.host_packages.groupBy({
by: ["host_id"], by: ["host_id"],
@@ -255,6 +256,16 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
}, },
_count: { id: 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 // Get total counts for all hosts at once
prisma.host_packages.groupBy({ prisma.host_packages.groupBy({
by: ["host_id"], by: ["host_id"],
@@ -263,12 +274,16 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
}, },
_count: { id: true }, _count: { id: true },
}), }),
]); ],
);
// Create lookup maps for O(1) access // Create lookup maps for O(1) access
const updateCountMap = new Map( const updateCountMap = new Map(
updateCounts.map((item) => [item.host_id, item._count.id]), 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( const totalCountMap = new Map(
totalCounts.map((item) => [item.host_id, item._count.id]), 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!) // Process hosts with counts from maps (no more DB queries!)
const hostsWithUpdateInfo = hosts.map((host) => { const hostsWithUpdateInfo = hosts.map((host) => {
const updatesCount = updateCountMap.get(host.id) || 0; const updatesCount = updateCountMap.get(host.id) || 0;
const securityUpdatesCount = securityUpdateCountMap.get(host.id) || 0;
const totalPackagesCount = totalCountMap.get(host.id) || 0; const totalPackagesCount = totalCountMap.get(host.id) || 0;
// Calculate effective status based on reporting interval // Calculate effective status based on reporting interval
@@ -292,6 +308,7 @@ router.get("/hosts", authenticateToken, requireViewHosts, async (_req, res) => {
return { return {
...host, ...host,
updatesCount, updatesCount,
securityUpdatesCount,
totalPackagesCount, totalPackagesCount,
isStale, isStale,
effectiveStatus, effectiveStatus,

View File

@@ -12,7 +12,6 @@ const {
} = require("../middleware/permissions"); } = require("../middleware/permissions");
const { queueManager, QUEUE_NAMES } = require("../services/automation"); const { queueManager, QUEUE_NAMES } = require("../services/automation");
const { pushIntegrationToggle, isConnected } = require("../services/agentWs"); const { pushIntegrationToggle, isConnected } = require("../services/agentWs");
const agentVersionService = require("../services/agentVersionService");
const { compareVersions } = require("../services/automation/shared/utils"); const { compareVersions } = require("../services/automation/shared/utils");
const router = express.Router(); const router = express.Router();
@@ -170,32 +169,20 @@ router.get("/agent/version", async (req, res) => {
}); });
} else { } else {
// Go agent version check // Go agent version check
// Detect server architecture and map to Go architecture names // Always check the server's local binary for the requested architecture
const os = require("node:os"); // The server's agents folder is the source of truth, not GitHub
const { exec } = require("node:child_process"); const { exec } = require("node:child_process");
const { promisify } = require("node:util"); const { promisify } = require("node:util");
const execAsync = promisify(exec); const execAsync = promisify(exec);
const serverArch = os.arch();
// Map Node.js architecture to Go architecture names
const archMap = {
x64: "amd64",
ia32: "386",
arm64: "arm64",
arm: "arm",
};
const serverGoArch = archMap[serverArch] || serverArch;
// If requested architecture matches server architecture, execute the binary
if (architecture === serverGoArch) {
const binaryName = `patchmon-agent-linux-${architecture}`; const binaryName = `patchmon-agent-linux-${architecture}`;
const binaryPath = path.join(__dirname, "../../../agents", binaryName); const binaryPath = path.join(__dirname, "../../../agents", binaryName);
if (!fs.existsSync(binaryPath)) { if (fs.existsSync(binaryPath)) {
// Binary doesn't exist, fall back to GitHub // Binary exists in server's agents folder - use its version
console.log(`Binary ${binaryName} not found, falling back to GitHub`); let serverVersion = null;
} else {
// Execute the binary to get its version // Try method 1: Execute binary (works for same architecture)
try { try {
const { stdout } = await execAsync(`${binaryPath} --help`, { const { stdout } = await execAsync(`${binaryPath} --help`, {
timeout: 10000, timeout: 10000,
@@ -207,12 +194,46 @@ router.get("/agent/version", async (req, res) => {
); );
if (versionMatch) { if (versionMatch) {
const serverVersion = versionMatch[1]; serverVersion = versionMatch[1];
}
} catch (execError) {
// Execution failed (likely cross-architecture) - try alternative method
console.warn(
`Failed to execute binary ${binaryName} to get version (may be cross-architecture): ${execError.message}`,
);
// 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; const agentVersion = req.query.currentVersion || serverVersion;
// Proper semantic version comparison: only update if server version is NEWER // Proper semantic version comparison: only update if server version is NEWER
const hasUpdate = const hasUpdate = compareVersions(serverVersion, agentVersion) > 0;
compareVersions(serverVersion, agentVersion) > 0;
return res.json({ return res.json({
currentVersion: agentVersion, currentVersion: agentVersion,
@@ -225,56 +246,24 @@ router.get("/agent/version", async (req, res) => {
agentType: "go", agentType: "go",
}); });
} }
} catch (execError) {
// Execution failed, fall back to GitHub // If we couldn't get version, fall through to error response
console.log( console.warn(
`Failed to execute binary ${binaryName}: ${execError.message}, falling back to GitHub`, `Could not determine version for binary ${binaryName} using any method`,
); );
} }
}
}
// Fall back to GitHub if architecture doesn't match or binary execution failed // Binary doesn't exist or couldn't get version - return error
try { // Don't fall back to GitHub - the server's agents folder is the source of truth
const versionInfo = await agentVersionService.getVersionInfo(); const agentVersion = req.query.currentVersion || "unknown";
const latestVersion = versionInfo.latestVersion; return res.status(404).json({
const agentVersion = error: `Agent binary not found for architecture: ${architecture}. Please ensure the binary is in the server's agents folder.`,
req.query.currentVersion || latestVersion || "unknown";
if (!latestVersion) {
return res.status(503).json({
error: "Unable to determine latest version from GitHub releases",
currentVersion: agentVersion, currentVersion: agentVersion,
latestVersion: null, latestVersion: null,
hasUpdate: false, hasUpdate: false,
});
}
// Proper semantic version comparison: only update if latest version is NEWER
const hasUpdate =
latestVersion !== null &&
compareVersions(latestVersion, agentVersion) > 0;
res.json({
currentVersion: agentVersion,
latestVersion: latestVersion,
hasUpdate: hasUpdate,
downloadUrl: `/api/v1/hosts/agent/download?arch=${architecture}`,
releaseNotes: `PatchMon Agent v${latestVersion}`,
minServerVersion: null,
architecture: architecture, architecture: architecture,
agentType: "go", agentType: "go",
}); });
} catch (serviceError) {
console.error(
"Failed to get version from agentVersionService:",
serviceError.message,
);
return res.status(500).json({
error: "Failed to get agent version from service",
details: serviceError.message,
});
}
} }
} catch (error) { } catch (error) {
console.error("Version check error:", error); console.error("Version check error:", error);

View File

@@ -10,8 +10,6 @@ ENV NODE_ENV=development \
RUN apk add --no-cache openssl tini curl libc6-compat RUN apk add --no-cache openssl tini curl libc6-compat
USER node
WORKDIR /app WORKDIR /app
COPY --chown=node:node package*.json ./ COPY --chown=node:node package*.json ./
@@ -20,7 +18,10 @@ COPY --chown=node:node agents ./agents_backup
COPY --chown=node:node agents ./agents COPY --chown=node:node agents ./agents
COPY --chmod=755 docker/backend.docker-entrypoint.sh ./entrypoint.sh COPY --chmod=755 docker/backend.docker-entrypoint.sh ./entrypoint.sh
RUN npm install --workspace=backend --ignore-scripts && cd backend && npx prisma generate USER node
RUN npm install --workspace=backend --ignore-scripts && cd backend && npx prisma generate && \
chmod -R u+w /app/node_modules/@prisma/engines 2>/dev/null || true
EXPOSE 3001 EXPOSE 3001
@@ -66,8 +67,6 @@ ENV NODE_ENV=production \
RUN apk add --no-cache openssl tini curl libc6-compat RUN apk add --no-cache openssl tini curl libc6-compat
USER node
WORKDIR /app WORKDIR /app
COPY --from=builder --chown=node:node /app/backend ./backend COPY --from=builder --chown=node:node /app/backend ./backend
@@ -76,6 +75,14 @@ COPY --chown=node:node agents ./agents_backup
COPY --chown=node:node agents ./agents COPY --chown=node:node agents ./agents
COPY --chmod=755 docker/backend.docker-entrypoint.sh ./entrypoint.sh COPY --chmod=755 docker/backend.docker-entrypoint.sh ./entrypoint.sh
# Ensure Prisma engines directory is writable for rootless Docker (Prisma 6.1.0+ requirement)
# This must be done as root before switching to node user
# Order: chown first (sets ownership), then chmod (sets permissions)
RUN chown -R node:node /app/node_modules/@prisma/engines && \
chmod -R u+w /app/node_modules/@prisma/engines
USER node
WORKDIR /app/backend WORKDIR /app/backend
EXPOSE 3001 EXPOSE 3001

View File

@@ -6,5 +6,5 @@ VITE_API_URL=http://localhost:3001/api/v1
# Application Metadata # Application Metadata
VITE_APP_NAME=PatchMon VITE_APP_NAME=PatchMon
VITE_APP_VERSION=1.3.4 VITE_APP_VERSION=1.3.5

View File

@@ -1,7 +1,7 @@
{ {
"name": "patchmon-frontend", "name": "patchmon-frontend",
"private": true, "private": true,
"version": "1.3.4", "version": "1.3.5",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -32,7 +32,7 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.14", "@types/react": "^18.3.14",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",

View File

@@ -248,7 +248,6 @@ const Hosts = () => {
const showFiltersParam = searchParams.get("showFilters"); const showFiltersParam = searchParams.get("showFilters");
const osFilterParam = searchParams.get("osFilter"); const osFilterParam = searchParams.get("osFilter");
const groupParam = searchParams.get("group"); const groupParam = searchParams.get("group");
const rebootParam = searchParams.get("reboot");
if (filter === "needsUpdates") { if (filter === "needsUpdates") {
setShowFilters(true); setShowFilters(true);
@@ -335,9 +334,15 @@ const Hosts = () => {
{ id: "status", label: "Status", visible: true, order: 10 }, { id: "status", label: "Status", visible: true, order: 10 },
{ id: "needs_reboot", label: "Reboot", visible: true, order: 11 }, { id: "needs_reboot", label: "Reboot", visible: true, order: 11 },
{ id: "updates", label: "Updates", visible: true, order: 12 }, { 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: "security_updates",
{ id: "actions", label: "Actions", visible: true, order: 15 }, 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"); const saved = localStorage.getItem("hosts-column-config");
@@ -781,6 +786,10 @@ const Hosts = () => {
aValue = a.updatesCount || 0; aValue = a.updatesCount || 0;
bValue = b.updatesCount || 0; bValue = b.updatesCount || 0;
break; break;
case "security_updates":
aValue = a.securityUpdatesCount || 0;
bValue = b.securityUpdatesCount || 0;
break;
case "needs_reboot": case "needs_reboot":
// Sort by boolean: false (0) comes before true (1) // Sort by boolean: false (0) comes before true (1)
aValue = a.needs_reboot ? 1 : 0; aValue = a.needs_reboot ? 1 : 0;
@@ -947,9 +956,15 @@ const Hosts = () => {
{ id: "status", label: "Status", visible: true, order: 10 }, { id: "status", label: "Status", visible: true, order: 10 },
{ id: "needs_reboot", label: "Reboot", visible: true, order: 11 }, { id: "needs_reboot", label: "Reboot", visible: true, order: 11 },
{ id: "updates", label: "Updates", visible: true, order: 12 }, { 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: "security_updates",
{ id: "actions", label: "Actions", visible: true, order: 15 }, 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); updateColumnConfig(defaultConfig);
}; };
@@ -1135,6 +1150,19 @@ const Hosts = () => {
{host.updatesCount || 0} {host.updatesCount || 0}
</button> </button>
); );
case "security_updates":
return (
<button
type="button"
onClick={() =>
navigate(`/packages?host=${host.id}&filter=security-updates`)
}
className="text-sm text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 font-medium hover:underline"
title="View security updates for this host"
>
{host.securityUpdatesCount || 0}
</button>
);
case "last_update": case "last_update":
return ( return (
<div className="text-sm text-secondary-500 dark:text-secondary-300"> <div className="text-sm text-secondary-500 dark:text-secondary-300">
@@ -1190,7 +1218,7 @@ const Hosts = () => {
navigate("/hosts", { replace: true }); navigate("/hosts", { replace: true });
}; };
const handleUpToDateClick = () => { const _handleUpToDateClick = () => {
// Filter to show only up-to-date hosts // Filter to show only up-to-date hosts
setStatusFilter("active"); setStatusFilter("active");
setShowFilters(true); setShowFilters(true);
@@ -1731,6 +1759,17 @@ const Hosts = () => {
{column.label} {column.label}
{getSortIcon("updates")} {getSortIcon("updates")}
</button> </button>
) : column.id === "security_updates" ? (
<button
type="button"
onClick={() =>
handleSort("security_updates")
}
className="flex items-center gap-2 hover:text-secondary-700"
>
{column.label}
{getSortIcon("security_updates")}
</button>
) : column.id === "needs_reboot" ? ( ) : column.id === "needs_reboot" ? (
<button <button
type="button" type="button"

216
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "patchmon", "name": "patchmon",
"version": "1.3.4", "version": "1.3.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "patchmon", "name": "patchmon",
"version": "1.3.4", "version": "1.3.5",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"workspaces": [ "workspaces": [
"backend", "backend",
@@ -23,7 +23,7 @@
}, },
"backend": { "backend": {
"name": "patchmon-backend", "name": "patchmon-backend",
"version": "1.3.4", "version": "1.3.5",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@bull-board/api": "^6.13.1", "@bull-board/api": "^6.13.1",
@@ -59,7 +59,7 @@
}, },
"frontend": { "frontend": {
"name": "patchmon-frontend", "name": "patchmon-frontend",
"version": "1.3.4", "version": "1.3.5",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
@@ -83,7 +83,7 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.14", "@types/react": "^18.3.14",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
@@ -131,19 +131,22 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3", "@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4", "@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.4", "@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.4", "@babel/types": "^7.28.5",
"@jridgewell/remapping": "^2.3.5", "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
@@ -160,12 +163,14 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.28.3", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.5",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28", "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2" "jsesc": "^3.0.2"
@@ -242,7 +247,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -270,11 +277,13 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4" "@babel/types": "^7.28.5"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -333,16 +342,18 @@
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.5",
"@babel/helper-globals": "^7.28.0", "@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.4", "@babel/types": "^7.28.5",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@@ -350,12 +361,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.28.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -547,6 +560,7 @@
"node_modules/@bull-board/ui": { "node_modules/@bull-board/ui": {
"version": "6.13.1", "version": "6.13.1",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@bull-board/api": "6.13.1" "@bull-board/api": "6.13.1"
} }
@@ -580,6 +594,7 @@
"node_modules/@dnd-kit/core": { "node_modules/@dnd-kit/core": {
"version": "6.3.1", "version": "6.3.1",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
@@ -895,7 +910,9 @@
} }
}, },
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27", "version": "1.0.0-beta.47",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
"integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -1020,6 +1037,7 @@
"version": "18.3.24", "version": "18.3.24",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -1038,19 +1056,21 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.7.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
"integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.28.0", "@babel/core": "^7.28.5",
"@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.27", "@rolldown/pluginutils": "1.0.0-beta.47",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0" "react-refresh": "^0.18.0"
}, },
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
@@ -1267,6 +1287,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.3", "baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741", "caniuse-lite": "^1.0.30001741",
@@ -1456,6 +1477,7 @@
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "4.5.0", "version": "4.5.0",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
}, },
@@ -2030,6 +2052,7 @@
"node_modules/express": { "node_modules/express": {
"version": "4.21.2", "version": "4.21.2",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
@@ -2795,6 +2818,76 @@
"lefthook-windows-x64": "1.13.5" "lefthook-windows-x64": "1.13.5"
} }
}, },
"node_modules/lefthook-darwin-arm64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-1.13.5.tgz",
"integrity": "sha512-BYt5CnAOXasVCS6i+A4ljUo9xru/B5uMFD6EWHhs3R26jGF7mBSDxM3ErzXTUaJRTP0kQI/XBmgqBryBqoqZOQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/lefthook-darwin-x64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-1.13.5.tgz",
"integrity": "sha512-ZDtLBzvI5e26C/RZ4irOHpELTd22x9lDTgF2+eCYcnrBWOkB7800V8tuAvBybsLGvg6JwKjFxn+NTRNZnCC2hw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/lefthook-freebsd-arm64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-1.13.5.tgz",
"integrity": "sha512-uQ/kQZSSedw74aGCpsfOPN4yVt3klg8grOP6gHQOCRUMv5oK/Lj3pe1PylpTuuhxWORWRzkauPMot26J0OZZdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/lefthook-freebsd-x64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-1.13.5.tgz",
"integrity": "sha512-6czek8XagVrI7ExURawkfrfX40Qjc/wktc8bLq/iXfRlmdvKDMrx2FrA82mDfEVCAEz+tTvkteK1TfR3icYF3Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/lefthook-linux-arm64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-1.13.5.tgz",
"integrity": "sha512-MjWtiuW1br+rpTtgG1KGV53mSGtL5MWQwgafYzrFleJ89fKb86F4TD/4mVNzk5thmZ+HVPZw9bRZGUHFBnNJWg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/lefthook-linux-x64": { "node_modules/lefthook-linux-x64": {
"version": "1.13.5", "version": "1.13.5",
"cpu": [ "cpu": [
@@ -2807,6 +2900,62 @@
"linux" "linux"
] ]
}, },
"node_modules/lefthook-openbsd-arm64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-1.13.5.tgz",
"integrity": "sha512-lYXrWf0/hBrwtG8ceaHq886bcqRKh3Lfv+jZJs+ykMLB6L/kaqk8tA4V2NHWydQ5h56o45ugs/580nMz36ZdRg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/lefthook-openbsd-x64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-1.13.5.tgz",
"integrity": "sha512-Ba1JrsRbfan4WKd8Q7gUhTxCUuppXzirDObd3JxpLRSLxA47yxhjMv7KByDunRDTvzTgsXoykZI6mPupkc1JiQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/lefthook-windows-arm64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-1.13.5.tgz",
"integrity": "sha512-Y/CpmEIb0hlFe+kTT/efWgX6+/gUTp5NItTF+gmUrY1/G/bTLIxdIRS7WpodVM0MEN24sOrQVTSi9DN9FvGoGg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/lefthook-windows-x64": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-1.13.5.tgz",
"integrity": "sha512-WJBqGNBlFJnunRwy12QyaDHdGULtostPqpYSZSS4boFJDY0lP5qtz9lAGmJ49aA5GQ19jrnDjGLwVPFiwIqksQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.1.3", "version": "3.1.3",
"dev": true, "dev": true,
@@ -3419,6 +3568,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -3548,6 +3698,7 @@
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"@prisma/config": "6.16.2", "@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2" "@prisma/engines": "6.16.2"
@@ -3737,6 +3888,7 @@
"node_modules/react": { "node_modules/react": {
"version": "18.3.1", "version": "18.3.1",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -3755,6 +3907,7 @@
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "18.3.1", "version": "18.3.1",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -3771,7 +3924,9 @@
} }
}, },
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.17.0", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4472,6 +4627,7 @@
"version": "4.0.3", "version": "4.0.3",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -4624,6 +4780,7 @@
"version": "7.1.7", "version": "7.1.7",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -4713,6 +4870,7 @@
"version": "4.0.3", "version": "4.0.3",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "patchmon", "name": "patchmon",
"version": "1.3.4", "version": "1.3.5",
"description": "Linux Patch Monitoring System", "description": "Linux Patch Monitoring System",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,