From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 12E321FF0C1 for ; Wed, 26 Aug 2026 10:19:14 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BC17621330; Wed, 26 Aug 2026 10:19:13 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=rPK6L+l9RZZzPd6M/tKsxjP1ZmC3R7pZhmYSN1M5L3sWn8yBJCf0ZBicLv2dRMnNKgBcT88wuPXPn6g4kCwreUgZqtVDu2WMUrSavA15g98TELqJT7Icjjxb+Sj6N30byOKWqjIHONJ4cfOHLMQ+E5gTdEghd7TIMHav4hkXsNO3W2I8cq+jl/xaPG2bSlmASpcL0ze1RGubhesJfqDk67YvdksqZh7PX62XhL81TEtMkZbivjMYBjNzlYhmK21ReCNesCFagzpjpOQIkmKsKGZ8qFB1b+taHPO1kuIztz/5K4qx+tp+E8uQwtRpd+s8dH9+7kzD2tQqgQU+zgj1Ww== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=ZXGPBVDWpGxfB3LW1IUrYQwSv9f6qPNZiyWGztS1c98=; b=ZmbqW5FbEiOV3gpR/hegcVXsrAsSVsiBeeGoYgyvP1aX44hfPnKlCsEyfO9p8BiPNd35SZ8BU0ZDQxmxsY/157I3LiB1E0chThE9jl/FGflk//8lUCoJbXHDg+yUoVvu170C14l2MEvX7en3XHZo3HYB+FmFoAQJGkMmpP95zYoR9FJcdpZm/A8L/I2vL04h+EToybrLxohhC1tu3FyRpkWMdO0i08xgiQtfSIFnfSI9ufYgcbeH6fe09RK2D8u/JQdALeaUHFUFfUzfVfkES8RuSpOnZCAlI+ZUo1iIbnojJqWB4PtBu9wjr/hYS//19TCbZAmXBsHFLY2YVbnh2Q== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=groupe-cyllene.com; dmarc=pass action=none header.from=groupe-cyllene.com; dkim=pass header.d=groupe-cyllene.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=groupecyllene.onmicrosoft.com; s=selector2-groupecyllene-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=ZXGPBVDWpGxfB3LW1IUrYQwSv9f6qPNZiyWGztS1c98=; b=FSOiAVh2k6jYDzxAQuYpSC5eCU1AZP1AtXBb07FDgpjvh11T58sM/yE+aoKT+u2WliWKmH7MLgLqSfKD2yv2ivzzDIVe9eCfVV+pRZneJ3U5/3PyfFYcoetObEzPnS1QYcGXepDKMJw5rcRnqZFNwWUjzLkq64AtDni1My6mCuo= Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=groupe-cyllene.com; From: Alexandre Derumier To: pve-devel@lists.proxmox.com Subject: [RFC v2 pve-manager 07/13] ui: add rdp console Date: Wed, 26 Aug 2026 09:43:39 +0200 Message-ID: <20260826074347.1256659-8-alexandre.derumier@groupe-cyllene.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260826074347.1256659-1-alexandre.derumier@groupe-cyllene.com> References: <20260826074347.1256659-1-alexandre.derumier@groupe-cyllene.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-ClientProxiedBy: PA7P264CA0229.FRAP264.PROD.OUTLOOK.COM (2603:10a6:102:372::8) To PR1P264MB3696.FRAP264.PROD.OUTLOOK.COM (2603:10a6:102:143::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PR1P264MB3696:EE_|PR0P264MB2257:EE_ X-MS-Office365-Filtering-Correlation-Id: f117488a-b722-4ffb-5c9c-08df0345d536 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|376014|23010399003|52116014|1800799024|366016|38350700014|6133799003|3023799007|56012099006|10067099003|5023799004|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: Xfxyz3lw5laimGwSkHq2EIuYPH6p4kRCe6ijo3X7ov9sgv/dQV2zsGjcwF45RiSfSK5AhbgKeH5y2lfwcnIjLqQMhHlEkjiT9YeCz+rmOv2y2xk8LiWAKGtVDKCL64bwDc/xvaF2dI5CpI1UQPvDxKbBivmjiPUcbmf/0gP+OXhuCU5/fCLtLcG3ZmZZoBwW1rWYw5X+nBl0NE0t/nGZklkVZ1JvpmropagZ+gIfpVBiAlewowu41Qy0HUfUbke2C1hZ/4PpKAhZWTF6VLVWr86oIV623PiFN1tdfwW7RbA30hZeJyFV3lshKUgfZ6/1aW+N+VDSKVHoNL/xMjC+k0mtJGdj1mmEC5ThkvOm2xF1dmjiwVLB91Gg8s1zBteWDHmx6HgOWBX/5rDR/NAofWVYS95ro6Gsugiv2xcLNQkDzPJrUPB1X4p1Fwq0C3ujW5CDHtJlz5fSa5YwHq01UfsOqOh+SFR8STVOI5TKKQo/FgjBXkAZ1wbeZGk0GjtEEFtqWS65UaUB2l/N7ybJAiEqwz6bO7MM3VyJP2KZ0j0wfzw9YWoGSfR/eWgUUH9Tn+zsnVQv+S0JVxrUx6cKi1Hgr4yPqZqoyXWU4awGDHGjZauwvw244LyAmLVjN9XPATIZohiFfwvqysxKE73fFckAlfsQPS4VqZsy3FQBo18gtCEGfBsZZMmzORJoBtWCIBxMGB07JIyDKOYVpriTVt6movIfnp46aEPrmOu2EHg= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PR1P264MB3696.FRAP264.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(376014)(23010399003)(52116014)(1800799024)(366016)(38350700014)(6133799003)(3023799007)(56012099006)(10067099003)(5023799004)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?aStPNnZyYXlRTldtbnBia3lXVWhFbWpRcUYwNUJEck5EdUFSbXZRbU40ckRV?= =?utf-8?B?RDhmcE0zRVhNcEhkR0ZxbEFTZFZIRVdvcTVTUFlSNjdTWEllL2Q0S1JnUUdD?= =?utf-8?B?WjZQVkVWVk55aGl2eGFzM3pmN2E3Y09pZ1gxRGFuZ1lsVWt3YitBTlhicy9G?= =?utf-8?B?RCtPUzRacmxDZGtpMENQak5NRUsvTTB2M0VlVVVMbzJMMUtJNHp3dXVKYUNq?= =?utf-8?B?VE0ycTNQUHU3THBrcDhZUWVRQ0RtcDRTakFTdFdKWXNNSDBMTDRhUW54T1J5?= =?utf-8?B?UXdmYXArNkpBcTE3SURmUk9FMiszTTBTd0J0UGNsa0h2Z082MzNWM0NtK3ZT?= =?utf-8?B?bzJmYVlTT0FxM3BjQ3dIUngxa0hlL0VTcHhNSzJOTUpQaEIyVklCc04yR2h2?= =?utf-8?B?SGttcmNTaFU2S0dsT2Z3SUNzTldBYk5uRWtnSDgrSUdmTnpHYlZGMlVEajFS?= =?utf-8?B?aVBROHFzUHBFOGpLZUMydTNuMm1jOGRSR2ovRXFUZkthTXlDL2NPeklYY3oz?= =?utf-8?B?WDhBdDJDakxDRTJqTWlsVEtJUUl2c1lSNFAydkEzSHlXdVgwalZzNWlGazZn?= =?utf-8?B?Y2VDeC84d0hNTlVSSWczNW9sdVFQTmphNGVmQjN4RkdneVc3QnJGeE5SRlpX?= =?utf-8?B?aVRFeERXaGx0YldnUFJuMUl3cEV6VlNhZ0k0dndwSEdUOVUrRjZ1cnI1Mlcx?= =?utf-8?B?Qmw3Q041ZWRrWGFJKzhHREhmUlBWSGxBZVEwbkJXa3h1RUttRDc4dDJOczVm?= =?utf-8?B?UC96Z2lKbnRBd0ZOZzBrWm5XYlhSUHo2WEJ6ampPLzNYdmJ3ZHhOQ25VeWtv?= =?utf-8?B?dWtyaFJNb0RPWC9QSmtQVGhLZHZWSUc0MWxwUURWTlRUN1E2QjZhdXRlZXZV?= =?utf-8?B?eVhrWVA3aGd4WWFaai9hZVpxbmxaOW4xOXAweXhEbVZMbGVpTGR2d0h3QWxH?= =?utf-8?B?WTJocUdUdTd6M21zU042MkcxQ3FGWVVnVXA3NEpqa1JSMGplRmwwQ3ArWXlB?= =?utf-8?B?UHc3WDFuMkVYSDFpM21JbXdjNzEyZ0p5V09zSTUvdUF2bHJBa3VnT1VlWThQ?= =?utf-8?B?NVN1cEc0TDR1dDZhaWk3c3pDUmhobkE4UlNSeHlwaXN1d1hYOVZUQkNsY0lh?= =?utf-8?B?MUhUTW5TemhmR3JYTGVadjk3OTlnTWhtZTlsdlI2OWptUU43SXd0amdmQkk3?= =?utf-8?B?aXFOcU40dmMwQ2ZyWXFRcFlxSVl2SlJPYWVBaCtVa2U5VlB5OEkzSVBZdk5R?= =?utf-8?B?Unk1VndxcjRYOWIzanlpdDcrdUR3b0QyVSsxc0JmeW5ScVRRQVpVcWF3bkhZ?= =?utf-8?B?dnRyemZxdjZTZnUxQXQ5REFWMnlMU1hDdEwvbWo1blJBQUhobG85MkphVDJE?= =?utf-8?B?RnlRNnkwclFaQW1KNVJINzErbWU5NW5ZMG0vM2loaU43eHhCbHhPZG1vdGla?= =?utf-8?B?QWVHTkN5OVRjakErczJsU3diTzFXbUoveVl4TWh4WnNlVTJnRlFlcG9FU2V4?= =?utf-8?B?QVNoSzFDUmtEMnJ6RkZ2ZFlzSEdDam56UnI4Y2tSbHMrL0EvVENlM0JzdVI2?= =?utf-8?B?eDE1MjdUTEhTamIremxuM1JiZjdCemlCNFVUdC9sVlpRRGhqUVFMVm5FTVMw?= =?utf-8?B?a2M0MWtnbU1OaW0xaXQxTWdsWTRHVlhEZWJnQWtBWVNXa3IxZlVLUDFLMnp1?= =?utf-8?B?TzlOZjFxVS9rSGVXU3BlTUtWRDYyMkx1c3A2eS9WU0krOWxtSXBGQ2lNMTg3?= =?utf-8?B?ME1YcDBYMDk1UVp5UmRHeGY3YXFTZmdYaG5rUG0zQ3JFd1FCeThick5EbnRM?= =?utf-8?B?ZEJoTXVxUmpLT0prNWZCb29Rd2ZLalFjNmJIbWhFd2FwdGtnSDRWZ3B0aUMv?= =?utf-8?B?eUcrSFA5S2JPWHZGWTl5bkR5Y2tpWDJKQjZzamF2UTFoNTNnaFpVSG1rOWc4?= =?utf-8?B?TUJQczNLQVhmYm5sZGNJcjd4Z0Q0bFA3Y1ZlZm5JY3hmUHkyR1VYTndnTkRo?= =?utf-8?B?K2FZdG5Id2JqSFhQc0UzamJocjBQdkF1R1pLUnF5TVY0NUc4UnVnRzJHM2gx?= =?utf-8?B?b1BwN0dHd1NrY1R3cHFwQ2x6K3BDWkRXYXpxOTFEYkkwWkJzdi8wNHRBcHdU?= =?utf-8?B?YUFxdEhpTW1iTzJiK29tamVHdVlhYTNNbUhBZldSOUFVaXQ0UlVqbHp1SExO?= =?utf-8?B?RUo2dS9oRHBwVFlQWEVrVjR4ZnpScyt3MTBnRzJpLzZ5eGhPTHM3V0IzMElR?= =?utf-8?B?clhFc2VSS3d1cU1FUGdsMnhuV1BjUWlVd2RJZ0pRcFE2dzR3aWcycjdlbXRS?= =?utf-8?B?TWtsQzZPMFpOZDVVL2pvblkzSlh4Z1ZGNmVWTTZaSERzbDB1d2xmZFVyaENk?= =?utf-8?Q?7UHybefJUintlBa0DM69154SWIC3KMgFZbyhD?= X-OriginatorOrg: groupe-cyllene.com X-MS-Exchange-CrossTenant-Network-Message-Id: f117488a-b722-4ffb-5c9c-08df0345d536 X-MS-Exchange-CrossTenant-AuthSource: PR1P264MB3696.FRAP264.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 26 Aug 2026 07:44:17.6973 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: ee11ccf7-112c-4284-848b-f229745e715b X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: dM8iXwDywN3bHIvqCwv6oy0yxvx2oQzKq3ttkJvlqrpZQNONxdBmkWmsm3TVWJs49G2mWWovu3pAe5H1Ysn4S5xYtczd9whgd706LiCTwAV4HQ/oPSK2cPASyy2IHCu8 X-MS-Exchange-Transport-CrossTenantHeadersStamped: PR0P264MB2257 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.222 Adjusted score from AWL reputation of From: address CONTENT_AFTER_HTML 0.001 More content after HTML close tag + other spam signs DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust RCVD_IN_MSPIKE_H2 0.001 Average reputation (+2) SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: DTJLVUF7TIU47PZ4GWXBEMIIGEFHV266 X-Message-ID-Hash: DTJLVUF7TIU47PZ4GWXBEMIIGEFHV266 X-MailFrom: Alexandre.DERUMIER@groupe-cyllene.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Alexandre Derumier --- Makefile | 2 +- PVE/Service/pveproxy.pm | 14 + rdp-web/Makefile | 18 + rdp-web/index.html.tpl | 615 +++++++++++++++++++++++++++ www/manager6/Utils.js | 31 +- www/manager6/button/ConsoleButton.js | 21 + www/manager6/qemu/Config.js | 6 +- www/manager6/qemu/DisplayEdit.js | 4 +- 8 files changed, 705 insertions(+), 6 deletions(-) create mode 100644 rdp-web/Makefile create mode 100644 rdp-web/index.html.tpl diff --git a/Makefile b/Makefile index b7dfc7f..18de847 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ DSC=$(PACKAGE)_$(DEB_VERSION).dsc DEB=$(PACKAGE)_$(DEB_VERSION)_all.deb DESTDIR= -SUBDIRS = aplinfo PVE bin www services configs kyber-web network-hooks test templates +SUBDIRS = aplinfo PVE bin www services configs kyber-web rdp-web network-hooks test templates all: $(SUBDIRS) set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done diff --git a/PVE/Service/pveproxy.pm b/PVE/Service/pveproxy.pm index bcc1353..ddc04ff 100755 --- a/PVE/Service/pveproxy.pm +++ b/PVE/Service/pveproxy.pm @@ -58,6 +58,7 @@ my $basedirs = { i18n => '/usr/share/pve-i18n', manager => '/usr/share/pve-manager', kyber => '/usr/share/pve-kyber-web', + rdp => '/usr/share/pve-rdp-web', novnc => '/usr/share/novnc-pve', yew_mobile => '/usr/share/pve-yew-mobile-gui', i18n_yew => '/usr/share/pve-yew-mobile-i18n', @@ -68,6 +69,9 @@ my $basedirs = { my $kyber_console_prefix = qr!^/api2/json/nodes/([^/]+)/qemu/(\d+)/kyber(/.*)$!; my $kyber_proxy_socket = '/run/pvekyberproxy.sock'; +my $rdp_console_prefix = qr!^/api2/json/nodes/([^/]+)/qemu/(\d+)/rdp/([^/]+)$!; +my $rdp_proxy_socket = '/run/pverdpproxy.sock'; + my sub check_console_access { my ($auth, $console_type, $node, $vmid) = @_; @@ -97,6 +101,12 @@ sub console_proxy { return { socket => $kyber_proxy_socket, path => $target, tls => 0 }; } + if (my ($node, $vmid, $token) = $path =~ $rdp_console_prefix) { + check_console_access($auth, 'RDP', $node, $vmid); + + return { socket => $rdp_proxy_socket, path => "/$vmid/$token", tls => 0 }; + } + return undef; } @@ -118,6 +128,7 @@ sub init { my $dirs = {}; add_dirs($dirs, '/kyber/' => "$basedirs->{kyber}/"); + add_dirs($dirs, '/rdp/' => "$basedirs->{rdp}/"); add_dirs($dirs, '/novnc/' => "$basedirs->{novnc}/"); add_dirs($dirs, '/pve-docs/' => "$basedirs->{docs}/"); add_dirs($dirs, '/pve-docs/api-viewer/extjs/' => "$basedirs->{extjs}/"); @@ -290,6 +301,7 @@ sub get_index { || $args->{mobile}; my $kyber = defined($args->{console}) && $args->{kyber}; + my $rdp = defined($args->{console}) && $args->{rdp}; my $novnc = defined($args->{console}) && $args->{novnc}; my $xtermjs = defined($args->{console}) && $args->{xtermjs}; @@ -336,6 +348,8 @@ sub get_index { if ($kyber) { $dir = $basedirs->{kyber}; + } elsif ($rdp) { + $dir = $basedirs->{rdp}; } elsif ($novnc) { $dir = $basedirs->{novnc}; } elsif ($xtermjs) { diff --git a/rdp-web/Makefile b/rdp-web/Makefile new file mode 100644 index 0000000..f51cfc2 --- /dev/null +++ b/rdp-web/Makefile @@ -0,0 +1,18 @@ +include ../defines.mk + +RDPDIR = $(DESTDIR)/usr/share/pve-rdp-web + +all: + +.PHONY: install +install: index.html.tpl + install -d $(RDPDIR) + install -m 0644 index.html.tpl $(RDPDIR)/index.html.tpl +# The client itself - rdp_client.js and rdp_client_bg.wasm beside it - is +# IronRDP built for the browser, shipped by pve-rdp-web rather than vendored +# here: a separate upstream with a wasm toolchain that has no business in this +# build. The page drives it directly and needs nothing else from it. + +.PHONY: clean distclean +distclean: clean +clean: diff --git a/rdp-web/index.html.tpl b/rdp-web/index.html.tpl new file mode 100644 index 0000000..eda7006 --- /dev/null +++ b/rdp-web/index.html.tpl @@ -0,0 +1,615 @@ + + + + + RDP console + + + +
+
+
+ + + + +
+ + + + + +
+
+
+
+
Connecting…
+ + + + + + + diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 44ffdfe..d707ac0 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -657,6 +657,7 @@ Ext.define('PVE.Utils', { virtio: 'VirtIO-GPU', 'virtio-gl': 'VirGL GPU', kyber: 'Kyber', + rdp: 'RDP', none: Proxmox.Utils.noneText, }, @@ -1455,6 +1456,8 @@ Ext.define('PVE.Utils', { Proxmox.Utils.openXtermJsViewer(consoleType, vmid, nodename, vmname, cmd); } else if (viewer === 'kyber') { PVE.Utils.openKyberViewer(consoleType, vmid, nodename, vmname); + } else if (viewer === 'rdp') { + PVE.Utils.openRdpViewer(consoleType, vmid, nodename, vmname); } else if (viewer === 'vv') { let url = '/nodes/' + nodename + '/spiceshell'; let params = { @@ -1478,22 +1481,27 @@ Ext.define('PVE.Utils', { }, defaultViewer: function (consoles, type) { - var allowSpice, allowXtermjs, allowKyber; + var allowSpice, allowXtermjs, allowKyber, allowRdp; if (consoles === true) { allowSpice = true; allowXtermjs = true; allowKyber = true; + allowRdp = true; } else if (typeof consoles === 'object') { allowSpice = consoles.spice; allowXtermjs = !!consoles.xtermjs; allowKyber = !!consoles.kyber; + allowRdp = !!consoles.rdp; } let dv = PVE.UIOptions.options.console || (type === 'kvm' ? 'vv' : 'xtermjs'); - // A Kyber display serves no VNC, so nothing else can show it. + // Neither display serves VNC, so nothing else can show them. if (allowKyber) { return 'kyber'; } + if (allowRdp) { + return 'rdp'; + } if (dv === 'vv' && !allowSpice) { dv = allowXtermjs ? 'xtermjs' : 'html5'; } else if (dv === 'xtermjs' && !allowXtermjs) { @@ -1542,6 +1550,24 @@ Ext.define('PVE.Utils', { } }, + // The RDP console talks to the VM's own RDP server through + // pve-rdpproxy rather than through noVNC, so it gets a window of its + // own. Its credentials are fetched by that page from rdpproxy; nothing + // is passed in the URL, so a copied link grants nothing on its own. + openRdpViewer: function (vmtype, vmid, nodename, vmname) { + let url = Ext.Object.toQueryString({ + console: vmtype, + rdp: 1, + vmid: vmid, + vmname: vmname, + node: nodename, + }); + let nw = window.open('?' + url, '_blank', 'innerWidth=1280,innerheight=800'); + if (nw) { + nw.focus(); + } + }, + openSpiceViewer: function (url, params) { var downloadWithName = function (uri, name) { var link = Ext.DomHelper.append(document.body, { @@ -1618,6 +1644,7 @@ Ext.define('PVE.Utils', { spice: !!conf.spice, xtermjs: !!conf.serial, kyber: !!conf.kyber, + rdp: !!conf.rdp, }; PVE.Utils.openDefaultConsoleWindow(consoles, 'kvm', vmid, nodename, vmname); }, diff --git a/www/manager6/button/ConsoleButton.js b/www/manager6/button/ConsoleButton.js index 63377e7..3c4406b 100644 --- a/www/manager6/button/ConsoleButton.js +++ b/www/manager6/button/ConsoleButton.js @@ -14,6 +14,7 @@ Ext.define('PVE.button.ConsoleButton', { enableXtermjs: true, // Off unless a VM says otherwise, so other guests show it greyed out. enableKyber: false, + enableRdp: false, nodename: undefined, @@ -42,6 +43,13 @@ Ext.define('PVE.button.ConsoleButton', { me.down('#kybermenu').setDisabled(!enable); }, + setEnableRdp: function (enable) { + var me = this; + + me.enableRdp = enable; + me.down('#rdpmenu').setDisabled(!enable); + }, + handler: function () { // main, general, handler let me = this; @@ -50,6 +58,7 @@ Ext.define('PVE.button.ConsoleButton', { spice: me.enableSpice, xtermjs: me.enableXtermjs, kyber: me.enableKyber, + rdp: me.enableRdp, }, me.consoleType, me.vmid, @@ -106,6 +115,18 @@ Ext.define('PVE.button.ConsoleButton', { view.openConsole(button.type); }, }, + { + xtype: 'menuitem', + itemId: 'rdpmenu', + text: 'RDP', + type: 'rdp', + iconCls: 'fa fa-fw fa-desktop', + disabled: true, + handler: function (button) { + let view = this.up('button'); + view.openConsole(button.type); + }, + }, { text: 'xterm.js', itemId: 'xtermjs', diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js index bcf54d1..79ab012 100644 --- a/www/manager6/qemu/Config.js +++ b/www/manager6/qemu/Config.js @@ -232,10 +232,11 @@ Ext.define('PVE.qemu.Config', { disabled: !caps.vms['VM.Console'], hidden: template, consoleType: 'kvm', - // disable spice/xterm/kyber for default action until status api call succeeded + // disable spice/xterm/kyber/rdp for default action until status api call succeeded enableSpice: false, enableXtermjs: false, enableKyber: false, + enableRdp: false, consoleName: vm.name, nodename: nodename, vmid: vmid, @@ -460,6 +461,7 @@ Ext.define('PVE.qemu.Config', { var spice = false; var xtermjs = false; var kyber = false; + var rdp = false; var lock; var rec; @@ -481,6 +483,7 @@ Ext.define('PVE.qemu.Config', { // 'kyber', which is also the only case with a controller // behind it. kyber = !!s.data.get('kyber'); + rdp = !!s.data.get('rdp'); } rec = s.data.get('tags'); @@ -503,6 +506,7 @@ Ext.define('PVE.qemu.Config', { consoleBtn.setEnableSpice(spice); consoleBtn.setEnableXtermJS(xtermjs); consoleBtn.setEnableKyber(kyber); + consoleBtn.setEnableRdp(rdp); statusTxt.update({ lock: lock }); diff --git a/www/manager6/qemu/DisplayEdit.js b/www/manager6/qemu/DisplayEdit.js index 79e1ea2..decc016 100644 --- a/www/manager6/qemu/DisplayEdit.js +++ b/www/manager6/qemu/DisplayEdit.js @@ -26,8 +26,8 @@ Ext.define('PVE.qemu.DisplayInputPanel', { return '4'; } else if (val === 'std' || val.match(/^qxl\d?$/) || val === 'vmware') { return '16'; - } else if (val.match(/^virtio/) || val === 'kyber') { - // kyber is a virtio-vga underneath, so it takes the same + } else if (val.match(/^virtio/) || val === 'kyber' || val === 'rdp') { + // Both are a virtio-vga underneath, so they take the same // memory as one. return '256'; } else if (get('matchNonGUIOption')) { -- 2.55.0