From b1831d7b3e501e9961129a1bd78390bf7f804fd0 Mon Sep 17 00:00:00 2001
From: rcourtman
Date: Wed, 5 Nov 2025 19:06:08 +0000
Subject: [PATCH] Add guest URL support for PVE hosts
Related to discussion #615
Add optional GuestURL field to PVE instances and cluster endpoints,
allowing users to specify a separate guest-accessible URL for web UI
navigation that differs from the internal management URL.
Backend changes:
- Add GuestURL field to PVEInstance and ClusterEndpoint structs
- Add GuestURL field to Node model
- Update cluster auto-discovery to preserve existing GuestURL values
- Update node creation logic to populate GuestURL from config
- Update API handlers to accept and persist GuestURL field
Frontend changes:
- Add GuestURL input field to NodeModal for configuration
- Update NodeGroupHeader and NodeSummaryTable to use GuestURL for navigation
- Add GuestURL to Node and PVENodeConfig TypeScript interfaces
When GuestURL is configured, it will be used for navigation links
instead of the Host URL, allowing users to access PVE hosts through
a reverse proxy or different domain while maintaining internal API
connections.
---
.../src/components/Settings/NodeModal.tsx | 25 +++++++++++++++
.../src/components/shared/NodeGroupHeader.tsx | 2 +-
.../components/shared/NodeSummaryTable.tsx | 2 +-
frontend-modern/src/types/api.ts | 1 +
frontend-modern/src/types/nodes.ts | 2 ++
internal/api/config_handlers.go | 32 +++++++++++++++----
internal/config/config.go | 4 ++-
internal/models/models.go | 3 +-
internal/monitoring/monitor.go | 5 +++
9 files changed, 66 insertions(+), 10 deletions(-)
diff --git a/frontend-modern/src/components/Settings/NodeModal.tsx b/frontend-modern/src/components/Settings/NodeModal.tsx
index 725ea39..29a0923 100644
--- a/frontend-modern/src/components/Settings/NodeModal.tsx
+++ b/frontend-modern/src/components/Settings/NodeModal.tsx
@@ -65,6 +65,7 @@ export const NodeModal: Component = (props) => {
const getCleanFormData = (nodeType: 'pve' | 'pbs' | 'pmg' = props.nodeType) => ({
name: '',
host: '',
+ guestURL: '',
authType: nodeType === 'pmg' ? 'password' : ('token' as 'password' | 'token'),
setupMode: 'auto' as 'auto' | 'manual',
user: '',
@@ -172,6 +173,7 @@ export const NodeModal: Component = (props) => {
setFormData({
name: node.name || '',
host: node.host || '',
+ guestURL: ('guestURL' in node ? node.guestURL : '') || '',
authType: node.hasPassword ? 'password' : 'token',
setupMode: 'auto',
user: username,
@@ -211,6 +213,7 @@ export const NodeModal: Component = (props) => {
type: props.nodeType,
name: normalizedName,
host: data.host,
+ guestURL: data.guestURL,
fingerprint: data.fingerprint,
verifySSL: data.verifySSL,
};
@@ -489,6 +492,28 @@ export const NodeModal: Component = (props) => {
+
+
diff --git a/frontend-modern/src/components/shared/NodeGroupHeader.tsx b/frontend-modern/src/components/shared/NodeGroupHeader.tsx
index 5777f0f..ae1d6f9 100644
--- a/frontend-modern/src/components/shared/NodeGroupHeader.tsx
+++ b/frontend-modern/src/components/shared/NodeGroupHeader.tsx
@@ -9,7 +9,7 @@ interface NodeGroupHeaderProps {
export const NodeGroupHeader: Component = (props) => {
const isOnline = () => props.node.status === 'online' && (props.node.uptime || 0) > 0;
- const nodeUrl = () => props.node.host || `https://${props.node.name}:8006`;
+ const nodeUrl = () => props.node.guestURL || props.node.host || `https://${props.node.name}:8006`;
const displayName = () => getNodeDisplayName(props.node);
const showActualName = () => hasAlternateDisplayName(props.node);
diff --git a/frontend-modern/src/components/shared/NodeSummaryTable.tsx b/frontend-modern/src/components/shared/NodeSummaryTable.tsx
index c9bfc96..dd9534f 100644
--- a/frontend-modern/src/components/shared/NodeSummaryTable.tsx
+++ b/frontend-modern/src/components/shared/NodeSummaryTable.tsx
@@ -509,7 +509,7 @@ export const NodeSummaryTable: Component = (props) => {
0 {
for _, ep := range instanceCfg.ClusterEndpoints {
if strings.EqualFold(ep.NodeName, node.Node) {
if effective := clusterEndpointEffectiveURL(ep); effective != "" {
connectionHost = effective
}
+ if ep.GuestURL != "" {
+ guestURL = ep.GuestURL
+ }
break
}
}
@@ -4939,6 +4943,7 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie
DisplayName: displayName,
Instance: instanceName,
Host: connectionHost,
+ GuestURL: guestURL,
Status: effectiveStatus,
Type: "node",
CPU: safeFloat(node.CPU), // Already in percentage