feat: add console link next to VM/LXC names
Clicking the name opens the Proxmox web console in a new tab. URLs are constructed in the API routes using the configured PROXMOX_API_URL. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNodes, getLXC } from '@/lib/proxmox';
|
||||
|
||||
const PROXMOX_API_URL = process.env.PROXMOX_API_URL || 'https://proxmox:8006/api2/json';
|
||||
const PROXMOX_WEBUI = PROXMOX_API_URL.replace('/api2/json', '');
|
||||
|
||||
/** @param {import('next').Request} _req */
|
||||
export async function GET(_req) {
|
||||
try {
|
||||
@@ -10,7 +13,13 @@ export async function GET(_req) {
|
||||
for (const node of nodes) {
|
||||
try {
|
||||
const lxcs = await getLXC(node.name);
|
||||
allLXC.push(...lxcs.map((lxc) => ({ ...lxc, node: node.name })));
|
||||
allLXC.push(
|
||||
...lxcs.map((lxc) => ({
|
||||
...lxc,
|
||||
node: node.name,
|
||||
consoleUrl: `${PROXMOX_WEBUI}/pve/console?node=${encodeURIComponent(node.name)}&vmid=${lxc.vmid}`,
|
||||
})),
|
||||
);
|
||||
} catch { /* skip node */ }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNodes, getVMs } from '@/lib/proxmox';
|
||||
|
||||
const PROXMOX_API_URL = process.env.PROXMOX_API_URL || 'https://proxmox:8006/api2/json';
|
||||
const PROXMOX_WEBUI = PROXMOX_API_URL.replace('/api2/json', '');
|
||||
|
||||
/** @param {import('next').Request} _req */
|
||||
export async function GET(_req) {
|
||||
try {
|
||||
@@ -10,7 +13,13 @@ export async function GET(_req) {
|
||||
for (const node of nodes) {
|
||||
try {
|
||||
const vms = await getVMs(node.name);
|
||||
allVMs.push(...vms.map((vm) => ({ ...vm, node: node.name })));
|
||||
allVMs.push(
|
||||
...vms.map((vm) => ({
|
||||
...vm,
|
||||
node: node.name,
|
||||
consoleUrl: `${PROXMOX_WEBUI}/pve/console?node=${encodeURIComponent(node.name)}&vmid=${vm.vmid}`,
|
||||
})),
|
||||
);
|
||||
} catch { /* skip node */ }
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,21 @@ export default function LXCList({ lxc, onPowerAction }) {
|
||||
{running.map((item) => (
|
||||
<tr key={item.vmid} className="border-b border-border-card last:border-0">
|
||||
<td className="px-4 py-2 text-text-secondary">{item.vmid}</td>
|
||||
<td className="px-4 py-2 font-medium text-text-primary">{item.name}</td>
|
||||
<td className="px-4 py-2">
|
||||
{item.consoleUrl ? (
|
||||
<a
|
||||
href={item.consoleUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-text-primary hover:text-accent"
|
||||
>
|
||||
{item.name}
|
||||
<span className="ml-1 text-[10px] text-text-secondary">[⟶]</span>
|
||||
</a>
|
||||
) : (
|
||||
<span className="font-medium text-text-primary">{item.name}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{item.node}</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{(item.cpu * 100).toFixed(1)}%</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{formatBytes(item.memory?.used || 0)} / {formatBytes(item.memory?.total || 0)}</td>
|
||||
@@ -82,7 +96,21 @@ export default function LXCList({ lxc, onPowerAction }) {
|
||||
{stopped.map((item) => (
|
||||
<tr key={item.vmid} className="border-b border-border-card last:border-0">
|
||||
<td className="px-4 py-2 text-text-secondary">{item.vmid}</td>
|
||||
<td className="px-4 py-2 font-medium text-text-primary">{item.name}</td>
|
||||
<td className="px-4 py-2">
|
||||
{item.consoleUrl ? (
|
||||
<a
|
||||
href={item.consoleUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-text-primary hover:text-accent"
|
||||
>
|
||||
{item.name}
|
||||
<span className="ml-1 text-[10px] text-text-secondary">[⟶]</span>
|
||||
</a>
|
||||
) : (
|
||||
<span className="font-medium text-text-primary">{item.name}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{item.node}</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{formatBytes(item.disk?.used || 0)}</td>
|
||||
<td className="px-4 py-2"><StatusBadge status={item.status} /></td>
|
||||
|
||||
@@ -37,7 +37,21 @@ export default function VMList({ vms, onPowerAction }) {
|
||||
{running.map((vm) => (
|
||||
<tr key={vm.vmid} className="border-b border-border-card last:border-0">
|
||||
<td className="px-4 py-2 text-text-secondary">{vm.vmid}</td>
|
||||
<td className="px-4 py-2 font-medium text-text-primary">{vm.name}</td>
|
||||
<td className="px-4 py-2">
|
||||
{vm.consoleUrl ? (
|
||||
<a
|
||||
href={vm.consoleUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-text-primary hover:text-accent"
|
||||
>
|
||||
{vm.name}
|
||||
<span className="ml-1 text-[10px] text-text-secondary">[⟶]</span>
|
||||
</a>
|
||||
) : (
|
||||
<span className="font-medium text-text-primary">{vm.name}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{vm.node}</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{(vm.cpu * 100).toFixed(1)}%</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{formatBytes(vm.memory?.used || 0)} / {formatBytes(vm.memory?.total || 0)}</td>
|
||||
@@ -83,7 +97,21 @@ export default function VMList({ vms, onPowerAction }) {
|
||||
{stopped.map((vm) => (
|
||||
<tr key={vm.vmid} className="border-b border-border-card last:border-0">
|
||||
<td className="px-4 py-2 text-text-secondary">{vm.vmid}</td>
|
||||
<td className="px-4 py-2 font-medium text-text-primary">{vm.name}</td>
|
||||
<td className="px-4 py-2">
|
||||
{vm.consoleUrl ? (
|
||||
<a
|
||||
href={vm.consoleUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-text-primary hover:text-accent"
|
||||
>
|
||||
{vm.name}
|
||||
<span className="ml-1 text-[10px] text-text-secondary">[⟶]</span>
|
||||
</a>
|
||||
) : (
|
||||
<span className="font-medium text-text-primary">{vm.name}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{vm.node}</td>
|
||||
<td className="px-4 py-2 text-text-secondary">{formatBytes(vm.disk?.used || 0)}</td>
|
||||
<td className="px-4 py-2"><StatusBadge status={vm.status} /></td>
|
||||
|
||||
Reference in New Issue
Block a user