From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 05F1D1FF0B7 for ; Tue, 25 Aug 2026 13:36:22 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id EA2862164A; Tue, 25 Aug 2026 13:35:40 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=E2yFi/i+1EiHlL03FqqcZdscxdpzL+XAnIaNW8N0eL9u6GJUi4ZmCyMQwNY9W66/4GFXgelhU6VCj1gy3P0HTygzryEUBB5vGpm0fu3uyLAOU8TX7yGm4LJhBD3HmqQBlgB6ds7eY0+UCkytqo5qBm8f2oTeFZD8g9AtkzAGXLhr8+LWT5zygco+cwu55y5IW4l18xExrDkip4+9YUTam/pCdZvi8umuOsJCwCOhNpxfN/TMKbULb5fc+5/GvyXJBvY+pk1Qi5FknFkllrFP6aKwM0H7bGmi4irevy/h7vYUpoX96sAEmxIIosiREYawXyUscOm3QK5sYLgopkayzA== 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=rotPJmdshB/NNqkAdGDNPGDYYDqFwQ5kAMzgviVJh+POkXXwMqPoO2XE6/zvTDe6vE1gxHllbOYm7DGhVYTUioJBiTAgYx66F15R+AKKmymSGY5w9PNcsvEx+yChclhSphaPsPto3JZ1LNDAXGBEVi0xh/v8RVCgRN4+jNFxTfk0XdaX5gldCiT5rBgWKlVlHPkYSnVVDIAv3B5tus35sd7vVWuQR8a2c/I16ZTKIkRmuLeSORB1RArX9BouAOzfYVngnvLN+cud1zNnd2sibpobK7ekWq7wJiXoGyFzFLOFNfPYJ0mhH4SGbVwpjc+ZYd7MB7vZ387iB5zVB1vcdw== 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=zHloCGftGQfHIpEibSPXrWnkezpAmQ96Xx/URTAbyKWW4GDNzshlgTi8Sqz+CCwxs7Wzt/BYEoZFujaG6MuDpEMHASWiF2t3IYhPcIgBds8w/I1hvge+Xra5Ie11HP6PdNweVDeie6zZRDKbfYMhUqj48h2a1XAQd00/pfw5qrU= 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 pve-manager 07/13] ui: add rdp console Date: Tue, 25 Aug 2026 13:34:32 +0200 Message-ID: <20260825113442.947620-8-alexandre.derumier@groupe-cyllene.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260825113442.947620-1-alexandre.derumier@groupe-cyllene.com> References: <20260825113442.947620-1-alexandre.derumier@groupe-cyllene.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-ClientProxiedBy: PR3P192CA0021.EURP192.PROD.OUTLOOK.COM (2603:10a6:102:56::26) To PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM (2603:10a6:102:14b::12) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PR0P264MB3691:EE_|PATP264MB7783:EE_ X-MS-Office365-Filtering-Correlation-Id: 4edd7d0e-ad6e-4a79-6438-08df029ce5ee X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|52116014|376014|1800799024|23010399003|366016|38350700014|6133799003|3023799007|56012099006|10067099003|5023799004|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: KyQxqZ1oRn9fWit34FTQyDcrt8nVGSSx56lK7xEPT4euYuFvjpyUcVnvK6iXcM6fu3+gJJ3u4Sbl4ZBN/eJiAhSkNCVFQVT75R29GPdX4vyp3OdxDh7QlPa4l5WX3D/A3kwvaQSIVXIQsCCjKXpbrk4z9AyCqulRx7MMGN3TaBrdDVX3Nvn22zP1QQIIScPZrHoriE+eEWgIgK9Jr7O/BD3KCt1uHGFIXUAbLUoIWp0uzwl/TackyO/DTb80Hwj65IuhqyEagV275lV5oTP03s66cYSyLzY8QDfQyiI7s5iGylX1lsg5XWV0quQLXo7Ht3+mCs1S0byTryUcH0y59R8lImOkyWAMGC4sQ0eu9i2Q9bF/EstrWFm+XfEJ5qcdXvj5c7qmATzFnNsWe43nmRsKKIUgUFKRFB4qa0tm2JoHWsP++nRCl0NM/t7+//85CCdwY/BSBS3+/lF/xtG2yjrgexwanmM5Br0YoZGq+mELp3ZEM6mI3tw8WZBsYrFz8csk+DKPAvvUOxJpRzwPq40j6jhogBSB9uwXfQgTfAGjxnWjPWcPqEAnterxX9GtbdtfoLlYEnwJf7szSXNpACVB5KE5aYJ16IKb9bV6csC+N7rlCJBmSvunY/9mWVf5XoRVOiBw9FpGKKs2oYoa2SEm8dKbBe8DtXh6fYFUjvPH6RCjWHqoeSffzfp863H0/sZLUiiWenSsg8L1GPMG7Z64lA9xWttNtF82weFRfoc= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(52116014)(376014)(1800799024)(23010399003)(366016)(38350700014)(6133799003)(3023799007)(56012099006)(10067099003)(5023799004)(18002099003)(22082099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?YlJROFVLTDB2SHdxaGJBWkFvVnhIQjJJSXdOMWtnUzhUK1dCRWE0M3czMC9o?= =?utf-8?B?T1pWY1hUMEMvTjRwY2tiYVBSZWdYVXRkY3Qwb25qeWV5d21tQmZjN1hoSGc0?= =?utf-8?B?KzVtRmUxbVN6S0wxZlFYU01BQkdRTVpEeW1YWks2bWU2UzFpWXBBWjE0Z0xp?= =?utf-8?B?ci90NWI0aEdIRm0vakFJU3ZXRFFrQ2tMRDRTZ1RpR3Y2T1hEYzhITXVZejcz?= =?utf-8?B?ZXNXdnpKUkxyZGdRd0ovYmw0Q3Z1OHAveDlXTXJzd05ZaGw0LzdLZk52WWFt?= =?utf-8?B?d3Z5RUcyNGRMNnZjWndxck1uTzVtUmVhV1FFb091Nmg2cWd1SlFaNnQybTlo?= =?utf-8?B?aFl6OThwZ3NGaGQ4NEdSSy9SYjF0azN3b3h5M0xwdlZTNlUrcUhoRTc3eVhy?= =?utf-8?B?OVpvUGh6MEd6bG5JUXlISnJFYnJtUnZFSWJqNzZuWmRZeXk1YlZoTjFqVExw?= =?utf-8?B?TGZTRGpueWtNMmJTYW5pL0t6VHR1N1pQeWtXcXQyZWVsRklHbWNITHBXWDI0?= =?utf-8?B?SEdGa2FxRzh5VFdkdXFwZzFvdHBGN3Z0SjhmQnRpcVpsdmhIL3lNcVJBcDM4?= =?utf-8?B?SkRuc0lHM1UwaXE5bHJQeVJJNklETEo0M1dCanFrODkxcCsyakQ2YmFXR09E?= =?utf-8?B?UTh2UmdnR1JGa1IyOUhJdjYxem5lYXFFM3hIN0NsMzRZaE9lcW9BeFRoVHVh?= =?utf-8?B?NEtlRXN5WXpSY3dSdFNPNmEyOG9kd2REMHNHa2FjSzBVZ1lyWUhWYXJFRkxi?= =?utf-8?B?QjZ6VWdxNzNEVjZITy84QUZ1ekRlYlZ3U3J4NytDY3piVEIwVnlNa3g0bUdD?= =?utf-8?B?b1g0SkpWM1duMGppMFJxOUt3UGVacWVlbzNtWTNiRGlXYVZ4NDZRd0xraW55?= =?utf-8?B?QUx3NWt3L1Y3Qm1MZGZXbE5OTU1jcGFCLy9pVmdWTVQvQ1QrUU15SFBwY290?= =?utf-8?B?eWs0bGpYWUFWWUhxK2YxWGdzYTJmNFlBaFhiTHBTK0JoTWlMSnp4TEg5TEUz?= =?utf-8?B?SnY0N1FLOFFCalUwclREMEUvR2c5TnB4MTBwZmlSdENtaVJ0QW1aUGR2NDYv?= =?utf-8?B?ZDByMTdZUTZXN3FyU2Y3cXQ5ZFhzdTk0OFBqTkk4NmFWWUdNOFZnOW1xaEVv?= =?utf-8?B?SFFob3U4Z2Q0SGFlaGVXMU1ZQ0dWUlZkbWdIamh4YjJMSTVPT0c3NGcwY0pD?= =?utf-8?B?S3F0SkVNWnhHa2I4KzRZck5Od21EZTZpSzRlUmorNTRYMzBQVzlIVXVjZzlm?= =?utf-8?B?aU9POXRlOFpVNVZrL0o3b2F1YllnclN5R1Z6dFZHNUdReWdPSVdWN0JSMEYr?= =?utf-8?B?MVowOU5sRHNWempGQUprYXFBTi9aZGNzTktjcGswK0pOdGY3VS9BekZ2T3pY?= =?utf-8?B?Y0Q1cjQ3UWFHRHdLY3g5ZWZVM1FTOGFMZzYwRWhPeGJTUTRndnE5SmVrV25O?= =?utf-8?B?V09SWGlpaEdIWXRiTDZXR1oxL3ExMHFEdG5idm1UU1o3VXJ1d2d2VGlqdEk3?= =?utf-8?B?eDV0dDR6c25WS0M0ckErVllxSGNSODk5SndnekFxZUFpL1Q0UERTVERhbkFo?= =?utf-8?B?ZnNETmlzVThacDltbzJ0NzhCemFXU2FmQ0lKY2ZQZVpaU2NCYjRuTE9hbWlp?= =?utf-8?B?dE9sZjlMbFFMdlhhc3gxUTJieTNmd1VHMms1cFhDblZMZlJMSkQyTFl1Q2Y5?= =?utf-8?B?MEF3ZFBTeVFIWEtXV0ZjVEZEODl5dGRWbDhkYUlGajllOG9Yd2RrZTk5N1VZ?= =?utf-8?B?MnNnSHpQQnBIV2taazJZSVhhQ2VIWWNiM0VMTTVDcHRvSU9sQUJXanhkbG5R?= =?utf-8?B?TWFyL04wMWlBU2wxOXU0Q1ZsNkJrZGd1YUZYVTFQM2FwU05NM3JBdDdXeHRq?= =?utf-8?B?ODQ1bHZ6YUsycllVMFd3cCt2SVVhakZ1TG9obFYrM3M0dU1UOHE0c3NZeUxx?= =?utf-8?B?UUdwUzNlQTcrR0ZxYnAxTXJ0M2E4aGZQYXUrU29RcnJoa2xEL1R6Z3g3QmRi?= =?utf-8?B?NHE5cWhBdmsreTZmV21QaitIdVkwMXcxT0x0cDlCcHhKMnB2TGpBbUlQYVFN?= =?utf-8?B?b2s0dFc0SFhMUmMwRW5NZHZhWEI1S21GcXkzUWJGRFJ2Zi9oUHFWNUtrREI4?= =?utf-8?B?Wnp0Z2JUdnN2ZkpKNXVMQkV6eGFEdnJtMWJmTlgrcENteExpVEd1NXhaTFlw?= =?utf-8?B?Zy9nSVhOVGtQYVFjMkoxY2ZmcE5Ua2R5RFZ6dmU5aXNTRU55YjJLbER3RXVR?= =?utf-8?B?NmI1WVFhNmhLTzBEdDRNMEQ2OWw1bHpLWHVURmR1SW9jTU8wKy8vcjJqNnR3?= =?utf-8?B?aHpMN0NRL0V6UmtEOU1LYStwS3VJdVpqWEV1TlQ0dy95Y2VLc2l4TVMraWpK?= =?utf-8?Q?XbetQfN44xzFavWWJzGaO3/RPrlUQyjF/qvJi?= X-OriginatorOrg: groupe-cyllene.com X-MS-Exchange-CrossTenant-Network-Message-Id: 4edd7d0e-ad6e-4a79-6438-08df029ce5ee X-MS-Exchange-CrossTenant-AuthSource: PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Aug 2026 11:35:00.8635 (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: lxacQoVdA82GrkkhWthZzJa2BzLv/idjikCiCywxgudmHFrzJ056iSz0e1KkJdiZjB8o+MUjzmsgAcaCXDCwaxRjRLP273b422kDFnVguXWwKQqpPyhtpp+x1hrwZa4f X-MS-Exchange-Transport-CrossTenantHeadersStamped: PATP264MB7783 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.050 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 SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: TRU4PHTU6ZKX42YAX4MASJ7UDK47ROYE X-Message-ID-Hash: TRU4PHTU6ZKX42YAX4MASJ7UDK47ROYE 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