initial commit
This commit is contained in:
195
src/lib/proxmox.js
Normal file
195
src/lib/proxmox.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import axios from 'axios';
|
||||
|
||||
function getEnv(key) {
|
||||
const val = process.env[key];
|
||||
if (!val) {
|
||||
throw new Error(`Missing ${key} in environment variables.`);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Proxmox API2 Authorization header.
|
||||
* Format: PVEAPIToken=<user>@<realm>!<token-id>=<secret>
|
||||
* @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_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(/^\/+/, '');
|
||||
const fullUrl = `${base}/${cleanPath}`;
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
try {
|
||||
const res = await axios({
|
||||
url: fullUrl,
|
||||
method,
|
||||
headers,
|
||||
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}`);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ProxmoxNode
|
||||
* @property {string} name
|
||||
* @property {'node'} type
|
||||
* @property {number} cpu
|
||||
* @property {{ total: number; used: number }} memory
|
||||
* @property {string} status
|
||||
* @property {number} uptime
|
||||
* @property {number} load
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches all Proxmox nodes and returns mapped stats.
|
||||
* @returns {Promise<ProxmoxNode[]>}
|
||||
*/
|
||||
export async function getNodes() {
|
||||
const data = await proxmoxFetch('/nodes');
|
||||
return (data.data || []).map((node) => ({
|
||||
name: node.node,
|
||||
type: 'node',
|
||||
cpu: node.cpu ?? 0,
|
||||
memory: {
|
||||
total: node.maxmem ?? 0,
|
||||
used: node.mem ?? 0,
|
||||
},
|
||||
status: node.status,
|
||||
uptime: node.uptime ?? 0,
|
||||
load: node.load ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ProxmoxVM
|
||||
* @property {number} vmid
|
||||
* @property {string} name
|
||||
* @property {string} type
|
||||
* @property {string} status
|
||||
* @property {number} cpu
|
||||
* @property {{ total: number; used: number }} memory
|
||||
* @property {{ total: number; used: number }} disk
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches QEMU VMs for a given node.
|
||||
* @param {string} nodeName
|
||||
* @returns {Promise<ProxmoxVM[]>}
|
||||
*/
|
||||
export async function getVMs(nodeName) {
|
||||
const data = await proxmoxFetch(`/nodes/${nodeName}/qemu`);
|
||||
return (data.data || []).map((vm) => ({
|
||||
vmid: vm.vmid,
|
||||
name: vm.name || `VM ${vm.vmid}`,
|
||||
type: 'qemu',
|
||||
status: vm.status,
|
||||
cpu: vm.cpu ?? 0,
|
||||
memory: {
|
||||
total: vm.maxmem ?? vm.maxmem ?? 0,
|
||||
used: vm.mem ?? 0,
|
||||
},
|
||||
disk: {
|
||||
total: 0,
|
||||
used: vm.disk ?? 0,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches LXC containers for a given node.
|
||||
* @param {string} nodeName
|
||||
* @returns {Promise<ProxmoxVM[]>}
|
||||
*/
|
||||
export async function getLXC(nodeName) {
|
||||
const data = await proxmoxFetch(`/nodes/${nodeName}/lxc`);
|
||||
return (data.data || []).map((vm) => ({
|
||||
vmid: vm.vmid,
|
||||
name: vm.name || `LXC ${vm.vmid}`,
|
||||
type: 'lxc',
|
||||
status: vm.status,
|
||||
cpu: vm.cpu ?? 0,
|
||||
memory: {
|
||||
total: vm.maxmem ?? vm.maxmem ?? 0,
|
||||
used: vm.mem ?? 0,
|
||||
},
|
||||
disk: {
|
||||
total: 0,
|
||||
used: vm.disk ?? 0,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SummaryStat
|
||||
* @property {number} totalNodes
|
||||
* @property {number} runningVMs
|
||||
* @property {number} stoppedVMs
|
||||
* @property {number} totalCPUs
|
||||
* @property {{ total: number; used: number }} totalMemory
|
||||
*/
|
||||
|
||||
/**
|
||||
* Aggregated summary across all nodes.
|
||||
* @returns {Promise<SummaryStat>}
|
||||
*/
|
||||
export async function getSummary() {
|
||||
const nodes = await getNodes();
|
||||
let totalVMs = [];
|
||||
let totalLXC = [];
|
||||
for (const node of nodes) {
|
||||
try {
|
||||
const vms = await getVMs(node.name);
|
||||
totalVMs = totalVMs.concat(vms);
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const lxcs = await getLXC(node.name);
|
||||
totalLXC = totalLXC.concat(lxcs);
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
|
||||
const allVMs = [...totalVMs, ...totalLXC];
|
||||
const runningVMs = allVMs.filter((vm) => vm.status === 'running').length;
|
||||
const stoppedVMs = allVMs.filter((vm) => vm.status !== 'running').length;
|
||||
|
||||
const totalMemTotal = nodes.reduce((s, n) => s + (n.memory?.total || 0), 0);
|
||||
const totalMemUsed = nodes.reduce((s, n) => s + (n.memory?.used || 0), 0);
|
||||
|
||||
return {
|
||||
totalNodes: nodes.length,
|
||||
runningVMs,
|
||||
stoppedVMs,
|
||||
totalCPUs: nodes.reduce((s, n) => s + n.cpu, 0),
|
||||
totalMemory: { total: totalMemTotal, used: totalMemUsed },
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user