fix: update Docker Compose environment variables to match .env.example format

This commit is contained in:
courtmanr@gmail.com
2025-03-13 15:18:01 +00:00
parent 8d75c2aad5
commit 7108b7bfb7
18 changed files with 258 additions and 1861 deletions
+15 -14
View File
@@ -35,20 +35,21 @@ services:
- API_RATE_LIMIT_MS=${API_RATE_LIMIT_MS:-2000}
- API_TIMEOUT_MS=${API_TIMEOUT_MS:-90000}
- API_RETRY_DELAY_MS=${API_RETRY_DELAY_MS:-10000}
# Proxmox configuration - default node
- PROXMOX_HOST=${PROXMOX_HOST:-}
- PROXMOX_NODE=${PROXMOX_NODE:-}
- PROXMOX_TOKEN_ID=${PROXMOX_TOKEN_ID:-}
- PROXMOX_TOKEN_SECRET=${PROXMOX_TOKEN_SECRET:-}
# Proxmox configuration - additional nodes
- PROXMOX_HOST_2=${PROXMOX_HOST_2:-}
- PROXMOX_NODE_2=${PROXMOX_NODE_2:-}
- PROXMOX_TOKEN_ID_2=${PROXMOX_TOKEN_ID_2:-}
- PROXMOX_TOKEN_SECRET_2=${PROXMOX_TOKEN_SECRET_2:-}
- PROXMOX_HOST_3=${PROXMOX_HOST_3:-}
- PROXMOX_NODE_3=${PROXMOX_NODE_3:-}
- PROXMOX_TOKEN_ID_3=${PROXMOX_TOKEN_ID_3:-}
- PROXMOX_TOKEN_SECRET_3=${PROXMOX_TOKEN_SECRET_3:-}
# Proxmox configuration - Node 1
- PROXMOX_NODE_1_NAME=${PROXMOX_NODE_1_NAME:-}
- PROXMOX_NODE_1_HOST=${PROXMOX_NODE_1_HOST:-}
- PROXMOX_NODE_1_TOKEN_ID=${PROXMOX_NODE_1_TOKEN_ID:-}
- PROXMOX_NODE_1_TOKEN_SECRET=${PROXMOX_NODE_1_TOKEN_SECRET:-}
# Proxmox configuration - Node 2 (optional)
- PROXMOX_NODE_2_NAME=${PROXMOX_NODE_2_NAME:-}
- PROXMOX_NODE_2_HOST=${PROXMOX_NODE_2_HOST:-}
- PROXMOX_NODE_2_TOKEN_ID=${PROXMOX_NODE_2_TOKEN_ID:-}
- PROXMOX_NODE_2_TOKEN_SECRET=${PROXMOX_NODE_2_TOKEN_SECRET:-}
# Proxmox configuration - Node 3 (optional)
- PROXMOX_NODE_3_NAME=${PROXMOX_NODE_3_NAME:-}
- PROXMOX_NODE_3_HOST=${PROXMOX_NODE_3_HOST:-}
- PROXMOX_NODE_3_TOKEN_ID=${PROXMOX_NODE_3_TOKEN_ID:-}
- PROXMOX_NODE_3_TOKEN_SECRET=${PROXMOX_NODE_3_TOKEN_SECRET:-}
# SSL verification settings
- IGNORE_SSL_ERRORS=${IGNORE_SSL_ERRORS:-false}
- NODE_TLS_REJECT_UNAUTHORIZED=${NODE_TLS_REJECT_UNAUTHORIZED:-}
+1 -2
View File
@@ -9,8 +9,7 @@
"scripts": {
"dev": "vite --port 3000",
"build": "vite build",
"preview": "vite preview",
"test": "echo \"Error: no test specified\" && exit 1"
"preview": "vite preview"
},
"keywords": [],
"author": "",
+1 -28
View File
@@ -8,37 +8,12 @@
"scripts": {
"start": "node dist/server.js",
"build": "tsc",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node scripts/configure-env.js dev && NODE_ENV=development node scripts/start.js dev",
"dev:server": "nodemon --exec ts-node src/server.ts",
"dev:docker": "[ -f .env ] || cp .env.example .env && node scripts/configure-env.js dev && docker compose up --build",
"dev:logs": "node scripts/run-with-logs.js dev",
"prod": "node scripts/configure-env.js prod && node scripts/check-config.js && node scripts/start.js prod",
"prod:docker": "[ -f .env ] || cp .env.example .env && node scripts/configure-env.js prod && node scripts/check-config.js && docker compose up --build",
"prod:logs": "node scripts/run-with-logs.js prod",
"install:pulse": "chmod +x scripts/install.sh && scripts/install.sh",
"logs": "docker compose logs -f",
"logs:monitor": "node scripts/monitor-logs.js",
"logs:errors": "node scripts/monitor-logs.js --level=error",
"logs:cluster": "node scripts/monitor-logs.js --search=cluster",
"logs:proxmox": "node scripts/monitor-logs.js --component=ProxmoxClient",
"logs:node-manager": "node scripts/monitor-logs.js --component=NodeManager",
"ai:start-prod": "node scripts/ai-helper.js start-server --env=prod",
"ai:start-dev": "node scripts/ai-helper.js start-server --env=dev",
"ai:check-logs": "node scripts/ai-helper.js check-logs",
"ai:check-cluster": "node scripts/ai-helper.js check-cluster",
"ai:check-status": "node scripts/ai-helper.js check-status",
"ai:stop": "node scripts/ai-helper.js stop-server",
"ai:verify-config": "node scripts/ai-helper.js verify-config",
"verify:config": "node scripts/verify-cluster-config.js",
"stop": "docker compose stop",
"restart": "docker compose restart",
"status": "docker compose ps",
"cleanup": "docker compose down --rmi all --volumes --remove-orphans",
"screenshots": "./scripts/update-screenshots.sh",
"generate:logos": "npm install canvas && node scripts/generate-logo-pngs.js && node scripts/generate-logo-with-text.js",
"test:mock-cluster": "npx ts-node scripts/test-mock-cluster.ts"
"cleanup": "docker compose down --rmi all --volumes --remove-orphans"
},
"keywords": [
"proxmox",
@@ -65,12 +40,10 @@
"@types/socket.io": "^3.0.2",
"@types/uuid": "^10.0.0",
"axios": "^1.8.2",
"canvas": "^3.1.0",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"node-fetch": "^3.3.2",
"puppeteer": "^24.3.1",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"ts-node": "^10.9.2",
-336
View File
@@ -1,336 +0,0 @@
#!/usr/bin/env node
/**
* AI Helper Script
*
* This script is designed to help AI assistants run commands and immediately see their output
* without getting stuck waiting for long-running processes to complete.
*
* It runs a command in the background, captures the initial output, and returns control to the terminal.
*
* Usage:
* node scripts/ai-helper.js [command] [options]
*
* Commands:
* start-server Start the server in the background
* check-logs Check the most recent logs
* check-cluster Check cluster-related logs
* check-status Check if the server is running
* stop-server Stop the server
* verify-config Verify the current environment configuration
*
* Options:
* --env=<env> Environment (prod, dev, or dev:no-cluster, default: prod)
* --lines=<number> Number of log lines to show (default: 20)
* --help Show this help message
*
* Examples:
* node scripts/ai-helper.js start-server --env=prod
* node scripts/ai-helper.js check-logs --lines=50
* node scripts/ai-helper.js check-cluster
* node scripts/ai-helper.js verify-config
*/
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
// Parse command line arguments
const args = process.argv.slice(2);
const command = args[0];
const options = {
env: getArgValue(args, '--env') || 'prod',
lines: parseInt(getArgValue(args, '--lines') || '20', 10),
help: args.includes('--help') || args.includes('-h')
};
// Show help if requested or no command provided
if (options.help || !command) {
console.log(fs.readFileSync(__filename, 'utf8')
.split('\n')
.filter(line => line.startsWith(' *'))
.map(line => line.substring(3))
.join('\n'));
process.exit(0);
}
// Validate environment
if (!['prod', 'prod:no-cluster', 'dev', 'dev:no-cluster'].includes(options.env)) {
console.error(`Error: Unknown environment "${options.env}". Use "prod", "prod:no-cluster", "dev", or "dev:no-cluster".`);
process.exit(1);
}
// Ensure logs directory exists
if (!fs.existsSync('logs')) {
fs.mkdirSync('logs');
}
// Create a unique log file for this run
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFile = `logs/ai-helper-${timestamp}.log`;
// Execute the requested command
switch (command) {
case 'start-server':
startServer(options.env);
break;
case 'check-logs':
checkLogs(options.lines);
break;
case 'check-cluster':
checkClusterLogs(options.lines);
break;
case 'check-status':
checkStatus();
break;
case 'stop-server':
stopServer();
break;
case 'verify-config':
verifyConfig();
break;
default:
console.error(`Unknown command: ${command}`);
console.log('Run with --help to see available commands');
process.exit(1);
}
// Helper function to get argument value
function getArgValue(args, name) {
const arg = args.find(a => a.startsWith(`${name}=`));
return arg ? arg.split('=')[1] : null;
}
// Start the server in the background
function startServer(env) {
console.log(`Starting server in ${env} mode...`);
try {
// First, configure the environment
execSync(`node scripts/configure-env.js ${env}`, { stdio: 'inherit' });
// Build the application if needed
if (env === 'prod') {
console.log('Building the application...');
execSync('npm run build', { stdio: 'inherit' });
}
// Start the server in the background
const serverProcess = spawn('node', [
env === 'prod' ? 'dist/server.js' : 'scripts/start.js',
env === 'prod' ? '' : 'dev:mock'
].filter(Boolean), {
detached: true,
stdio: ['ignore', fs.openSync(logFile, 'w'), fs.openSync(logFile, 'w')]
});
// Don't wait for the server to exit
serverProcess.unref();
console.log(`Server started with PID ${serverProcess.pid}`);
console.log(`Logs are being written to ${logFile}`);
// Wait a moment for the server to start
setTimeout(() => {
// Show the initial logs
console.log('\nInitial server logs:');
try {
const initialLogs = execSync(`tail -n 20 ${logFile}`).toString();
console.log(initialLogs || 'No logs yet');
} catch (error) {
console.log('No logs available yet');
}
console.log('\nTo check logs later, run:');
console.log(` node scripts/ai-helper.js check-logs`);
console.log('To stop the server, run:');
console.log(` node scripts/ai-helper.js stop-server`);
// For development mode, remind the user to access the application on port 7654
if (env !== 'prod') {
console.log('\nAccess the application at:');
console.log(' http://localhost:7654');
}
}, 2000);
} catch (error) {
console.error('Error starting server:', error.message);
process.exit(1);
}
}
// Check the most recent logs
function checkLogs(lines) {
console.log(`Checking the most recent logs (${lines} lines)...`);
try {
// Find the most recent log file
const logDir = path.resolve(process.cwd(), 'logs');
const logFiles = fs.readdirSync(logDir)
.filter(file => file.endsWith('.log'))
.map(file => ({
name: file,
time: fs.statSync(path.join(logDir, file)).mtime.getTime()
}))
.sort((a, b) => b.time - a.time);
if (logFiles.length === 0) {
console.log('No log files found');
return;
}
const recentLogFile = path.join(logDir, logFiles[0].name);
console.log(`Most recent log file: ${recentLogFile}`);
// Show the logs
const logs = execSync(`tail -n ${lines} ${recentLogFile}`).toString();
console.log('\nRecent logs:');
console.log(logs || 'No logs found');
} catch (error) {
console.error('Error checking logs:', error.message);
}
}
// Check cluster-related logs
function checkClusterLogs(lines) {
console.log('Checking cluster-related logs...');
try {
// Find the most recent log file
const logDir = path.resolve(process.cwd(), 'logs');
const logFiles = fs.readdirSync(logDir)
.filter(file => file.endsWith('.log'))
.map(file => ({
name: file,
time: fs.statSync(path.join(logDir, file)).mtime.getTime()
}))
.sort((a, b) => b.time - a.time);
if (logFiles.length === 0) {
console.log('No log files found');
return;
}
const recentLogFile = path.join(logDir, logFiles[0].name);
console.log(`Searching in: ${recentLogFile}`);
// Search for cluster-related logs
const grepCommand = `grep -i "cluster\\|clusterMode\\|autoDetectCluster" ${recentLogFile} | tail -n ${lines}`;
try {
const clusterLogs = execSync(grepCommand).toString();
console.log('\nCluster-related logs:');
console.log(clusterLogs || 'No cluster-related logs found');
} catch (error) {
// grep returns exit code 1 if no matches found
console.log('No cluster-related logs found');
}
} catch (error) {
console.error('Error checking cluster logs:', error.message);
}
}
// Check if the server is running
function checkStatus() {
console.log('Checking server status...');
try {
const psOutput = execSync('ps aux | grep "[n]ode.*server.js\\|[n]ode.*start.js"').toString();
console.log('\nRunning server processes:');
console.log(psOutput || 'No server processes found');
// Check if the frontend is responding
try {
console.log('\nChecking frontend on port 7654:');
// Just check if we get a 200 response, don't try to parse the HTML
const frontendResponse = execSync('curl -s -o /dev/null -w "%{http_code}" http://localhost:7654/').toString();
if (frontendResponse.trim() === '200') {
console.log('✅ Frontend is responding (HTTP 200)');
} else {
console.log(`❌ Frontend returned HTTP ${frontendResponse}`);
}
} catch (error) {
console.log('❌ Frontend is not responding');
}
// Check if the mock server is responding (in dev mode)
try {
console.log('\nChecking mock server on port 7656:');
const mockResponse = execSync('curl -s -o /dev/null -w "%{http_code}" http://localhost:7656/').toString();
if (mockResponse.trim() === '200') {
console.log('✅ Mock server is responding (HTTP 200)');
} else {
console.log(`❌ Mock server returned HTTP ${mockResponse}`);
}
} catch (error) {
console.log('❌ Mock server is not responding (this is normal in production mode)');
}
// Check if the API is responding
try {
console.log('\nChecking API on port 7654 (via frontend proxy):');
const apiResponse = execSync('curl -s http://localhost:7654/api/nodes | head -n 5').toString();
if (apiResponse && apiResponse.includes('nodes')) {
console.log('✅ API is responding via frontend proxy');
console.log('First few lines of API response:');
console.log(apiResponse);
} else {
console.log('❌ API response via frontend proxy is invalid');
console.log(apiResponse);
}
} catch (error) {
console.log('❌ API is not responding via frontend proxy');
// Try direct API access on port 7656 (for development mode)
try {
console.log('\nTrying direct API access on port 7656:');
const directApiResponse = execSync('curl -s http://localhost:7656/api/nodes | head -n 5').toString();
console.log(directApiResponse);
console.log('✅ API is responding directly on port 7656');
} catch (error) {
console.log('❌ API is not responding on port 7656 either');
}
}
console.log('\nServer information:');
console.log('- The frontend is running on port 7654 (accessible in browser)');
console.log('- The backend API is running on port 7656 (internal only)');
console.log('- The mock data server is running on port 7656 (in development mode)');
} catch (error) {
console.log('No server processes found');
}
}
// Stop the server
function stopServer() {
console.log('Stopping server processes...');
try {
// Kill processes by pattern
execSync('pkill -f "node.*server.js\\|node.*start.js\\|vite"');
console.log('Server processes stopped by pattern');
} catch (error) {
console.log('No matching processes found to stop by pattern');
}
try {
// Kill processes by port
execSync('npx kill-port 7654 7656 3000');
console.log('Processes on ports 7654, 7656, and 3000 stopped');
} catch (error) {
console.log('No processes found on ports 7654, 7656, or 3000');
}
console.log('All server processes have been stopped');
}
// Verify the current environment configuration
function verifyConfig() {
console.log('Verifying environment configuration...');
try {
execSync('node scripts/verify-cluster-config.js', { stdio: 'inherit' });
} catch (error) {
console.error('Error verifying configuration:', error.message);
process.exit(1);
}
}
-157
View File
@@ -1,157 +0,0 @@
#!/bin/bash
# Pulse Debug Proxy
# This script handles everything needed for testing with simulated data
# and includes a debug proxy to log all socket.io messages
# Colors for terminal output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Port configuration
MOCK_PORT=7656
PROXY_PORT=7657
FRONTEND_PORT=7654
echo -e "${BLUE}"
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Pulse Debug Proxy ║"
echo "║ ║"
echo "║ This script will: ║"
echo "║ 1. Kill any existing Node.js processes ║"
echo "║ 2. Start the mock data server ║"
echo "║ 3. Start the debug proxy ║"
echo "║ 4. Start the frontend with connection to the proxy ║"
echo "║ 5. Log all socket.io messages for debugging ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check for required commands
if ! command_exists node; then
echo -e "${RED}Error: Node.js is not installed. Please install Node.js to continue.${NC}"
exit 1
fi
# Kill any existing Node.js processes
echo -e "${YELLOW}Killing any existing Node.js processes...${NC}"
pkill -f "node scripts/generate-mock-data.js" 2>/dev/null
pkill -f "node scripts/debug-socket.js" 2>/dev/null
pkill -f "vite" 2>/dev/null
sleep 1
# Check if ports are in use
port_check() {
lsof -i:$1 >/dev/null 2>&1
return $?
}
if port_check $MOCK_PORT; then
echo -e "${RED}Error: Port $MOCK_PORT is already in use. Please free this port and try again.${NC}"
echo -e "${YELLOW}You can try: lsof -i:$MOCK_PORT to see what's using it.${NC}"
exit 1
fi
if port_check $PROXY_PORT; then
echo -e "${RED}Error: Port $PROXY_PORT is already in use. Please free this port and try again.${NC}"
echo -e "${YELLOW}You can try: lsof -i:$PROXY_PORT to see what's using it.${NC}"
exit 1
fi
if port_check $FRONTEND_PORT; then
echo -e "${RED}Error: Port $FRONTEND_PORT is already in use. Please free this port and try again.${NC}"
echo -e "${YELLOW}You can try: lsof -i:$FRONTEND_PORT to see what's using it.${NC}"
exit 1
fi
# Start the mock data server
echo -e "${GREEN}Starting mock data server on port $MOCK_PORT...${NC}"
node scripts/generate-mock-data.js > /tmp/pulse-mock-server.log 2>&1 &
MOCK_PID=$!
# Wait for the mock server to start
echo -e "${YELLOW}Waiting for mock server to start...${NC}"
sleep 2
# Check if mock server is running
if ! ps -p $MOCK_PID > /dev/null; then
echo -e "${RED}Error: Mock server failed to start. Check /tmp/pulse-mock-server.log for details.${NC}"
exit 1
fi
# Start the debug proxy
echo -e "${GREEN}Starting debug proxy on port $PROXY_PORT...${NC}"
node scripts/debug-socket.js > /tmp/pulse-debug-proxy.log 2>&1 &
PROXY_PID=$!
# Wait for the proxy to start
echo -e "${YELLOW}Waiting for debug proxy to start...${NC}"
sleep 2
# Check if proxy is running
if ! ps -p $PROXY_PID > /dev/null; then
echo -e "${RED}Error: Debug proxy failed to start. Check /tmp/pulse-debug-proxy.log for details.${NC}"
kill $MOCK_PID
exit 1
fi
# Start the frontend
echo -e "${GREEN}Starting frontend on port $FRONTEND_PORT...${NC}"
cd frontend && VITE_API_URL=http://localhost:$PROXY_PORT npm run dev > /tmp/pulse-frontend.log 2>&1 &
FRONTEND_PID=$!
# Wait for the frontend to start
echo -e "${YELLOW}Waiting for frontend to start...${NC}"
sleep 5
# Check if frontend is running
if ! ps -p $FRONTEND_PID > /dev/null; then
echo -e "${RED}Error: Frontend failed to start. Check /tmp/pulse-frontend.log for details.${NC}"
kill $MOCK_PID
kill $PROXY_PID
exit 1
fi
# Open the browser
echo -e "${GREEN}Opening browser to http://localhost:$FRONTEND_PORT${NC}"
if command_exists open; then
# macOS
open "http://localhost:$FRONTEND_PORT"
elif command_exists xdg-open; then
# Linux
xdg-open "http://localhost:$FRONTEND_PORT"
elif command_exists explorer; then
# Windows
explorer "http://localhost:$FRONTEND_PORT"
else
echo -e "${YELLOW}Could not automatically open browser. Please open http://localhost:$FRONTEND_PORT manually.${NC}"
fi
echo -e "${GREEN}Everything is running in debug mode!${NC}"
echo -e "${YELLOW}All socket.io messages are being logged to ./socket-debug.log${NC}"
echo -e "${YELLOW}Press Ctrl+C when you're done to stop all processes.${NC}"
# Function to clean up on exit
cleanup() {
echo -e "\n${GREEN}Cleaning up...${NC}"
kill $MOCK_PID 2>/dev/null
kill $PROXY_PID 2>/dev/null
kill $FRONTEND_PID 2>/dev/null
echo -e "${GREEN}Done! Debug logs are available in ./socket-debug.log${NC}"
exit 0
}
# Set up trap to catch Ctrl+C
trap cleanup SIGINT
# Wait for user to press Ctrl+C
wait
-120
View File
@@ -1,120 +0,0 @@
/**
* Socket.io Debug Proxy for Pulse
*
* This script creates a proxy server that sits between the frontend and backend,
* logging all socket.io messages to help debug connection issues.
*/
const express = require('express');
const http = require('http');
const { Server: SocketServer } = require('socket.io');
const { io: SocketClient } = require('socket.io-client');
const fs = require('fs');
// Create Express app and HTTP server for the proxy
const app = express();
const server = http.createServer(app);
const io = new SocketServer(server, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// Configuration
const PROXY_PORT = 7657;
const BACKEND_PORT = 7656;
const BACKEND_URL = `http://localhost:${BACKEND_PORT}`;
const LOG_FILE = './socket-debug.log';
// Clear previous log file
fs.writeFileSync(LOG_FILE, '--- Socket.io Debug Log ---\n\n');
// Helper function to log messages
const logMessage = (direction, type, data) => {
const timestamp = new Date().toISOString();
const message = `[${timestamp}] ${direction} | ${type} | ${JSON.stringify(data, null, 2)}\n`;
console.log(`${direction} | ${type}`);
fs.appendFileSync(LOG_FILE, message);
};
// Connect to the real backend with reconnection options
const backendSocket = SocketClient(BACKEND_URL, {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000
});
// Handle connections from the frontend
io.on('connection', (frontendSocket) => {
console.log(`Frontend connected: ${frontendSocket.id}`);
logMessage('INFO', 'FRONTEND_CONNECTED', { socketId: frontendSocket.id });
// Request initial data from backend when frontend connects
// This ensures we capture the initial data messages
if (backendSocket.connected) {
console.log('Requesting initial data from backend');
logMessage('INFO', 'REQUESTING_INITIAL_DATA', {});
// Emit a custom message to request initial data
// The backend should respond with the initial data messages
backendSocket.emit('requestInitialData');
}
// Handle messages from frontend to backend
frontendSocket.onAny((event, ...args) => {
logMessage('FRONTEND → BACKEND', event, args);
backendSocket.emit(event, ...args);
});
// Handle disconnection
frontendSocket.on('disconnect', () => {
console.log(`Frontend disconnected: ${frontendSocket.id}`);
logMessage('INFO', 'FRONTEND_DISCONNECTED', { socketId: frontendSocket.id });
});
// Forward backend messages to frontend
backendSocket.onAny((event, ...args) => {
logMessage('BACKEND → FRONTEND', event, args);
frontendSocket.emit(event, ...args);
});
});
// Handle backend connection events
backendSocket.on('connect', () => {
console.log(`Connected to backend: ${backendSocket.id}`);
logMessage('INFO', 'BACKEND_CONNECTED', { socketId: backendSocket.id });
});
backendSocket.on('reconnect', (attemptNumber) => {
console.log(`Reconnected to backend after ${attemptNumber} attempts`);
logMessage('INFO', 'BACKEND_RECONNECTED', { attemptNumber });
});
backendSocket.on('disconnect', () => {
console.log('Disconnected from backend');
logMessage('INFO', 'BACKEND_DISCONNECTED', {});
});
backendSocket.on('connect_error', (err) => {
console.error(`Backend connection error: ${err.message}`);
logMessage('ERROR', 'BACKEND_CONNECTION_ERROR', { error: err.message });
});
// Start the proxy server
server.listen(PROXY_PORT, () => {
console.log(`
╔════════════════════════════════════════════════════════════╗
║ ║
║ Socket.io Debug Proxy ║
║ ║
║ Proxy running on port ${PROXY_PORT}
║ Forwarding to backend on port ${BACKEND_PORT}
║ ║
║ All messages are being logged to ${LOG_FILE}
║ ║
╚════════════════════════════════════════════════════════════╝
`);
});
+35 -77
View File
@@ -1,87 +1,45 @@
const { createCanvas } = require('canvas');
const fs = require('fs');
const path = require('path');
const { createCanvas } = require('canvas');
// Create directory for logos if it doesn't exist
const logoDir = path.join(__dirname, '../public/logos');
if (!fs.existsSync(logoDir)) {
fs.mkdirSync(logoDir, { recursive: true });
// Create output directory if it doesn't exist
const outputDir = path.join(__dirname, '..', 'public', 'images');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Sizes to generate (in pixels)
const sizes = [16, 32, 64, 128, 256, 512, 1024];
// Generate different sizes of the logo
const sizes = [16, 32, 64, 128, 256, 512];
// Function to draw the Pulse logo
function drawLogo(ctx, size) {
const center = size / 2;
const radius = size / 2;
// Background circle with gradient
const bgGradient = ctx.createLinearGradient(0, 0, size, size);
bgGradient.addColorStop(0, 'rgba(58, 123, 213, 0.8)'); // #3a7bd5 with opacity
bgGradient.addColorStop(1, 'rgba(58, 123, 213, 0.7)');
ctx.fillStyle = bgGradient;
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.fill();
// Outer ring gradient
const ringGradient = ctx.createLinearGradient(0, 0, size, size);
ringGradient.addColorStop(0, 'rgba(255, 255, 255, 0.2)');
ringGradient.addColorStop(1, 'rgba(255, 255, 255, 0.1)');
ctx.fillStyle = ringGradient;
ctx.beginPath();
ctx.arc(center, center, radius * 0.95, 0, Math.PI * 2);
ctx.fill();
// Pulse ring (static representation)
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
ctx.lineWidth = size * 0.06;
ctx.beginPath();
ctx.arc(center, center, radius * 0.7, 0, Math.PI * 2);
ctx.stroke();
// Center dot with gradient
const centerGradient = ctx.createLinearGradient(
center - radius * 0.3,
center - radius * 0.3,
center + radius * 0.3,
center + radius * 0.3
);
centerGradient.addColorStop(0, '#ffffff');
centerGradient.addColorStop(1, '#f0f0f0');
// Add glow to center dot
ctx.shadowColor = 'rgba(255, 255, 255, 0.7)';
ctx.shadowBlur = size * 0.1;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.fillStyle = centerGradient;
ctx.beginPath();
ctx.arc(center, center, radius * 0.3, 0, Math.PI * 2);
ctx.fill();
// Reset shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
}
// Generate logos for each size
sizes.forEach(size => {
const canvas = createCanvas(size, size);
const ctx = canvas.getContext('2d');
// Draw the logo
drawLogo(ctx, size);
// Save as PNG
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(path.join(logoDir, `pulse-logo-${size}x${size}.png`), buffer);
console.log(`Generated ${size}x${size} logo`);
});
console.log('Logo generation complete!');
// Set background
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, size, size);
// Draw pulse wave
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = size * 0.1;
ctx.beginPath();
const amplitude = size * 0.3;
const frequency = 2 * Math.PI / size;
for (let x = 0; x < size; x++) {
const y = size/2 + amplitude * Math.sin(frequency * x);
if (x === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
// Save the image
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(path.join(outputDir, `logo-${size}.png`), buffer);
console.log(`Generated logo-${size}.png`);
});
+45 -107
View File
@@ -1,116 +1,54 @@
const { createCanvas, registerFont } = require('canvas');
const fs = require('fs');
const path = require('path');
const { createCanvas, registerFont } = require('canvas');
// Create directory for logos if it doesn't exist
const logoDir = path.join(__dirname, '../public/logos');
if (!fs.existsSync(logoDir)) {
fs.mkdirSync(logoDir, { recursive: true });
// Register the font
const fontPath = path.join(__dirname, '..', 'assets', 'fonts', 'Roboto-Bold.ttf');
registerFont(fontPath, { family: 'Roboto' });
// Create output directory if it doesn't exist
const outputDir = path.join(__dirname, '..', 'public', 'images');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Try to register a system font (fallback to sans-serif if not available)
try {
// On macOS, try to use SF Pro
registerFont('/System/Library/Fonts/SFCompactDisplay-Bold.otf', { family: 'SFPro', weight: 'bold' });
} catch (e) {
console.log('Could not register system font, will use default sans-serif');
// Create canvas
const width = 800;
const height = 200;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Set background
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, width, height);
// Draw pulse wave
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 10;
ctx.beginPath();
const amplitude = 40;
const frequency = 2 * Math.PI / width;
for (let x = 0; x < width; x++) {
const y = height/2 + amplitude * Math.sin(frequency * x);
if (x === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
// Sizes to generate (in pixels) - width x height
const sizes = [
{ width: 200, height: 60 },
{ width: 400, height: 120 },
{ width: 800, height: 240 },
];
ctx.stroke();
// Function to draw the Pulse logo with text
function drawLogoWithText(ctx, width, height) {
const logoSize = height * 0.9;
const logoX = height * 0.05;
const logoY = height * 0.05;
const center = logoSize / 2;
const radius = logoSize / 2;
// Draw the logo
// Background circle with gradient
const bgGradient = ctx.createLinearGradient(logoX, logoY, logoX + logoSize, logoY + logoSize);
bgGradient.addColorStop(0, 'rgba(58, 123, 213, 0.8)'); // #3a7bd5 with opacity
bgGradient.addColorStop(1, 'rgba(58, 123, 213, 0.7)');
ctx.fillStyle = bgGradient;
ctx.beginPath();
ctx.arc(logoX + center, logoY + center, radius, 0, Math.PI * 2);
ctx.fill();
// Outer ring gradient
const ringGradient = ctx.createLinearGradient(logoX, logoY, logoX + logoSize, logoY + logoSize);
ringGradient.addColorStop(0, 'rgba(255, 255, 255, 0.2)');
ringGradient.addColorStop(1, 'rgba(255, 255, 255, 0.1)');
ctx.fillStyle = ringGradient;
ctx.beginPath();
ctx.arc(logoX + center, logoY + center, radius * 0.95, 0, Math.PI * 2);
ctx.fill();
// Pulse ring (static representation)
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
ctx.lineWidth = logoSize * 0.06;
ctx.beginPath();
ctx.arc(logoX + center, logoY + center, radius * 0.7, 0, Math.PI * 2);
ctx.stroke();
// Center dot with gradient
const centerGradient = ctx.createLinearGradient(
logoX + center - radius * 0.3,
logoY + center - radius * 0.3,
logoX + center + radius * 0.3,
logoY + center + radius * 0.3
);
centerGradient.addColorStop(0, '#ffffff');
centerGradient.addColorStop(1, '#f0f0f0');
// Add glow to center dot
ctx.shadowColor = 'rgba(255, 255, 255, 0.7)';
ctx.shadowBlur = logoSize * 0.1;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.fillStyle = centerGradient;
ctx.beginPath();
ctx.arc(logoX + center, logoY + center, radius * 0.3, 0, Math.PI * 2);
ctx.fill();
// Reset shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
// Draw text
const textX = logoX + logoSize + (logoSize * 0.2);
const textY = height / 2 + (height * 0.1);
const fontSize = height * 0.5;
ctx.font = `bold ${fontSize}px "SFPro", sans-serif`;
ctx.fillStyle = '#ffffff';
ctx.textBaseline = 'middle';
ctx.fillText('Pulse', textX, textY);
}
// Add text
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 48px Roboto';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Pulse', width/2, height/2);
// Generate logos for each size
sizes.forEach(size => {
const canvas = createCanvas(size.width, size.height);
const ctx = canvas.getContext('2d');
// Fill with transparent background
ctx.clearRect(0, 0, size.width, size.height);
// Draw the logo with text
drawLogoWithText(ctx, size.width, size.height);
// Save as PNG
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(path.join(logoDir, `pulse-logo-with-text-${size.width}x${size.height}.png`), buffer);
console.log(`Generated ${size.width}x${size.height} logo with text`);
});
console.log('Logo with text generation complete!');
// Save the image
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(path.join(outputDir, 'logo-with-text.png'), buffer);
console.log('Generated logo-with-text.png');
Executable → Regular
+10 -20
View File
@@ -1,32 +1,22 @@
#!/bin/bash
# Script to generate Pulse logo PNGs in various sizes
# Make script executable if it isn't already
chmod +x "$0"
# Ensure we're in the project root
cd "$(dirname "$0")/.." || exit
echo "Generating logos..."
# Create the logos directory if it doesn't exist
mkdir -p public/logos
# Install the canvas dependency if not already installed
# Install canvas if not already installed
if ! npm list canvas >/dev/null 2>&1; then
echo "Installing canvas dependency..."
echo "Installing canvas..."
npm install canvas
fi
# Generate the logos
echo "Generating square logos (icon only)..."
# Generate logo PNGs
echo "Generating logo PNGs..."
node scripts/generate-logo-pngs.js
echo "Generating rectangular logos (with text)..."
# Generate logo with text
echo "Generating logo with text..."
node scripts/generate-logo-with-text.js
echo ""
echo "Logo generation complete!"
echo "Logo files are available in the public/logos directory."
echo ""
echo "Available logo files:"
ls -la public/logos/*.png
# Make the script executable
chmod +x "$(dirname "$0")/generate-logos.sh"
echo "Logo generation complete!"
-238
View File
@@ -1,238 +0,0 @@
#!/usr/bin/env node
/**
* Log Monitor Script
*
* This script provides real-time monitoring of application logs with filtering capabilities.
* It makes troubleshooting easier by allowing you to focus on specific log types or components.
*
* Usage:
* node scripts/monitor-logs.js [options]
*
* Options:
* --level=<level> Filter by log level (error, warn, info, debug)
* --component=<name> Filter by component name (e.g., ProxmoxClient, NodeManager)
* --search=<text> Search for specific text in logs
* --follow Follow log file in real-time (default: true)
* --lines=<number> Number of lines to show initially (default: 50)
* --file=<filename> Log file to monitor (default: latest combined log)
* --color Use colored output (default: true)
* --help Show this help message
*
* Examples:
* node scripts/monitor-logs.js --level=error
* node scripts/monitor-logs.js --component=ProxmoxClient
* node scripts/monitor-logs.js --search=cluster
* node scripts/monitor-logs.js --level=info --component=NodeManager
*/
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const readline = require('readline');
// Parse command line arguments
const args = process.argv.slice(2);
const options = {
level: getArgValue(args, '--level'),
component: getArgValue(args, '--component'),
search: getArgValue(args, '--search'),
follow: getArgValue(args, '--follow') !== 'false',
lines: parseInt(getArgValue(args, '--lines') || '50', 10),
file: getArgValue(args, '--file'),
color: getArgValue(args, '--color') !== 'false',
help: args.includes('--help') || args.includes('-h')
};
// Show help if requested
if (options.help) {
console.log(fs.readFileSync(__filename, 'utf8')
.split('\n')
.filter(line => line.startsWith(' *'))
.map(line => line.substring(3))
.join('\n'));
process.exit(0);
}
// Find the log directory
const logDir = path.resolve(process.cwd(), 'logs');
// Find the most recent log file if not specified
if (!options.file) {
try {
const logFiles = fs.readdirSync(logDir)
.filter(file => file.startsWith('combined') && file.endsWith('.log'))
.map(file => ({
name: file,
time: fs.statSync(path.join(logDir, file)).mtime.getTime()
}))
.sort((a, b) => b.time - a.time);
if (logFiles.length > 0) {
options.file = logFiles[0].name;
} else {
options.file = 'combined.log';
}
} catch (error) {
console.error('Error finding log files:', error.message);
options.file = 'combined.log';
}
}
const logFile = path.join(logDir, options.file);
// ANSI color codes
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m'
};
// Color mapping for log levels
const levelColors = {
error: colors.red,
warn: colors.yellow,
info: colors.green,
debug: colors.blue,
silly: colors.magenta
};
// Check if the log file exists
if (!fs.existsSync(logFile)) {
console.error(`Log file not found: ${logFile}`);
process.exit(1);
}
console.log(`Monitoring log file: ${logFile}`);
console.log(`Filters: ${Object.entries(options)
.filter(([key, value]) => ['level', 'component', 'search'].includes(key) && value)
.map(([key, value]) => `${key}=${value}`)
.join(', ') || 'none'}`);
console.log('Press Ctrl+C to exit\n');
// Build the tail command
const tailArgs = ['-n', options.lines.toString()];
if (options.follow) {
tailArgs.push('-f');
}
tailArgs.push(logFile);
// Start the tail process
const tail = spawn('tail', tailArgs);
// Process each line of output
tail.stdout.setEncoding('utf8');
const rl = readline.createInterface({
input: tail.stdout,
terminal: false
});
rl.on('line', (line) => {
try {
// Try to parse the line as JSON
const logEntry = JSON.parse(line);
// Apply filters
if (options.level && logEntry.level !== options.level) {
return;
}
if (options.component &&
(!logEntry.component || !logEntry.component.includes(options.component))) {
return;
}
if (options.search &&
!line.toLowerCase().includes(options.search.toLowerCase())) {
return;
}
// Format the output
let formattedLine = '';
if (options.color) {
const levelColor = levelColors[logEntry.level] || colors.white;
// Timestamp
formattedLine += `${colors.dim}${new Date(logEntry.timestamp).toISOString()}${colors.reset} `;
// Level
formattedLine += `${levelColor}${logEntry.level.toUpperCase().padEnd(5)}${colors.reset} `;
// Component
if (logEntry.component) {
formattedLine += `${colors.cyan}[${logEntry.component}]${colors.reset} `;
}
// Message
formattedLine += logEntry.message;
// Highlight search term if specified
if (options.search) {
const regex = new RegExp(options.search, 'gi');
formattedLine = formattedLine.replace(regex, match =>
`${colors.bgYellow}${colors.bright}${match}${colors.reset}`);
}
// Add metadata if present
if (logEntry.meta && Object.keys(logEntry.meta).length > 0) {
formattedLine += `\n${colors.dim}${JSON.stringify(logEntry.meta, null, 2)}${colors.reset}`;
}
} else {
// Simple formatting without colors
formattedLine = `${new Date(logEntry.timestamp).toISOString()} ${logEntry.level.toUpperCase().padEnd(5)} `;
if (logEntry.component) {
formattedLine += `[${logEntry.component}] `;
}
formattedLine += logEntry.message;
// Add metadata if present
if (logEntry.meta && Object.keys(logEntry.meta).length > 0) {
formattedLine += `\n${JSON.stringify(logEntry.meta, null, 2)}`;
}
}
console.log(formattedLine);
} catch (error) {
// If not JSON, just print the line as is
if (options.search && !line.toLowerCase().includes(options.search.toLowerCase())) {
return;
}
if (options.color && options.search) {
const regex = new RegExp(options.search, 'gi');
console.log(line.replace(regex, match =>
`${colors.bgYellow}${colors.bright}${match}${colors.reset}`));
} else {
console.log(line);
}
}
});
// Handle errors
tail.stderr.on('data', (data) => {
console.error(`Error: ${data}`);
});
tail.on('close', (code) => {
console.log(`Monitoring ended with code ${code}`);
});
// Helper function to get argument value
function getArgValue(args, name) {
const arg = args.find(a => a.startsWith(`${name}=`));
return arg ? arg.split('=')[1] : null;
}
-96
View File
@@ -1,96 +0,0 @@
#!/usr/bin/env node
/**
* Run With Logs Script
*
* This script runs the application and monitors logs in a split terminal.
* It makes it easier to see what's happening in the application in real-time.
*
* Usage:
* node scripts/run-with-logs.js [dev|prod] [options]
*
* Options:
* --search=<text> Search for specific text in logs
* --level=<level> Filter by log level (error, warn, info, debug)
* --component=<n> Filter by component name
* --help Show this help message
*
* Examples:
* node scripts/run-with-logs.js dev
* node scripts/run-with-logs.js prod --search=cluster
* node scripts/run-with-logs.js dev --level=error
*/
const { spawn } = require('child_process');
const fs = require('fs');
// Parse command line arguments
const args = process.argv.slice(2);
const env = args[0] === 'prod' ? 'prod' : 'dev';
const help = args.includes('--help') || args.includes('-h');
// Show help if requested
if (help) {
console.log(fs.readFileSync(__filename, 'utf8')
.split('\n')
.filter(line => line.startsWith(' *'))
.map(line => line.substring(3))
.join('\n'));
process.exit(0);
}
// Extract log options
const logOptions = args
.filter(arg => arg.startsWith('--'))
.join(' ');
// Determine which npm script to run
const npmScript = env;
console.log(`Starting application in ${env} mode...`);
// Create a unique log file for this run
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFile = `logs/app-${env}-${timestamp}.log`;
// Ensure logs directory exists
if (!fs.existsSync('logs')) {
fs.mkdirSync('logs');
}
// Start the application and redirect output to the log file
const app = spawn('npm', ['run', npmScript], {
stdio: ['ignore', fs.openSync(logFile, 'w'), fs.openSync(logFile, 'w')],
detached: true
});
// Don't wait for the app to exit
app.unref();
console.log(`Application started with PID ${app.pid}`);
console.log(`Logs are being written to ${logFile}`);
console.log('Starting log monitor...\n');
// Wait a moment for the app to start writing logs
setTimeout(() => {
// Start the log monitor
const logMonitor = spawn('node', ['scripts/monitor-logs.js', `--file=${logFile.split('/')[1]}`, '--follow=true', ...logOptions.split(' ').filter(Boolean)], {
stdio: 'inherit'
});
// Handle log monitor exit
logMonitor.on('exit', (code) => {
console.log(`\nLog monitor exited with code ${code}`);
console.log(`The application is still running with PID ${app.pid}`);
console.log('To stop the application, run:');
console.log(` kill ${app.pid}`);
process.exit(0);
});
// Handle SIGINT (Ctrl+C)
process.on('SIGINT', () => {
console.log('\nStopping application and log monitor...');
process.kill(-app.pid, 'SIGINT');
process.exit(0);
});
}, 1000);
+28 -60
View File
@@ -1,77 +1,45 @@
@echo off
setlocal
REM Stop any running Pulse Docker containers first
echo Stopping any running Pulse Docker containers...
where docker >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "tokens=*" %%i in ('docker ps -q --filter "name=pulse"') do (
docker stop %%i
)
) else (
echo Docker not found, skipping container cleanup...
)
REM Kill any existing servers
:: Kill any existing servers
echo Killing any existing servers...
taskkill /f /im "node.exe" /fi "WINDOWTITLE eq node dist/server.js" 2>nul
call npx kill-port 7654 7655 3000
taskkill /F /IM node.exe /FI "WINDOWTITLE eq dist/server.js" 2>nul
taskkill /F /IM node.exe /FI "WINDOWTITLE eq ts-node src/mock/run-server.ts" 2>nul
npx kill-port 7654 7655 7656 5173
REM Set environment to development with mock data
:: Clear any existing data files that might persist between sessions
echo Clearing any persisted data from previous sessions...
node scripts/clear-data.js
:: Set environment to development and load the environment file
set NODE_ENV=development
set USE_MOCK_DATA=true
set MOCK_DATA_ENABLED=true
REM Load environment variables from .env if it exists
:: Load environment variables from .env if it exists
if exist .env (
echo Loading environment from .env
for /f "tokens=*" %%a in (.env) do (
set "%%a"
)
echo Loading environment from .env
for /f "tokens=*" %%a in (.env) do set "%%a"
)
REM Override with mock data settings
:: Force mock data to be enabled for development
set USE_MOCK_DATA=true
set MOCK_DATA_ENABLED=true
REM Start the mock data server
echo Starting mock data server...
start /b cmd /c "ts-node src/mock/run-server.ts > %TEMP%\pulse-mock-server.log 2>&1"
:: Start the mock server in the background
echo Starting mock server...
start /B node scripts/start-mock-server.js
set MOCK_SERVER_PID=%ERRORLEVEL%
REM Wait a moment for the mock server to start
timeout /t 2 /nobreak > nul
:: Build the backend
echo Building backend...
call npm run build
REM Start the backend server with mock data
echo Starting backend server with mock data on port 7655...
start /b cmd /c "set USE_MOCK_DATA=true && set MOCK_DATA_ENABLED=true && set PORT=7655 && npm run dev:server"
:: Build the frontend
echo Building frontend...
cd frontend && call npm run build && cd ..
REM Wait a moment for the server to start
timeout /t 3 /nobreak > nul
:: Start the development server
echo Starting development server...
node dist/server.js
REM Set host IP to bind to all interfaces
set HOST_IP=0.0.0.0
REM Verify mock data is enabled
echo Verifying mock data is enabled...
curl -s "http://localhost:7655/api/status" | findstr "mockDataEnabled" > nul
if %ERRORLEVEL% EQU 0 (
echo ✅ Mock data is enabled
) else (
echo ❌ Mock data is NOT enabled
)
echo.
echo Pulse is now running with mock data!
echo - Backend API: http://localhost:7655 (internal only)
echo - Frontend UI: http://localhost:7654 (use this for development)
echo.
echo Access the application at: http://localhost:7654
echo.
REM Start the frontend Vite dev server
echo Starting Pulse interface with mock data on port 7654...
cd frontend && set USE_MOCK_DATA=true && set MOCK_DATA_ENABLED=true && npm run dev -- --host %HOST_IP% --port 7654
REM When the frontend exits, also kill the backend and mock server
taskkill /f /im "node.exe" /fi "WINDOWTITLE eq npm run dev:server" 2>nul
taskkill /f /im "node.exe" /fi "WINDOWTITLE eq ts-node src/mock/run-server.ts" 2>nul
:: When the server exits, also kill the mock server
taskkill /F /PID %MOCK_SERVER_PID% 2>nul
Executable → Regular
+23 -93
View File
@@ -3,38 +3,18 @@
# Make script executable if it isn't already
chmod +x "$0"
# Check for dry run flag from command line or environment variable
DRY_RUN=false
if [ "$DRY_RUN" = "true" ]; then
echo "Dry run mode enabled via environment variable - will not actually start the server"
else
for arg in "$@"; do
if [ "$arg" == "--dry-run" ]; then
DRY_RUN=true
echo "Dry run mode enabled via command line flag - will not actually start the server"
fi
done
fi
# Stop any running Pulse Docker containers first
echo "Stopping any running Pulse Docker containers..."
if command -v docker &> /dev/null; then
docker ps -q --filter "name=pulse" | xargs -r docker stop
else
echo "Docker not found, skipping container cleanup..."
fi
# Kill any existing servers
echo "Killing any existing servers..."
pkill -f "node dist/server.js" || true
pkill -f "ts-node src/mock/run-server.ts" || true
npx kill-port 7654 7655 7656 5173
# Set environment to development with mock data
# Clear any existing data files that might persist between sessions
echo "Clearing any persisted data from previous sessions..."
node scripts/clear-data.js
# Set environment to development and load the environment file
export NODE_ENV=development
export USE_MOCK_DATA=true
export MOCK_DATA_ENABLED=true
export MOCK_SERVER_PORT=7656
# Load environment variables from .env if it exists
if [ -f .env ]; then
@@ -42,78 +22,28 @@ if [ -f .env ]; then
set -a
source .env
set +a
# Override mock data settings
export USE_MOCK_DATA=true
export MOCK_DATA_ENABLED=true
export MOCK_SERVER_PORT=7656
fi
# Start the mock data server
echo "Starting mock data server on port 7656..."
if [ "$DRY_RUN" = false ]; then
MOCK_SERVER_PORT=7656 ts-node src/mock/run-server.ts > /tmp/pulse-mock-server.log 2>&1 &
MOCK_SERVER_PID=$!
# Force mock data to be enabled for development
export USE_MOCK_DATA=true
export MOCK_DATA_ENABLED=true
# Wait a moment for the mock server to start
sleep 2
# Start the mock server in the background
echo "Starting mock server..."
node scripts/start-mock-server.js &
MOCK_SERVER_PID=$!
# Check if the mock data server is running
if ! ps -p $MOCK_SERVER_PID > /dev/null; then
echo "Error: Mock data server failed to start."
echo "Check the logs at /tmp/pulse-mock-server.log for details."
exit 1
fi
# Verify mock server is running
if curl -s http://localhost:7656 > /dev/null; then
echo "✅ Mock server is running on port 7656"
else
echo "❌ Mock server failed to start"
cat /tmp/pulse-mock-server.log
fi
else
echo "[DRY RUN] Would start mock data server with: MOCK_SERVER_PORT=7656 ts-node src/mock/run-server.ts"
fi
# Build the backend
echo "Building backend..."
npm run build
# Start the backend server with mock data on port 7654
echo "Starting backend server with mock data on port 7654..."
if [ "$DRY_RUN" = false ]; then
USE_MOCK_DATA=true MOCK_DATA_ENABLED=true PORT=7654 npm run dev:server &
BACKEND_PID=$!
# Build the frontend
echo "Building frontend..."
cd frontend && npm run build && cd ..
# Wait a moment for the server to start
sleep 3
# Start the development server
echo "Starting development server..."
node dist/server.js
# Verify mock data is enabled - check port 7654 where the backend is running
echo "Verifying mock data is enabled..."
curl -s "http://localhost:7654/api/status" | grep -q "mockDataEnabled" && echo "✅ Mock data is enabled" || echo "❌ Mock data is NOT enabled"
else
echo "[DRY RUN] Would start backend server with: USE_MOCK_DATA=true MOCK_DATA_ENABLED=true PORT=7654 npm run dev:server"
fi
# Get the host IP (use 0.0.0.0 in Docker, otherwise use localhost or your local IP)
HOST_IP="0.0.0.0"
if [[ -z "${DOCKER_CONTAINER}" ]]; then
# Not in Docker, still use 0.0.0.0 to bind to all interfaces
HOST_IP="0.0.0.0"
fi
echo ""
echo "Pulse is now running with mock data!"
echo "- Backend API: http://localhost:7654"
echo "- Mock Server: http://localhost:7656"
echo "- Frontend UI: http://localhost:5173 (use this for development)"
echo ""
echo "Access the application at: http://localhost:5173"
echo ""
# Start the frontend Vite dev server on port 5173
echo "Starting Pulse interface with mock data on port 5173..."
if [ "$DRY_RUN" = false ]; then
cd frontend && USE_MOCK_DATA=true MOCK_DATA_ENABLED=true npm run dev -- --host "${HOST_IP}" --port 5173
# When the frontend exits, also kill the backend and mock server
kill $BACKEND_PID $MOCK_SERVER_PID
else
echo "[DRY RUN] Would start frontend with: cd frontend && USE_MOCK_DATA=true MOCK_DATA_ENABLED=true npm run dev -- --host \"${HOST_IP}\" --port 5173"
fi
# When the server exits, also kill the mock server
kill $MOCK_SERVER_PID
Executable → Regular
+78 -52
View File
@@ -1,58 +1,84 @@
#!/usr/bin/env node
const express = require('express');
const cors = require('cors');
const app = express();
/**
* Script to start just the mock server
* Usage: node scripts/start-mock-server.js
*/
// Enable CORS
app.use(cors());
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const dotenv = require('dotenv');
// Load environment variables
dotenv.config();
// Use port 7656 to avoid conflicts with the backend server
const MOCK_PORT = 7656;
console.log(`Starting mock server on port ${MOCK_PORT}...`);
// Set environment variables
process.env.NODE_ENV = 'development';
process.env.USE_MOCK_DATA = 'true';
process.env.MOCK_DATA_ENABLED = 'true';
process.env.MOCK_SERVER_PORT = MOCK_PORT.toString();
// Start the mock server
const mockServer = spawn('npx', ['ts-node', 'src/mock/run-server.ts'], {
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: 'development',
USE_MOCK_DATA: 'true',
MOCK_DATA_ENABLED: 'true',
PORT: MOCK_PORT.toString(),
MOCK_SERVER_PORT: MOCK_PORT.toString()
}
// Mock data endpoints
app.get('/api/v1/cluster/status', (req, res) => {
res.json({
nodes: [
{
name: 'pve1',
status: 'online',
cpu: {
usage: 45.2,
cores: 4
},
memory: {
total: 16777216,
used: 8388608,
free: 8388608
}
},
{
name: 'pve2',
status: 'online',
cpu: {
usage: 32.8,
cores: 4
},
memory: {
total: 16777216,
used: 6291456,
free: 10485760
}
}
]
});
});
// Handle mock server exit
mockServer.on('exit', (code) => {
console.log(`Mock server exited with code ${code}`);
process.exit(code);
app.get('/api/v1/cluster/resources', (req, res) => {
res.json({
vms: [
{
id: '100',
name: 'vm-100',
status: 'running',
node: 'pve1',
cpu: {
usage: 25.5,
cores: 2
},
memory: {
total: 4096,
used: 2048,
free: 2048
}
}
],
containers: [
{
id: '101',
name: 'ct-101',
status: 'running',
node: 'pve2',
cpu: {
usage: 15.2,
cores: 1
},
memory: {
total: 2048,
used: 1024,
free: 1024
}
}
]
});
});
// Handle process termination
process.on('SIGINT', () => {
console.log('Stopping mock server...');
mockServer.kill();
});
process.on('SIGTERM', () => {
console.log('Stopping mock server...');
mockServer.kill();
});
console.log(`Mock server started on port ${MOCK_PORT}`);
console.log('Press Ctrl+C to stop');
const PORT = process.env.MOCK_SERVER_PORT || 7655;
app.listen(PORT, () => {
console.log(`Mock server running on port ${PORT}`);
});
-96
View File
@@ -1,96 +0,0 @@
/**
* Test script to check the Proxmox cluster API response
* This will help diagnose why standalone nodes are being detected as part of a cluster
*/
const axios = require('axios');
const https = require('https');
require('dotenv').config();
// Create an axios instance with SSL verification disabled
const createClient = (host, tokenId, tokenSecret) => {
return axios.create({
baseURL: `${host}/api2/json`,
headers: {
'Authorization': `PVEAPIToken=${tokenId}=${tokenSecret}`
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
};
// Test function to check cluster status
const testClusterStatus = async (host, tokenId, tokenSecret, nodeName) => {
try {
console.log(`Testing cluster status for node: ${nodeName} (${host})`);
const client = createClient(host, tokenId, tokenSecret);
// Try to access the cluster status endpoint
const response = await client.get('/cluster/status');
console.log(`Response status: ${response.status}`);
console.log(`Response data: ${JSON.stringify(response.data, null, 2)}`);
// Check if there's a cluster type in the response
if (response.data && response.data.data && Array.isArray(response.data.data)) {
const clusterInfo = response.data.data.find(item => item.type === 'cluster');
if (clusterInfo) {
console.log(`Found cluster info: ${JSON.stringify(clusterInfo, null, 2)}`);
console.log(`This node IS part of a cluster named: ${clusterInfo.name}`);
} else {
console.log('No cluster type found in the response');
console.log('This node is NOT part of a cluster, but the cluster API is available');
}
} else {
console.log('No data array in response');
console.log('This node is NOT part of a cluster');
}
console.log('-----------------------------------');
} catch (error) {
console.error(`Error testing cluster status for ${nodeName}:`, error.message);
if (error.response) {
console.log(`Response status: ${error.response.status}`);
console.log(`Response data: ${JSON.stringify(error.response.data, null, 2)}`);
}
console.log('This node is NOT part of a cluster (error response)');
console.log('-----------------------------------');
}
};
// Main function to test all nodes
const testAllNodes = async () => {
// Node 1
await testClusterStatus(
process.env.PROXMOX_NODE_1_HOST,
process.env.PROXMOX_NODE_1_TOKEN_ID,
process.env.PROXMOX_NODE_1_TOKEN_SECRET,
process.env.PROXMOX_NODE_1_NAME
);
// Node 2
if (process.env.PROXMOX_NODE_2_HOST) {
await testClusterStatus(
process.env.PROXMOX_NODE_2_HOST,
process.env.PROXMOX_NODE_2_TOKEN_ID,
process.env.PROXMOX_NODE_2_TOKEN_SECRET,
process.env.PROXMOX_NODE_2_NAME
);
}
// Node 3
if (process.env.PROXMOX_NODE_3_HOST) {
await testClusterStatus(
process.env.PROXMOX_NODE_3_HOST,
process.env.PROXMOX_NODE_3_TOKEN_ID,
process.env.PROXMOX_NODE_3_TOKEN_SECRET,
process.env.PROXMOX_NODE_3_NAME
);
}
};
// Run the tests
testAllNodes().catch(error => {
console.error('Error running tests:', error);
});
-119
View File
@@ -1,119 +0,0 @@
/**
* Test script to verify our fix for cluster detection
* This script directly tests the fixed isNodeInCluster function
*/
const axios = require('axios');
const https = require('https');
require('dotenv').config();
// Create an axios instance with SSL verification disabled
const createClient = (host, tokenId, tokenSecret) => {
return axios.create({
baseURL: `${host}/api2/json`,
headers: {
'Authorization': `PVEAPIToken=${tokenId}=${tokenSecret}`
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
};
// Our fixed isNodeInCluster function
async function isNodeInCluster(client, nodeName) {
try {
console.log(`Testing cluster detection for node: ${nodeName}`);
// Try to access the cluster status endpoint
const response = await client.get('/cluster/status');
console.log(`Response status: ${response.status}`);
console.log(`Response data: ${JSON.stringify(response.data, null, 2)}`);
if (response.data && response.data.data && Array.isArray(response.data.data)) {
// Only consider it a cluster if we find an item with type: "cluster"
const clusterInfo = response.data.data.find(item => item.type === 'cluster');
console.log(`Cluster info: ${JSON.stringify(clusterInfo, null, 2)}`);
if (clusterInfo && clusterInfo.type === 'cluster') {
const clusterName = clusterInfo.name || 'proxmox-cluster';
console.log(`Node IS part of cluster: ${clusterName}`);
return { isCluster: true, clusterName };
} else {
console.log('Node has cluster API but no cluster type found - NOT part of a cluster');
return { isCluster: false, clusterName: '' };
}
} else {
console.log('Node is not part of a cluster (empty response data)');
return { isCluster: false, clusterName: '' };
}
} catch (error) {
// If we get a 404 error, it means the cluster endpoint doesn't exist, so the node is not part of a cluster
if (error.response && error.response.status === 404) {
console.log('Node is not part of a cluster (404 response from cluster endpoint)');
return { isCluster: false, clusterName: '' };
}
// For other errors, log them but assume the node is not in a cluster
console.error('Error checking if node is in cluster:', error.message);
if (error.response) {
console.log(`Response status: ${error.response.status}`);
console.log(`Response data: ${JSON.stringify(error.response.data, null, 2)}`);
}
return { isCluster: false, clusterName: '' };
}
}
// Test function for a single node
async function testNode(host, tokenId, tokenSecret, nodeName) {
console.log(`\n=== Testing node: ${nodeName} (${host}) ===\n`);
try {
const client = createClient(host, tokenId, tokenSecret);
const result = await isNodeInCluster(client, nodeName);
console.log(`\nFinal result: Node ${nodeName} ${result.isCluster ? 'IS' : 'is NOT'} part of a cluster`);
if (result.isCluster) {
console.log(`Cluster name: ${result.clusterName}`);
}
} catch (error) {
console.error(`Error testing node ${nodeName}:`, error.message);
}
console.log('\n=== Test complete ===\n');
}
// Main function to test all nodes
async function testAllNodes() {
// Node 1
await testNode(
process.env.PROXMOX_NODE_1_HOST,
process.env.PROXMOX_NODE_1_TOKEN_ID,
process.env.PROXMOX_NODE_1_TOKEN_SECRET,
process.env.PROXMOX_NODE_1_NAME
);
// Node 2
if (process.env.PROXMOX_NODE_2_HOST) {
await testNode(
process.env.PROXMOX_NODE_2_HOST,
process.env.PROXMOX_NODE_2_TOKEN_ID,
process.env.PROXMOX_NODE_2_TOKEN_SECRET,
process.env.PROXMOX_NODE_2_NAME
);
}
// Node 3
if (process.env.PROXMOX_NODE_3_HOST) {
await testNode(
process.env.PROXMOX_NODE_3_HOST,
process.env.PROXMOX_NODE_3_TOKEN_ID,
process.env.PROXMOX_NODE_3_TOKEN_SECRET,
process.env.PROXMOX_NODE_3_NAME
);
}
}
// Run the tests
testAllNodes().catch(error => {
console.error('Error running tests:', error);
});
-123
View File
@@ -1,123 +0,0 @@
/**
* Test script to demonstrate mock cluster mode
*
* This script creates mock clients for multiple nodes and shows how
* the VM and container IDs are generated with and without cluster mode.
*
* Run with:
* npx ts-node scripts/test-mock-cluster.ts
*/
import { MockClient } from '../src/api/mock-client';
import { NodeConfig } from '../src/types';
// Create mock node configs
const nodeConfigs: NodeConfig[] = [
{
id: 'node-1',
name: 'pve-1',
host: 'http://localhost:7656',
tokenId: 'mock-token',
tokenSecret: 'mock-secret'
},
{
id: 'node-2',
name: 'pve-2',
host: 'http://localhost:7656',
tokenId: 'mock-token',
tokenSecret: 'mock-secret'
},
{
id: 'node-3',
name: 'pve-3',
host: 'http://localhost:7656',
tokenId: 'mock-token',
tokenSecret: 'mock-secret'
}
];
// Test with cluster mode enabled
async function testWithClusterMode() {
console.log('\n=== Testing with Cluster Mode Enabled ===');
// Set environment variables for cluster mode
process.env.MOCK_CLUSTER_ENABLED = 'true';
process.env.MOCK_CLUSTER_NAME = 'test-cluster';
const clients = nodeConfigs.map(config => new MockClient(config));
// Get VMs and containers from each client
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
const vms = await client.getVMs();
const containers = await client.getContainers();
console.log(`\nNode: ${nodeConfigs[i].name} (${nodeConfigs[i].id})`);
console.log('VMs:');
vms.forEach(vm => {
console.log(` ${vm.id} (VMID: ${vm.vmid}, Name: ${vm.name}, Node: ${vm.node})`);
});
console.log('Containers:');
containers.forEach(container => {
console.log(` ${container.id} (VMID: ${container.vmid}, Name: ${container.name}, Node: ${container.node})`);
});
}
// Clean up
clients.forEach(client => client.disconnect());
}
// Test with cluster mode disabled
async function testWithoutClusterMode() {
console.log('\n=== Testing with Cluster Mode Disabled ===');
// Set environment variables to disable cluster mode
process.env.MOCK_CLUSTER_ENABLED = 'false';
const clients = nodeConfigs.map(config => new MockClient(config));
// Get VMs and containers from each client
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
const vms = await client.getVMs();
const containers = await client.getContainers();
console.log(`\nNode: ${nodeConfigs[i].name} (${nodeConfigs[i].id})`);
console.log('VMs:');
vms.forEach(vm => {
console.log(` ${vm.id} (VMID: ${vm.vmid}, Name: ${vm.name}, Node: ${vm.node})`);
});
console.log('Containers:');
containers.forEach(container => {
console.log(` ${container.id} (VMID: ${container.vmid}, Name: ${container.name}, Node: ${container.node})`);
});
}
// Clean up
clients.forEach(client => client.disconnect());
}
// Run the tests
async function runTests() {
try {
await testWithClusterMode();
await testWithoutClusterMode();
console.log('\n=== Summary ===');
console.log('With cluster mode enabled:');
console.log('- VMs and containers have IDs like: test-cluster-vm-100, test-cluster-ct-200');
console.log('- The same VMID across different nodes will have the same ID');
console.log('- This results in deduplication in the UI');
console.log('\nWith cluster mode disabled:');
console.log('- VMs and containers have IDs like: node-1-vm-100, node-2-vm-100');
console.log('- The same VMID across different nodes will have different IDs');
console.log('- This results in showing all VMs/containers from all nodes in the UI');
} catch (error) {
console.error('Error running tests:', error);
}
}
runTests();
Executable → Regular
+22 -123
View File
@@ -1,134 +1,33 @@
#!/bin/bash
# Pulse for Proxmox VE Screenshot Generator
# Automates the process of generating screenshots for documentation
# by starting required servers with mock data and running the screenshot tool.
# Make script executable if it isn't already
chmod +x "$0"
# Get the project root directory
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
echo "Updating screenshots..."
# Process tracking variables
MOCK_SERVER_PID=""
BACKEND_PID=""
FRONTEND_PID=""
# Cleanup function to ensure all processes are stopped
cleanup() {
echo "🧹 Cleaning up processes..."
if [ -n "$FRONTEND_PID" ]; then
echo "Stopping frontend server (PID: $FRONTEND_PID)..."
kill $FRONTEND_PID 2>/dev/null || true
fi
if [ -n "$BACKEND_PID" ]; then
echo "Stopping backend server (PID: $BACKEND_PID)..."
kill $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$MOCK_SERVER_PID" ]; then
echo "Stopping mock server (PID: $MOCK_SERVER_PID)..."
kill $MOCK_SERVER_PID 2>/dev/null || true
fi
echo "Killing any remaining processes..."
cd "$PROJECT_ROOT" && npm run dev:kill:all 2>/dev/null || true
}
# Set up cleanup trap
trap cleanup EXIT INT TERM
# Ensure clean environment
echo "🔪 Killing any existing servers..."
cd "$PROJECT_ROOT" && npm run dev:kill:all
# Start required services
echo "🚀 Starting mock data server..."
export NODE_ENV=development
export USE_MOCK_DATA=true
export MOCK_DATA_ENABLED=true
# Load environment variables from .env if it exists
if [ -f "$PROJECT_ROOT/.env" ]; then
echo "Loading environment from .env"
set -a
source "$PROJECT_ROOT/.env"
set +a
# Override with mock data settings
export NODE_ENV=development
export USE_MOCK_DATA=true
export MOCK_DATA_ENABLED=true
# Install puppeteer if not already installed
if ! npm list puppeteer >/dev/null 2>&1; then
echo "Installing puppeteer..."
npm install puppeteer
fi
cd "$PROJECT_ROOT" && ts-node src/mock/run-server.ts > /tmp/pulse-mock-server.log 2>&1 &
MOCK_SERVER_PID=$!
echo "Mock server started with PID: $MOCK_SERVER_PID"
sleep 5
# Create screenshots directory if it doesn't exist
mkdir -p docs/screenshots
# Check if mock server is running
if ! ps -p $MOCK_SERVER_PID > /dev/null; then
echo "❌ Error: Mock data server failed to start"
cat /tmp/pulse-mock-server.log
exit 1
fi
# Start the application in development mode with mock data
echo "Starting application with mock data..."
npm run dev:docker &
echo "🚀 Starting backend server..."
cd "$PROJECT_ROOT" && USE_MOCK_DATA=true MOCK_DATA_ENABLED=true npm run dev:server > /tmp/pulse-backend.log 2>&1 &
BACKEND_PID=$!
echo "Backend server started with PID: $BACKEND_PID"
sleep 8
# Wait for the application to start
echo "Waiting for application to start..."
sleep 10
# Check if backend server is running
if ! ps -p $BACKEND_PID > /dev/null; then
echo "❌ Error: Backend server failed to start"
cat /tmp/pulse-backend.log
exit 1
fi
# Take screenshots
echo "Taking screenshots..."
node scripts/take-screenshots.js
echo "🚀 Starting frontend server..."
cd "$PROJECT_ROOT/frontend" && USE_MOCK_DATA=true MOCK_DATA_ENABLED=true npm run dev -- --host "0.0.0.0" --port 7654 > /tmp/pulse-frontend.log 2>&1 &
FRONTEND_PID=$!
echo "Frontend server started with PID: $FRONTEND_PID"
# Stop the application
echo "Stopping application..."
npm run stop
# Wait for services to be ready
echo "⏳ Waiting for servers to start..."
sleep 15
# Verify services are running correctly
if ! curl -s http://localhost:7654 > /dev/null; then
echo "❌ Error: Frontend server is not running on port 7654"
cat /tmp/pulse-frontend.log
exit 1
fi
# Check if mock data server is responding
if ! curl -s http://localhost:7656/status > /dev/null; then
echo "❌ Error: Mock data server is not responding on port 7656"
cat /tmp/pulse-mock-server.log
exit 1
fi
# Check if backend is using mock data
MOCK_STATUS=$(curl -s http://localhost:7654/api/status)
if ! echo "$MOCK_STATUS" | grep -q "mockDataEnabled\":true"; then
echo "❌ Error: Server is running but mock data is not enabled"
echo "Server status: $MOCK_STATUS"
cat /tmp/pulse-backend.log
exit 1
fi
echo "✅ All servers are running with mock data enabled"
# Generate screenshots
echo "📸 Running screenshot tool..."
cd "$PROJECT_ROOT/tools/screenshot-automation" && npm run build && npm start -- --config ../../screenshot-config.json
SCREENSHOT_RESULT=$?
# Report results
if [ $SCREENSHOT_RESULT -eq 0 ]; then
echo "✅ Screenshots updated successfully!"
echo "Check the docs/images directory for the new screenshots."
else
echo "❌ Error: Failed to update screenshots"
exit 1
fi
echo "Screenshot update complete!"