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 0ED391FF0B7 for ; Tue, 25 Aug 2026 13:36:09 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id AC49D21603; Tue, 25 Aug 2026 13:35:28 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=HnVEM/zwLgUNn9jFsdBQysvcHkcAdbfGjE7EvDMCCgHdXOvbt5Imy3CkXF4O3bnDLkxML40+o5MJ/G0ju/NQ02+3S8fSONsssPnTOHoBGX24lm1RazAV+cLl3qhFqwCLUUrOmY20BWiuIF3s+aGo6a0fls9cBVsqODwqGAkQ54EYAcy5toElgDDTsvVig2dYcvmJPHZd16QDE8acI1B73evc/bie7Y//T/MUgBSLBxhZ7xH1pKuFJqjDSLSX2QYH8xVWOwJpopqsb3v5GRUybM0Pz435NCT+QlK4qDFFmgcXWzR3aX5zCGCfxNEL6+fXUj8yKfZXU87UFg+IyyiWxw== 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=pga7Byghco+jTnGolE/x9FYJrv6t7Qp3GpqlyP4rRsA=; b=pdamBsf5AkBu9gQq2co4MYp6P+NQfVOEjF/QyVtqwDS8M0gUEKnV05RwziA/wh+BzA9J5NueKdCWmTKGbcBtNHcT9G+fcmHzDNRh9dgJIUN17Ks8kBxtsToYvyIUxQg6/Gzf57En3BtvM+7yVhkhIqkYpsDoMbmoHs677J6yJ9oRAXsK2n4shYK6URIgNnL7OdN5udCVW0duC4lS6IZ0abjn4LrK/yB/J9owr6x/UERYMUFKyGxT0+Q1wPoclO/eTadfHFCJnqxqJGRy9FhS2g63zlTbDuQI/x4c4DhRr0lxGTrPOwoeJj5Z/maYWIHZNLrhl/+KAKSzHfZOGlPdVA== 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=pga7Byghco+jTnGolE/x9FYJrv6t7Qp3GpqlyP4rRsA=; b=UKiiP143//clblT8vYtxD7TcOYWCNsmBrFVV7m/ArpH23/6zbEGolZurpiCC/ID1BPssOIoNfn3D7RQSTjHNjEf388tU+ZSJNa7DLU6ap23rDL7BYR9rT25+fH31PGIjUTt3auwvAVMyHphKS/vmhEOWoCOLmzxkYmv0e/YvYsg= 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 06/13] ui: add kyber console Date: Tue, 25 Aug 2026 13:34:31 +0200 Message-ID: <20260825113442.947620-7-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: 5a57100f-0aba-47f2-7f5e-08df029ce5b2 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: zbrSnT/PGsGY1DDyhg3JV+w87tgoBxhrY7+91Nc1Q7GQTtmLcor1OpJXq/CzOV6nNNhRhojpy8ZgOeMCXPlK1+znpLRtujHOgp24/GMSceHBJqExTZdwPwfEKu9tV1hRRD8ublzoFeElE85Hrbl6EiF0TqJqCuoAv3xKRWavQxxmpn/8hTr9GjcDBntFAUN3ITrXhPg25D4CYEGuiVufW6+CHWH4A8wL+z/JlFkgWkZSOi2nVGnnnOpC64E8U3fiyk/YYXrjKQ6EKNc8VGKXD8hsQusuPwQbqOC4ZNjIiXjF0HNhBqklFlugrljAHpu64q5sSh8f8/s31tVIvbe5DMKict0aK8yCs/rOaYzvbQ0HLr6/+Y4I2isTgnORsjcRBq/3ftRCC6kWxSTYkUdaebcle9DjOqqyLHDiAzwdD2SO2Gv0Xv6WoRGCNzyAWOKqyCGY9gM25s+yXWbFRFE9L/Z1zjY9fV2cuYWd1HnpMVIJ0hAHZPYuHljjqCgYS1jVLdYVHyOxKeZ3wjMCcAZ+26O6LGeWjWMptYHsMs42WBD+gxyJEh0vyIBLdF2H/teGUlT14gRFb/PfzNZZNEv7ppHozDgSfiAzFu+yEZl03ov06Szv4P2DMzNRw+lfM5U+WC7ZkuHilCayCBxLF2fGujthEF4kWq6JnrOu9HljCpFz50BNI+a9CgtivPzHo7j/PCf2ZOpHwe6f4ay6LadUcX4YEZaZSS3DcvMqDHc3Nrs= 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?cGJLWmJUTmRCNkllSXlSckpvU3E0WlFFWCtCWm8wNDRRdWdkd3luZTA4ZzZJ?= =?utf-8?B?TVdqVmhXTEkzWUZlQlAvcjQzSDVXNEV1bWFqak5UQXYxM1VpZ3Q1czNEYUdR?= =?utf-8?B?Rm0yTHFDOEs1ZkxZUUpULzlUclpoK2RSRGFSVTc0SzQrc2pEcUVrcU1YdEJH?= =?utf-8?B?ZDQ0ZFNNbWhiaWpUWXhZOHFBRVBZVGw5MUhIM2R6Snpvd1FFdjBpcEE1dTMr?= =?utf-8?B?WnFtUS9QbnBSK2V1dTdYc00vYXlXbHZadTlqQjE1TWlZdGlma3ZsZ0paMmln?= =?utf-8?B?MXhNY1BDdXVQTDZ4OGZnL01BbFRFanpxLzh3RFk3VGV3QnhaZ0lMSDE5bWhh?= =?utf-8?B?STZiRnZ2ZXVhWnk4dUcyUm9uS1dmQ1pxTm90ekF5MDRRbXdFK0N5eHBnQkE4?= =?utf-8?B?UjQza3p2MWZyWWRJUTlGZG9ENDM4Sm5qTE15dGovakVxT3l1enZNWFY4UitF?= =?utf-8?B?SEVnWXE5allRd1gyMlQ4V3lyNis1N0huZDViZDBqNVdhQ1VJSWhmcVdOekQ1?= =?utf-8?B?Zm13dS9zeWtueTc4azVuZFBmUUtZcmJ0ZER0emxwWGdnUTZkejAwcUZsMmoz?= =?utf-8?B?N2ppcjM1b2puQitjMlVYakJrWFUyVlpvSGRtbTR0elRkR2ZuUGttY3B5QmR2?= =?utf-8?B?eHl0MUF4bmE0UmZUelFMSDFmTVlxMDZXT3lUNS9QVVZwTWtocEdsdWlUeVFk?= =?utf-8?B?WTBwOUpXdCtXb1MrZGNEb1RnMjNtTFpvYkdib2NRQnB4Ri93aHRBYWliU0k5?= =?utf-8?B?NDh0enpZUGxDSGhrdWNGdTVSY1d4UEZKMis0NEx6cmtzdHdyUjhIZXN0Ym9m?= =?utf-8?B?WFV0bnh4WUV2RS9Ga0xYVjlNRzk0L3JSZ0tOYno4MmYrVFNYQnhBQzU4bUk4?= =?utf-8?B?cVIxQmxCcjUrSmlxWjYvTVNZTHl0OXJJZnpUM3hwL2M0NEFia3Y2b0w2d2FB?= =?utf-8?B?ODdkU1l5THc0ZzRkNGU4cG80UEFDcFZyS2tueDdPZFM1a2pCTWhwV0J3cDd3?= =?utf-8?B?cGZYeHR1RnpxODJWTDB3c0tUSktsdHBZQUpTY0dRaDdJTzVOMW50akRFUW9Q?= =?utf-8?B?RGtFY05Zb3c3UWg3SUN1VkVwcWxMbVRXUzFpVU1JUkhYMUYwV0ZRdFg0NDNw?= =?utf-8?B?VFE2VHBVbURJZEVZUkhGS3EvMjBIOHRURTlwQnRxcW0rV09RNGNIVU1XQzBH?= =?utf-8?B?ZUdmaDREMHQrNDZQeGZGL1dRVW05R0NLMThDdTEzKy9YMzJGZGtMdlBOVktG?= =?utf-8?B?TEhVejFQcGp5akFLdG1xTFp0VkN5SVQ3Z0FpVjhrUjhPYm1hblBMYUZXUWxT?= =?utf-8?B?RXRwWFhraG5ocnAxbVROZ0NkUkREaHhmcTJjaGVvbnluOENQK0xFWW1Gbmpx?= =?utf-8?B?MkdRNDRUdWpvNllITzdjNnVyODFFZE1BcG1qUkg5SUZHaXdmSmlVdDBEbXd6?= =?utf-8?B?NkZjNzBxOEM0d2RkKzF3M1o0bEFDcEhNUURLRWFydlFtYnlJRDRBOXBpMlh3?= =?utf-8?B?S1A0VGlTOXdtZXlidGJBYnlRMnU3WTQyOEw1YkxWcUdxZGx6WlpQM3dxSHJD?= =?utf-8?B?dFQxcVRoaFN5RTVsWlpCNk5qSytSN1NwSVBaU04yOHZHMTBaMmVtM2E5aVo3?= =?utf-8?B?c0Q5bnFYb3Jqb0p4bXZzUFlFNzZ6SjVXd1JHK3IrbWFvcHkralJ2OHdURk1x?= =?utf-8?B?UWxranVwRHFkb3MyK2hmdHg2OE9yN05ubUFyR0lFTFl3aTNldHhKU09Kby81?= =?utf-8?B?UDFYckJpZXhDYnpyd0JyS2FuYXdtbFRQWFNrUHdZSk1jSS82ZEZ6b1JRZWtm?= =?utf-8?B?WGZZejc5RlU1UEpPWm5vUjN6QUdmSDBONml5MWVKWEdnY25DN081VEYxc3pT?= =?utf-8?B?cVpJbXhIL1pnQzVzMS91Y0Ryb2pjbnV0Y25MdmlzMWN0MjhTdm5Cc2dmZ2F2?= =?utf-8?B?bEFpQjBveGxFR3dvZTJ3bXR0WU1vN1BnMytURitSQm9yeHJpdjRHYS9zZ2o1?= =?utf-8?B?WXN6eUpCQU9sOC9yYjFVTmwvb3hyektaQ1ltYlgrT3JzdEpET2FTK1k0TlVE?= =?utf-8?B?NWN1cFZIZ2cwWStRSDM4aVNtSlFVVE9EL2pWMklyOURnVFdiY1pTQ29KNjlL?= =?utf-8?B?Rjk2V295a3V1YXIxbmZHMEIrckFlVXRub1hIVVhBK0UyRy9Nd1RqWERCbEt5?= =?utf-8?B?LzkxOVJRbE1vbFVHaURnU0Nncm1nSGlMNW82dUJMemVGTkFWQ1Q5RHdXK1ow?= =?utf-8?B?U3BHZUdmay94cURNL2lMMDBiKzRHcHRIZ3RaZnpXakpIVmYyR0NxSms4MmJX?= =?utf-8?B?cU81ZnB4T1BTUWkrMFJwbzJEWXcwQzZ5SWhJMnlRV0JpYXRRaFFsNmJ2cU1U?= =?utf-8?Q?65O8q4p9hDUJvsxPTwWlNyX79C26mzaYe6mrF?= X-OriginatorOrg: groupe-cyllene.com X-MS-Exchange-CrossTenant-Network-Message-Id: 5a57100f-0aba-47f2-7f5e-08df029ce5b2 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.3672 (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: s16g0NQ2ROdaSaHU7VKiiov70LVI7rCPr9/VelR+BJuzJAD8HpzfgczMoKCvLAwXvh5ir705FcggHG7vLCMOpKz1PV1OQUtj8zPq3YnhFYI0RjUePQVBgsjRMgSmzyq2 X-MS-Exchange-Transport-CrossTenantHeadersStamped: PATP264MB7783 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.057 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: YKMFVCFIRAM2A7ZZTXKDGQVUHXI5HP4O X-Message-ID-Hash: YKMFVCFIRAM2A7ZZTXKDGQVUHXI5HP4O 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/HTTPServer.pm | 4 +- PVE/Service/pveproxy.pm | 53 +++- kyber-web/Makefile | 22 ++ kyber-web/index.html.tpl | 438 +++++++++++++++++++++++++++ www/manager6/Utils.js | 30 +- www/manager6/button/ConsoleButton.js | 22 ++ www/manager6/qemu/AudioEdit.js | 3 + www/manager6/qemu/Config.js | 9 +- www/manager6/qemu/DisplayEdit.js | 4 +- 10 files changed, 581 insertions(+), 6 deletions(-) create mode 100644 kyber-web/Makefile create mode 100644 kyber-web/index.html.tpl diff --git a/Makefile b/Makefile index 6c8b82e..b7dfc7f 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 network-hooks test templates +SUBDIRS = aplinfo PVE bin www services configs kyber-web network-hooks test templates all: $(SUBDIRS) set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done diff --git a/PVE/HTTPServer.pm b/PVE/HTTPServer.pm index 34403e8..c3fef48 100755 --- a/PVE/HTTPServer.pm +++ b/PVE/HTTPServer.pm @@ -120,11 +120,13 @@ sub auth_handler { $isUpload = 1; } + my $is_kyber_console = $rel_uri =~ m|^/nodes/[^/]+/qemu/\d+/kyber/|; + # Skip CSRF check for file upload (difficult to pass CSRF header with native html forms). # Also skip the check with API tokens, as one of the design goals of API tokens was to # provide stateless API access without requiring round-trips to get such CSRF tokens. # CSRF-prevention also does not make much sense outside of the browser context. - if ($method ne 'GET' && !($api_token || $isUpload)) { + if ($method ne 'GET' && !($api_token || $isUpload || $is_kyber_console)) { my $euid = $>; PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0; } diff --git a/PVE/Service/pveproxy.pm b/PVE/Service/pveproxy.pm index dfdd014..bcc1353 100755 --- a/PVE/Service/pveproxy.pm +++ b/PVE/Service/pveproxy.pm @@ -20,6 +20,10 @@ use PVE::Cluster; use PVE::Daemon; use PVE::DataCenterConfig; use PVE::HTTPServer; +use HTTP::Status qw(HTTP_BAD_REQUEST); +use PVE::Exception; +use PVE::INotify; +use PVE::RPCEnvironment; use PVE::SafeSyslog; use PVE::pvecfg; use PVE::Tools; @@ -53,6 +57,7 @@ my $basedirs = { fontlogos => '/usr/share/fonts-font-logos', i18n => '/usr/share/pve-i18n', manager => '/usr/share/pve-manager', + kyber => '/usr/share/pve-kyber-web', novnc => '/usr/share/novnc-pve', yew_mobile => '/usr/share/pve-yew-mobile-gui', i18n_yew => '/usr/share/pve-yew-mobile-i18n', @@ -60,6 +65,41 @@ my $basedirs = { xtermjs => '/usr/share/pve-xtermjs', }; +my $kyber_console_prefix = qr!^/api2/json/nodes/([^/]+)/qemu/(\d+)/kyber(/.*)$!; +my $kyber_proxy_socket = '/run/pvekyberproxy.sock'; + +my sub check_console_access { + my ($auth, $console_type, $node, $vmid) = @_; + + PVE::Exception::raise( + "the $console_type console for VM $vmid must be opened on node '$node'\n", + code => HTTP_BAD_REQUEST, + ) if $node ne PVE::INotify::nodename(); + + my $rpcenv = PVE::RPCEnvironment::get(); + $rpcenv->check($auth->{userid}, "/vms/$vmid", ['VM.Console']); + + return; +} + +sub console_proxy { + my ($server, $reqstate, $auth, $method, $path) = @_; + + if (my ($node, $vmid, $rest) = $path =~ $kyber_console_prefix) { + check_console_access($auth, 'Kyber', $node, $vmid); + + my $target = $path; + + if (my $query = $reqstate->{request}->url->query()) { + $target .= "?$query"; + } + + return { socket => $kyber_proxy_socket, path => $target, tls => 0 }; + } + + return undef; +} + sub init { my ($self) = @_; @@ -77,6 +117,7 @@ sub init { my $dirs = {}; + add_dirs($dirs, '/kyber/' => "$basedirs->{kyber}/"); add_dirs($dirs, '/novnc/' => "$basedirs->{novnc}/"); add_dirs($dirs, '/pve-docs/' => "$basedirs->{docs}/"); add_dirs($dirs, '/pve-docs/api-viewer/extjs/' => "$basedirs->{extjs}/"); @@ -134,6 +175,7 @@ sub init { }, }, dirs => $dirs, + local_http_proxy_handler => \&console_proxy, }; if (defined($proxyconf->{DHPARAMS})) { @@ -247,6 +289,7 @@ sub get_index { (is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile})) || $args->{mobile}; + my $kyber = defined($args->{console}) && $args->{kyber}; my $novnc = defined($args->{console}) && $args->{novnc}; my $xtermjs = defined($args->{console}) && $args->{xtermjs}; @@ -291,7 +334,9 @@ sub get_index { # by default, load the normal index my $dir = $basedirs->{manager}; - if ($novnc) { + if ($kyber) { + $dir = $basedirs->{kyber}; + } elsif ($novnc) { $dir = $basedirs->{novnc}; } elsif ($xtermjs) { $dir = $basedirs->{xtermjs}; @@ -305,6 +350,12 @@ sub get_index { $template->process("$dir/index.html.tpl", $vars, \$page) || die $template->error(), "\n"; my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8"); + + if ($kyber) { + $headers->header('Cross-Origin-Opener-Policy' => 'same-origin'); + $headers->header('Cross-Origin-Embedder-Policy' => 'require-corp'); + } + my $resp = HTTP::Response->new(200, "OK", $headers, $page); return $resp; diff --git a/kyber-web/Makefile b/kyber-web/Makefile new file mode 100644 index 0000000..28b07a0 --- /dev/null +++ b/kyber-web/Makefile @@ -0,0 +1,22 @@ +include ../defines.mk + +KYBERDIR = $(DESTDIR)/usr/share/pve-kyber-web + +all: + +.PHONY: install +install: index.html.tpl + install -d $(KYBERDIR) + install -m 0644 index.html.tpl $(KYBERDIR)/index.html.tpl +# The client itself - kyclient_wasm.js, kyclient_wasm_bg.wasm and the audio +# worklet beside them - is built from kyber-web and shipped by its own package +# rather than vendored here. It is AGPL-3.0-or-later, same as pve-manager, but +# it is a separate upstream with its own release cadence and a wasm toolchain +# that has no business in this build. +# +# The page drives that SDK directly and needs nothing else from it: no bundle, +# no stylesheets, and none of the demo client's UI. + +.PHONY: clean distclean +distclean: clean +clean: diff --git a/kyber-web/index.html.tpl b/kyber-web/index.html.tpl new file mode 100644 index 0000000..31f5e76 --- /dev/null +++ b/kyber-web/index.html.tpl @@ -0,0 +1,438 @@ + + + + + Kyber console + + + + + +
+
+
+ + + +
+ + + + + +
+
+
+
+
Connecting…
+ + + + diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index c86a00c..44ffdfe 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -656,6 +656,7 @@ Ext.define('PVE.Utils', { serial3: gettext('Serial terminal') + ' 3', virtio: 'VirtIO-GPU', 'virtio-gl': 'VirGL GPU', + kyber: 'Kyber', none: Proxmox.Utils.noneText, }, @@ -1452,6 +1453,8 @@ Ext.define('PVE.Utils', { PVE.Utils.openVNCViewer(consoleType, vmid, nodename, vmname, cmd); } else if (viewer === 'xtermjs') { Proxmox.Utils.openXtermJsViewer(consoleType, vmid, nodename, vmname, cmd); + } else if (viewer === 'kyber') { + PVE.Utils.openKyberViewer(consoleType, vmid, nodename, vmname); } else if (viewer === 'vv') { let url = '/nodes/' + nodename + '/spiceshell'; let params = { @@ -1475,16 +1478,22 @@ Ext.define('PVE.Utils', { }, defaultViewer: function (consoles, type) { - var allowSpice, allowXtermjs; + var allowSpice, allowXtermjs, allowKyber; if (consoles === true) { allowSpice = true; allowXtermjs = true; + allowKyber = true; } else if (typeof consoles === 'object') { allowSpice = consoles.spice; allowXtermjs = !!consoles.xtermjs; + allowKyber = !!consoles.kyber; } let dv = PVE.UIOptions.options.console || (type === 'kvm' ? 'vv' : 'xtermjs'); + // A Kyber display serves no VNC, so nothing else can show it. + if (allowKyber) { + return 'kyber'; + } if (dv === 'vv' && !allowSpice) { dv = allowXtermjs ? 'xtermjs' : 'html5'; } else if (dv === 'xtermjs' && !allowXtermjs) { @@ -1515,6 +1524,24 @@ Ext.define('PVE.Utils', { } }, + // The Kyber console streams the display over its own controller rather + // than through noVNC, so it gets a window of its own. Everything it + // needs is fetched by that page from kyberproxy; nothing is passed in + // the URL, so a copied link grants nothing on its own. + openKyberViewer: function (vmtype, vmid, nodename, vmname) { + let url = Ext.Object.toQueryString({ + console: vmtype, + kyber: 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, { @@ -1590,6 +1617,7 @@ Ext.define('PVE.Utils', { let consoles = { spice: !!conf.spice, xtermjs: !!conf.serial, + kyber: !!conf.kyber, }; PVE.Utils.openDefaultConsoleWindow(consoles, 'kvm', vmid, nodename, vmname); }, diff --git a/www/manager6/button/ConsoleButton.js b/www/manager6/button/ConsoleButton.js index d64f280..63377e7 100644 --- a/www/manager6/button/ConsoleButton.js +++ b/www/manager6/button/ConsoleButton.js @@ -12,6 +12,8 @@ Ext.define('PVE.button.ConsoleButton', { enableSpice: true, enableXtermjs: true, + // Off unless a VM says otherwise, so other guests show it greyed out. + enableKyber: false, nodename: undefined, @@ -33,6 +35,13 @@ Ext.define('PVE.button.ConsoleButton', { me.down('#xtermjs').setDisabled(!enable); }, + setEnableKyber: function (enable) { + var me = this; + + me.enableKyber = enable; + me.down('#kybermenu').setDisabled(!enable); + }, + handler: function () { // main, general, handler let me = this; @@ -40,6 +49,7 @@ Ext.define('PVE.button.ConsoleButton', { { spice: me.enableSpice, xtermjs: me.enableXtermjs, + kyber: me.enableKyber, }, me.consoleType, me.vmid, @@ -84,6 +94,18 @@ Ext.define('PVE.button.ConsoleButton', { view.openConsole(button.type); }, }, + { + xtype: 'menuitem', + itemId: 'kybermenu', + text: 'Kyber', + type: 'kyber', + 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/AudioEdit.js b/www/manager6/qemu/AudioEdit.js index ba588ed..49f03e1 100644 --- a/www/manager6/qemu/AudioEdit.js +++ b/www/manager6/qemu/AudioEdit.js @@ -35,6 +35,9 @@ Ext.define('PVE.qemu.AudioInputPanel', { fieldLabel: gettext('Backend Driver'), comboItems: [ ['spice', 'SPICE'], + // Puts the guest's audio on the same D-Bus as its display, + // which is where the Kyber and RDP consoles read it. + ['dbus', gettext('D-Bus (Kyber/RDP console)')], ['none', `${Proxmox.Utils.NoneText} (${gettext('Dummy Device')})`], ], }, diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js index 842d35d..bcf54d1 100644 --- a/www/manager6/qemu/Config.js +++ b/www/manager6/qemu/Config.js @@ -232,9 +232,10 @@ Ext.define('PVE.qemu.Config', { disabled: !caps.vms['VM.Console'], hidden: template, consoleType: 'kvm', - // disable spice/xterm for default action until status api call succeeded + // disable spice/xterm/kyber for default action until status api call succeeded enableSpice: false, enableXtermjs: false, + enableKyber: false, consoleName: vm.name, nodename: nodename, vmid: vmid, @@ -458,6 +459,7 @@ Ext.define('PVE.qemu.Config', { var qmpstatus; var spice = false; var xtermjs = false; + var kyber = false; var lock; var rec; @@ -475,6 +477,10 @@ Ext.define('PVE.qemu.Config', { spice = !!s.data.get('spice'); xtermjs = !!s.data.get('serial'); + // Reported by the status API only for a display set to + // 'kyber', which is also the only case with a controller + // behind it. + kyber = !!s.data.get('kyber'); } rec = s.data.get('tags'); @@ -496,6 +502,7 @@ Ext.define('PVE.qemu.Config', { consoleBtn.setEnableSpice(spice); consoleBtn.setEnableXtermJS(xtermjs); + consoleBtn.setEnableKyber(kyber); statusTxt.update({ lock: lock }); diff --git a/www/manager6/qemu/DisplayEdit.js b/www/manager6/qemu/DisplayEdit.js index 3f583ad..79e1ea2 100644 --- a/www/manager6/qemu/DisplayEdit.js +++ b/www/manager6/qemu/DisplayEdit.js @@ -26,7 +26,9 @@ Ext.define('PVE.qemu.DisplayInputPanel', { return '4'; } else if (val === 'std' || val.match(/^qxl\d?$/) || val === 'vmware') { return '16'; - } else if (val.match(/^virtio/)) { + } else if (val.match(/^virtio/) || val === 'kyber') { + // kyber is a virtio-vga underneath, so it takes the same + // memory as one. return '256'; } else if (get('matchNonGUIOption')) { return 'N/A'; -- 2.55.0