diff --git a/src/components/Dashboard.js b/src/components/Dashboard.js index f6d2ae4..3bacdaf 100644 --- a/src/components/Dashboard.js +++ b/src/components/Dashboard.js @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; import NodeList from './NodeList'; import VMList from './VMList'; import LXCList from './LXCList'; @@ -45,6 +45,11 @@ export default function Dashboard() { setPowerActionStatus({ text: `${action.charAt(0).toUpperCase() + action.slice(1)}ing ${type === 'qemu' ? 'VM' : 'LXC'} ${vmid}...`, type: 'pending' }); try { await axios.post('/api/power', { node, vmid, type, action }); + // Immediately refetch all data instead of waiting for polling interval + await mutate('/api/nodes'); + await mutate('/api/vms'); + await mutate('/api/lxc'); + await mutate('/api/status'); setPowerActionStatus({ text: `${action.charAt(0).toUpperCase() + action.slice(1)} successful`, type: 'success' }); } catch (err) { setPowerActionStatus({ text: err.message || `${action.charAt(0).toUpperCase() + action.slice(1)} failed`, type: 'error' }); diff --git a/src/lib/proxmox.js b/src/lib/proxmox.js index 46c4a02..af09823 100644 --- a/src/lib/proxmox.js +++ b/src/lib/proxmox.js @@ -8,33 +8,13 @@ function getEnv(key) { return val; } -/** - * Generates the Proxmox API2 Authorization header. - * Format: PVEAPIToken=@!= - * @param {string} _method - * @param {string} _path - * @param {string} _body - * @returns {{ Authorization: string }} - */ function createAuthHeaders(_method, _path, _body) { - const TOKEN_ID = getEnv('PROXMOX_TOKEN_ID'); // e.g. "root@pam!monitorapp" + const TOKEN_ID = getEnv('PROXMOX_TOKEN_ID'); const TOKEN_SECRET = getEnv('PROXMOX_TOKEN_SECRET'); return { Authorization: `PVEAPIToken=${TOKEN_ID}=${TOKEN_SECRET}` }; } -/** - * Fetches the Proxmox API with authentication. - * @param {string} path - * @param {'GET'|'POST'|'PUT'|'DELETE'} [method='GET'] - * @param {string} [body] - * @returns {Promise<*>} - */ async function proxmoxFetch(path, method = 'GET', body) { - const headers = { - ...createAuthHeaders(method, path, body), - 'Content-Type': 'application/json', - }; - const baseUrl = getEnv('PROXMOX_API_URL'); const base = baseUrl.replace(/\/+$/, ''); const cleanPath = path.replace(/^\/+/, ''); @@ -46,18 +26,47 @@ async function proxmoxFetch(path, method = 'GET', body) { const res = await axios({ url: fullUrl, method, - headers, + headers: { + ...createAuthHeaders(method, path, body), + 'Content-Type': 'application/json', + }, data: body, }); - return res.data; } catch (err) { const status = err.response?.status || 'Unknown Status'; const message = err.response?.data?.message || err.response?.data || err.message; throw new Error(`Proxmox API error ${status}: ${message}`); } +} +/** + * Sends a power action to a VM or LXC. + * Uses /api2/extjs endpoint (different from /api2/json used by other routes). + * @param {string} nodeName + * @param {number} vmid + * @param {'qemu'|'lxc'} type + * @param {'start'|'stop'|'restart'} action + * @returns {Promise} + */ +export async function powerControl(nodeName, vmid, type, action) { + const baseUrl = getEnv('PROXMOX_API_URL'); + const extjsUrl = baseUrl.replace('/api2/json', '/api2/extjs'); + const res = await axios({ + url: `${extjsUrl}/nodes/${nodeName}/${type}/${vmid}/status/${action}`, + method: 'POST', + headers: { + ...createAuthHeaders('POST', '', ''), + 'Content-Type': 'application/json', + }, + data: '{}', + }); + + const data = res.data?.data; + if (res.data?.success === 0 || !data) { + throw new Error(res.data?.message || 'Power operation failed'); + } } /** @@ -71,10 +80,6 @@ async function proxmoxFetch(path, method = 'GET', body) { * @property {number} load */ -/** - * Fetches all Proxmox nodes and returns mapped stats. - * @returns {Promise} - */ export async function getNodes() { const data = await proxmoxFetch('/nodes'); return (data.data || []).map((node) => ({ @@ -102,11 +107,6 @@ export async function getNodes() { * @property {{ total: number; used: number }} disk */ -/** - * Fetches QEMU VMs for a given node. - * @param {string} nodeName - * @returns {Promise} - */ export async function getVMs(nodeName) { const data = await proxmoxFetch(`/nodes/${nodeName}/qemu`); return (data.data || []).map((vm) => ({ @@ -126,11 +126,6 @@ export async function getVMs(nodeName) { })); } -/** - * Fetches LXC containers for a given node. - * @param {string} nodeName - * @returns {Promise} - */ export async function getLXC(nodeName) { const data = await proxmoxFetch(`/nodes/${nodeName}/lxc`); return (data.data || []).map((vm) => ({ @@ -159,26 +154,6 @@ export async function getLXC(nodeName) { * @property {{ total: number; used: number }} totalMemory */ -/** - * Aggregated summary across all nodes. - * @returns {Promise} - */ -/** - * Sends a power action to a VM or LXC. - * @param {string} nodeName - * @param {number} vmid - * @param {'qemu'|'lxc'} type - * @param {'start'|'stop'|'restart'} action - * @returns {Promise} - */ -export async function powerControl(nodeName, vmid, type, action) { - await proxmoxFetch(`/nodes/${nodeName}/${type}/${vmid}/status/${action}`, 'POST'); -} - -/** - * Aggregated summary across all nodes. - * @returns {Promise} - */ export async function getSummary() { const nodes = await getNodes(); let totalVMs = [];