all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH pve-xtermjs 0/3] xterm.js: fix obstructed start button
@ 2026-09-02  7:40 Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 1/3] xterm.js: add tidy make target Dominik Csapak
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Dominik Csapak @ 2026-09-02  7:40 UTC (permalink / raw)
  To: pve-devel

The first two patches are just formatting cleanup to make development
easier when using proxmox-biome as LSP and the third patch is the actual
fix.

Not sure how this was broken, I tested this with old chrome/firefox
versions and our older xterm.js packages and could still reproduce. Then
tested on a PVE 8.4 install and it also happened. (Though with older
xterm.js releases it was not as noticable since the default
background-color changed in the last few releases)

So my current guess is that either something unrelated changed which
has an influence on this (desktop environments, etc.) or this was simply
broken since a long time.

Dominik Csapak (3):
  xterm.js: add tidy make target
  xterm.js: run make tidy
  xterm.js: fix obstructed start button

 xterm.js/Makefile              |   4 +
 xterm.js/src/index.html.hbs.in |   2 +-
 xterm.js/src/index.html.tpl.in |   2 +-
 xterm.js/src/main.js           | 562 ++++++++++++++++++---------------
 xterm.js/src/style.css         | 125 ++++----
 xterm.js/src/util.js           | 158 ++++-----
 6 files changed, 454 insertions(+), 399 deletions(-)

-- 
2.47.3





^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH pve-xtermjs 1/3] xterm.js: add tidy make target
  2026-09-02  7:40 [PATCH pve-xtermjs 0/3] xterm.js: fix obstructed start button Dominik Csapak
@ 2026-09-02  7:40 ` Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 2/3] xterm.js: run make tidy Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 3/3] xterm.js: fix obstructed start button Dominik Csapak
  2 siblings, 0 replies; 4+ messages in thread
From: Dominik Csapak @ 2026-09-02  7:40 UTC (permalink / raw)
  To: pve-devel

to adhere to our javascript/css style

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 xterm.js/Makefile | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/xterm.js/Makefile b/xterm.js/Makefile
index 044246f..fe32c01 100644
--- a/xterm.js/Makefile
+++ b/xterm.js/Makefile
@@ -79,3 +79,7 @@ clean:
 .PHONY: dinstall
 dinstall: deb
 	dpkg -i $(DEB)
+
+.PHONY: tidy
+tidy: src/main.js src/util.js src/style.css
+	proxmox-biome format --fix $?
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH pve-xtermjs 2/3] xterm.js: run make tidy
  2026-09-02  7:40 [PATCH pve-xtermjs 0/3] xterm.js: fix obstructed start button Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 1/3] xterm.js: add tidy make target Dominik Csapak
@ 2026-09-02  7:40 ` Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 3/3] xterm.js: fix obstructed start button Dominik Csapak
  2 siblings, 0 replies; 4+ messages in thread
From: Dominik Csapak @ 2026-09-02  7:40 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 xterm.js/src/main.js   | 538 +++++++++++++++++++++--------------------
 xterm.js/src/style.css | 121 ++++-----
 xterm.js/src/util.js   | 158 ++++++------
 3 files changed, 421 insertions(+), 396 deletions(-)

diff --git a/xterm.js/src/main.js b/xterm.js/src/main.js
index 534e8cf..e9097f1 100644
--- a/xterm.js/src/main.js
+++ b/xterm.js/src/main.js
@@ -1,12 +1,12 @@
 console.log('xtermjs: starting');
 
 var states = {
-    start:         1,
-    connecting:    2,
-    connected:     3,
+    start: 1,
+    connecting: 2,
+    connected: 3,
     disconnecting: 4,
-    disconnected:  5,
-    reconnecting:  6,
+    disconnected: 5,
+    reconnecting: 6,
 };
 
 var term,
@@ -31,90 +31,90 @@ var cmdOpts = getQueryParameter('cmd-opts');
 function getBaseUrl() {
     var baseUrl = '';
     if (remote) {
-	baseUrl += `/${remote_type}/remotes/${remote}`;
+        baseUrl += `/${remote_type}/remotes/${remote}`;
     } else {
-	baseUrl += `/nodes/${nodename}`;
+        baseUrl += `/nodes/${nodename}`;
     }
 
     if (type === 'kvm') {
-	baseUrl += `/qemu/${vmid}`;
+        baseUrl += `/qemu/${vmid}`;
     } else if (type === 'lxc') {
-	baseUrl += `/lxc/${vmid}`;
+        baseUrl += `/lxc/${vmid}`;
     } else if (remote) {
-	baseUrl += `/nodes/${nodename}`;
+        baseUrl += `/nodes/${nodename}`;
     }
 
     return baseUrl;
 }
 
 const baseUrl = getBaseUrl();
-const statusUrlSuffix = (remote ? '/status' : '/status/current');
-const startUrlSuffix = (remote ? '/start' : '/status/start');
+const statusUrlSuffix = remote ? '/status' : '/status/current';
+const startUrlSuffix = remote ? '/start' : '/status/start';
 
 function updateState(newState, msg, code) {
     var timeout, severity, message;
     switch (newState) {
-	case states.connecting:
-	    message = "Connecting...";
-	    timeout = 0;
-	    severity = severities.warning;
-	    break;
-	case states.connected:
-	    window.onbeforeunload = windowUnload;
-	    message = "Connected";
-	    break;
-	case states.disconnecting:
-	    window.onbeforeunload = undefined;
-	    message = "Disconnecting...";
-	    timeout = 0;
-	    severity = severities.warning;
-	    break;
-	case states.reconnecting:
-	    window.onbeforeunload = undefined;
-	    message = "Reconnecting...";
-	    timeout = 0;
-	    severity = severities.warning;
-	    break;
-	case states.disconnected:
-	    window.onbeforeunload = undefined;
-	    switch (state) {
-		case states.start:
-		case states.connecting:
-		case states.reconnecting:
-		    message = "Connection failed";
-		    timeout = 0;
-		    severity = severities.error;
-		    break;
-		case states.connected:
-		case states.disconnecting:
-		    var time_since_started = new Date() - starttime;
-		    timeout = 5000;
-		    if (time_since_started > 5*1000 || type === 'shell') {
-			message = "Connection closed";
-		    } else {
-			message = "Connection failed";
-			severity = severities.error;
-		    }
-		    break;
-		case states.disconnected:
-		    // no state change
-		    break;
-		default:
-		    throw "unknown state";
-	    }
-	    break;
-	default:
-	    throw "unknown state";
+        case states.connecting:
+            message = 'Connecting...';
+            timeout = 0;
+            severity = severities.warning;
+            break;
+        case states.connected:
+            window.onbeforeunload = windowUnload;
+            message = 'Connected';
+            break;
+        case states.disconnecting:
+            window.onbeforeunload = undefined;
+            message = 'Disconnecting...';
+            timeout = 0;
+            severity = severities.warning;
+            break;
+        case states.reconnecting:
+            window.onbeforeunload = undefined;
+            message = 'Reconnecting...';
+            timeout = 0;
+            severity = severities.warning;
+            break;
+        case states.disconnected:
+            window.onbeforeunload = undefined;
+            switch (state) {
+                case states.start:
+                case states.connecting:
+                case states.reconnecting:
+                    message = 'Connection failed';
+                    timeout = 0;
+                    severity = severities.error;
+                    break;
+                case states.connected:
+                case states.disconnecting:
+                    var time_since_started = new Date() - starttime;
+                    timeout = 5000;
+                    if (time_since_started > 5 * 1000 || type === 'shell') {
+                        message = 'Connection closed';
+                    } else {
+                        message = 'Connection failed';
+                        severity = severities.error;
+                    }
+                    break;
+                case states.disconnected:
+                    // no state change
+                    break;
+                default:
+                    throw 'unknown state';
+            }
+            break;
+        default:
+            throw 'unknown state';
     }
     let msgArr = [];
     if (msg) {
-	msgArr.push(msg);
+        msgArr.push(msg);
     }
     if (code !== undefined) {
-	msgArr.push(`Code: ${code}`);
+        msgArr.push(`Code: ${code}`);
     }
     if (msgArr.length > 0) {
-	message += ` (${msgArr.join(', ')})`;
+        message += ` (${msgArr.join(', ')})`;
     }
     state = newState;
     showMsg(message, timeout, severity);
@@ -129,47 +129,56 @@ createTerminal();
 
 function startConnection(params, term) {
     API2Request({
-	method: 'POST',
-	params: params,
-	url: baseUrl + '/termproxy',
-	success: function(result) {
-	    var port = encodeURIComponent(result.data.port);
-	    ticket = result.data.ticket;
-	    socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + baseUrl + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
-
-	    socket = new WebSocket(socketURL, 'binary');
-	    socket.binaryType = 'arraybuffer';
-	    socket.onopen = runTerminal;
-	    socket.onclose = tryReconnect;
-	    socket.onerror = tryReconnect;
-	    updateState(states.connecting);
-	},
-	failure: function(msg) {
-	    updateState(states.disconnected,msg);
-	}
+        method: 'POST',
+        params: params,
+        url: baseUrl + '/termproxy',
+        success: function (result) {
+            var port = encodeURIComponent(result.data.port);
+            ticket = result.data.ticket;
+            socketURL =
+                protocol +
+                location.hostname +
+                (location.port ? ':' + location.port : '') +
+                '/api2/json' +
+                baseUrl +
+                '/vncwebsocket?port=' +
+                port +
+                '&vncticket=' +
+                encodeURIComponent(ticket);
+
+            socket = new WebSocket(socketURL, 'binary');
+            socket.binaryType = 'arraybuffer';
+            socket.onopen = runTerminal;
+            socket.onclose = tryReconnect;
+            socket.onerror = tryReconnect;
+            updateState(states.connecting);
+        },
+        failure: function (msg) {
+            updateState(states.disconnected, msg);
+        },
     });
 }
 
 function startGuest() {
     API2Request({
-	method: 'POST',
-	url: baseUrl + startUrlSuffix,
-	success: function(result) {
-	    showMsg('Guest started successfully', 0);
-	    setTimeout(function() {
-		location.reload();
-	    }, 1000);
-	},
-	failure: function(msg) {
-	    if (msg.match(/already running/)) {
-		showMsg('Guest started successfully', 0);
-		setTimeout(function() {
-		    location.reload();
-		}, 1000);
-	    } else {
-		updateState(states.disconnected,msg);
-	    }
-	}
+        method: 'POST',
+        url: baseUrl + startUrlSuffix,
+        success: function (result) {
+            showMsg('Guest started successfully', 0);
+            setTimeout(function () {
+                location.reload();
+            }, 1000);
+        },
+        failure: function (msg) {
+            if (msg.match(/already running/)) {
+                showMsg('Guest started successfully', 0);
+                setTimeout(function () {
+                    location.reload();
+                }, 1000);
+            } else {
+                updateState(states.disconnected, msg);
+            }
+        },
     });
 }
 
@@ -179,94 +188,97 @@ function createTerminal() {
     term.loadAddon(fitAddon);
     let loadedWebgl = false;
     try {
-	if (detectWebgl()) {
-	    const webglAddon = new WebglAddon.WebglAddon();
-	    term.loadAddon(webglAddon);
-	    loadedWebgl = true;
-	}
-    } catch (_e) { }
+        if (detectWebgl()) {
+            const webglAddon = new WebglAddon.WebglAddon();
+            term.loadAddon(webglAddon);
+            loadedWebgl = true;
+        }
+    } catch (_e) {}
 
     if (!loadedWebgl) {
-	console.warn("webgl-addon loading failed, falling back to regular dom renderer");
+        console.warn('webgl-addon loading failed, falling back to regular dom renderer');
     }
 
     term.onResize(function (size) {
-	if (state === states.connected) {
-	    socket.send("1:" + size.cols + ":" + size.rows + ":");
-	}
+        if (state === states.connected) {
+            socket.send('1:' + size.cols + ':' + size.rows + ':');
+        }
     });
 
-    protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
+    protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
 
     var params = {};
     switch (type) {
-	case 'upgrade':
-	    params.cmd = 'upgrade';
-	    break;
-	case 'cmd':
-	    params.cmd = decodeURI(cmd);
-	    if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
-		params['cmd-opts'] = decodeURI(cmdOpts);
-	    }
-	    break;
+        case 'upgrade':
+            params.cmd = 'upgrade';
+            break;
+        case 'cmd':
+            params.cmd = decodeURI(cmd);
+            if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== '') {
+                params['cmd-opts'] = decodeURI(cmdOpts);
+            }
+            break;
     }
     if (type === 'kvm' || type === 'lxc') {
-	API2Request({
-	    method: 'GET',
-	    url: baseUrl + statusUrlSuffix,
-	    success: function(result) {
-		if (result.data.status === 'running') {
-		    startConnection(params, term);
-		} else {
-		    document.getElementById('connect_dlg').classList.add('pve_open');
-		}
-	    },
-	    failure: function(msg) {
-		updateState(states.disconnected, msg);
-	    },
-	});
+        API2Request({
+            method: 'GET',
+            url: baseUrl + statusUrlSuffix,
+            success: function (result) {
+                if (result.data.status === 'running') {
+                    startConnection(params, term);
+                } else {
+                    document.getElementById('connect_dlg').classList.add('pve_open');
+                }
+            },
+            failure: function (msg) {
+                updateState(states.disconnected, msg);
+            },
+        });
     } else {
-	startConnection(params, term);
+        startConnection(params, term);
     }
 }
 
 function runTerminal() {
-    socket.onmessage = function(event) {
-	var answer = new Uint8Array(event.data);
-	if (state === states.connected) {
-	    term.write(answer);
-	} else if(state === states.connecting) {
-	    if (answer[0] === 79 && answer[1] === 75) { // "OK"
-		updateState(states.connected);
-		term.write(answer.slice(2));
-
-		// delay initial focus and resize to after next frame
-		requestAnimationFrame(() => requestAnimationFrame(() => {
-		    term.focus();
-		    fitAddon.fit();
-		}));
-	    } else {
-		socket.close();
-	    }
-	}
+    socket.onmessage = function (event) {
+        var answer = new Uint8Array(event.data);
+        if (state === states.connected) {
+            term.write(answer);
+        } else if (state === states.connecting) {
+            if (answer[0] === 79 && answer[1] === 75) {
+                // "OK"
+                updateState(states.connected);
+                term.write(answer.slice(2));
+
+                // delay initial focus and resize to after next frame
+                requestAnimationFrame(() =>
+                    requestAnimationFrame(() => {
+                        term.focus();
+                        fitAddon.fit();
+                    }),
+                );
+            } else {
+                socket.close();
+            }
+        }
     };
 
-    term.onData(function(data) {
-	if (state === states.connected) {
-	    socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" +  data);
-	}
+    term.onData(function (data) {
+        if (state === states.connected) {
+            socket.send('0:' + unescape(encodeURIComponent(data)).length.toString() + ':' + data);
+        }
     });
 
-    ping = setInterval(function() {
-	socket.send("2");
-    }, 30*1000);
+    ping = setInterval(function () {
+        socket.send('2');
+    }, 30 * 1000);
 
-    window.addEventListener('resize', function() {
-	clearTimeout(resize);
-	resize = setTimeout(function() {
-	    // done resizing
-	    fitAddon.fit();
-	}, 250);
+    window.addEventListener('resize', function () {
+        clearTimeout(resize);
+        resize = setTimeout(function () {
+            // done resizing
+            fitAddon.fit();
+        }, 250);
     });
 
     // for remote sessions, this line needs to be sent by PDM
@@ -277,111 +289,121 @@ function runTerminal() {
 
 function getLxcStatus(callback) {
     API2Request({
-	method: 'GET',
-	url: baseUrl + statusUrlSuffix,
-	success: function(result) {
-	    if (typeof callback === 'function') {
-		callback(true, result);
-	    }
-	},
-	failure: function(msg) {
-	    if (typeof callback === 'function') {
-		callback(false, msg);
-	    }
-	}
+        method: 'GET',
+        url: baseUrl + statusUrlSuffix,
+        success: function (result) {
+            if (typeof callback === 'function') {
+                callback(true, result);
+            }
+        },
+        failure: function (msg) {
+            if (typeof callback === 'function') {
+                callback(false, msg);
+            }
+        },
     });
 }
 
 function checkMigration() {
     var apitype = type;
     if (apitype === 'kvm') {
-	apitype = 'qemu';
+        apitype = 'qemu';
     }
 
     var url = '/cluster/resources';
     if (remote) {
-	url = `/${remote_type}/remotes/${remote}/resources`;
+        url = `/${remote_type}/remotes/${remote}/resources`;
     }
 
     API2Request({
-	method: 'GET',
-	params: {
-	    type: 'vm'
-	},
-	url: url,
-	success: function(result) {
-	    // if not yet migrated , wait and try again
-	    // if not migrating and stopped, cancel
-	    // if started, connect
-	    result.data.forEach(function(entity) {
-		if (entity.id === (apitype + '/' + vmid)) {
-		    var started = entity.status === 'running';
-		    var migrated = entity.node !== nodename;
-		    if (migrated) {
-			if (started) {
-			    // goto different node
-			    let url = '?console=' + type +
-				'&xtermjs=1&vmid=' + vmid + '&vmname=' +
-				vmname + '&node=' + entity.node;
-			    if (remote) {
-				url += '&remote=' + remote + '&remote-type=' + remote_type;
-			    }
-			    location.href = url;
-			} else {
-			    // wait again
-			    updateState(states.reconnecting, 'waiting for migration to finish...');
-			    setTimeout(checkMigration, 5000);
-			}
-		    } else {
-			if (type === 'lxc') {
-			    // we have to check the status of the
-			    // container to know if it has the
-			    // migration lock
-			    getLxcStatus(function(success, result) {
-				if (success) {
-				    if (result.data.lock === 'migrate') {
-					// still waiting
-					updateState(states.reconnecting, 'waiting for migration to finish...');
-					setTimeout(checkMigration, 5000);
-				    } else if (started) {
-					// container was rebooted
-					location.reload();
-				    } else {
-					stopTerminal();
-				    }
-				} else {
-				    // probably the status call failed because
-				    // the ct is already somewhere else, so retry
-				    setTimeout(checkMigration, 1000);
-				}
-			    });
-			} else if (started) {
-			    // this happens if we have old data in
-			    // /cluster/resources, or the connection
-			    // disconnected, so simply try to reload here
-			    location.reload();
-			} else if (type === 'kvm') {
-			    // it seems the guest simply stopped
-			    stopTerminal();
-			}
-		    }
-
-		    return;
-		}
-	    });
-	},
-	failure: function(msg) {
-	    errorTerminal({msg: msg});
-	}
+        method: 'GET',
+        params: {
+            type: 'vm',
+        },
+        url: url,
+        success: function (result) {
+            // if not yet migrated , wait and try again
+            // if not migrating and stopped, cancel
+            // if started, connect
+            result.data.forEach(function (entity) {
+                if (entity.id === apitype + '/' + vmid) {
+                    var started = entity.status === 'running';
+                    var migrated = entity.node !== nodename;
+                    if (migrated) {
+                        if (started) {
+                            // goto different node
+                            let url =
+                                '?console=' +
+                                type +
+                                '&xtermjs=1&vmid=' +
+                                vmid +
+                                '&vmname=' +
+                                vmname +
+                                '&node=' +
+                                entity.node;
+                            if (remote) {
+                                url += '&remote=' + remote + '&remote-type=' + remote_type;
+                            }
+                            location.href = url;
+                        } else {
+                            // wait again
+                            updateState(states.reconnecting, 'waiting for migration to finish...');
+                            setTimeout(checkMigration, 5000);
+                        }
+                    } else {
+                        if (type === 'lxc') {
+                            // we have to check the status of the
+                            // container to know if it has the
+                            // migration lock
+                            getLxcStatus(function (success, result) {
+                                if (success) {
+                                    if (result.data.lock === 'migrate') {
+                                        // still waiting
+                                        updateState(
+                                            states.reconnecting,
+                                            'waiting for migration to finish...',
+                                        );
+                                        setTimeout(checkMigration, 5000);
+                                    } else if (started) {
+                                        // container was rebooted
+                                        location.reload();
+                                    } else {
+                                        stopTerminal();
+                                    }
+                                } else {
+                                    // probably the status call failed because
+                                    // the ct is already somewhere else, so retry
+                                    setTimeout(checkMigration, 1000);
+                                }
+                            });
+                        } else if (started) {
+                            // this happens if we have old data in
+                            // /cluster/resources, or the connection
+                            // disconnected, so simply try to reload here
+                            location.reload();
+                        } else if (type === 'kvm') {
+                            // it seems the guest simply stopped
+                            stopTerminal();
+                        }
+                    }
+
+                    return;
+                }
+            });
+        },
+        failure: function (msg) {
+            errorTerminal({ msg: msg });
+        },
     });
 }
 
 function tryReconnect(event) {
     var time_since_started = new Date() - starttime;
     var type = getQueryParameter('console');
-    if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
-	stopTerminal(event);
-	return;
+    if (time_since_started < 5 * 1000 || type === 'shell' || type === 'cmd') {
+        // 5 seconds
+        stopTerminal(event);
+        return;
     }
 
     updateState(states.disconnecting, 'Detecting migration...');
@@ -394,11 +416,11 @@ function clearEvents() {
 }
 
 function windowUnload(e) {
-    let message = "Are you sure you want to leave this page?";
+    let message = 'Are you sure you want to leave this page?';
 
     e = e || window.event;
     if (e) {
-	e.returnValue = message;
+        e.returnValue = message;
     }
 
     return message;
@@ -428,7 +450,7 @@ function errorTerminal(event) {
 //
 // firefox will fail on the getContext anyway if there is not hardware support
 function detectWebgl() {
-    const canvas = document.createElement("canvas");
-    const gl = canvas.getContext("webgl2", { failIfMajorPerformanceCaveat: true });
+    const canvas = document.createElement('canvas');
+    const gl = canvas.getContext('webgl2', { failIfMajorPerformanceCaveat: true });
     return !!gl;
 }
diff --git a/xterm.js/src/style.css b/xterm.js/src/style.css
index 04db7d5..7962ae3 100644
--- a/xterm.js/src/style.css
+++ b/xterm.js/src/style.css
@@ -1,10 +1,11 @@
-html,body {
+html,
+body {
     height: 100%;
     min-height: 100%;
     margin: 0;
     padding: 0;
     overflow: hidden;
-    font-family: Consolas,"DejaVu Sans Mono","Liberation Mono",Courier,monospace;
+    font-family: Consolas, "DejaVu Sans Mono", "Liberation Mono", Courier, monospace;
     background-color: #101010;
 }
 
@@ -12,7 +13,7 @@ html,body {
     background-color: #101010;
     color: #f0f0f0;
     font-size: 10pt;
-    font-family: Consolas,"DejaVu Sans Mono","Liberation Mono",Courier,monospace;
+    font-family: Consolas, "DejaVu Sans Mono", "Liberation Mono", Courier, monospace;
     font-variant-ligatures: none;
     -moz-osx-font-smoothing: grayscale;
     -webkit-font-smoothing: antialiased;
@@ -43,45 +44,45 @@ html,body {
 }
 
 #status_bar {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  z-index: 500;
-  transform: translateY(-100%);
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 500;
+    transform: translateY(-100%);
 
-  transition: 0.25s ease-in-out;
+    transition: 0.25s ease-in-out;
 
-  visibility: hidden;
-  opacity: 0;
+    visibility: hidden;
+    opacity: 0;
 
-  padding: 5px;
+    padding: 5px;
 
-  display: flex;
-  flex-direction: row;
-  justify-content: center;
-  align-content: center;
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-content: center;
 
-  line-height: 25px;
-  color: #fff;
+    line-height: 25px;
+    color: #fff;
 
-  border-bottom: 1px solid rgba(0, 0, 0, 0.9);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.9);
 }
 
 #status_bar.open {
-  transform: translateY(0);
-  visibility: visible;
-  opacity: 1;
+    transform: translateY(0);
+    visibility: visible;
+    opacity: 1;
 }
 
 #status_bar.normal {
-  background: rgba(128,128,128,0.9);
+    background: rgba(128, 128, 128, 0.9);
 }
 #status_bar.error {
-  background: rgba(200,55,55,0.9);
+    background: rgba(200, 55, 55, 0.9);
 }
 #status_bar.warning {
-  background: rgba(180,180,30,0.9);
+    background: rgba(180, 180, 30, 0.9);
 }
 
 #pve_start_info {
@@ -92,53 +93,53 @@ html,body {
 }
 
 #connect_dlg {
-  transition: 0.2s ease-in-out;
+    transition: 0.2s ease-in-out;
 
-  transform: scale(0, 0);
-  visibility: hidden;
-  opacity: 0;
-  font-family: Helvetica;
+    transform: scale(0, 0);
+    visibility: hidden;
+    opacity: 0;
+    font-family: Helvetica;
 }
 
 #connect_dlg.pve_open {
-  transform: scale(1, 1);
-  visibility: visible;
-  opacity: 1;
+    transform: scale(1, 1);
+    visibility: visible;
+    opacity: 1;
 }
 
 #connect_btn {
-  cursor: pointer;
-  padding: 6px;
-  color: white;
-  background:#4c4c4c;;
-  border-radius: 8px;
-  text-align: center;
-  font-size: 20px;
-  box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.5);
+    cursor: pointer;
+    padding: 6px;
+    color: white;
+    background: #4c4c4c;
+    border-radius: 8px;
+    text-align: center;
+    font-size: 20px;
+    box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.5);
 }
 #connect_btn div {
-  margin: 2px;
-  padding: 5px 30px;
-  border: 1px solid #2f2f2f;
-  border-bottom-width: 2px;
-  border-radius: 5px;
-  background:#4c4c4c;;
+    margin: 2px;
+    padding: 5px 30px;
+    border: 1px solid #2f2f2f;
+    border-bottom-width: 2px;
+    border-radius: 5px;
+    background: #4c4c4c;
 
-  /* This avoids it jumping around when :active */
-  vertical-align: middle;
+    /* This avoids it jumping around when :active */
+    vertical-align: middle;
 }
 #connect_btn div:active {
-  border-bottom-width: 1px;
-  margin-top: 3px;
+    border-bottom-width: 1px;
+    margin-top: 3px;
 }
 
 div.center {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
 }
diff --git a/xterm.js/src/util.js b/xterm.js/src/util.js
index 464af2f..8c290d0 100644
--- a/xterm.js/src/util.js
+++ b/xterm.js/src/util.js
@@ -1,12 +1,14 @@
 function urlEncode(object) {
-    var i,value, params = [];
+    var i,
+        value,
+        params = [];
 
     for (i in object) {
-	if (object.hasOwnProperty(i)) {
-	    value = object[i];
-	    if (value === undefined) value = '';
-	    params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value)));
-	}
+        if (object.hasOwnProperty(i)) {
+            value = object[i];
+            if (value === undefined) value = '';
+            params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value)));
+        }
     }
 
     return params.join('&');
@@ -14,9 +16,9 @@ function urlEncode(object) {
 
 var msgtimeout;
 var severities = {
-    normal:  1,
+    normal: 1,
     warning: 2,
-    error:   3,
+    error: 3,
 };
 
 function showMsg(message, timeout, severity) {
@@ -32,23 +34,23 @@ function showMsg(message, timeout, severity) {
     severity = severity || severities.normal;
 
     switch (severity) {
-	case severities.normal: 
-	    status_bar.classList.add('normal');
-	    break;
-	case severities.warning: 
-	    status_bar.classList.add('warning');
-	    break;
-	case severities.error: 
-	    status_bar.classList.add('error');
-	    break;
-	default:
-	    throw "unknown severity";
+        case severities.normal:
+            status_bar.classList.add('normal');
+            break;
+        case severities.warning:
+            status_bar.classList.add('warning');
+            break;
+        case severities.error:
+            status_bar.classList.add('error');
+            break;
+        default:
+            throw 'unknown severity';
     }
 
     status_bar.classList.add('open');
 
     if (timeout !== 0) {
-	msgtimeout = setTimeout(hideMsg, timeout || 1500);
+        msgtimeout = setTimeout(hideMsg, timeout || 1500);
     }
 }
 
@@ -59,12 +61,12 @@ function hideMsg() {
 
 function getQueryParameter(name) {
     var params = location.search.slice(1).split('&');
-    var result = "";
-    params.forEach(function(param) {
-	var components = param.split('=');
-	if (components[0] === name) {
-	    result = components.slice(1).join('=');
-	}
+    var result = '';
+    params.forEach(function (param) {
+        var components = param.split('=');
+        if (components[0] === name) {
+            result = components.slice(1).join('=');
+        }
     });
     return result;
 }
@@ -76,56 +78,56 @@ function API2Request(reqOpts) {
 
     var xhr = new XMLHttpRequest();
 
-    xhr.onload = function() {
-	var scope = reqOpts.scope || this;
-	var result;
-	var errmsg;
-
-	if (xhr.readyState === 4) {
-	    var ctype = xhr.getResponseHeader('Content-Type');
-	    if (xhr.status === 200) {
-		if (ctype.match(/application\/json;/)) {
-		    result = JSON.parse(xhr.responseText);
-		} else {
-		    errmsg = 'got unexpected content type ' + ctype;
-		}
-	    } else {
-		errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText;
-	    }
-	} else {
-	    errmsg = 'Connection error - server offline?';
-	}
-
-	if (errmsg !== undefined) {
-	    if (reqOpts.failure) {
-		reqOpts.failure.call(scope, errmsg);
-	    }
-	} else {
-	    if (reqOpts.success) {
-		reqOpts.success.call(scope, result);
-	    }
-	}
-	if (reqOpts.callback) {
-	    reqOpts.callback.call(scope, errmsg === undefined);
-	}
-    }
+    xhr.onload = function () {
+        var scope = reqOpts.scope || this;
+        var result;
+        var errmsg;
+
+        if (xhr.readyState === 4) {
+            var ctype = xhr.getResponseHeader('Content-Type');
+            if (xhr.status === 200) {
+                if (ctype.match(/application\/json;/)) {
+                    result = JSON.parse(xhr.responseText);
+                } else {
+                    errmsg = 'got unexpected content type ' + ctype;
+                }
+            } else {
+                errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText;
+            }
+        } else {
+            errmsg = 'Connection error - server offline?';
+        }
+
+        if (errmsg !== undefined) {
+            if (reqOpts.failure) {
+                reqOpts.failure.call(scope, errmsg);
+            }
+        } else {
+            if (reqOpts.success) {
+                reqOpts.success.call(scope, result);
+            }
+        }
+        if (reqOpts.callback) {
+            reqOpts.callback.call(scope, errmsg === undefined);
+        }
+    };
 
     var data = urlEncode(reqOpts.params || {});
 
     if (reqOpts.method === 'GET') {
-	xhr.open(reqOpts.method, "/api2/json" + reqOpts.url + '?' + data);
+        xhr.open(reqOpts.method, '/api2/json' + reqOpts.url + '?' + data);
     } else {
-	xhr.open(reqOpts.method, "/api2/json" + reqOpts.url);
+        xhr.open(reqOpts.method, '/api2/json' + reqOpts.url);
     }
     xhr.setRequestHeader('Cache-Control', 'no-cache');
     if (reqOpts.method === 'POST' || reqOpts.method === 'PUT') {
-	xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-	xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken);
-	xhr.send(data);
+        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+        xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken);
+        xhr.send(data);
     } else if (reqOpts.method === 'GET') {
-	xhr.send();
+        xhr.send();
     } else {
-	throw "unknown method";
+        throw 'unknown method';
     }
 }
 
@@ -133,18 +135,18 @@ function getTerminalSettings() {
     var res = {};
     var settings = ['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight', 'scrollback'];
     var booleanSettings = ['reflowCursorLine', 'rescaleOverlappingGlyphs'];
-    if(localStorage) {
-	settings.forEach(function(setting) {
-	    var val = localStorage.getItem('pve-xterm-' + setting);
-	    if (val !== undefined && val !== null) {
-		res[setting] = val;
-	    }
-	});
-	booleanSettings.forEach(function(setting) {
-	    if (localStorage.getItem('pve-xterm-' + setting) === 'true') {
-		res[setting] = true;
-	    }
-	});
+    if (localStorage) {
+        settings.forEach(function (setting) {
+            var val = localStorage.getItem('pve-xterm-' + setting);
+            if (val !== undefined && val !== null) {
+                res[setting] = val;
+            }
+        });
+        booleanSettings.forEach(function (setting) {
+            if (localStorage.getItem('pve-xterm-' + setting) === 'true') {
+                res[setting] = true;
+            }
+        });
     }
     return res;
 }
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH pve-xtermjs 3/3] xterm.js: fix obstructed start button
  2026-09-02  7:40 [PATCH pve-xtermjs 0/3] xterm.js: fix obstructed start button Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 1/3] xterm.js: add tidy make target Dominik Csapak
  2026-09-02  7:40 ` [PATCH pve-xtermjs 2/3] xterm.js: run make tidy Dominik Csapak
@ 2026-09-02  7:40 ` Dominik Csapak
  2 siblings, 0 replies; 4+ messages in thread
From: Dominik Csapak @ 2026-09-02  7:40 UTC (permalink / raw)
  To: pve-devel

Depending on the window height, the xterm.js container sometimes
overlapped the 'start now' button in case a guest is not running. This
made it impossible to press that button.

Fix this by adding a 'display: none' CSS style when the terminal is
disconnected.

Reported in the forum:
https://forum.proxmox.com/threads/186055/

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 xterm.js/src/index.html.hbs.in |  2 +-
 xterm.js/src/index.html.tpl.in |  2 +-
 xterm.js/src/main.js           | 26 ++++++++++++++++++++++++--
 xterm.js/src/style.css         |  4 ++++
 4 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/xterm.js/src/index.html.hbs.in b/xterm.js/src/index.html.hbs.in
index 9857e9d..20c5814 100644
--- a/xterm.js/src/index.html.hbs.in
+++ b/xterm.js/src/index.html.hbs.in
@@ -18,7 +18,7 @@
 		<div id="connect_btn"><div> Start Now </div></div>
 	    </div>
 	</div>
-	<div id="terminal-container"></div>
+	<div id="terminal-container" class="disconnected"></div>
 	</div>
 	<script type="text/javascript">
 	    if (typeof(PVE) === 'undefined') PVE = {};
diff --git a/xterm.js/src/index.html.tpl.in b/xterm.js/src/index.html.tpl.in
index 0084158..2d36c0e 100644
--- a/xterm.js/src/index.html.tpl.in
+++ b/xterm.js/src/index.html.tpl.in
@@ -18,7 +18,7 @@
 		<div id="connect_btn"><div> Start Now </div></div>
 	    </div>
 	</div>
-	<div id="terminal-container"></div>
+	<div id="terminal-container" class="disconnected"></div>
 	</div>
 	<script type="text/javascript">
 	    if (typeof(PVE) === 'undefined') PVE = {};
diff --git a/xterm.js/src/main.js b/xterm.js/src/main.js
index e9097f1..66b00fa 100644
--- a/xterm.js/src/main.js
+++ b/xterm.js/src/main.js
@@ -47,36 +47,56 @@ function getBaseUrl() {
     return baseUrl;
 }
 
+function clearClasses(element) {
+    if (!element) {
+        return;
+    }
+
+    element.classList.values().forEach((cls) => element.classList.remove(cls));
+}
+
+function addClass(element, cls) {
+    if (!element || !cls) {
+        return;
+    }
+    element.classList.add(cls);
+}
+
 const baseUrl = getBaseUrl();
 const statusUrlSuffix = remote ? '/status' : '/status/current';
 const startUrlSuffix = remote ? '/start' : '/status/start';
 
 function updateState(newState, msg, code) {
-    var timeout, severity, message;
+    var timeout, severity, message, cls;
     switch (newState) {
         case states.connecting:
+            cls = 'connecting';
             message = 'Connecting...';
             timeout = 0;
             severity = severities.warning;
             break;
         case states.connected:
             window.onbeforeunload = windowUnload;
+            cls = 'connected';
             message = 'Connected';
             break;
         case states.disconnecting:
             window.onbeforeunload = undefined;
+            cls = 'disconnected';
             message = 'Disconnecting...';
             timeout = 0;
             severity = severities.warning;
             break;
         case states.reconnecting:
             window.onbeforeunload = undefined;
+            cls = 'reconnecting';
             message = 'Reconnecting...';
             timeout = 0;
             severity = severities.warning;
             break;
         case states.disconnected:
             window.onbeforeunload = undefined;
+            cls = 'disconnected';
             switch (state) {
                 case states.start:
                 case states.connecting:
@@ -106,6 +126,8 @@ function updateState(newState, msg, code) {
         default:
             throw 'unknown state';
     }
+    clearClasses(terminalContainer);
+    addClass(terminalContainer, cls);
     let msgArr = [];
     if (msg) {
         msgArr.push(msg);
@@ -227,7 +249,7 @@ function createTerminal() {
                 if (result.data.status === 'running') {
                     startConnection(params, term);
                 } else {
-                    document.getElementById('connect_dlg').classList.add('pve_open');
+                    addClass(document.getElementById('connect_dlg'), 'pve_open');
                 }
             },
             failure: function (msg) {
diff --git a/xterm.js/src/style.css b/xterm.js/src/style.css
index 7962ae3..001b4c7 100644
--- a/xterm.js/src/style.css
+++ b/xterm.js/src/style.css
@@ -36,6 +36,10 @@ body {
     width: auto;
 }
 
+#terminal-container.disconnected {
+    display: none;
+}
+
 #wrap {
     height: 100%;
     width: auto;
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-02  7:44 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02  7:40 [PATCH pve-xtermjs 0/3] xterm.js: fix obstructed start button Dominik Csapak
2026-09-02  7:40 ` [PATCH pve-xtermjs 1/3] xterm.js: add tidy make target Dominik Csapak
2026-09-02  7:40 ` [PATCH pve-xtermjs 2/3] xterm.js: run make tidy Dominik Csapak
2026-09-02  7:40 ` [PATCH pve-xtermjs 3/3] xterm.js: fix obstructed start button Dominik Csapak

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal