1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

feat(console): one VNC console page, driven from the panel's bar

The VM page offered a lite and a full VNC console. Lite lacked a clipboard,
display settings and a way to reconnect; full put noVNC's own side bar under
the panel's bar, with a Fullscreen item that called a function noVNC does
not have, status messages hidden behind the bar, saved noVNC settings that
overrode the Access tab's, connection settings that cannot apply, and a
Connect button that reused the page's two-minute token and failed.

console-vnc.html and static/js/console_vnc.js replace both, on noVNC's RFB
with Bootstrap controls, and keep what the full view did:
- Status in the bar (Connecting, Connected, Disconnected, Connection lost,
  Could not connect, Authentication failed with the server's reason on
  hover); Disconnect, then Reconnect, which loads the page again for a new
  token.
- The VNC password in a dialog (with a username when the server asks for
  one), not a browser prompt; without passwordless console it is not in
  the page.
- Send key(s) gains Hold Ctrl / Alt / Windows (noVNC's extra keys).
- Clipboard: what the VM copies, sending text to the VM's clipboard, and
  typing it key by key (new: works in firmware, installers and login
  prompts; Shift for the characters a US keyboard shifts, 25 ms a key as
  firmware drops faster keys, verified in a UEFI shell).
- View: scaling (none, fit, resize the VM's screen), clip, drag a clipped
  screen, view only, dot cursor, quality, compression; it starts as
  Settings > Console says (a touch screen starts fitted).
- A Keyboard item on touch devices (noVNC's on-screen keyboard input,
  key events and Android's text-only input), Fullscreen with keyboard
  lock, the bell.
Dropped with the full view: noVNC's websocket host/port/path, repeater ID
and log level (the panel fixes them), automatic reconnect (the token is
single use in time), XVP power buttons (QEMU has no XVP) and shared-mode off.

The bar is compact (about 45 px in the 850x600 window). The Access tab has
one Console button instead of the Lite/Full split and four switches, and
it and the VM list open one window per VM (opening it again reloads it).
This commit is contained in:
catborise 2026-10-27 00:25:27 +03:00
parent 3a560e9f85
commit 0532471759
12 changed files with 520 additions and 843 deletions

View file

@ -59,8 +59,8 @@
{% endif %}
{% if "console" in actions %}
<button class="btn btn-sm btn-secondary" type="button" data-url="{% url 'console' instance.compute_id instance.uuid %}"
onclick="window.open(this.dataset.url, '', 'width=850,height=650')"
<button class="btn btn-sm btn-secondary" type="button" data-url="{% url 'console' instance.compute_id instance.uuid %}" data-window="console-{{ instance.uuid }}"
onclick="window.open(this.dataset.url, this.dataset.window, 'width=850,height=650')"
title="{% trans "Console" %}">{% bs_icon 'eye-fill' %}</button>
{% else %}
<button class="btn btn-sm btn-secondary disabled" title="{% trans "VNC Console" %}" disabled>{% bs_icon 'eye-fill' %}</button>

View file

@ -37,49 +37,11 @@
{% if not can_open_console %}
<p>{% trans "Only the owners of this instance and administrators can open its console." %}</p>
{% else %}
{% if instance.console_type == 'vnc' %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="scale"
{% if app_settings.CONSOLE_SCALE == 'True' %} checked {% endif %}
id="scale">
<label class="form-check-label" for="scale">{% trans "Scale" %}</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="view_only"
{% if app_settings.CONSOLE_VIEW_ONLY == 'True' %} checked {% endif %}
id="view_only">
<label class="form-check-label" for="view_only">{% trans "View Only" %}</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="resize_session"
{% if app_settings.CONSOLE_RESIZE_SESSION == 'True' %} checked {% endif %}
id="resize_session">
<label class="form-check-label" for="resize_session">{% trans "Resize Session" %}</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="clip_viewport"
{% if app_settings.CONSOLE_CLIP_VIEWPORT == 'True' %} checked {% endif %}
id="clip_viewport">
<label class="form-check-label" for="clip_viewport">{% trans "Clip viewport" %}</label>
</div>
{% endif %}
{% if instance.status == 1 %}
<!-- Split button -->
<div class="btn-group float-end" role="group">
<button type="button" id="consoleBtnGroup" class="btn btn-lg btn-success"
onclick="open_console('lite')">{% trans 'Console' %}</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#"
title="{% trans "Console port" %}: {{ instance.console_port }}"
onclick="open_console('lite')">{% trans "Console" %} - {% trans "Lite" %}</a>
<a class="dropdown-item" href="#"
title="{% trans "Console port" %}: {{ instance.console_port }}"
onclick="open_console('full')">{% trans "Console" %} - {% trans "Full" %}</a>
</div>
</div>
{# one window per VM: opening it again reloads it #}
<button type="button" class="btn btn-lg btn-success float-end" id="console_button"
data-url="{% url 'console' compute.id instance.uuid %}" data-window="console-{{ instance.uuid }}"
onclick="window.open(this.dataset.url, this.dataset.window, 'width=850,height=650')">{% trans "Console" %}</button>
{% else %}
<button class="btn btn-lg btn-success float-end disabled">{% trans "Console" %}</button>
{% endif %}
@ -153,17 +115,3 @@
{% endif %}
</div>
</div>
{% block script %}
{% if can_open_console %}
<script>
function open_console(view_style) {
const sc = $("#scale").is(':checked');
const vo = $("#view_only").is(':checked');
const rs = $("#resize_session").is(':checked');
const cv = $("#clip_viewport").is(':checked');
const options = new URLSearchParams({view: view_style, view_only: vo, scale: sc, clip_viewport: cv, resize_session: rs});
window.open('{% url 'console' compute.id instance.uuid %}?' + options, '', 'width=850,height=600')
}
</script>
{% endif %}
{% endblock %}

View file

@ -84,7 +84,7 @@ class PageControls:
return bool(doc.xpath("//form[@action=$a]", a=reverse(f"instances:{name}", args=[pk])))
found = {
"console": bool(doc.xpath("//button[@id='consoleBtnGroup']")),
"console": bool(doc.xpath("//button[@id='console_button']")),
"vv file": bool(doc.xpath("//a[@href=$a]", a=reverse("instances:getvvfile", args=[pk]))),
"vdi": bool(doc.xpath("//*[@id='vdiconsole']")),
"power": form("poweroff") or form("poweron"),