From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id D2B201FF183 for ; Wed, 13 Aug 2025 10:28:11 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0859C1EB45; Wed, 13 Aug 2025 10:29:51 +0200 (CEST) From: Shan Shaji To: pve-devel@lists.proxmox.com Date: Wed, 13 Aug 2025 10:29:37 +0200 Message-ID: <20250813082937.22142-1-s.shaji@proxmox.com> X-Mailer: git-send-email 2.50.1 MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1755073755323 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.201 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH pve_flutter_frontend v3] ui: enable noVNC console on iOS X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" The noVNC console was disabled in iOS and was only available in android. To fix the issue enabled the noVNC console view on iOS devices. The changes also includes a refactor of the function responsible for displaying the webview. Additionally, an `AppBar` has been added to the console view on to allow users to easily close the view. To avoid the body of the Scaffold to resize when the keyboard pops up set `resizeToAvoidBottomInset` [0] to `false` The `dart format` command was also ran in the file to fix the file formatting. - [0] https://api.flutter.dev/flutter/material/Scaffold/resizeToAvoidBottomInset.html Signed-off-by: Shan Shaji --- changes since v2: - Set `resizeToAvoidBottomInset` top false to avoid the Scaffold body to resize automatically when the keyboard pops up. - Update commit message. changes since v1: - Rebased with master. - Update commit message. lib/widgets/pve_console_menu_widget.dart | 176 +++++++++++------------ 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/lib/widgets/pve_console_menu_widget.dart b/lib/widgets/pve_console_menu_widget.dart index 473595d..8f1b889 100644 --- a/lib/widgets/pve_console_menu_widget.dart +++ b/lib/widgets/pve_console_menu_widget.dart @@ -97,45 +97,19 @@ class PveConsoleMenu extends StatelessWidget { } }, ), - if (Platform.isAndroid) // web_view is only available for mobile :( + + // web_view is only available for mobile :( + // xterm.js doesn't work that well on mobile + if (Platform.isAndroid || Platform.isIOS) ListTile( title: const Text( - //type == "qemu" ? "noVNC Console" : "xterm.js Console", - "noVNC Console", // xterm.js doesn't work that well on mobile + "noVNC Console", style: TextStyle(fontWeight: FontWeight.bold), ), subtitle: const Text("Open console view"), - onTap: () async { - if (Platform.isAndroid) { - if (['qemu', 'lxc'].contains(type)) { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.immersive); - Navigator.of(context) - .push(_createHTMLConsoleRoute()) - .then((completion) { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.edgeToEdge, - overlays: [ - SystemUiOverlay.top, - SystemUiOverlay.bottom - ]); - }); - } else if (type == 'node') { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.immersive); - Navigator.of(context) - .push(_createHTMLConsoleRoute()) - .then((completion) { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.edgeToEdge, - overlays: [ - SystemUiOverlay.top, - SystemUiOverlay.bottom - ]); - }); - } - } else { - print('not implemented for current platform'); + onTap: () { + if (['qemu', 'lxc'].contains(type) || type == 'node') { + _openNoVncConsole(context); } }, ), @@ -145,6 +119,18 @@ class PveConsoleMenu extends StatelessWidget { ); } + Future _openNoVncConsole(BuildContext context) async { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + await Navigator.push(context, _createHTMLConsoleRoute()); + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.edgeToEdge, + overlays: [ + SystemUiOverlay.top, + SystemUiOverlay.bottom, + ], + ); + } + void showTextDialog(BuildContext context, String title, String content) { showDialog( context: context, @@ -226,74 +212,80 @@ class PVEWebConsoleState extends State { WidgetsFlutterBinding.ensureInitialized(); return FutureBuilder( - future: CookieManager.instance().setCookie( - url: WebUri(consoleUrl), - name: 'PVEAuthCookie', - value: ticket, - ), - builder: (context, snapshot) { - return SafeArea( - child: InAppWebView( - onReceivedServerTrustAuthRequest: (controller, challenge) async { - final cert = challenge.protectionSpace.sslCertificate; - final certBytes = cert?.x509Certificate?.encoded; - final sslError = challenge.protectionSpace.sslError?.message; + future: CookieManager.instance().setCookie( + url: WebUri(consoleUrl), + name: 'PVEAuthCookie', + value: ticket, + ), + builder: (context, snapshot) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar(), + body: InAppWebView( + onReceivedServerTrustAuthRequest: (controller, challenge) async { + final cert = challenge.protectionSpace.sslCertificate; + final certBytes = cert?.x509Certificate?.encoded; + final sslError = challenge.protectionSpace.sslError?.message; - String? issuedTo = cert?.issuedTo?.CName.toString(); - String? hash = certBytes != null - ? sha256.convert(certBytes).toString() - : null; + String? issuedTo = cert?.issuedTo?.CName.toString(); + String? hash = certBytes != null + ? sha256.convert(certBytes).toString() + : null; - final settings = - await ProxmoxGeneralSettingsModel.fromLocalStorage(); + final settings = + await ProxmoxGeneralSettingsModel.fromLocalStorage(); - bool trust = false; - if (hash != null && settings.trustedFingerprints != null) { - trust = settings.trustedFingerprints!.contains(hash); - } + bool trust = false; + if (hash != null && settings.trustedFingerprints != null) { + trust = settings.trustedFingerprints!.contains(hash); + } - if (!trust) { - // format hash to '01:23:...' format - String? formattedHash = hash?.toUpperCase().replaceAllMapped( - RegExp(r"[a-zA-Z0-9]{2}"), - (match) => "${match.group(0)}:"); - formattedHash = formattedHash?.substring( - 0, formattedHash.length - 1); // remove last ':' + if (!trust) { + // format hash to '01:23:...' format + String? formattedHash = hash?.toUpperCase().replaceAllMapped( + RegExp(r"[a-zA-Z0-9]{2}"), (match) => "${match.group(0)}:"); + formattedHash = formattedHash?.substring( + 0, formattedHash.length - 1); // remove last ':' - if (context.mounted) { - trust = await showTLSWarning( - context, - sslError ?? 'An unknown TLS error has occurred', - issuedTo ?? 'unknown', - formattedHash ?? 'unknown'); - } + if (context.mounted) { + trust = await showTLSWarning( + context, + sslError ?? 'An unknown TLS error has occurred', + issuedTo ?? 'unknown', + formattedHash ?? 'unknown'); } + } - // save Fingerprint - if (trust && hash != null) { - await settings - .rebuild((b) => b..trustedFingerprints.add(hash)) - .toLocalStorage(); - print(settings.toJson()); - } + // save Fingerprint + if (trust && hash != null) { + await settings + .rebuild((b) => b..trustedFingerprints.add(hash)) + .toLocalStorage(); + print(settings.toJson()); + } - final action = trust - ? ServerTrustAuthResponseAction.PROCEED - : ServerTrustAuthResponseAction.CANCEL; - return ServerTrustAuthResponse(action: action); - }, - onWebViewCreated: (controller) { - webViewController = controller; - controller.loadUrl( - urlRequest: URLRequest(url: WebUri(consoleUrl))); - }, - ), - ); - }); + final action = trust + ? ServerTrustAuthResponseAction.PROCEED + : ServerTrustAuthResponseAction.CANCEL; + return ServerTrustAuthResponse(action: action); + }, + onWebViewCreated: (controller) { + webViewController = controller; + controller.loadUrl( + urlRequest: URLRequest(url: WebUri(consoleUrl))); + }, + ), + ); + }, + ); } - Future showTLSWarning(BuildContext context, String sslError, - String issuedTo, String hash) async { + Future showTLSWarning( + BuildContext context, + String sslError, + String issuedTo, + String hash, + ) async { return await showDialog( context: context, barrierDismissible: false, -- 2.50.1 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel