feat: redesign the VNC console (#1233)

* chore: bump @rancher/shell to 3.0.15-rc.1 for the Rc components

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: rebuild the VNC console toolbar with Rc components

Name and namespace, a connection status pill with its own menu, Rc tertiary buttons with the design's icons, and the keyboard hint under the screen. noVNC's grey backdrop is gone and the screen keeps a thin frame that turns into the keyboard-focus ring.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: move VNC shortcut keys into one Rc dropdown menu

Saved shortcuts, Ctrl and Alt toggles that apply to the next key, Print Screen, F1 to F12 and the way into custom shortcuts now share the Shortcut keys menu, replacing the cascading popover and the separate custom keys menu. Saved shortcuts can carry a name; the old list format still reads. Focus goes back to the screen after a key is sent.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: replace the VNC settings popover with the View options panel

Automatic or Manual scaling on the shell ButtonGroup, a zoom slider with step buttons, JPEG quality and compression with their trade-off spelled out, and Reset to defaults. The screen now scales to fit by default, inside its focus ring. View only is gone with the old panel.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: confirm Ctrl+Alt+Del and Soft reboot in an RcModal

Small RcModal with the VM's name in the title and a destructive primary button, coloured the way the shell's own dialogs do it. Esc and the background now close it too, and the keyboard goes back to the screen afterwards.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: rebuild VNC custom shortcuts as an RcModal with a key recorder

Record a combination by pressing it, name it, and see the saved ones as RcTags with a delete button each. Every change is saved straight away. Keys keep the format older versions read; names live in their own preference so those versions keep working.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: show VNC connecting and connection failures as console overlays

A spinner with the VM's name while connecting (and the attempt count while retrying), and a card with an icon badge, title, explanation and Rc buttons when the console cannot be used: in use, unavailable, connection lost or failed. Take over confirms in an RcModal.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: float a compact VNC bar over the screen in full screen

Name, connection status and Exit full screen in a floating bar instead of the toolbar, with the keyboard on the screen. Take over leaves full screen first, since its modal opens outside the console.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: VNC console review fixes

Show a not-running state instead of connecting forever when the VM has no instance, and find the VM by its uid so the name shows. Keep a failed shortcut save from looking saved. Offer Take over only when another session holds the console, and Reconnect now while retrying. Wrap the toolbar on narrow windows, keep the screen black so overlays read in the light theme, keep the destructive button red on focus, announce only the status label, let Esc and Tab leave the key recorder, release held modifiers when the menu closes, and let Ctrl+T and Ctrl+N through in full screen.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: use the redrawn VNC console icons

Power, maximize, minimize and wifi-off on the design's 24px grid, the 20px connecting spinner, and a status card badge that stays dark on the black screen. The screen is only black while there is no live picture.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: run standalone Harvester Prime with the SUSE brand

Standalone Harvester reports ui-brand=harvester, which has no stylesheet, so Prime only got a green primary on top of the default palette: 33 of the 36 SUSE theme tokens differed from Rancher Prime (blue tertiary buttons, another green, dark text on primary buttons, no SUSE font). On the Prime flavor the brand is now switched to suse in this browser, and all 36 match Rancher Prime in light and dark.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: name recorded VNC shortcut letters after the key pressed

macOS Option turns letters into symbols (Option+G gives ©), so a recorded Alt+Cmd+G was labelled and sent as ©. Letter keys now take their name from the key code when recording, labelling and sending, which also fixes shortcuts saved before.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: give the VNC shortcut menu chips the secondary button colours

The key chips, held modifiers and the manage item used the link colour, so they stayed blue under the SUSE brand. They now take RcButton's secondary, active and tertiary tokens and follow the brand like the toolbar buttons.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: VNC console review fixes, round three

Wait for the instance to run before connecting and retry refusals right after a start, keep Reconnect now counting retries, stop key sending once the VM is down, only rename option-mangled letters (Alt without Ctrl, dead keys) so other layouts keep their keys, give the keyboard back to View when its panel closes, add a shortcut with Enter, keep long shortcut names to two lines, only replace Harvester's own brand and refresh the favicon, and stop relying on stylesheet order for the spinner and danger button.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: change the VNC menu keys with the held modifiers

The keys under Hold modifiers follow what is held, so the menu sends what the old cascading menu did: Ctrl+N/T/W/Y with Ctrl, Ctrl+Alt+Del with Ctrl and Alt (through the same confirmation as its button), and Print Screen and F1-F12 alone or with Alt. Ctrl+Alt+F1-F12 stays for switching Linux consoles. The recorder points to the menu for browser-reserved combinations.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: bring View only back to the VNC View menu

The View menu has a View only switch again, as the old settings panel did: the screen stays live but no mouse or keyboard input reaches the VM, the status reads Connected (view only), the key menus are disabled and the hint says how to turn it off. The design does not have it yet.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: move View only into the VNC connection menu

View only is how this session uses the connection, so it sits in the Connected menu (also in full screen) as a checkbox item, and the View menu is only about how the screen is shown.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: keep the VNC console's controls usable around the connection

Full screen goes over the whole page and its bar gets Shortcut keys and Ctrl+Alt+Del, so menus and the confirmation show there too and Take over no longer has to leave full screen. Soft reboot works whatever the console's state, since it does not go through it. The Shortcut keys menu always opens: while keys cannot be sent it says why and disables only the keys, so custom shortcuts can still be managed. The focus ring now sits 2px off the screen so it shows against a dark framebuffer edge in the light theme.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: disable the VNC console's input controls while View only is on

View only sends nothing to the VM, so Shortcut keys (managing shortcuts included), Ctrl+Alt+Del and Soft reboot are disabled instead of the menu opening to explain why. The menu still opens while disconnected, as there are shortcuts to manage. The hint under the screen takes the new wording, read raw since t() would escape its quotes.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: fill the window with the VNC console in full screen

The screen takes the whole window, with no padding, ring or inset, and black around a picture of another shape. It always fits in full screen, whatever View mode is saved, which comes back on leaving. The bar floats over the screen, slightly transparent until it is hovered, holds the keyboard focus or has a menu open, and no longer has Ctrl+Alt+Del, which stays in the Shortcut keys menu.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: show the mouse over the VNC console

noVNC hides the mouse over the screen until the guest sends a pointer image, and a text console or a guest that draws its own pointer never does. Show the system pointer unless one arrives, so it can be seen in the window and in full screen. A guest that sends an image keeps it.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: close the VNC console's menus on a click on the screen in Firefox

Firefox delivers the click to the canvas, where noVNC stops it, so an open menu never saw a click outside it. Full screen makes this matter, with little else left to click. noVNC does nothing else with a click, so let it through.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: make View only a toggle in the VNC console toolbar

View only moves from the Connected menu to the shared ToggleSwitch between the connection status and Shortcut keys, in the toolbar and in the full screen bar, so its state is always in view and the status no longer has to repeat it. Turning it off hands the keyboard back to the console. The full screen bar no longer gets only half the window from its left: 50%, which would have squeezed its buttons with the wider content; it wraps when it really does not fit.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: leave View only out of the VNC console's full screen bar

View only is set from the toolbar. A session that enters full screen with it on stays that way, with Shortcut keys disabled, and changes it after leaving.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: make View only a checkbox in the VNC console toolbar

It takes the shared Checkbox instead of ToggleSwitch, in the same place between the connection status and Shortcut keys. Clicking its label now turns it off as well as on.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: save the VNC view settings only when the user changes them

The previous console wrote its defaults to localStorage the first time it
opened, so Manual zoom at 100% looked like a choice and the new Automatic
default never reached anyone who had used the console. Settings now live
under a new key and are saved only when the user changes them.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: keep View only on when the VNC View panel resets its settings

View only moved to the toolbar, but Reset to defaults in the View panel
still set it back to off, so the screen started sending the mouse and
keyboard to the VM again. The reset now leaves View only as it is.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: give the VNC quality and compression sliders their full height

The slider rule's flex: 1 is meant for the zoom row. In the quality and
compression sections, which are columns, it set the height instead and
left those two sliders 4px tall, so a click on the visible thumb missed.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: reach the VNC shortcut keys from the keyboard after holding a modifier

RcDropdown collects its items when the menu opens, and holding Ctrl or
Alt swaps the keys below the toggles while it is open, so the arrow keys
stopped at Alt. The menu now collects its items again on each key press,
as RcDropdown does for its submenus.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: show a failed custom shortcut save inside the dialog

The dialog only reported a failed save with a growl, and the console
page's layout has no growls, so a delete or an add that could not be
saved changed nothing and said nothing. The error now shows as an alert
in the dialog until the next change or until it closes.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: keep the focus in the custom shortcuts dialog after adding or deleting

The delete buttons and Add shortcut were disabled while a change saved,
and disabling the button that was just pressed dropped the keyboard's
focus to the page. They now stay enabled, and a change made while one saves is
ignored. After a delete the next row's button takes the focus, or the last
one's, and after an add, or once the list is empty, the recorder does.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: record a modifier pressed on its own as a custom VNC shortcut

The recorder held every modifier for the key that ends the combination,
so the Windows key on its own could not be saved any more, while the old
dialog could. Modifiers released before any other key are now the
combination, once the last of them is released.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: stop the custom shortcut form saying a new shortcut is already saved

The list shows a new shortcut while it saves, and the form kept it until
the save came back, so every add showed "This key combination is already
saved." as an alert for as long as the save took. The form now empties
as soon as Add shortcut is pressed, and the shortcut goes back in it if
the save fails, unless the next one was started meanwhile.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: make the VNC Reconnect button an RcButton

The plain button took the global 40px button height inside the 32px
Disconnected pill, so its focus ring stuck out above and below the pill.
As a small ghost RcButton it is 24px tall and its ring stays inside.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: carry View only over from the previous VNC console

The new storage key dropped a View only that someone turned on in the
previous console, so their mouse and keyboard reached the VM again
after the upgrade. View only was off unless the user turned it on, so
it is read from the previous key until this console saves its own
settings; the display settings still start fresh.

The comment on the key now says the settings are written once the user
changes one, rather than only the ones changed.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: warn that a VNC soft reboot loses unsaved work

The soft reboot confirmation said the console disconnects while the VM
restarts and reconnects when it is back, which it does not: a soft
reboot restarts the guest in the same VM instance and pod, so the
console stays connected. It also dropped the previous dialog's warning
that unsaved work is lost, which is back.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

---------

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
Marcelo Fukumoto
2026-11-13 04:43:04 +01:00
committed by GitHub
parent 08e483e3c8
commit f3ca9e4c44
21 changed files with 3325 additions and 849 deletions
@@ -0,0 +1,109 @@
import ZoomableRFB from '../ZoomableRFB';
const GUEST_POINTER = 'url(guest-pointer) 0 0, default';
jest.mock('@novnc/novnc/core/rfb', () => ({
__esModule: true,
default: class {
constructor() {
this.scaleViewport = true;
this._canvas = { style: {} };
this._cursorImage = { rgbaPixels: new Uint8Array() };
this._display = { autoscale: jest.fn() };
}
_screenSize() {
return { w: 1000, h: 500 };
}
_fixScrollbars() {}
_handleMouse(ev) {
ev.stopPropagation();
}
// As noVNC does: nothing visible hides the mouse, an image is shown as the pointer.
_refreshCursor() {
this._canvas.style.cursor = this._cursorImage.rgbaPixels.length ? 'url(guest-pointer) 0 0, default' : 'none';
}
},
}), { virtual: true });
describe('zoomableRFB', () => {
describe('the pointer over the screen', () => {
it('should be the system one while the guest sends no pointer image', () => {
const rfb = new ZoomableRFB();
rfb._refreshCursor();
expect(rfb._canvas.style.cursor).toBe('default');
});
it('should be the system one when the image the guest sends is fully transparent', () => {
const rfb = new ZoomableRFB();
rfb._cursorImage = { rgbaPixels: new Uint8Array([255, 255, 255, 0, 0, 0, 0, 0]) };
rfb._refreshCursor();
expect(rfb._canvas.style.cursor).toBe('default');
});
it('should be the one the guest sends when it has visible pixels', () => {
const rfb = new ZoomableRFB();
rfb._cursorImage = { rgbaPixels: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]) };
rfb._refreshCursor();
expect(rfb._canvas.style.cursor).toBe(GUEST_POINTER);
});
it('should go back to the system one when the guest stops sending an image', () => {
const rfb = new ZoomableRFB();
rfb._cursorImage = { rgbaPixels: new Uint8Array([0, 0, 0, 255]) };
rfb._refreshCursor();
rfb._cursorImage = { rgbaPixels: [] };
rfb._refreshCursor();
expect(rfb._canvas.style.cursor).toBe('default');
});
});
describe('mouse events on the screen', () => {
it('should let a click through, so an open menu sees it as a click outside', () => {
const rfb = new ZoomableRFB();
const stopPropagation = jest.fn();
rfb._handleMouse({ type: 'click', stopPropagation });
expect(stopPropagation).not.toHaveBeenCalled();
});
it('should leave the others to noVNC', () => {
const rfb = new ZoomableRFB();
const stopPropagation = jest.fn();
rfb._handleMouse({ type: 'mousedown', stopPropagation });
expect(stopPropagation).toHaveBeenCalledTimes(1);
});
});
describe('the fitted screen', () => {
it('should keep room for the focus ring', () => {
const rfb = new ZoomableRFB();
rfb._updateScale();
expect(rfb._display.autoscale).toHaveBeenLastCalledWith(992, 492);
});
it('should take all the room when it is edge to edge', () => {
const rfb = new ZoomableRFB();
rfb.edgeToEdge = true;
expect(rfb._display.autoscale).toHaveBeenLastCalledWith(1000, 500);
});
});
});