Support multiple standalone Proxmox hosts with shared credentials
Comma-separated host list logs into every host, merges VM lists tagged by origin host, and routes SPICE connections to the right one. Also adds a username format hint (user@realm) on the login form.
This commit is contained in:
+67
-15
@@ -8,7 +8,10 @@ const ProxmoxClient = require('./proxmox');
|
||||
|
||||
const store = new Store({ name: 'proxmox-spice-client' });
|
||||
let mainWindow;
|
||||
let client; // ProxmoxClient instance (persists across login → VM views)
|
||||
// One ProxmoxClient per logged-in host — several standalone Proxmox hosts can
|
||||
// share the same root@pam credentials without being a real PVE cluster, so we
|
||||
// log into every host the user entered and merge their VM lists.
|
||||
let clients = [];
|
||||
|
||||
// ---------- Window management ----------
|
||||
|
||||
@@ -120,34 +123,83 @@ ipcMain.handle('credentials:clear', () => {
|
||||
// ---------- IPC: Proxmox ----------
|
||||
|
||||
ipcMain.handle('proxmox:login', async (_e, { host, username, password }) => {
|
||||
try {
|
||||
client = new ProxmoxClient(host);
|
||||
await client.login(username, password);
|
||||
createWindow('vms.html', 680, 520, true);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message };
|
||||
const hosts = String(host || '')
|
||||
.split(',')
|
||||
.map((h) => h.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (hosts.length === 0) {
|
||||
return { success: false, error: 'Bitte mindestens einen Host angeben.' };
|
||||
}
|
||||
|
||||
const attempts = await Promise.all(
|
||||
hosts.map(async (h) => {
|
||||
const c = new ProxmoxClient(h);
|
||||
try {
|
||||
await c.login(username, password);
|
||||
return { host: h, client: c, success: true };
|
||||
} catch (err) {
|
||||
return { host: h, success: false, error: err.message };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const succeeded = attempts.filter((a) => a.success);
|
||||
const failed = attempts.filter((a) => !a.success);
|
||||
|
||||
if (succeeded.length === 0) {
|
||||
// Single host → show the real error. Multiple hosts, all failing → the
|
||||
// per-host errors are usually identical (wrong creds), so the first is enough.
|
||||
return { success: false, error: attempts[0].error };
|
||||
}
|
||||
|
||||
clients = succeeded;
|
||||
createWindow('vms.html', 680, 520, true);
|
||||
|
||||
if (failed.length > 0) {
|
||||
setImmediate(() => {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'warning',
|
||||
title: 'Nicht alle Hosts erreichbar',
|
||||
message: `${failed.length} von ${hosts.length} Host(s) konnten nicht angemeldet werden.`,
|
||||
detail: failed.map((f) => `${f.host}: ${f.error}`).join('\n'),
|
||||
buttons: ['OK'],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('proxmox:getVMs', async () => {
|
||||
try {
|
||||
const vms = await client.getSpiceVMs();
|
||||
return { success: true, vms };
|
||||
const perHost = await Promise.all(
|
||||
clients.map(async ({ host, client }) => {
|
||||
try {
|
||||
const vms = await client.getSpiceVMs();
|
||||
return vms.map((vm) => ({ ...vm, host }));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
return { success: true, vms: perHost.flat() };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('proxmox:logout', () => {
|
||||
client = null;
|
||||
clients = [];
|
||||
createWindow('login.html', 460, 500, false);
|
||||
});
|
||||
|
||||
ipcMain.handle('proxmox:connect', async (_e, { node, vmid }) => {
|
||||
ipcMain.handle('proxmox:connect', async (_e, { host, node, vmid }) => {
|
||||
try {
|
||||
const params = await client.getSpiceTicket(node, vmid);
|
||||
const vvPath = buildVVFile(params, vmid);
|
||||
const entry = clients.find((c) => c.host === host) || clients[0];
|
||||
if (!entry) throw new Error('Nicht angemeldet.');
|
||||
const params = await entry.client.getSpiceTicket(node, vmid);
|
||||
const vvPath = buildVVFile(params, vmid, entry.client);
|
||||
launchRemoteViewer(vvPath);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
@@ -179,7 +231,7 @@ ipcMain.handle('settings:browseViewerPath', async () => {
|
||||
|
||||
// ---------- SPICE helpers ----------
|
||||
|
||||
function buildVVFile(params, vmid) {
|
||||
function buildVVFile(params, vmid, client) {
|
||||
const lines = ['[virt-viewer]', `type=${params.type || 'spice'}`];
|
||||
|
||||
const host = params.proxy || params.host || client.host.split(':')[0];
|
||||
|
||||
Reference in New Issue
Block a user