From cc20e77fea90d0beffa86dc96734eedc793d87cd Mon Sep 17 00:00:00 2001 From: "courtmanr@gmail.com" Date: Mon, 21 Apr 2025 10:26:50 +0100 Subject: [PATCH] feat: Improve update stability and fix server dependencies - Refactor backend data fetching to use parallel Promises for nodes - Replace backend setInterval with setTimeout for accurate polling interval - Decouple frontend UI render interval from backend data arrival - Add missing dotenv dependency to server package.json - Update server Express dependency to stable v4 --- public/app.js | 40 +++- server/.env | 2 +- server/index.js | 249 +++++++++++++-------- server/package-lock.json | 457 +++++++++++++++++---------------------- server/package.json | 3 +- 5 files changed, 393 insertions(+), 358 deletions(-) diff --git a/public/app.js b/public/app.js index 9fc04a1f3..16ba42c52 100644 --- a/public/app.js +++ b/public/app.js @@ -132,6 +132,7 @@ document.addEventListener('DOMContentLoaded', function() { const AVERAGING_WINDOW_SIZE = 5; const dashboardHistory = {}; let filterStatus = 'all'; // New state variable for status filter + let initialDataReceived = false; // Flag to control initial rendering // --- WebSocket Connection --- const socket = io(); @@ -759,14 +760,22 @@ document.addEventListener('DOMContentLoaded', function() { vmsData = data.vms || []; containersData = data.containers || []; metricsData = data.metrics || []; - console.log('[socket.on("rawData")] Parsed data'); + console.log('[socket.on("rawData")] Parsed data and updated stores'); - // Update UI tables - updateNodesTable(nodesData); - updateVmsTable(vmsData); - updateContainersTable(containersData); - refreshDashboardData(); // Process and update the main dashboard - console.log('[socket.on("rawData")] Processed data and updated UI'); + // Set flag after first successful data parse + if (!initialDataReceived) { + initialDataReceived = true; + console.log('[socket.on("rawData")] Initial data received, enabling UI updates.'); + // Optional: Trigger immediate first render instead of waiting for interval + // updateAllUITables(); + } + + // --- REMOVED UI update calls from here --- + // updateNodesTable(nodesData); + // updateVmsTable(vmsData); + // updateContainersTable(containersData); + // refreshDashboardData(); + // console.log('[socket.on("rawData")] Processed data and updated UI'); } catch (e) { console.error('Error processing received rawData:', e, jsonData); @@ -843,6 +852,23 @@ document.addEventListener('DOMContentLoaded', function() { updateSortUI('main-table', document.querySelector('#main-table th[data-sort="id"]')); // Data is requested on socket 'connect' event + // --- Frontend Render Interval --- + function updateAllUITables() { + // Update UI tables using the currently stored data + updateNodesTable(nodesData); + updateVmsTable(vmsData); + updateContainersTable(containersData); + refreshDashboardData(); // Process and update the main dashboard + } + + setInterval(() => { + if (initialDataReceived) { + // console.log('[UI Interval] Updating UI tables...'); + updateAllUITables(); + } + }, 2000); // Update UI every 2 seconds + // --- End Frontend Render Interval --- + // --- Fetch and display version --- fetch('/api/version') .then(response => response.json()) diff --git a/server/.env b/server/.env index d4e7f9c2a..bf4f61c49 100644 --- a/server/.env +++ b/server/.env @@ -6,4 +6,4 @@ PROXMOX_ENABLED=true PROXMOX_ALLOW_SELF_SIGNED_CERTS=true PORT=7654 USE_MOCK_DATA=false -VERBOSE=false \ No newline at end of file +VERBOSE=false \ No newline at end of file diff --git a/server/index.js b/server/index.js index 61434083b..413f12765 100644 --- a/server/index.js +++ b/server/index.js @@ -155,9 +155,100 @@ const io = new Server(server, { // let connectedClients = 0; let initialNodesLogged = false; // Flag to log node count only once +// Helper function to fetch data for a single node +async function fetchDataForNode(nodeName) { + const nodeData = { + vms: [], + containers: [], + metrics: [] + }; + + try { + // Fetch VMs + const vmsResponse = await proxmoxApi.get(`/nodes/${nodeName}/qemu`); + if (vmsResponse.data.data && Array.isArray(vmsResponse.data.data)) { + nodeData.vms = vmsResponse.data.data.map(vm => ({ ...vm, node: nodeName })); + + // Collect metrics for running VMs in parallel + const vmMetricPromises = nodeData.vms + .filter(vm => vm.status === 'running') + .map(async (vm) => { + try { + const [rrdData, currentData] = await Promise.all([ + proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/rrddata`, { params: { timeframe: 'hour', cf: 'AVERAGE' } }), + proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/status/current`) + ]); + + let metricData = { + id: vm.vmid, name: vm.name, node: nodeName, type: 'qemu', data: [], + current: currentData?.data?.data || null + }; + if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data; + return metricData; // Return successful metric data + } catch (err) { + console.error(`Failed to get metrics for VM ${vm.vmid} on node ${nodeName}: ${err.message}`); + return null; // Return null on error for this specific VM + } + }); + const vmMetricsResults = await Promise.allSettled(vmMetricPromises); + vmMetricsResults.forEach(result => { + if (result.status === 'fulfilled' && result.value) { + nodeData.metrics.push(result.value); + } + // Optionally log rejected promises if needed: + // else if (result.status === 'rejected') { console.error(...) } + }); + } + } catch (err) { + console.error(`Error fetching VMs from ${nodeName}: ${err.message}`); + // Continue to fetch containers even if VMs fail + } + + try { + // Fetch containers + const ctsResponse = await proxmoxApi.get(`/nodes/${nodeName}/lxc`); + if (ctsResponse.data.data && Array.isArray(ctsResponse.data.data)) { + nodeData.containers = ctsResponse.data.data.map(ct => ({ ...ct, node: nodeName })); + + // Collect metrics for running containers in parallel + const ctMetricPromises = nodeData.containers + .filter(ct => ct.status === 'running') + .map(async (ct) => { + try { + const [rrdData, currentData] = await Promise.all([ + proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/rrddata`, { params: { timeframe: 'hour', cf: 'AVERAGE' } }), + proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/status/current`) + ]); + + let metricData = { + id: ct.vmid, name: ct.name, node: nodeName, type: 'lxc', data: [], + current: currentData?.data?.data || null + }; + if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data; + return metricData; // Return successful metric data + } catch (err) { + console.error(`Failed to get metrics for container ${ct.vmid} on node ${nodeName}: ${err.message}`); + return null; // Return null on error for this specific container + } + }); + + const ctMetricsResults = await Promise.allSettled(ctMetricPromises); + ctMetricsResults.forEach(result => { + if (result.status === 'fulfilled' && result.value) { + nodeData.metrics.push(result.value); + } + // Optionally log rejected promises if needed + }); + } + } catch (err) { + console.error(`Error fetching containers from ${nodeName}: ${err.message}`); + } + + return nodeData; // Return collected data for this node +} + // Helper function to get raw Proxmox data async function fetchRawProxmoxData() { - // const configuredNodeName = proxmoxConfig.node1.name; // No longer needed for API calls const rawData = { nodes: [], vms: [], @@ -166,13 +257,13 @@ async function fetchRawProxmoxData() { }; let nodesToQuery = []; - let discoveredNodeName = null; // To store the node name discovered via /version + let discoveredNodeName = null; try { // Attempt to fetch cluster nodes const nodesResponse = await proxmoxApi.get('/nodes'); nodesToQuery = nodesResponse.data.data || []; - rawData.nodes = nodesToQuery; // Store the nodes list if successful + rawData.nodes = nodesToQuery; if (nodesToQuery.length === 0) { console.warn('Proxmox API returned 0 nodes from /nodes endpoint. Attempting single node discovery.'); @@ -227,97 +318,32 @@ async function fetchRawProxmoxData() { } } - // For each node (either from /nodes or the single discovered node), fetch guests and metrics - for (const node of nodesToQuery) { - const nodeName = node.node; // Use the node name from the list (discovered or from /nodes) - if (!nodeName) { - console.error("Node object missing 'node' property:", node); - continue; // Skip if node name is missing - } - try { - // Fetch VMs - const vmsResponse = await proxmoxApi.get(`/nodes/${nodeName}/qemu`); - if (vmsResponse.data.data && Array.isArray(vmsResponse.data.data)) { - rawData.vms.push(...vmsResponse.data.data.map(vm => ({ - ...vm, - node: nodeName // Ensure node name is correctly assigned - }))); + // --- Start Parallel Fetching --- + const nodeDataPromises = nodesToQuery.map(node => { + const nodeName = node.node; + if (!nodeName) { + console.error("Node object missing 'node' property:", node); + return Promise.resolve(null); // Resolve immediately for invalid node objects + } + return fetchDataForNode(nodeName); + }); - // Collect metrics for running VMs - for (const vm of vmsResponse.data.data.filter(vm => vm.status === 'running')) { - try { - // Get traditional RRD data - const rrdData = await proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/rrddata`, { - params: { timeframe: 'hour', cf: 'AVERAGE' } - }); + const results = await Promise.allSettled(nodeDataPromises); - // Try to get real-time "current" values - const currentData = await proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/status/current`); - - let metricData = { - id: vm.vmid, name: vm.name, node: nodeName, type: 'qemu', data: [], - current: currentData?.data?.data || null - }; - if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data; - rawData.metrics.push(metricData); - - if (rrdData?.data?.data?.length > 1 && DEBUG_METRICS) { - const newest = rrdData.data.data[rrdData.data.data.length - 1].time; - const secondNewest = rrdData.data.data[rrdData.data.data.length - 2].time; - console.log(`VM ${vm.name || vm.vmid} metrics update interval: ${newest - secondNewest} seconds`); - if (currentData?.data?.data) console.log(`VM ${vm.name || vm.vmid} current metrics: CPU=${currentData.data.data.cpu}, Memory=${currentData.data.data.mem}`, `[RRD last update: ${new Date(newest * 1000).toISOString()}]`); - } - } catch (err) { - console.error(`Failed to get metrics for VM ${vm.vmid} on node ${nodeName}: ${err.message}`); - } - } - } - } catch (err) { - console.error(`Error fetching VMs from ${nodeName}: ${err.message}`); - } - - try { - // Fetch containers - const ctsResponse = await proxmoxApi.get(`/nodes/${nodeName}/lxc`); - if (ctsResponse.data.data && Array.isArray(ctsResponse.data.data)) { - rawData.containers.push(...ctsResponse.data.data.map(ct => ({ - ...ct, - node: nodeName // Ensure node name is correctly assigned - }))); - - // Collect metrics for running containers - for (const ct of ctsResponse.data.data.filter(ct => ct.status === 'running')) { - try { - // Get traditional RRD data - const rrdData = await proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/rrddata`, { - params: { timeframe: 'hour', cf: 'AVERAGE' } - }); - - // Try to get real-time "current" values - const currentData = await proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/status/current`); - - let metricData = { - id: ct.vmid, name: ct.name, node: nodeName, type: 'lxc', data: [], - current: currentData?.data?.data || null - }; - if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data; - rawData.metrics.push(metricData); - - if (rrdData?.data?.data?.length > 1 && DEBUG_METRICS) { - const newest = rrdData.data.data[rrdData.data.data.length - 1].time; - const secondNewest = rrdData.data.data[rrdData.data.data.length - 2].time; - console.log(`Container ${ct.name || ct.vmid} metrics update interval: ${newest - secondNewest} seconds`); - if (currentData?.data?.data) console.log(`Container ${ct.name || ct.vmid} current metrics: CPU=${currentData.data.data.cpu}, Memory=${currentData.data.data.mem}`, `[RRD last update: ${new Date(newest * 1000).toISOString()}]`); - } - } catch (err) { - console.error(`Failed to get metrics for container ${ct.vmid} on node ${nodeName}: ${err.message}`); - } - } - } - } catch (err) { - console.error(`Error fetching containers from ${nodeName}: ${err.message}`); - } - } // End loop through nodesToQuery + results.forEach((result, index) => { + const nodeName = nodesToQuery[index]?.node; // Get corresponding node name + if (result.status === 'fulfilled' && result.value) { + // Aggregate successful results + const nodeData = result.value; + rawData.vms.push(...nodeData.vms); + rawData.containers.push(...nodeData.containers); + rawData.metrics.push(...nodeData.metrics); + } else if (result.status === 'rejected') { + console.error(`Failed to fetch data for node ${nodeName || 'UNKNOWN'}: ${result.reason}`); + // Optionally add placeholder data or mark node as errored in rawData.nodes if needed + } + }); + // --- End Parallel Fetching --- // console.log(`Collected raw data: ${rawData.nodes.length} nodes, ${rawData.vms.length} VMs, ${rawData.containers.length} containers, ${rawData.metrics.length} metric sets`); return rawData; @@ -359,7 +385,43 @@ io.on('connection', (socket) => { }); }); -// Periodic update interval for all connected clients +// --- Use recursive setTimeout for reliable interval after async operations --- +let updateTimeoutId = null; // To potentially clear timeout if needed + +async function runUpdateCycle() { + // Clear previous timeout ID if somehow it was still set (belt-and-suspenders) + if (updateTimeoutId) clearTimeout(updateTimeoutId); + updateTimeoutId = null; + + // Only poll if clients are connected + if (io.engine.clientsCount > 0) { + try { + console.log(`[interval] Updating raw data for ${io.engine.clientsCount} client(s)...`); + const data = await fetchRawProxmoxData(); + io.emit('rawData', data); + } catch (error) { + console.error(`[interval] Error during update: ${error.message}`); + } + } else { + // console.log('[interval] No clients connected, skipping Proxmox API poll.'); + } + + // Schedule the next update cycle regardless of errors or client count + // This ensures polling resumes when clients reconnect + scheduleNextUpdate(); +} + +function scheduleNextUpdate() { + // Schedule the next run after UPDATE_INTERVAL milliseconds + updateTimeoutId = setTimeout(runUpdateCycle, UPDATE_INTERVAL); +} + +// Start the first update cycle +scheduleNextUpdate(); +// --- End recursive setTimeout implementation --- + +// Periodic update interval for all connected clients - REMOVED +/* const updateInterval = setInterval(async () => { // Only poll if clients are connected if (io.engine.clientsCount > 0) { @@ -375,6 +437,7 @@ const updateInterval = setInterval(async () => { // console.log('[interval] No clients connected, skipping Proxmox API poll.'); } }, UPDATE_INTERVAL); +*/ // Start the server server.listen(PORT, () => { diff --git a/server/package-lock.json b/server/package-lock.json index 4e8ca9cfb..2c42a34ba 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "axios": "^1.6.0", "cors": "^2.8.5", - "express": "^5.1.0", + "dotenv": "^16.3.1", + "express": "^4.18.2", "socket.io": "^4.7.2" }, "devDependencies": { @@ -54,6 +55,12 @@ "node": ">= 0.6" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -81,23 +88,27 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/bytes": { @@ -167,9 +178,9 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -197,13 +208,10 @@ } }, "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", @@ -219,22 +227,20 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "ms": "2.0.0" } }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -253,6 +259,28 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -398,102 +426,64 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 18" + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -544,12 +534,12 @@ } }, "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/function-bind": { @@ -666,12 +656,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" @@ -692,12 +682,6 @@ "node": ">= 0.10" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -708,26 +692,44 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "engines": { - "node": ">=18" - }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -797,15 +799,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -816,13 +809,10 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -844,12 +834,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -868,14 +858,14 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "engines": { @@ -896,22 +886,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -939,61 +913,51 @@ "license": "MIT" }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">= 18" + "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">= 18" + "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { @@ -1185,35 +1149,13 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" @@ -1234,6 +1176,15 @@ "node": ">= 0.8" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1243,12 +1194,6 @@ "node": ">= 0.8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/server/package.json b/server/package.json index 9a4c24862..7d6fe6984 100644 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,8 @@ "dependencies": { "axios": "^1.6.0", "cors": "^2.8.5", - "express": "^5.1.0", + "dotenv": "^16.3.1", + "express": "^4.18.2", "socket.io": "^4.7.2" }, "devDependencies": {