mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
fix(console): a console that cannot open says why and starts no client
A stopped VM, an unreachable or unsupported console and a disabled serial console rendered the lite VNC page under the error: it connected to novncd anyway, showed "Something went wrong" and offered keys, Scale and Send CtrlAltDel for a console that was not there. A refused console was a bare text 403 inside the popup. - console-error.html: the console shell with the VM's name and the reason, no menu and no script; a refused or unknown VM gets it with 403 and without the VM's name, as before for everyone (no superuser exception). - The view signs the console token only for a page that connects. - console-menu.html: the Send key(s) menu and Fullscreen, included by the console pages; the serial page lists only the keys it sends (the VNC groups did nothing there). - Every console window is titled "<VM> - Console". - The lite page no longer takes a VNC password from its link. - The shell lost its 850px x 600px corner radius (a grey arc on the serial and error pages) and spans the window (container-fluid).
This commit is contained in:
parent
976955fff8
commit
3a560e9f85
8 changed files with 139 additions and 133 deletions
|
|
@ -7,6 +7,7 @@
|
|||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% if instance %}{{ instance.name }} - {% endif %}{% trans "Console" %}</title>
|
||||
{# error pages render without app_settings: they get zephyr #}
|
||||
{% with theme=app_settings.BOOTSTRAP_THEME|default:"zephyr" %}<link rel="stylesheet" href="{% static "css/themes/"|add:theme|add:".min.css" %}" id="wvc_css">{% endwith %}
|
||||
<link href="{% static "css/webvirtcloud.css" %}" rel="stylesheet">
|
||||
|
|
@ -24,8 +25,6 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #494949;
|
||||
border-bottom-right-radius: 850px 600px;
|
||||
|
||||
}
|
||||
|
||||
#main_container canvas {
|
||||
|
|
@ -42,10 +41,11 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<div id='main_container' class="container">
|
||||
<div id="main_container">
|
||||
<nav class="navbar sticky-top navbar-expand-md navbar-dark bg-primary" aria-label="console navbar">
|
||||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand">{{ instance.name }}</a>
|
||||
{% block navbar %}
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
|
|
@ -53,61 +53,12 @@
|
|||
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav me-auto my-2 my-lg-0">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" role="button"
|
||||
id="dropdownMenuLink" aria-haspopup="true" aria-expanded="false">
|
||||
{% trans "Send key(s)" %}
|
||||
</a>
|
||||
<div class="dropdown-menu shadow" aria-labelledby="dropdownMenuLink" style="min-width: 250px;">
|
||||
<h6 class="dropdown-header">{% trans "System" %}</h6>
|
||||
<a class="dropdown-item" id="ctrlaltdel" href="#">Ctrl+Alt+Del</a>
|
||||
<a class="dropdown-item" id="ctrlaltbackspace" href="#">Ctrl+Alt+Backspace</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="dropdown-header">{% trans "Navigation & Desktop" %}</h6>
|
||||
<a class="dropdown-item" id="alttab" href="#">Alt+Tab</a>
|
||||
<a class="dropdown-item" id="superkey" href="#">Windows / Super Key</a>
|
||||
<a class="dropdown-item" id="sendesc" href="#">Escape</a>
|
||||
<a class="dropdown-item" id="sendtab" href="#">Tab</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="dropdown-header">{% trans "Terminal Control" %}</h6>
|
||||
<a class="dropdown-item" id="ctrlc" href="#">Ctrl+C</a>
|
||||
<a class="dropdown-item" id="ctrlz" href="#">Ctrl+Z</a>
|
||||
<a class="dropdown-item" id="ctrld" href="#">Ctrl+D</a>
|
||||
<a class="dropdown-item" id="ctrlbackslash" href="#">Ctrl+\</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="dropdown-header">{% trans "Virtual Consoles (Ctrl+Alt+F#)" %}</h6>
|
||||
<div class="px-3 py-1">
|
||||
<div class="row g-1 text-center">
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf1" href="#">F1</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf2" href="#">F2</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf3" href="#">F3</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf4" href="#">F4</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf5" href="#">F5</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf6" href="#">F6</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf7" href="#">F7</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf8" href="#">F8</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf9" href="#">F9</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf10" href="#">F10</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf11" href="#">F11</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf12" href="#">F12</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item" id="fullscreen_button">
|
||||
<a class="nav-link" href="#">{% trans "Fullscreen" %}</a>
|
||||
</li>
|
||||
{% block navbarmenu %}{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</nav>
|
||||
{% if console_error %}
|
||||
<div class="alert alert-danger alert-dismissible rounded-0 m-0" role="alert">
|
||||
{{ console_error }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans "Close" %}"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script src="{% static "js/jquery.js" %}"></script>
|
||||
|
|
|
|||
7
console/templates/console-error.html
Normal file
7
console/templates/console-error.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "console-base.html" %}
|
||||
{# a console that cannot open: why, and no console client #}
|
||||
{% block navbar %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="alert alert-danger rounded-0 m-0" role="alert">{{ console_error }}</div>
|
||||
{% endblock %}
|
||||
53
console/templates/console-menu.html
Normal file
53
console/templates/console-menu.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{% load i18n %}
|
||||
{# the console pages' Send key(s) menu and Fullscreen; serial: only the keys a terminal takes #}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" role="button"
|
||||
id="dropdownMenuLink" aria-haspopup="true" aria-expanded="false">
|
||||
{% trans "Send key(s)" %}
|
||||
</a>
|
||||
<div class="dropdown-menu shadow" aria-labelledby="dropdownMenuLink" style="min-width: 250px;">
|
||||
{% if not serial %}
|
||||
<h6 class="dropdown-header">{% trans "System" %}</h6>
|
||||
<a class="dropdown-item" id="ctrlaltdel" href="#">Ctrl+Alt+Del</a>
|
||||
<a class="dropdown-item" id="ctrlaltbackspace" href="#">Ctrl+Alt+Backspace</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="dropdown-header">{% trans "Navigation & Desktop" %}</h6>
|
||||
<a class="dropdown-item" id="alttab" href="#">Alt+Tab</a>
|
||||
<a class="dropdown-item" id="superkey" href="#">Windows / Super Key</a>
|
||||
<a class="dropdown-item" id="sendesc" href="#">Escape</a>
|
||||
<a class="dropdown-item" id="sendtab" href="#">Tab</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% endif %}
|
||||
<h6 class="dropdown-header">{% trans "Terminal Control" %}</h6>
|
||||
<a class="dropdown-item" id="ctrlc" href="#">Ctrl+C</a>
|
||||
<a class="dropdown-item" id="ctrlz" href="#">Ctrl+Z</a>
|
||||
<a class="dropdown-item" id="ctrld" href="#">Ctrl+D</a>
|
||||
<a class="dropdown-item" id="ctrlbackslash" href="#">Ctrl+\</a>
|
||||
{% if serial %}
|
||||
<a class="dropdown-item" id="sendesc" href="#">Escape</a>
|
||||
<a class="dropdown-item" id="sendtab" href="#">Tab</a>
|
||||
{% else %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<h6 class="dropdown-header">{% trans "Virtual Consoles (Ctrl+Alt+F#)" %}</h6>
|
||||
<div class="px-3 py-1">
|
||||
<div class="row g-1 text-center">
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf1" href="#">F1</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf2" href="#">F2</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf3" href="#">F3</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf4" href="#">F4</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf5" href="#">F5</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf6" href="#">F6</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf7" href="#">F7</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf8" href="#">F8</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf9" href="#">F9</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf10" href="#">F10</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf11" href="#">F11</a></div>
|
||||
<div class="col-3"><a class="btn btn-sm btn-outline-secondary w-100 py-1" id="ctrlaltf12" href="#">F12</a></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item" id="fullscreen_button">
|
||||
<a class="nav-link" href="#">{% trans "Fullscreen" %}</a>
|
||||
</li>
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
or the fragment:
|
||||
http://example.com/#host=HOST&port=PORT&encrypt=1
|
||||
-->
|
||||
<title>WebVirtCloud - noVNC</title>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
|
||||
|
|
@ -96,6 +95,8 @@
|
|||
<!-- end scripts -->
|
||||
{% endblock %}
|
||||
|
||||
{% block navbarmenu %}{% include "console-menu.html" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="noVNC_fallback_error" class="noVNC_center">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,6 @@
|
|||
Connect parameters are provided in query string:
|
||||
http://example.com/?scale=true (the proxy host, port and path come from the server)
|
||||
-->
|
||||
<title>WebVirtCloud - noVNC - Lite</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Always force latest IE rendering engine (even in intranet) &
|
||||
Chrome Frame. Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href='{% static "js/novnc/app/styles/lite.css" %}'>
|
||||
|
||||
|
|
@ -114,26 +106,6 @@
|
|||
document.getElementById('noVNC_status').textContent = text;
|
||||
}
|
||||
|
||||
// This function extracts the value of one variable from the
|
||||
// query string. If the variable isn't defined in the URL
|
||||
// it returns the default value instead.
|
||||
function readQueryVariable(name, defaultValue) {
|
||||
// A URL with a query parameter can look like this:
|
||||
// https://www.example.com?myqueryparam=myvalue
|
||||
//
|
||||
// Note that we use location.href instead of location.search
|
||||
// because Firefox < 53 has a bug w.r.t location.search
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
|
||||
if (match) {
|
||||
// We have to decode the URL since want the cleartext value
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bindKeyAction('machineShutdownButton', machineShutdown);
|
||||
bindKeyAction('machineRebootButton', machineReboot);
|
||||
bindKeyAction('machineResetButton', machineReset);
|
||||
|
|
@ -153,10 +125,12 @@
|
|||
const host = window.location.hostname;
|
||||
let port = window.location.port;
|
||||
|
||||
// the VNC password, for a user who may open the console without it;
|
||||
// otherwise the server asks for it (credentialsrequired)
|
||||
{% if perms.instances.passwordless_console and console_passwd %}
|
||||
const password = '{{ console_passwd|escapejs }}';
|
||||
const credentials = { password: '{{ console_passwd|escapejs }}' };
|
||||
{% else %}
|
||||
const password = readQueryVariable('password');
|
||||
const credentials = {};
|
||||
{% endif %}
|
||||
|
||||
const path = 'novncd/';
|
||||
|
|
@ -190,7 +164,7 @@
|
|||
|
||||
// Creating a new RFB object will start a new connection
|
||||
rfb = new RFB(document.getElementById('noVNC_container'), url,
|
||||
{ credentials: { password: password } });
|
||||
{ credentials: credentials });
|
||||
|
||||
// Add listeners to important events from the RFB module
|
||||
rfb.addEventListener("connect", connectedToServer);
|
||||
|
|
@ -208,6 +182,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block navbarmenu %}
|
||||
{% include "console-menu.html" %}
|
||||
<li class="nav-item" id="scale_button">
|
||||
<a class="nav-link{% if scale %} active{% endif %}" href="#" role="button" aria-pressed="{{ scale|yesno:"true,false" }}">{% trans "Scale" %}</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
{% load static %}
|
||||
|
||||
{% block head %}
|
||||
<title>WebVirtCloud - XTerm</title>
|
||||
<link rel="stylesheet" href="{% static 'css/xterm.css' %}"/>
|
||||
<script src="{% static 'js/xterm/xterm.js' %}"></script>
|
||||
<script src="{% static 'js/xterm/addon-fit.js' %}"></script>
|
||||
|
|
@ -14,6 +13,8 @@
|
|||
</style>
|
||||
|
||||
{% endblock %}
|
||||
{% block navbarmenu %}{% include "console-menu.html" with serial=True %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div style="background: white; padding-bottom: 5px;">
|
||||
<span style="font-size: small;">Status: <span style="font-size: small;" id="status">connecting...</span></span>
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class ConsoleViewsTestCase(TestCase):
|
|||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "console-vnc-lite.html")
|
||||
self.assertTemplateUsed(response, "console-error.html")
|
||||
self.assertIn("'rdp' is not supported", str(response.context["console_error"]))
|
||||
|
||||
@override_settings(SERIAL_CONSOLE_ENABLED=True)
|
||||
|
|
@ -204,9 +204,32 @@ class ConsoleViewsTestCase(TestCase):
|
|||
url = self.url
|
||||
response = self.client.get(url)
|
||||
|
||||
# Non-permitted user attempting to view other user's instance
|
||||
# Non-permitted user attempting to view other user's instance: the
|
||||
# console's own error page, without the VM's name or a console token
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertTemplateUsed(response, "console-error.html")
|
||||
self.assertIn("permission", response.content.decode("utf-8").lower())
|
||||
self.assertNotContains(response, "vm-alpha", status_code=403)
|
||||
self.assertNotIn("token", response.context)
|
||||
|
||||
@override_settings(SERIAL_CONSOLE_ENABLED=False)
|
||||
@patch("console.views.wvmInstance")
|
||||
def test_a_console_that_cannot_open_gets_no_console_client(self, mock_wvm):
|
||||
# the page says why; it neither connects to novncd nor offers keys
|
||||
conn = mock_wvm.return_value
|
||||
conn.instance.isActive.return_value = 1
|
||||
self.client.force_login(self.admin_user)
|
||||
for console_type, running in (("vnc", 0), ("pty", 1), ("rdp", 1), (None, 1)):
|
||||
with self.subTest(console_type=console_type, running=running):
|
||||
conn.get_console_type.return_value = console_type
|
||||
conn.instance.isActive.return_value = running
|
||||
response = self.client.get(self.url)
|
||||
self.assertTemplateUsed(response, "console-error.html")
|
||||
self.assertNotIn("token", response.context)
|
||||
page = html.fromstring(response.content)
|
||||
self.assertEqual(page.xpath("//script[@type='module'] | //*[@id='dropdownMenuLink']"), [])
|
||||
self.assertEqual(len(page.xpath("//div[@role='alert']")), 1)
|
||||
self.assertEqual(page.findtext(".//title"), "vm-alpha - Console")
|
||||
|
||||
@patch("console.views.wvmInstance")
|
||||
def test_console_lite_uses_the_signed_token(self, mock_wvm):
|
||||
|
|
@ -220,7 +243,6 @@ class ConsoleViewsTestCase(TestCase):
|
|||
signed = response.context["token"]
|
||||
self.assertEqual(TimestampSigner(salt="console.novnc").unsign(signed),
|
||||
f"{self.compute.id}:{self.instance.uuid}:{self.admin_user.id}")
|
||||
self.assertNotContains(response, "readQueryVariable('token'")
|
||||
self.assertContains(response, f"const token = '{escapejs(signed)}'")
|
||||
|
||||
@patch("console.views.wvmInstance")
|
||||
|
|
@ -238,8 +260,8 @@ class ConsoleViewsTestCase(TestCase):
|
|||
self.client.force_login(self.alice)
|
||||
response = self.client.get(self.url + "?host=attacker.example&port=443&path=x")
|
||||
self.assertNotContains(response, "attacker.example")
|
||||
for name in ("host", "port", "path"):
|
||||
self.assertNotContains(response, f"readQueryVariable('{name}'")
|
||||
# nor a password from it: a link would leave it in history and logs
|
||||
self.assertNotContains(response, "readQueryVariable")
|
||||
self.assertContains(response, "const host = window.location.hostname;")
|
||||
|
||||
@patch("console.views.wvmInstance")
|
||||
|
|
@ -394,7 +416,7 @@ class ConsoleViewsTestCase(TestCase):
|
|||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "console-vnc-lite.html")
|
||||
self.assertTemplateUsed(response, "console-error.html")
|
||||
self.assertIsNotNone(response.context["console_error"])
|
||||
|
||||
def test_an_unknown_vm_gives_the_superuser_an_error_page(self):
|
||||
|
|
@ -402,10 +424,19 @@ class ConsoleViewsTestCase(TestCase):
|
|||
url = reverse("console", args=[self.compute.id, "no-such-uuid"])
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "console-vnc-lite.html")
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertTemplateUsed(response, "console-error.html")
|
||||
self.assertIsNotNone(response.context["console_error"])
|
||||
|
||||
@override_settings(SERIAL_CONSOLE_ENABLED=True)
|
||||
@patch("console.views.wvmInstance")
|
||||
def test_the_serial_console_offers_only_the_keys_a_terminal_takes(self, mock_wvm):
|
||||
mock_wvm.return_value.get_console_type.return_value = "pty"
|
||||
self.client.force_login(self.admin_user)
|
||||
page = html.fromstring(self.client.get(self.url).content)
|
||||
keys = page.xpath("//*[@aria-labelledby='dropdownMenuLink']//a/@id")
|
||||
self.assertEqual(sorted(keys), ["ctrlbackslash", "ctrlc", "ctrld", "ctrlz", "sendesc", "sendtab"])
|
||||
|
||||
@patch("console.views.wvmInstance")
|
||||
def test_console_send_keys_menu_elements(self, mock_wvm):
|
||||
mock_conn = MagicMock()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
from appsettings.settings import app_settings
|
||||
from console import tokens
|
||||
from django.conf import settings
|
||||
from django.http.response import HttpResponseForbidden
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from instances.models import Instance
|
||||
|
|
@ -22,30 +21,22 @@ def to_bool(val, default=False):
|
|||
|
||||
def console(request, compute_id, uuid):
|
||||
"""The console page of the VM with this UUID on this compute. The page's
|
||||
query gives the view (lite or full) and its display options."""
|
||||
console_error = None
|
||||
token = ""
|
||||
query gives the view (lite or full) and its display options. A console
|
||||
that cannot open gets a page that says why, and no console client."""
|
||||
view_type = "full" if request.GET.get("view", "").strip().lower() == "full" else "lite"
|
||||
view_only = to_bool(request.GET.get("view_only"), default=to_bool(app_settings.CONSOLE_VIEW_ONLY))
|
||||
scale = to_bool(request.GET.get("scale"), default=to_bool(app_settings.CONSOLE_SCALE))
|
||||
resize_session = to_bool(request.GET.get("resize_session"), default=to_bool(app_settings.CONSOLE_RESIZE_SESSION))
|
||||
clip_viewport = to_bool(request.GET.get("clip_viewport"), default=to_bool(app_settings.CONSOLE_CLIP_VIEWPORT))
|
||||
|
||||
instance = Instance.objects.filter(compute_id=compute_id, uuid=uuid).first()
|
||||
# Same answer for "no such VM" and "not allowed", so the response
|
||||
# does not reveal whether a VM exists.
|
||||
if instance is None or not can_open_console(request.user, instance):
|
||||
console_error = _("User does not have permission to access console or host/instance not exist")
|
||||
return render(request, "console-error.html", {"console_error": console_error}, status=403)
|
||||
|
||||
try:
|
||||
instance = Instance.objects.filter(compute_id=compute_id, uuid=uuid).first()
|
||||
if instance is None and request.user.is_superuser:
|
||||
raise Instance.DoesNotExist
|
||||
# Same answer for "no such VM" and "not allowed", so the response
|
||||
# does not reveal whether a VM exists.
|
||||
if instance is None or not can_open_console(request.user, instance):
|
||||
console_error = _(
|
||||
"User does not have permission to access console or host/instance not exist"
|
||||
)
|
||||
return HttpResponseForbidden(console_error)
|
||||
|
||||
# Generate secure signed token for noVNC daemon
|
||||
token = tokens.SIGNER.sign(f"{compute_id}:{uuid}:{request.user.id}")
|
||||
|
||||
conn = wvmInstance(
|
||||
instance.compute.hostname,
|
||||
instance.compute.login,
|
||||
|
|
@ -62,40 +53,36 @@ def console(request, compute_id, uuid):
|
|||
unreachable = instance.compute.type in (CONN_TCP, CONN_TLS) and bool(
|
||||
conn.get_console_socket() or is_loopback(conn.get_console_listener_addr())
|
||||
)
|
||||
except (libvirtError, ValueError, IndexError, Instance.DoesNotExist):
|
||||
except (libvirtError, ValueError, IndexError):
|
||||
running = unreachable = False
|
||||
console_type = None
|
||||
console_passwd = None
|
||||
|
||||
if console_type and not running: # a shut-off VM has no console to connect to
|
||||
if console_type is None:
|
||||
console_error = _("Fail to get console. Please check the console configuration of your VM.")
|
||||
elif not running: # a shut-off VM has no console to connect to
|
||||
console_error = _("The VM is not running: start it to open its console.")
|
||||
response = render(request, "console-vnc-lite.html", locals())
|
||||
elif console_type == "vnc" and unreachable:
|
||||
console_error = _(
|
||||
"This console listens only on its host, which the panel reaches over TCP or TLS. "
|
||||
"Let the console listen on all interfaces in the VM's console settings, "
|
||||
"or add the host as an SSH host."
|
||||
)
|
||||
response = render(request, "console-vnc-lite.html", locals())
|
||||
elif console_type == "vnc":
|
||||
console_page = "console-vnc-" + view_type + ".html"
|
||||
response = render(request, console_page, locals())
|
||||
elif console_type == "pty" and not settings.SERIAL_CONSOLE_ENABLED:
|
||||
console_error = _(
|
||||
"Serial console is disabled on this server. "
|
||||
"Please switch VM graphics to VNC or ask an administrator."
|
||||
)
|
||||
response = render(request, "console-vnc-lite.html", locals())
|
||||
elif console_type == "pty":
|
||||
response = render(request, "console-xterm.html", locals())
|
||||
elif console_type not in ("vnc", "pty"):
|
||||
console_error = _(
|
||||
"Console type '%(type)s' is not supported. Please switch the VM graphics to VNC."
|
||||
) % {"type": console_type}
|
||||
else:
|
||||
if console_type is None:
|
||||
console_error = _(
|
||||
"Fail to get console. Please check the console configuration of your VM."
|
||||
)
|
||||
else:
|
||||
console_error = _(
|
||||
"Console type '%(type)s' is not supported. Please switch the VM graphics to VNC."
|
||||
) % {"type": console_type}
|
||||
response = render(request, "console-vnc-lite.html", locals())
|
||||
return response
|
||||
console_error = None
|
||||
if console_error:
|
||||
return render(request, "console-error.html", {"instance": instance, "console_error": console_error})
|
||||
|
||||
# the console daemons' credential for this page
|
||||
token = tokens.SIGNER.sign(f"{compute_id}:{uuid}:{request.user.id}")
|
||||
console_page = "console-xterm.html" if console_type == "pty" else "console-vnc-" + view_type + ".html"
|
||||
return render(request, console_page, locals())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue