Compare commits

...

26 Commits

Author SHA1 Message Date
renovate[bot]
d8741ba083 fix(deps): update dependency react-router-dom to v7 2025-10-29 18:54:15 +00:00
9 Technology Group LTD
1e75f2b1fe Merge pull request #242 from PatchMon/release/1-3-1
Agent update initiation from server
2025-10-28 21:55:57 +00:00
Muhammad Ibrahim
79317b0052 Added server initiated Agent update 2025-10-28 21:49:19 +00:00
Muhammad Ibrahim
77a945a5b6 fix: add Prisma connection pool variables to update_env_file function
- Added DB_CONNECTION_LIMIT, DB_POOL_TIMEOUT, DB_CONNECT_TIMEOUT, DB_IDLE_TIMEOUT, DB_MAX_LIFETIME
- Sets defaults (30, 20, 10, 300, 1800) if not already in .env
- Checks for missing variables during update mode
- Automatically adds them with comment header
- Ensures existing installations get connection pool settings on update
2025-10-28 19:55:42 +00:00
9 Technology Group LTD
276d910e83 Merge pull request #238 from PatchMon/release/1-3-1
fix: handle non-git installations in update mode
2025-10-28 19:33:22 +00:00
Muhammad Ibrahim
dae536e96b fix: handle non-git installations in update mode
- Added fallback to re-initialize git repository if .git directory missing
- Automatically detects current version from package.json
- Attempts to match to correct release branch
- Falls back to main branch if version detection fails
- Prevents update failures for legacy installations
2025-10-28 19:21:11 +00:00
9 Technology Group LTD
8361caabe8 Merge pull request #236 from PatchMon/release/1-3-1
Release/1 3 1
2025-10-28 19:03:34 +00:00
Muhammad Ibrahim
f6d23e45b2 feat: make triangular accents more visible across background
- Increased triangle opacity from 2-5% to 5-13%
- Increased coverage from 30% to 60% of grid positions
- Slightly larger triangles (50-150px vs 40-120px)
- Smaller cell size (180px vs 200px) for better distribution
- Now clearly visible across entire background
2025-10-28 18:55:30 +00:00
Muhammad Ibrahim
aba0f5cb6b feat: add subtle triangular accents to clean gradient background
- Kept clean radial gradient base
- Added very faint triangular shapes (2-5% opacity)
- Sparse pattern - only 30% of grid positions
- Random sizes (40-120px) and positions for organic feel
- Perfect balance of clean + subtle geometric detail
2025-10-28 18:53:46 +00:00
Muhammad Ibrahim
2ec2b3992c feat: replace with clean radial gradient background
- Removed busy triangular pattern
- Clean, subtle radial gradient like modern Linux wallpapers
- Light center (top-left) flowing to darker bottom-right corner
- Uses theme colors but much more minimalist
- Daily color rotation maintained
2025-10-28 18:52:00 +00:00
Muhammad Ibrahim
f85721b292 feat: improve background with low-poly triangular pattern
- Replaced square grid with triangular mesh for more organic look
- Increased default cell size from 75px to 150px for subtlety
- Added variance to point positions for natural randomness
- Each cell now renders two triangles with gradient fills
- Maintains theme colors and daily variation
2025-10-28 18:49:39 +00:00
Muhammad Ibrahim
1d2c003830 fix: replace trianglify with pure browser gradient mesh generator
- Removed trianglify dependency (which required Node.js canvas)
- Implemented custom gradient mesh using HTML5 Canvas API
- Browser-native solution works in Docker without native dependencies
- Maintains daily variation and theme color support
- No more 'i.from is not a function' errors
2025-10-28 18:47:56 +00:00
Muhammad Ibrahim
2975da0f69 fix: use SVG-based rendering for Trianglify in browser instead of Node.js canvas 2025-10-28 18:35:52 +00:00
Muhammad Ibrahim
93760d03e1 feat: maintain nginx-unprivileged security while adding canvas runtime libraries via multi-stage build 2025-10-28 18:35:03 +00:00
Muhammad Ibrahim
43fb54a683 fix: change nginx-unprivileged to nginx:alpine for canvas runtime dependencies 2025-10-28 18:33:43 +00:00
Muhammad Ibrahim
e9368d1a95 feat: add canvas runtime dependencies to frontend Docker image for Trianglify support 2025-10-28 18:31:55 +00:00
Muhammad Ibrahim
3ce8c02a31 fix: suppress Trianglify errors in production to reduce console noise 2025-10-28 18:30:49 +00:00
Muhammad Ibrahim
ac420901a6 fix: add try-catch protection for Trianglify canvas generation to prevent runtime errors in Docker 2025-10-28 18:25:13 +00:00
Muhammad Ibrahim
eb0218bdcb fix: unset color variables before sourcing .env to prevent ANSI escape sequence errors 2025-10-28 18:21:52 +00:00
9 Technology Group LTD
1f6f58360f Merge pull request #235 from PatchMon/release/1-3-1
fix: Remove --ignore-scripts to allow trianglify/canvas to install
2025-10-28 18:12:04 +00:00
Muhammad Ibrahim
746451c296 debug: Add verbose logging to npm install for ARM64 builds
Added echo statements and --loglevel=verbose to see exactly what npm is
doing during the long ARM64 build process. This will help diagnose why
it's taking so long under QEMU emulation.
2025-10-28 18:01:02 +00:00
Muhammad Ibrahim
285e4c59ee fix: Install canvas build dependencies to make trianglify work
Canvas requires native build tools to compile:
- python3: For node-gyp
- make, g++, pkgconfig: C++ compiler and build tools
- cairo-dev, jpeg-dev, pango-dev, giflib-dev: Native libraries

By installing these dependencies before npm install, canvas can properly
build its native bindings for both AMD64 and ARM64 architectures.

Removed try-catch blocks around trianglify since it should now work properly.
2025-10-28 17:36:18 +00:00
Muhammad Ibrahim
9050595b7c fix: Make trianglify/canvas optional and handle gracefully
Canvas requires Python and native build tools which fail in Docker builds
with --ignore-scripts. Since trianglify is only used for cosmetic backgrounds
on the login and layout pages, we can make it optional.

Changes:
- Added try-catch around trianglify usage in Login and Layout
- Using --ignore-scripts in frontend Dockerfile to skip canvas native build
- Gracefully fail when trianglify is not available (background won't render)

This allows the frontend to build and run even without canvas/trianglify.
2025-10-28 17:35:15 +00:00
Muhammad Ibrahim
cc46940b0c fix: Remove --ignore-scripts to allow trianglify/canvas to install
The 'i.from is not a function' error occurs because trianglify depends
on canvas, and --ignore-scripts prevented canvas from installing properly.

Solution: Remove --ignore-scripts and allow npm to run install scripts,
but gracefully handle canvas rebuild failures since native bindings
are optional for the production build.
2025-10-28 17:31:39 +00:00
9 Technology Group LTD
203a065479 Merge pull request #234 from PatchMon/release/1-3-1
fix: Add npm fetch retries to handle transient network errors
2025-10-28 17:18:12 +00:00
Muhammad Ibrahim
8864de6c15 fix: Add npm fetch retries to handle transient network errors
ARM64 builds under QEMU are slower and more prone to network timeouts.
Changed from --fetch-retries=0 to --fetch-retries=3 with exponential backoff:
- Min timeout: 20 seconds
- Max timeout: 120 seconds

This should handle transient ECONNRESET errors from npm registry.
2025-10-28 17:10:45 +00:00
16 changed files with 673 additions and 488 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1430,6 +1430,69 @@ router.patch(
}, },
); );
// Force agent update for specific host
router.post(
"/:hostId/force-agent-update",
authenticateToken,
requireManageHosts,
async (req, res) => {
try {
const { hostId } = req.params;
// Get host to verify it exists
const host = await prisma.hosts.findUnique({
where: { id: hostId },
});
if (!host) {
return res.status(404).json({ error: "Host not found" });
}
// Get queue manager
const { QUEUE_NAMES } = require("../services/automation");
const queueManager = req.app.locals.queueManager;
if (!queueManager) {
return res.status(500).json({
error: "Queue manager not available",
});
}
// Get the agent-commands queue
const queue = queueManager.queues[QUEUE_NAMES.AGENT_COMMANDS];
// Add job to queue
await queue.add(
"update_agent",
{
api_id: host.api_id,
type: "update_agent",
},
{
attempts: 3,
backoff: {
type: "exponential",
delay: 2000,
},
},
);
res.json({
success: true,
message: "Agent update queued successfully",
host: {
id: host.id,
friendlyName: host.friendly_name,
apiId: host.api_id,
},
});
} catch (error) {
console.error("Force agent update error:", error);
res.status(500).json({ error: "Failed to force agent update" });
}
},
);
// Serve the installation script (requires API authentication) // Serve the installation script (requires API authentication)
router.get("/install", async (req, res) => { router.get("/install", async (req, res) => {
try { try {

View File

@@ -176,6 +176,15 @@ function pushSettingsUpdate(apiId, newInterval) {
); );
} }
function pushUpdateAgent(apiId) {
const ws = apiIdToSocket.get(apiId);
safeSend(ws, JSON.stringify({ type: "update_agent" }));
}
function getConnectionByApiId(apiId) {
return apiIdToSocket.get(apiId);
}
function pushUpdateNotification(apiId, updateInfo) { function pushUpdateNotification(apiId, updateInfo) {
const ws = apiIdToSocket.get(apiId); const ws = apiIdToSocket.get(apiId);
if (ws && ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {
@@ -330,10 +339,12 @@ module.exports = {
broadcastSettingsUpdate, broadcastSettingsUpdate,
pushReportNow, pushReportNow,
pushSettingsUpdate, pushSettingsUpdate,
pushUpdateAgent,
pushUpdateNotification, pushUpdateNotification,
pushUpdateNotificationToAll, pushUpdateNotificationToAll,
// Expose read-only view of connected agents // Expose read-only view of connected agents
getConnectedApiIds: () => Array.from(apiIdToSocket.keys()), getConnectedApiIds: () => Array.from(apiIdToSocket.keys()),
getConnectionByApiId,
isConnected: (apiId) => { isConnected: (apiId) => {
const ws = apiIdToSocket.get(apiId); const ws = apiIdToSocket.get(apiId);
return !!ws && ws.readyState === WebSocket.OPEN; return !!ws && ws.readyState === WebSocket.OPEN;

View File

@@ -190,6 +190,19 @@ class QueueManager {
// For settings update, we need additional data // For settings update, we need additional data
const { update_interval } = job.data; const { update_interval } = job.data;
agentWs.pushSettingsUpdate(api_id, update_interval); agentWs.pushSettingsUpdate(api_id, update_interval);
} else if (type === "update_agent") {
// Force agent to update by sending WebSocket command
const ws = agentWs.getConnectionByApiId(api_id);
if (ws && ws.readyState === 1) {
// WebSocket.OPEN
agentWs.pushUpdateAgent(api_id);
console.log(`✅ Update command sent to agent ${api_id}`);
} else {
console.error(`❌ Agent ${api_id} is not connected`);
throw new Error(
`Agent ${api_id} is not connected. Cannot send update command.`,
);
}
} else { } else {
console.error(`Unknown agent command type: ${type}`); console.error(`Unknown agent command type: ${type}`);
} }

View File

@@ -48,7 +48,7 @@ WORKDIR /app/backend
RUN npm cache clean --force &&\ RUN npm cache clean --force &&\
rm -rf node_modules ~/.npm /root/.npm &&\ rm -rf node_modules ~/.npm /root/.npm &&\
npm ci --ignore-scripts --legacy-peer-deps --no-audit --prefer-online --fetch-retries=0 &&\ npm ci --ignore-scripts --legacy-peer-deps --no-audit --prefer-online --fetch-retries=3 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 &&\
PRISMA_CLI_BINARY_TYPE=binary npm run db:generate &&\ PRISMA_CLI_BINARY_TYPE=binary npm run db:generate &&\
npm prune --omit=dev &&\ npm prune --omit=dev &&\
npm cache clean --force npm cache clean --force

View File

@@ -21,9 +21,13 @@ WORKDIR /app/frontend
COPY frontend/package*.json ./ COPY frontend/package*.json ./
RUN npm cache clean --force &&\ RUN echo "=== Starting npm install ===" &&\
npm cache clean --force &&\
rm -rf node_modules ~/.npm /root/.npm &&\ rm -rf node_modules ~/.npm /root/.npm &&\
npm install --ignore-scripts --legacy-peer-deps --no-audit --prefer-online --fetch-retries=0 echo "=== npm install ===" &&\
npm install --ignore-scripts --legacy-peer-deps --no-audit --prefer-online --fetch-retries=3 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 &&\
echo "=== npm install completed ===" &&\
npm cache clean --force
COPY frontend/ ./ COPY frontend/ ./

View File

@@ -27,8 +27,7 @@
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^6.30.1", "react-router-dom": "^7.0.0"
"trianglify": "^4.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.14", "@types/react": "^18.3.14",

View File

@@ -28,7 +28,6 @@ import {
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { FaReddit, FaYoutube } from "react-icons/fa"; import { FaReddit, FaYoutube } from "react-icons/fa";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import trianglify from "trianglify";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { useColorTheme } from "../contexts/ColorThemeContext"; import { useColorTheme } from "../contexts/ColorThemeContext";
import { useUpdateNotification } from "../contexts/UpdateNotificationContext"; import { useUpdateNotification } from "../contexts/UpdateNotificationContext";
@@ -237,31 +236,93 @@ const Layout = ({ children }) => {
navigate("/hosts?action=add"); navigate("/hosts?action=add");
}; };
// Generate Trianglify background for dark mode // Generate clean radial gradient background with subtle triangular accents for dark mode
useEffect(() => { useEffect(() => {
const generateBackground = () => { const generateBackground = () => {
if ( if (
bgCanvasRef.current && !bgCanvasRef.current ||
themeConfig?.login && !themeConfig?.login ||
document.documentElement.classList.contains("dark") !document.documentElement.classList.contains("dark")
) { ) {
// Get current date as seed for daily variation return;
}
const canvas = bgCanvasRef.current;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
// Get theme colors - pick first color from each palette
const xColors = themeConfig.login.xColors || [
"#667eea",
"#764ba2",
"#f093fb",
"#4facfe",
];
const yColors = themeConfig.login.yColors || [
"#667eea",
"#764ba2",
"#f093fb",
"#4facfe",
];
// Use date for daily color rotation
const today = new Date(); const today = new Date();
const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; const seed =
today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate();
const random = (s) => {
const x = Math.sin(s) * 10000;
return x - Math.floor(x);
};
// Generate pattern with selected theme configuration const color1 = xColors[Math.floor(random(seed) * xColors.length)];
const pattern = trianglify({ const color2 = yColors[Math.floor(random(seed + 1000) * yColors.length)];
width: window.innerWidth,
height: window.innerHeight,
cellSize: themeConfig.login.cellSize,
variance: themeConfig.login.variance,
seed: dateSeed,
xColors: themeConfig.login.xColors,
yColors: themeConfig.login.yColors,
});
// Render to canvas // Create clean radial gradient from center to bottom-right corner
pattern.toCanvas(bgCanvasRef.current); const gradient = ctx.createRadialGradient(
canvas.width * 0.3, // Center slightly left
canvas.height * 0.3, // Center slightly up
0,
canvas.width * 0.5, // Expand to cover screen
canvas.height * 0.5,
Math.max(canvas.width, canvas.height) * 1.2,
);
// Subtle gradient with darker corners
gradient.addColorStop(0, color1);
gradient.addColorStop(0.6, color2);
gradient.addColorStop(1, "#0a0a0a"); // Very dark edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Add subtle triangular shapes as accents across entire background
const cellSize = 180;
const cols = Math.ceil(canvas.width / cellSize) + 1;
const rows = Math.ceil(canvas.height / cellSize) + 1;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const idx = y * cols + x;
// Draw more triangles (less sparse)
if (random(seed + idx + 5000) > 0.4) {
const baseX =
x * cellSize + random(seed + idx * 3) * cellSize * 0.8;
const baseY =
y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8;
const size = 50 + random(seed + idx * 4) * 100;
ctx.beginPath();
ctx.moveTo(baseX, baseY);
ctx.lineTo(baseX + size, baseY);
ctx.lineTo(baseX + size / 2, baseY - size * 0.866);
ctx.closePath();
// More visible white with slightly higher opacity
ctx.fillStyle = `rgba(255, 255, 255, ${0.05 + random(seed + idx * 5) * 0.08})`;
ctx.fill();
}
}
} }
}; };

View File

@@ -187,6 +187,16 @@ const HostDetail = () => {
}, },
}); });
// Force agent update mutation
const forceAgentUpdateMutation = useMutation({
mutationFn: () =>
adminHostsAPI.forceAgentUpdate(hostId).then((res) => res.data),
onSuccess: () => {
queryClient.invalidateQueries(["host", hostId]);
queryClient.invalidateQueries(["hosts"]);
},
});
const updateFriendlyNameMutation = useMutation({ const updateFriendlyNameMutation = useMutation({
mutationFn: (friendlyName) => mutationFn: (friendlyName) =>
adminHostsAPI adminHostsAPI
@@ -703,6 +713,29 @@ const HostDetail = () => {
/> />
</button> </button>
</div> </div>
<div>
<p className="text-xs text-secondary-500 dark:text-secondary-300 mb-1.5">
Force Update
</p>
<button
type="button"
onClick={() => forceAgentUpdateMutation.mutate()}
disabled={forceAgentUpdateMutation.isPending}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-primary-600 dark:text-primary-400 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-md hover:bg-primary-100 dark:hover:bg-primary-900/40 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<RefreshCw
className={`h-3 w-3 ${
forceAgentUpdateMutation.isPending
? "animate-spin"
: ""
}`}
/>
{forceAgentUpdateMutation.isPending
? "Updating..."
: "Update Now"}
</button>
</div>
</div> </div>
</div> </div>
)} )}

View File

@@ -17,7 +17,6 @@ import { useEffect, useId, useRef, useState } from "react";
import { FaReddit, FaYoutube } from "react-icons/fa"; import { FaReddit, FaYoutube } from "react-icons/fa";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import trianglify from "trianglify";
import DiscordIcon from "../components/DiscordIcon"; import DiscordIcon from "../components/DiscordIcon";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { useColorTheme } from "../contexts/ColorThemeContext"; import { useColorTheme } from "../contexts/ColorThemeContext";
@@ -57,27 +56,87 @@ const Login = () => {
const navigate = useNavigate(); const navigate = useNavigate();
// Generate Trianglify background based on selected theme // Generate clean radial gradient background with subtle triangular accents
useEffect(() => { useEffect(() => {
const generateBackground = () => { const generateBackground = () => {
if (canvasRef.current && themeConfig?.login) { if (!canvasRef.current || !themeConfig?.login) return;
// Get current date as seed for daily variation
const canvas = canvasRef.current;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const ctx = canvas.getContext("2d");
// Get theme colors - pick first color from each palette
const xColors = themeConfig.login.xColors || [
"#667eea",
"#764ba2",
"#f093fb",
"#4facfe",
];
const yColors = themeConfig.login.yColors || [
"#667eea",
"#764ba2",
"#f093fb",
"#4facfe",
];
// Use date for daily color rotation
const today = new Date(); const today = new Date();
const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; const seed =
today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate();
const random = (s) => {
const x = Math.sin(s) * 10000;
return x - Math.floor(x);
};
// Generate pattern with selected theme configuration const color1 = xColors[Math.floor(random(seed) * xColors.length)];
const pattern = trianglify({ const color2 = yColors[Math.floor(random(seed + 1000) * yColors.length)];
width: canvasRef.current.offsetWidth,
height: canvasRef.current.offsetHeight,
cellSize: themeConfig.login.cellSize,
variance: themeConfig.login.variance,
seed: dateSeed,
xColors: themeConfig.login.xColors,
yColors: themeConfig.login.yColors,
});
// Render to canvas // Create clean radial gradient from center to bottom-right corner
pattern.toCanvas(canvasRef.current); const gradient = ctx.createRadialGradient(
canvas.width * 0.3, // Center slightly left
canvas.height * 0.3, // Center slightly up
0,
canvas.width * 0.5, // Expand to cover screen
canvas.height * 0.5,
Math.max(canvas.width, canvas.height) * 1.2,
);
// Subtle gradient with darker corners
gradient.addColorStop(0, color1);
gradient.addColorStop(0.6, color2);
gradient.addColorStop(1, "#0a0a0a"); // Very dark edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Add subtle triangular shapes as accents across entire background
const cellSize = 180;
const cols = Math.ceil(canvas.width / cellSize) + 1;
const rows = Math.ceil(canvas.height / cellSize) + 1;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const idx = y * cols + x;
// Draw more triangles (less sparse)
if (random(seed + idx + 5000) > 0.4) {
const baseX =
x * cellSize + random(seed + idx * 3) * cellSize * 0.8;
const baseY =
y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8;
const size = 50 + random(seed + idx * 4) * 100;
ctx.beginPath();
ctx.moveTo(baseX, baseY);
ctx.lineTo(baseX + size, baseY);
ctx.lineTo(baseX + size / 2, baseY - size * 0.866);
ctx.closePath();
// More visible white with slightly higher opacity
ctx.fillStyle = `rgba(255, 255, 255, ${0.05 + random(seed + idx * 5) * 0.08})`;
ctx.fill();
}
}
} }
}; };
@@ -90,7 +149,7 @@ const Login = () => {
window.addEventListener("resize", handleResize); window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize);
}, [themeConfig]); // Regenerate when theme changes }, [themeConfig]);
// Check if signup is enabled // Check if signup is enabled
useEffect(() => { useEffect(() => {

View File

@@ -95,6 +95,7 @@ export const adminHostsAPI = {
api.put("/hosts/bulk/groups", { hostIds, groupIds }), api.put("/hosts/bulk/groups", { hostIds, groupIds }),
toggleAutoUpdate: (hostId, autoUpdate) => toggleAutoUpdate: (hostId, autoUpdate) =>
api.patch(`/hosts/${hostId}/auto-update`, { auto_update: autoUpdate }), api.patch(`/hosts/${hostId}/auto-update`, { auto_update: autoUpdate }),
forceAgentUpdate: (hostId) => api.post(`/hosts/${hostId}/force-agent-update`),
updateFriendlyName: (hostId, friendlyName) => updateFriendlyName: (hostId, friendlyName) =>
api.patch(`/hosts/${hostId}/friendly-name`, { api.patch(`/hosts/${hostId}/friendly-name`, {
friendly_name: friendlyName, friendly_name: friendlyName,

739
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1797,7 +1797,12 @@ create_agent_version() {
cp "$APP_DIR/agents/patchmon-agent.sh" "$APP_DIR/backend/" cp "$APP_DIR/agents/patchmon-agent.sh" "$APP_DIR/backend/"
print_status "Agent version management removed - using file-based approach" print_status "Agent version management removed - using file-based approach"
# Ensure we close the conditional and the function properly fi
# Make agent binaries executable
if [ -d "$APP_DIR/agents" ]; then
chmod +x "$APP_DIR/agents/patchmon-agent-linux-"* 2>/dev/null || true
print_status "Agent binaries made executable"
fi fi
return 0 return 0
@@ -2703,6 +2708,13 @@ update_env_file() {
: ${TFA_MAX_REMEMBER_SESSIONS:=5} : ${TFA_MAX_REMEMBER_SESSIONS:=5}
: ${TFA_SUSPICIOUS_ACTIVITY_THRESHOLD:=3} : ${TFA_SUSPICIOUS_ACTIVITY_THRESHOLD:=3}
# Prisma Connection Pool
: ${DB_CONNECTION_LIMIT:=30}
: ${DB_POOL_TIMEOUT:=20}
: ${DB_CONNECT_TIMEOUT:=10}
: ${DB_IDLE_TIMEOUT:=300}
: ${DB_MAX_LIFETIME:=1800}
# Track which variables were added # Track which variables were added
local added_vars=() local added_vars=()
@@ -2764,6 +2776,21 @@ update_env_file() {
if ! grep -q "^TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=" "$env_file"; then if ! grep -q "^TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=" "$env_file"; then
added_vars+=("TFA_SUSPICIOUS_ACTIVITY_THRESHOLD") added_vars+=("TFA_SUSPICIOUS_ACTIVITY_THRESHOLD")
fi fi
if ! grep -q "^DB_CONNECTION_LIMIT=" "$env_file"; then
added_vars+=("DB_CONNECTION_LIMIT")
fi
if ! grep -q "^DB_POOL_TIMEOUT=" "$env_file"; then
added_vars+=("DB_POOL_TIMEOUT")
fi
if ! grep -q "^DB_CONNECT_TIMEOUT=" "$env_file"; then
added_vars+=("DB_CONNECT_TIMEOUT")
fi
if ! grep -q "^DB_IDLE_TIMEOUT=" "$env_file"; then
added_vars+=("DB_IDLE_TIMEOUT")
fi
if ! grep -q "^DB_MAX_LIFETIME=" "$env_file"; then
added_vars+=("DB_MAX_LIFETIME")
fi
# If there are missing variables, add them # If there are missing variables, add them
if [ ${#added_vars[@]} -gt 0 ]; then if [ ${#added_vars[@]} -gt 0 ]; then
@@ -2849,6 +2876,25 @@ EOF
echo "TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=$TFA_SUSPICIOUS_ACTIVITY_THRESHOLD" >> "$env_file" echo "TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=$TFA_SUSPICIOUS_ACTIVITY_THRESHOLD" >> "$env_file"
fi fi
# Add Prisma connection pool config if missing
if printf '%s\n' "${added_vars[@]}" | grep -q "DB_CONNECTION_LIMIT"; then
echo "" >> "$env_file"
echo "# Database Connection Pool Configuration (Prisma)" >> "$env_file"
echo "DB_CONNECTION_LIMIT=$DB_CONNECTION_LIMIT" >> "$env_file"
fi
if printf '%s\n' "${added_vars[@]}" | grep -q "DB_POOL_TIMEOUT"; then
echo "DB_POOL_TIMEOUT=$DB_POOL_TIMEOUT" >> "$env_file"
fi
if printf '%s\n' "${added_vars[@]}" | grep -q "DB_CONNECT_TIMEOUT"; then
echo "DB_CONNECT_TIMEOUT=$DB_CONNECT_TIMEOUT" >> "$env_file"
fi
if printf '%s\n' "${added_vars[@]}" | grep -q "DB_IDLE_TIMEOUT"; then
echo "DB_IDLE_TIMEOUT=$DB_IDLE_TIMEOUT" >> "$env_file"
fi
if printf '%s\n' "${added_vars[@]}" | grep -q "DB_MAX_LIFETIME"; then
echo "DB_MAX_LIFETIME=$DB_MAX_LIFETIME" >> "$env_file"
fi
print_status ".env file updated with ${#added_vars[@]} new variable(s)" print_status ".env file updated with ${#added_vars[@]} new variable(s)"
print_info "Added variables: ${added_vars[*]}" print_info "Added variables: ${added_vars[*]}"
else else
@@ -2918,11 +2964,37 @@ update_installation() {
print_info "Installation directory: $instance_dir" print_info "Installation directory: $instance_dir"
print_info "Service name: $service_name" print_info "Service name: $service_name"
# Verify it's a git repository # Verify it's a git repository, if not, initialize it
if [ ! -d "$instance_dir/.git" ]; then if [ ! -d "$instance_dir/.git" ]; then
print_error "Installation directory is not a git repository" print_warning "Installation directory is not a git repository"
print_error "Cannot perform git-based update" print_info "Attempting to re-initialize as git repository..."
exit 1
cd "$instance_dir" || exit 1
# Initialize git repository
git init
git remote add origin https://github.com/PatchMon/PatchMon.git
# Fetch all branches
git fetch origin
# Try to determine current version from package.json or default to main
local current_branch="main"
if [ -f "$instance_dir/backend/package.json" ]; then
local pkg_version=$(grep '"version"' "$instance_dir/backend/package.json" | head -1 | sed 's/.*"version": "\(.*\)".*/\1/')
if [ -n "$pkg_version" ]; then
# Check if there's a release branch for this version
if git ls-remote --heads origin | grep -q "release/$(echo $pkg_version | sed 's/\./-/g')"; then
current_branch="release/$(echo $pkg_version | sed 's/\./-/g')"
fi
fi
fi
# Reset to the determined branch
git reset --hard "origin/$current_branch"
git checkout -B "$current_branch" "origin/$current_branch"
print_success "Repository initialized successfully"
fi fi
# Add git safe.directory to avoid ownership issues when running as root # Add git safe.directory to avoid ownership issues when running as root
@@ -2931,6 +3003,8 @@ update_installation() {
# Load existing .env to get database credentials # Load existing .env to get database credentials
if [ -f "$instance_dir/backend/.env" ]; then if [ -f "$instance_dir/backend/.env" ]; then
# Unset color variables before sourcing to prevent ANSI escape sequences from leaking into .env
unset RED GREEN YELLOW BLUE NC
source "$instance_dir/backend/.env" source "$instance_dir/backend/.env"
print_status "Loaded existing configuration" print_status "Loaded existing configuration"
@@ -3026,6 +3100,12 @@ update_installation() {
print_info "Building frontend..." print_info "Building frontend..."
npm run build npm run build
# Make agent binaries executable
if [ -d "$instance_dir/agents" ]; then
chmod +x "$instance_dir/agents/patchmon-agent-linux-"* 2>/dev/null || true
print_status "Agent binaries made executable"
fi
# Run database migrations with self-healing # Run database migrations with self-healing
print_info "Running database migrations..." print_info "Running database migrations..."
cd "$instance_dir/backend" cd "$instance_dir/backend"