diff --git a/package.json b/package.json index 3d400c537..95c48366a 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,6 @@ "build": "tsc", "lint": "eslint . --ext .ts", "test": "echo \"Error: no test specified\" && exit 1", - "test:startup": "ts-node src/scripts/startup-check.ts", - "test:api": "ts-node src/scripts/proxmox-api-test.ts", "dev:kill:backend": "npx kill-port 7654 7655", "dev:kill:frontend": "npx kill-port 9513", "dev:kill:all": "npm run dev:kill:backend && npm run dev:kill:frontend", diff --git a/src/scripts/README.md b/src/scripts/README.md deleted file mode 100644 index f2c6bb5b4..000000000 --- a/src/scripts/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# ProxMox API Test Scripts - -This directory contains scripts for testing the ProxMox API integration. - -## Available Scripts - -### ProxMox API Test (`proxmox-api-test.ts`) - -A comprehensive test script that: - -1. Loads ProxMox API credentials from the `.env` file -2. **Automatically discovers the correct node names** from the API -3. Tests all relevant API endpoints -4. Creates a detailed report showing successful API patterns and response structures - -#### Usage - -```bash -# Run the API test -npm run test:api -``` - -#### Features - -- **Node Name Auto-Discovery**: The script automatically discovers the correct ProxMox node names (like "pve" or "pve2") instead of hardcoding them -- **Comprehensive Testing**: Tests multiple endpoints for each node -- **Detailed Reporting**: Generates a JSON report file in the `reports` directory -- **Response Structure Analysis**: Shows the structure of API responses for reference -- **API Pattern Documentation**: Documents common patterns in the ProxMox API - -#### How It Works - -1. The script first connects to the cluster API (not node-specific) to discover available nodes -2. It uses the `/api2/json/nodes` endpoint to get the actual node names -3. It maps the discovered node names to the configured nodes by IP address -4. It tests all endpoints with the discovered node names -5. It generates a detailed report including: - - Discovered node names - - Successful API patterns - - Sample response structures - - Any errors encountered - - Connection statistics - -#### Environment Variables - -The script uses the following environment variables from your `.env` file: - -``` -# Node 1 -PROXMOX_NODE_1_NAME=Proxmox Node 1 -PROXMOX_NODE_1_HOST=https://192.168.0.132:8006 -PROXMOX_NODE_1_TOKEN_ID=root@pam!pulse -PROXMOX_NODE_1_TOKEN_SECRET=e1850350-6afc-4b8e-ae28-472152af84f9 - -# Node 2 -PROXMOX_NODE_2_NAME=Proxmox Node 2 -PROXMOX_NODE_2_HOST=https://192.168.0.141:8006 -PROXMOX_NODE_2_TOKEN_ID=root@pam!pulse -PROXMOX_NODE_2_TOKEN_SECRET=63e21f63-4cfd-4ba0-9b14-fc681c59d932 -``` - -### Startup Check (`startup-check.ts`) - -A script to check the configuration and connections during application startup. - -```bash -# Run the startup check -npm run test:startup -``` \ No newline at end of file diff --git a/src/scripts/proxmox-api-test-mock.ts b/src/scripts/proxmox-api-test-mock.ts deleted file mode 100644 index 9e6ca6843..000000000 --- a/src/scripts/proxmox-api-test-mock.ts +++ /dev/null @@ -1,411 +0,0 @@ -/** - * ProxMox API Test Mock Generator - * - * This script generates a mock test report with anonymized data - * for documentation and development purposes. - * - * It creates a report file in the reports directory that mimics - * the structure of a real API test but with sanitized data. - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; - -// Define types -interface NodeConfig { - id: string; - name: string; - host: string; - tokenId: string; - tokenSecret: string; -} - -interface DiscoveredNode { - id: string; - name: string; - nodeName: string; - host: string; - ipAddress: string; -} - -interface TestResult { - endpoint: string; - success: boolean; - responseTime: number; - statusCode: number; - data: any; -} - -interface NodeTestResult { - nodeConfig: NodeConfig; - discoveredNode: DiscoveredNode; - testResults: TestResult[]; - overallSuccess: boolean; - totalTests: number; - successfulTests: number; - failedTests: number; - averageResponseTime: number; -} - -interface TestReport { - timestamp: string; - nodes: NodeTestResult[]; - overallSuccess: boolean; - totalTests: number; - successfulTests: number; - failedTests: number; - averageResponseTime: number; -} - -// Generate random data helpers -const randomBetween = (min: number, max: number): number => - Math.floor(Math.random() * (max - min + 1) + min); - -const randomFloat = (min: number, max: number): number => - parseFloat((Math.random() * (max - min) + min).toFixed(2)); - -// Generate mock node status data -function generateNodeStatusData() { - return { - data: { - memory: { - free: randomBetween(8000000000, 12000000000), - total: 16539889664, - used: randomBetween(4000000000, 8000000000) - }, - kversion: "Linux 6.8.12-8-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-8 (2025-01-24T12:32Z)", - pveversion: "pve-manager/8.3.4/65224a0f9cd294a3", - wait: randomFloat(0.0001, 0.001), - rootfs: { - free: randomBetween(20000000000, 30000000000), - total: 33501757440, - avail: randomBetween(20000000000, 25000000000), - used: randomBetween(5000000000, 10000000000) - }, - idle: 0, - cpu: randomFloat(0.01, 0.1), - ksm: { - shared: 0 - }, - loadavg: [ - (randomFloat(0.01, 0.2)).toFixed(2), - (randomFloat(0.1, 0.3)).toFixed(2), - (randomFloat(0.1, 0.3)).toFixed(2) - ], - "current-kernel": { - machine: "x86_64", - sysname: "Linux", - version: "#1 SMP PREEMPT_DYNAMIC PMX 6.8.12-8 (2025-01-24T12:32Z)", - release: "6.8.12-8-pve" - }, - swap: { - used: randomBetween(1000000000, 3000000000), - total: 8589930496, - free: randomBetween(5000000000, 7000000000) - }, - cpuinfo: { - sockets: 1, - user_hz: 100, - cores: 4, - flags: "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a rdseed adx smap clflushopt clwb intel_pt sha_ni xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk avx_vnni dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi umip pku ospke waitpkg gfni vaes vpclmulqdq rdpid movdiri movdir64b fsrm md_clear serialize arch_lbr ibt flush_l1d arch_capabilities", - hvm: "1", - model: "Intel(R) CPU", - mhz: "3200.000", - cpus: 4 - }, - "boot-info": { - secureboot: 0, - mode: "efi" - }, - uptime: randomBetween(1000000, 2000000) - } - }; -} - -// Generate mock container data -function generateContainers() { - const containers = []; - const containerNames = [ - "web-server", "database", "cache", "monitoring", "proxy", - "backup", "app-server", "auth-service", "api-gateway", "logger" - ]; - - for (let i = 0; i < randomBetween(5, 10); i++) { - containers.push({ - netout: randomBetween(1000000, 10000000), - netin: randomBetween(10000000, 100000000), - uptime: randomBetween(1000000, 2000000), - vmid: randomBetween(100, 999), - diskread: randomBetween(10000000, 1000000000), - status: Math.random() > 0.2 ? "running" : "stopped", - mem: randomBetween(50000000, 500000000), - pid: Math.random() > 0.2 ? randomBetween(1000, 20000) : undefined, - type: "lxc", - cpu: randomFloat(0.001, 0.02), - maxmem: randomBetween(500000000, 2000000000), - name: containerNames[i % containerNames.length], - tags: "alpine;community-script;docker", - diskwrite: randomBetween(10000000, 1000000000), - maxswap: 536870912, - swap: randomBetween(5000000, 50000000), - cpus: randomBetween(1, 4), - maxdisk: randomBetween(1000000000, 5000000000), - disk: randomBetween(500000000, 3000000000) - }); - } - - return { data: containers }; -} - -// Generate mock storage data -function generateStorage() { - return { - data: [ - { - total: 0, - content: "backup", - used: 0, - shared: 1, - type: "pbs", - storage: "backup-pbs", - avail: 0, - enabled: 1, - active: 0 - }, - { - total: randomBetween(400000000000, 500000000000), - used_fraction: randomFloat(0.2, 0.4), - content: "images,rootdir", - shared: 0, - used: randomBetween(100000000000, 200000000000), - type: "lvmthin", - avail: randomBetween(300000000000, 400000000000), - storage: "local-lvm", - enabled: 1, - active: 1 - }, - { - type: "dir", - shared: 0, - content: "vztmpl,backup,iso", - used: randomBetween(5000000000, 10000000000), - used_fraction: randomFloat(0.1, 0.3), - total: 33501757440, - active: 1, - enabled: 1, - avail: randomBetween(20000000000, 30000000000), - storage: "local" - } - ] - }; -} - -// Generate mock network data -function generateNetwork() { - return { - data: [ - { - method6: "manual", - families: ["inet"], - iface: "wlan0", - method: "manual", - priority: 6, - type: "unknown" - }, - { - type: "eth", - exists: 1, - families: ["inet"], - priority: 4, - method: "manual", - iface: "eth1", - method6: "manual" - }, - { - netmask: "24", - families: ["inet"], - bridge_fd: "0", - cidr: "10.0.0.1/24", - type: "bridge", - method6: "manual", - active: 1, - address: "10.0.0.1", - iface: "vmbr0", - method: "static", - bridge_stp: "off", - bridge_ports: "eth0", - gateway: "10.0.0.254", - autostart: 1, - priority: 5 - }, - { - families: ["inet"], - exists: 1, - type: "eth", - method6: "manual", - active: 1, - iface: "eth0", - method: "manual", - priority: 3 - } - ] - }; -} - -// Generate mock version data -function generateVersion() { - return { - data: { - version: "8.3.4", - release: "8.3", - repoid: "65224a0f9cd294a3" - } - }; -} - -// Generate a complete mock test report -function generateMockTestReport(): TestReport { - const timestamp = new Date().toISOString(); - - // Create mock node config - const nodeConfig: NodeConfig = { - id: "node-1", - name: "Proxmox Node 1", - host: "https://10.0.0.1:8006", - tokenId: "root@pam!pulse", - tokenSecret: uuidv4() - }; - - // Create mock discovered node - const discoveredNode: DiscoveredNode = { - id: "node-1", - name: "Proxmox Node 1", - nodeName: "pve", - host: "https://10.0.0.1:8006", - ipAddress: "10.0.0.1" - }; - - // Create mock test results - const testResults: TestResult[] = [ - { - endpoint: "/nodes/pve/status", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: generateNodeStatusData() - }, - { - endpoint: "/nodes/pve/qemu", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: { data: [] } - }, - { - endpoint: "/nodes/pve/lxc", - success: true, - responseTime: randomBetween(200, 500), - statusCode: 200, - data: generateContainers() - }, - { - endpoint: "/nodes/pve/storage", - success: true, - responseTime: randomBetween(100, 200), - statusCode: 200, - data: generateStorage() - }, - { - endpoint: "/nodes/pve/network", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: generateNetwork() - }, - { - endpoint: "/nodes/pve/tasks", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: { data: [], total: 0 } - }, - { - endpoint: "/nodes/pve/subscription", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: { - data: { - status: "notfound", - message: "There is no subscription key", - serverid: "MOCK000000000000000000000000000000", - url: "https://www.proxmox.com/en/proxmox-virtual-environment/pricing" - } - } - }, - { - endpoint: "/nodes/pve/version", - success: true, - responseTime: randomBetween(20, 50), - statusCode: 200, - data: generateVersion() - } - ]; - - // Calculate test statistics - const totalTests = testResults.length; - const successfulTests = testResults.filter(test => test.success).length; - const failedTests = totalTests - successfulTests; - const averageResponseTime = Math.floor( - testResults.reduce((sum, test) => sum + test.responseTime, 0) / totalTests - ); - - // Create node test result - const nodeTestResult: NodeTestResult = { - nodeConfig, - discoveredNode, - testResults, - overallSuccess: failedTests === 0, - totalTests, - successfulTests, - failedTests, - averageResponseTime - }; - - // Create complete test report - return { - timestamp, - nodes: [nodeTestResult], - overallSuccess: failedTests === 0, - totalTests, - successfulTests, - failedTests, - averageResponseTime - }; -} - -// Save the test report to a file -function saveTestReport(report: TestReport): void { - const reportDir = path.join(process.cwd(), 'reports'); - if (!fs.existsSync(reportDir)) { - fs.mkdirSync(reportDir, { recursive: true }); - } - - const filename = `proxmox-api-test-${new Date().toISOString().replace(/:/g, '-')}.json`; - const filePath = path.join(reportDir, filename); - - fs.writeFileSync(filePath, JSON.stringify(report, null, 2)); - console.log(`Mock test report saved to ${filePath}`); -} - -// Main function -function main() { - console.log('Generating mock ProxMox API test report...'); - const mockReport = generateMockTestReport(); - saveTestReport(mockReport); - console.log('Done!'); -} - -// Run the script -main(); \ No newline at end of file diff --git a/src/scripts/proxmox-api-test.ts b/src/scripts/proxmox-api-test.ts deleted file mode 100644 index c56d145dc..000000000 --- a/src/scripts/proxmox-api-test.ts +++ /dev/null @@ -1,507 +0,0 @@ -#!/usr/bin/env node - -import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; -import https from 'https'; -import dotenv from 'dotenv'; -import { createLogger } from '../utils/logger'; -import fs from 'fs'; -import path from 'path'; - -// Load environment variables -dotenv.config(); - -// Create logger -const logger = createLogger('ProxmoxApiTest'); - -// Define types -interface NodeConfig { - id: string; - name: string; - host: string; - tokenId: string; - tokenSecret: string; -} - -interface DiscoveredNode { - id: string; - name: string; - nodeName: string; // The actual ProxMox node name (e.g., 'pve', 'pve2') - host: string; - ipAddress: string; -} - -interface TestResult { - endpoint: string; - success: boolean; - responseTime: number; - statusCode?: number; - error?: string; - data?: any; -} - -interface NodeTestReport { - nodeConfig: NodeConfig; - discoveredNode: DiscoveredNode; - testResults: TestResult[]; - overallSuccess: boolean; - totalTests: number; - successfulTests: number; - failedTests: number; - averageResponseTime: number; -} - -interface TestReport { - timestamp: string; - nodes: NodeTestReport[]; - overallSuccess: boolean; - totalTests: number; - successfulTests: number; - failedTests: number; - averageResponseTime: number; -} - -// Parse node configurations from environment variables -function parseNodeConfigs(): NodeConfig[] { - const nodes: NodeConfig[] = []; - const nodePattern = /^PROXMOX_NODE_(\d+)_/; - - // Get all environment variables that match the node pattern - const nodeEnvVars = Object.keys(process.env).filter(key => nodePattern.test(key)); - - // Extract unique node numbers - const nodeNumbers = new Set(); - nodeEnvVars.forEach(key => { - const match = key.match(nodePattern); - if (match && match[1]) { - nodeNumbers.add(match[1]); - } - }); - - // Parse configuration for each node - Array.from(nodeNumbers).forEach(nodeNumber => { - const name = process.env[`PROXMOX_NODE_${nodeNumber}_NAME`]; - const host = process.env[`PROXMOX_NODE_${nodeNumber}_HOST`]; - const tokenId = process.env[`PROXMOX_NODE_${nodeNumber}_TOKEN_ID`]; - const tokenSecret = process.env[`PROXMOX_NODE_${nodeNumber}_TOKEN_SECRET`]; - - if (name && host && tokenId && tokenSecret) { - nodes.push({ - id: `node-${nodeNumber}`, - name, - host, - tokenId, - tokenSecret - }); - } else { - logger.warn(`Incomplete configuration for node ${nodeNumber}, skipping.`); - } - }); - - return nodes; -} - -// Create an Axios client for a node -function createApiClient(nodeConfig: NodeConfig): AxiosInstance { - // Get timeout from environment variable or use default - const apiTimeoutMs = parseInt(process.env.API_TIMEOUT_MS || '10000', 10); - - const axiosConfig: AxiosRequestConfig = { - baseURL: `${nodeConfig.host}/api2/json`, - headers: { - 'Authorization': `PVEAPIToken=${nodeConfig.tokenId}=${nodeConfig.tokenSecret}` - }, - timeout: apiTimeoutMs, - httpsAgent: new https.Agent({ - rejectUnauthorized: false - }) - }; - - return axios.create(axiosConfig); -} - -// Extract IP address from host URL -function extractIpAddress(host: string): string { - try { - const url = new URL(host); - return url.hostname; - } catch (error) { - logger.error('Failed to extract IP address from host', { host, error }); - return host; - } -} - -// Discover node names from the cluster API -async function discoverNodes(nodes: NodeConfig[]): Promise { - logger.info('Discovering ProxMox node names...'); - - const discoveredNodes: DiscoveredNode[] = []; - - // Try each node until we get a successful response - for (const nodeConfig of nodes) { - try { - const client = createApiClient(nodeConfig); - const response = await client.get('/nodes'); - - if (response.data && response.data.data) { - const apiNodes = response.data.data; - logger.info(`Successfully discovered ${apiNodes.length} nodes from the cluster API`, { - source: nodeConfig.host - }); - - // Map discovered nodes to our configured nodes by IP address - const ipAddress = extractIpAddress(nodeConfig.host); - - // Find the matching node in the API response - const matchingNode = apiNodes.find((node: any) => { - // Try to match by IP address if available in the API response - if (node.ip && node.ip === ipAddress) { - return true; - } - - // Otherwise, try to match by node ID or name - return node.id === nodeConfig.id || node.name === nodeConfig.name; - }); - - if (matchingNode) { - discoveredNodes.push({ - id: nodeConfig.id, - name: nodeConfig.name, - nodeName: matchingNode.node, // This is the actual ProxMox node name (e.g., 'pve') - host: nodeConfig.host, - ipAddress - }); - logger.info(`Mapped configured node ${nodeConfig.name} to ProxMox node ${matchingNode.node}`); - } else { - // If we can't find a direct match, try to match by position in the list - // This is a fallback and not ideal - const nodeIndex = nodes.findIndex(n => n.id === nodeConfig.id); - if (nodeIndex >= 0 && nodeIndex < apiNodes.length) { - discoveredNodes.push({ - id: nodeConfig.id, - name: nodeConfig.name, - nodeName: apiNodes[nodeIndex].node, - host: nodeConfig.host, - ipAddress - }); - logger.warn(`Mapped configured node ${nodeConfig.name} to ProxMox node ${apiNodes[nodeIndex].node} by position (fallback)`); - } else { - logger.error(`Could not map configured node ${nodeConfig.name} to any ProxMox node`); - } - } - - // We've successfully discovered nodes, so we can break out of the loop - break; - } - } catch (error) { - logger.warn(`Failed to discover nodes from ${nodeConfig.host}`, { error }); - } - } - - // If we couldn't discover any nodes, try a different approach - if (discoveredNodes.length === 0) { - logger.warn('Could not discover nodes from the cluster API, trying individual node APIs...'); - - for (const nodeConfig of nodes) { - try { - const client = createApiClient(nodeConfig); - const response = await client.get('/nodes'); - - if (response.data && response.data.data) { - const apiNodes = response.data.data; - const ipAddress = extractIpAddress(nodeConfig.host); - - // Find the node that matches our IP address - for (const apiNode of apiNodes) { - try { - // Try to access the node's status endpoint - const statusResponse = await client.get(`/nodes/${apiNode.node}/status`); - if (statusResponse.data && statusResponse.data.data) { - discoveredNodes.push({ - id: nodeConfig.id, - name: nodeConfig.name, - nodeName: apiNode.node, - host: nodeConfig.host, - ipAddress - }); - logger.info(`Discovered node ${apiNode.node} from ${nodeConfig.host}`); - break; - } - } catch (error) { - logger.debug(`Failed to access status for node ${apiNode.node}`, { error }); - } - } - } - } catch (error) { - logger.warn(`Failed to discover nodes from ${nodeConfig.host}`, { error }); - } - } - } - - return discoveredNodes; -} - -// Test a specific API endpoint -async function testEndpoint( - client: AxiosInstance, - endpoint: string, - description: string -): Promise { - logger.info(`Testing endpoint: ${endpoint} (${description})`); - - const startTime = Date.now(); - try { - const response = await client.get(endpoint); - const endTime = Date.now(); - - logger.info(`✅ Successfully tested ${endpoint}`, { - statusCode: response.status, - responseTime: endTime - startTime, - dataSize: JSON.stringify(response.data).length - }); - - return { - endpoint, - success: true, - responseTime: endTime - startTime, - statusCode: response.status, - data: response.data - }; - } catch (error: any) { - const endTime = Date.now(); - - logger.error(`❌ Failed to test ${endpoint}`, { - error: error.message, - responseTime: endTime - startTime, - statusCode: error.response?.status - }); - - return { - endpoint, - success: false, - responseTime: endTime - startTime, - statusCode: error.response?.status, - error: error.message - }; - } -} - -// Test all endpoints for a node -async function testNodeEndpoints( - nodeConfig: NodeConfig, - discoveredNode: DiscoveredNode -): Promise { - logger.info(`Testing endpoints for node ${discoveredNode.name} (${discoveredNode.nodeName})`); - - const client = createApiClient(nodeConfig); - const testResults: TestResult[] = []; - - // Define the endpoints to test - const endpoints = [ - { path: `/nodes/${discoveredNode.nodeName}/status`, description: 'Node status' }, - { path: `/nodes/${discoveredNode.nodeName}/qemu`, description: 'List VMs' }, - { path: `/nodes/${discoveredNode.nodeName}/lxc`, description: 'List containers' }, - { path: `/nodes/${discoveredNode.nodeName}/storage`, description: 'List storage' }, - { path: `/nodes/${discoveredNode.nodeName}/network`, description: 'Network configuration' }, - { path: `/nodes/${discoveredNode.nodeName}/tasks`, description: 'Recent tasks' }, - { path: `/nodes/${discoveredNode.nodeName}/subscription`, description: 'Subscription info' }, - { path: `/nodes/${discoveredNode.nodeName}/version`, description: 'ProxMox version' } - ]; - - // Test each endpoint - for (const endpoint of endpoints) { - const result = await testEndpoint(client, endpoint.path, endpoint.description); - testResults.push(result); - } - - // Calculate statistics - const successfulTests = testResults.filter(result => result.success).length; - const failedTests = testResults.length - successfulTests; - const totalResponseTime = testResults.reduce((total, result) => total + result.responseTime, 0); - const averageResponseTime = totalResponseTime / testResults.length; - - return { - nodeConfig, - discoveredNode, - testResults, - overallSuccess: failedTests === 0, - totalTests: testResults.length, - successfulTests, - failedTests, - averageResponseTime - }; -} - -// Generate a test report -function generateTestReport(nodeReports: NodeTestReport[]): TestReport { - const totalTests = nodeReports.reduce((total, report) => total + report.totalTests, 0); - const successfulTests = nodeReports.reduce((total, report) => total + report.successfulTests, 0); - const failedTests = totalTests - successfulTests; - const totalResponseTime = nodeReports.reduce((total, report) => total + (report.averageResponseTime * report.totalTests), 0); - const averageResponseTime = totalResponseTime / totalTests; - - return { - timestamp: new Date().toISOString(), - nodes: nodeReports, - overallSuccess: failedTests === 0, - totalTests, - successfulTests, - failedTests, - averageResponseTime - }; -} - -// Save the test report to a file -function saveTestReport(report: TestReport): void { - const reportDir = path.join(process.cwd(), 'reports'); - if (!fs.existsSync(reportDir)) { - fs.mkdirSync(reportDir, { recursive: true }); - } - - const filename = `proxmox-api-test-${new Date().toISOString().replace(/:/g, '-')}.json`; - const filePath = path.join(reportDir, filename); - - fs.writeFileSync(filePath, JSON.stringify(report, null, 2)); - logger.info(`Test report saved to ${filePath}`); -} - -// Print a summary of the test report -function printTestReportSummary(report: TestReport): void { - logger.info('=== ProxMox API Test Report Summary ==='); - logger.info(`Timestamp: ${report.timestamp}`); - logger.info(`Overall Success: ${report.overallSuccess ? '✅ Yes' : '❌ No'}`); - logger.info(`Total Tests: ${report.totalTests}`); - logger.info(`Successful Tests: ${report.successfulTests}`); - logger.info(`Failed Tests: ${report.failedTests}`); - logger.info(`Average Response Time: ${report.averageResponseTime.toFixed(2)}ms`); - - logger.info('\nNode Reports:'); - report.nodes.forEach(nodeReport => { - logger.info(`\n Node: ${nodeReport.discoveredNode.name} (${nodeReport.discoveredNode.nodeName})`); - logger.info(` Host: ${nodeReport.discoveredNode.host}`); - logger.info(` Success: ${nodeReport.overallSuccess ? '✅ Yes' : '❌ No'}`); - logger.info(` Tests: ${nodeReport.successfulTests}/${nodeReport.totalTests} successful`); - logger.info(` Average Response Time: ${nodeReport.averageResponseTime.toFixed(2)}ms`); - - if (nodeReport.failedTests > 0) { - logger.info('\n Failed Endpoints:'); - nodeReport.testResults - .filter(result => !result.success) - .forEach(result => { - logger.info(` - ${result.endpoint}: ${result.error}`); - }); - } - }); -} - -// Print API patterns and response structures -function printApiPatterns(report: TestReport): void { - logger.info('\n=== ProxMox API Patterns and Response Structures ==='); - - // Get a successful node report to use as an example - const exampleNodeReport = report.nodes.find(nodeReport => nodeReport.successfulTests > 0); - if (!exampleNodeReport) { - logger.warn('No successful API calls to show patterns for.'); - return; - } - - // Print example response structures for each endpoint - logger.info('\nExample Response Structures:'); - exampleNodeReport.testResults - .filter(result => result.success && result.data) - .forEach(result => { - logger.info(`\nEndpoint: ${result.endpoint}`); - logger.info('Response Structure:'); - - // Print a simplified version of the response structure - const structure = simplifyResponseStructure(result.data); - logger.info(JSON.stringify(structure, null, 2)); - }); - - // Print API patterns - logger.info('\nAPI Patterns:'); - logger.info('1. All endpoints return a data object with the actual response data.'); - logger.info('2. Most list endpoints return an array of objects in the data property.'); - logger.info('3. Status endpoints return a single object with status information.'); - logger.info('4. Error responses include a message property with error details.'); - logger.info('5. Authentication is done via PVEAPIToken in the Authorization header.'); - logger.info('6. Node names must be used in the URL path for node-specific endpoints.'); -} - -// Simplify a response structure for display -function simplifyResponseStructure(data: any): any { - if (Array.isArray(data)) { - return data.length > 0 ? [simplifyResponseStructure(data[0])] : []; - } else if (typeof data === 'object' && data !== null) { - const result: Record = {}; - for (const key in data) { - if (key === 'data' && Array.isArray(data[key]) && data[key].length > 0) { - result[key] = [simplifyResponseStructure(data[key][0])]; - } else if (key === 'data' && typeof data[key] === 'object' && data[key] !== null) { - result[key] = simplifyResponseStructure(data[key]); - } else { - result[key] = typeof data[key]; - } - } - return result; - } else { - return typeof data; - } -} - -// Main function -async function main() { - logger.info('Starting ProxMox API Test'); - - // Parse node configurations - const nodeConfigs = parseNodeConfigs(); - logger.info(`Found ${nodeConfigs.length} node configurations in .env file`); - - if (nodeConfigs.length === 0) { - logger.error('No valid node configurations found. Please check your .env file.'); - process.exit(1); - } - - // Discover node names - const discoveredNodes = await discoverNodes(nodeConfigs); - logger.info(`Discovered ${discoveredNodes.length} node names`); - - if (discoveredNodes.length === 0) { - logger.error('Failed to discover any node names. Please check your ProxMox API configuration.'); - process.exit(1); - } - - // Test endpoints for each node - const nodeReports: NodeTestReport[] = []; - for (const discoveredNode of discoveredNodes) { - const nodeConfig = nodeConfigs.find(config => config.id === discoveredNode.id); - if (nodeConfig) { - const nodeReport = await testNodeEndpoints(nodeConfig, discoveredNode); - nodeReports.push(nodeReport); - } - } - - // Generate and save the test report - const testReport = generateTestReport(nodeReports); - saveTestReport(testReport); - - // Print the test report summary - printTestReportSummary(testReport); - - // Print API patterns and response structures - printApiPatterns(testReport); - - // Exit with appropriate code - if (testReport.overallSuccess) { - logger.info('✅ All tests completed successfully!'); - process.exit(0); - } else { - logger.warn('⚠️ Some tests failed. See report for details.'); - process.exit(1); - } -} - -// Run the main function -main().catch(error => { - logger.error('Unexpected error during API test', { error }); - process.exit(1); -}); \ No newline at end of file diff --git a/src/scripts/startup-check.ts b/src/scripts/startup-check.ts deleted file mode 100644 index d5b4b6a3c..000000000 --- a/src/scripts/startup-check.ts +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env node - -import config from '../config'; -import { validateAndLogHelp } from '../utils/config-validator'; -import { ProxmoxClient } from '../api/proxmox-client'; -import { createLogger } from '../utils/logger'; - -const logger = createLogger('StartupCheck'); - -/** - * Run startup checks for configuration and connections - */ -async function runStartupChecks() { - logger.info('Running startup checks...'); - - // Validate configuration - logger.info('Validating configuration...'); - const isConfigValid = await validateAndLogHelp(config); - - if (!isConfigValid) { - logger.error('❌ Configuration validation failed.'); - return false; - } - - logger.info('✅ Configuration validation passed.'); - - // Test connections to all nodes - logger.info('Testing connections to all ProxMox nodes...'); - const connectionResults = await Promise.all( - config.nodes.map(async (node) => { - const client = new ProxmoxClient(node, config.ignoreSSLErrors); - const connected = await client.testConnection(); - return { node, connected }; - }) - ); - - // Log connection results - const failedConnections = connectionResults.filter(result => !result.connected); - if (failedConnections.length > 0) { - logger.warn(`❌ Failed to connect to ${failedConnections.length} of ${config.nodes.length} nodes:`); - failedConnections.forEach(result => { - logger.warn(` - ${result.node.name} (${result.node.host})`); - }); - - if (failedConnections.length === config.nodes.length) { - logger.error('❌ All connections failed.'); - return false; - } else { - logger.warn(`⚠️ ${failedConnections.length} of ${config.nodes.length} connections failed.`); - return true; - } - } else { - logger.info(`✅ Successfully connected to all ${config.nodes.length} ProxMox nodes.`); - return true; - } -} - -// Export for use in other modules -export { runStartupChecks }; - -// Run directly if this script is executed directly -if (require.main === module) { - runStartupChecks().then(success => { - if (success) { - logger.info('✅ All startup checks passed.'); - process.exit(0); - } else { - logger.error('❌ Some startup checks failed. See logs above for details.'); - process.exit(1); - } - }).catch(error => { - logger.error('Unexpected error during startup checks', { error }); - process.exit(1); - }); -} \ No newline at end of file