From 9cfcfd99717032314ac9e611767da9113c73ffae Mon Sep 17 00:00:00 2001 From: Rob Bond Date: Mon, 11 May 2026 13:05:41 +0100 Subject: [PATCH] feat: open console in new tab instead of iframe panel - Remove ConsolePanel and /api/vnc route (iframe session auth won't work cross-origin) - VMList/LXCList console buttons now open Proxmox web UI console in new tab - Use web UI console URL format so browser session cookies handle auth automatically - Add resize=scale and xtermjs=1 for better terminal UX Co-Authored-By: Claude Opus 4.7 --- src/app/api/lxc/route.js | 2 +- src/app/api/vms/route.js | 2 +- src/app/api/vnc/route.js | 45 ---------------------------------- src/components/ConsolePanel.js | 43 -------------------------------- src/components/Dashboard.js | 14 +++++------ 5 files changed, 8 insertions(+), 98 deletions(-) delete mode 100644 src/app/api/vnc/route.js delete mode 100644 src/components/ConsolePanel.js diff --git a/src/app/api/lxc/route.js b/src/app/api/lxc/route.js index d66ecf3..fd65095 100644 --- a/src/app/api/lxc/route.js +++ b/src/app/api/lxc/route.js @@ -17,7 +17,7 @@ export async function GET(_req) { ...lxcs.map((lxc) => ({ ...lxc, node: node.name, - consoleUrl: `${PROXMOX_WEB_URL}/?console=lxc&novnc=1&node=${encodeURIComponent(node.name)}&vmid=${lxc.vmid}&token=${encodeURIComponent(PROXMOX_CONSOLE_TOKEN)}`, + consoleUrl: `${PROXMOX_WEB_URL}/?console=lxc&vmid=${lxc.vmid}&node=${encodeURIComponent(node.name)}&resize=scale&xtermjs=1`, })), ); } catch { /* skip node */ } diff --git a/src/app/api/vms/route.js b/src/app/api/vms/route.js index abac524..8acabb7 100644 --- a/src/app/api/vms/route.js +++ b/src/app/api/vms/route.js @@ -17,7 +17,7 @@ export async function GET(_req) { ...vms.map((vm) => ({ ...vm, node: node.name, - consoleUrl: `${PROXMOX_WEB_URL}/?console=qemu&novnc=1&node=${encodeURIComponent(node.name)}&vmid=${vm.vmid}&token=${encodeURIComponent(PROXMOX_CONSOLE_TOKEN)}`, + consoleUrl: `${PROXMOX_WEB_URL}/?console=qemu&vmid=${vm.vmid}&node=${encodeURIComponent(node.name)}&resize=scale&novnc=1`, })), ); } catch { /* skip node */ } diff --git a/src/app/api/vnc/route.js b/src/app/api/vnc/route.js deleted file mode 100644 index 69a7308..0000000 --- a/src/app/api/vnc/route.js +++ /dev/null @@ -1,45 +0,0 @@ -import { NextResponse } from 'next/server'; -import axios from 'axios'; - -const PROXMOX_API_URL = process.env.PROXMOX_API_URL; -const PROXMOX_WEB_URL = process.env.PROXMOX_WEB_URL || PROXMOX_API_URL?.replace('/api2/json', ''); - -/** @param {import('next').Request} req */ -export async function GET(req) { - const url = new URL(req.url); - const node = url.searchParams.get('node'); - const vmid = url.searchParams.get('vmid'); - const type = url.searchParams.get('type'); - - if (!node || !vmid || !type) { - return NextResponse.json({ error: 'Missing node, vmid, or type' }, { status: 400 }); - } - - try { - const ticketRes = await axios({ - url: `${PROXMOX_API_URL}/nodes/${node}/${type}/${vmid}/vncproxy`, - method: 'POST', - headers: { - Authorization: `PVEAPIToken=${process.env.PROXMOX_TOKEN_ID}=${process.env.PROXMOX_TOKEN_SECRET}`, - 'Content-Type': 'application/json', - }, - data: JSON.stringify({ websocket: 0 }), - }); - - const ticket = ticketRes.data?.ticket; - - if (!ticket) { - return NextResponse.json( - { error: 'No VNC ticket returned from Proxmox API' }, - { status: 502 }, - ); - } - - const consoleUrl = `${PROXMOX_WEB_URL}/?vncticket=${encodeURIComponent(ticket)}&novnc=1&node=${encodeURIComponent(node)}&vmid=${vmid}`; - - return NextResponse.json({ consoleUrl }); - } catch (err) { - const msg = err.response?.data?.message || err.response?.data || err.message; - return NextResponse.json({ error: msg }, { status: 502 }); - } -} diff --git a/src/components/ConsolePanel.js b/src/components/ConsolePanel.js deleted file mode 100644 index d1ece79..0000000 --- a/src/components/ConsolePanel.js +++ /dev/null @@ -1,43 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -/** - * @param {{ vmid: number; name: string; node: string; type: string; consoleUrl: string }} vm - * @param {() => void} onClose - * @returns {import('react').ReactNode} - */ -export default function ConsolePanel({ vm, onClose }) { - const [loading, setLoading] = useState(true); - - return ( -
- {/* Header */} -
-
- Console - - ({vm.type.toUpperCase()} {vm.vmid} on {vm.node}) - -
-
- {loading && Loading…} - -
-
- {/* Frame */} -