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 D018A1FF186 for ; Fri, 29 Aug 2025 17:18:37 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9F52A32025; Fri, 29 Aug 2025 17:18:45 +0200 (CEST) From: Shan Shaji To: pve-devel@lists.proxmox.com Date: Fri, 29 Aug 2025 17:18:02 +0200 Message-ID: <20250829151802.201476-1-s.shaji@proxmox.com> X-Mailer: git-send-email 2.47.2 MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1756480682805 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.016 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 POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_2 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_4 0.1 random spam to be learned in bayes RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [config.name] Subject: [pve-devel] [PATCH pve_flutter_frontend v2] feat: ui: add edit button in guests options page 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 Cc: Thomas Lamprecht Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" On the options page for VMs and CTs it was easy to change the configs by mistake. To avoid that, added an edit button on the top of the screen. The toggle buttons will only be enabled if the edit button is clicked. Suggested-by: Thomas Lamprecht Signed-off-by: Shan Shaji --- changes since v1: - Rebase with master lib/bloc/pve_lxc_overview_bloc.dart | 11 +++++ lib/bloc/pve_qemu_overview_bloc.dart | 11 +++++ lib/states/pve_lxc_overview_state.dart | 21 ++++++---- lib/states/pve_qemu_overview_state.dart | 21 ++++++---- lib/widgets/pve_config_switch_list_tile.dart | 8 +++- lib/widgets/pve_icon_button_widget.dart | 43 ++++++++++++++++++++ lib/widgets/pve_lxc_options_widget.dart | 16 +++++++- lib/widgets/pve_lxc_overview.dart | 22 ++++++---- lib/widgets/pve_qemu_options_widget.dart | 17 ++++++++ lib/widgets/pve_qemu_overview.dart | 2 +- 10 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 lib/widgets/pve_icon_button_widget.dart diff --git a/lib/bloc/pve_lxc_overview_bloc.dart b/lib/bloc/pve_lxc_overview_bloc.dart index e287f97..b856006 100644 --- a/lib/bloc/pve_lxc_overview_bloc.dart +++ b/lib/bloc/pve_lxc_overview_bloc.dart @@ -89,6 +89,11 @@ class PveLxcOverviewBloc yield latestState.rebuild((b) => b..errorMessage = ''); } } + + if (event is LockLxcOptions) { + yield latestState + .rebuild((b) => b..isOptionsLocked = event.isLockOptions); + } } Future> _preProcessRRDdata() async { @@ -131,3 +136,9 @@ class RevertPendingLxcConfig extends PveLxcOverviewEvent { RevertPendingLxcConfig(this.cField); } + +class LockLxcOptions extends PveLxcOverviewEvent { + final bool isLockOptions; + + LockLxcOptions(this.isLockOptions); +} diff --git a/lib/bloc/pve_qemu_overview_bloc.dart b/lib/bloc/pve_qemu_overview_bloc.dart index 3d0fd0e..98b1261 100644 --- a/lib/bloc/pve_qemu_overview_bloc.dart +++ b/lib/bloc/pve_qemu_overview_bloc.dart @@ -94,6 +94,11 @@ class PveQemuOverviewBloc yield latestState.rebuild((b) => b..errorMessage = ''); } } + + if (event is LockQemuOptions) { + yield latestState + .rebuild((b) => b..isOptionsLocked = event.isLockOptions); + } } Future> _preProcessRRDdata() async { @@ -136,3 +141,9 @@ class RevertPendingQemuConfig extends PveQemuOverviewEvent { RevertPendingQemuConfig(this.cField); } + +class LockQemuOptions extends PveQemuOverviewEvent { + final bool isLockOptions; + + LockQemuOptions(this.isLockOptions); +} \ No newline at end of file diff --git a/lib/states/pve_lxc_overview_state.dart b/lib/states/pve_lxc_overview_state.dart index c10c2e7..a162121 100644 --- a/lib/states/pve_lxc_overview_state.dart +++ b/lib/states/pve_lxc_overview_state.dart @@ -10,6 +10,7 @@ abstract class PveLxcOverviewState implements Built { // Fields String get nodeID; + bool get isOptionsLocked; PveNodesLxcStatusModel? get currentStatus; BuiltList? get rrdData; PveNodesLxcConfigModel? get config; @@ -20,13 +21,15 @@ abstract class PveLxcOverviewState [void Function(PveLxcOverviewStateBuilder) updates]) = _$PveLxcOverviewState; - factory PveLxcOverviewState.init(String nodeID) => - PveLxcOverviewState((b) => b - //base - ..errorMessage = '' - ..isBlank = true - ..isLoading = false - ..isSuccess = false - //class - ..nodeID = nodeID); + factory PveLxcOverviewState.init(String nodeID) => PveLxcOverviewState( + (b) => b + //base + ..errorMessage = '' + ..isBlank = true + ..isLoading = false + ..isSuccess = false + //class + ..nodeID = nodeID + ..isOptionsLocked = true, + ); } diff --git a/lib/states/pve_qemu_overview_state.dart b/lib/states/pve_qemu_overview_state.dart index 43201bc..8d8dd96 100644 --- a/lib/states/pve_qemu_overview_state.dart +++ b/lib/states/pve_qemu_overview_state.dart @@ -8,6 +8,7 @@ abstract class PveQemuOverviewState with PveBaseState implements Built { String get nodeID; + bool get isOptionsLocked; PveQemuStatusModel? get currentStatus; BuiltList? get rrdData; PveNodesQemuConfigModel? get config; @@ -18,13 +19,15 @@ abstract class PveQemuOverviewState [void Function(PveQemuOverviewStateBuilder) updates]) = _$PveQemuOverviewState; - factory PveQemuOverviewState.init(String nodeID) => - PveQemuOverviewState((b) => b - //base - ..errorMessage = '' - ..isBlank = true - ..isLoading = false - ..isSuccess = false - //class - ..nodeID = nodeID); + factory PveQemuOverviewState.init(String nodeID) => PveQemuOverviewState( + (b) => b + //base + ..errorMessage = '' + ..isBlank = true + ..isLoading = false + ..isSuccess = false + //class + ..nodeID = nodeID + ..isOptionsLocked = true, + ); } diff --git a/lib/widgets/pve_config_switch_list_tile.dart b/lib/widgets/pve_config_switch_list_tile.dart index c209fbe..1f2e6c0 100644 --- a/lib/widgets/pve_config_switch_list_tile.dart +++ b/lib/widgets/pve_config_switch_list_tile.dart @@ -7,6 +7,7 @@ class PveConfigSwitchListTile extends StatelessWidget { final Widget? title; final ValueChanged? onChanged; final VoidCallback? onDeleted; + final bool disable; const PveConfigSwitchListTile({ super.key, @@ -16,6 +17,7 @@ class PveConfigSwitchListTile extends StatelessWidget { this.title, this.onChanged, this.onDeleted, + this.disable = false, }); @override Widget build(BuildContext context) { @@ -26,7 +28,11 @@ class PveConfigSwitchListTile extends StatelessWidget { return SwitchListTile( title: _getTitle(), value: pBool ?? value ?? defaultValue!, - onChanged: pending != null ? null : onChanged, + onChanged: disable + ? null + : pending != null + ? null + : onChanged, ); } diff --git a/lib/widgets/pve_icon_button_widget.dart b/lib/widgets/pve_icon_button_widget.dart new file mode 100644 index 0000000..0d30ce8 --- /dev/null +++ b/lib/widgets/pve_icon_button_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class PveIconButton extends StatelessWidget { + final IconData icon; + final String label; + final Color? color; + final VoidCallback? onPressed; + + const PveIconButton({ + super.key, + required this.icon, + required this.label, + this.color, + this.onPressed, + }); + + const PveIconButton.edit({ + super.key, + this.color, + this.onPressed, + }) : icon = Icons.edit, + label = 'Edit'; + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: onPressed, + child: Row( + spacing: 2, + children: [ + Icon( + icon, + color: color, + ), + Text( + label, + style: TextStyle(color: color), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart index 7ad0224..6adc6d9 100644 --- a/lib/widgets/pve_lxc_options_widget.dart +++ b/lib/widgets/pve_lxc_options_widget.dart @@ -3,6 +3,7 @@ import 'package:pve_flutter_frontend/bloc/pve_lxc_overview_bloc.dart'; import 'package:pve_flutter_frontend/states/pve_lxc_overview_state.dart'; import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart'; import 'package:pve_flutter_frontend/widgets/pve_config_switch_list_tile.dart'; +import 'package:pve_flutter_frontend/widgets/pve_icon_button_widget.dart'; class PveLxcOptions extends StatelessWidget { final PveLxcOverviewBloc? lxcBloc; @@ -16,7 +17,17 @@ class PveLxcOptions extends StatelessWidget { final config = state.config; if (config != null) { return Scaffold( - appBar: AppBar(), + appBar: AppBar( + actions: [ + if (state.isOptionsLocked) + PveIconButton.edit( + color: Theme.of(context).colorScheme.onPrimary, + onPressed: () => lxcBloc!.events.add( + LockLxcOptions(false), + ), + ) + ], + ), body: SingleChildScrollView( child: Column( children: [ @@ -25,6 +36,7 @@ class PveLxcOptions extends StatelessWidget { subtitle: Text(config.hostname ?? 'undefined'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Start on boot"), value: config.onboot, defaultValue: false, @@ -47,6 +59,7 @@ class PveLxcOptions extends StatelessWidget { subtitle: Text("${config.arch}"), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("/dev/console"), value: config.console, defaultValue: true, @@ -65,6 +78,7 @@ class PveLxcOptions extends StatelessWidget { subtitle: Text(config.cmode?.name ?? 'tty'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Protection"), value: config.protection, defaultValue: false, diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart index fe43a26..3d66207 100644 --- a/lib/widgets/pve_lxc_overview.dart +++ b/lib/widgets/pve_lxc_overview.dart @@ -151,14 +151,20 @@ class PveLxcOverview extends StatelessWidget { state.nodeID, 'lxc')), createActionCard( - 'Options', - Icons.settings, - () => Navigator.of(context) - .push(MaterialPageRoute( - builder: (context) => PveLxcOptions( - lxcBloc: lxcBloc, - ), - fullscreenDialog: true))), + 'Options', + Icons.settings, + () { + return Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => PveLxcOptions( + lxcBloc: lxcBloc + ..events.add(LockLxcOptions(true)), + ), + fullscreenDialog: true, + ), + ); + }, + ), if (!resourceBloc.latestState.isStandalone) createActionCard( 'Migrate', diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart index 7ed0a3e..2c3f05f 100644 --- a/lib/widgets/pve_qemu_options_widget.dart +++ b/lib/widgets/pve_qemu_options_widget.dart @@ -4,6 +4,7 @@ import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart'; import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart'; import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart'; import 'package:pve_flutter_frontend/widgets/pve_config_switch_list_tile.dart'; +import 'package:pve_flutter_frontend/widgets/pve_icon_button_widget.dart'; class PveQemuOptions extends StatelessWidget { final String guestID; @@ -24,6 +25,15 @@ class PveQemuOptions extends StatelessWidget { icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), + actions: [ + if (state.isOptionsLocked) + PveIconButton.edit( + onPressed: () => bloc.events.add( + LockQemuOptions(false), + ), + color: Theme.of(context).colorScheme.onPrimary, + ) + ], ), body: SingleChildScrollView( child: Form( @@ -36,6 +46,7 @@ class PveQemuOptions extends StatelessWidget { subtitle: Text(config.name ?? 'VM$guestID'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Start on boot"), value: config.onboot, defaultValue: false, @@ -61,6 +72,7 @@ class PveQemuOptions extends StatelessWidget { subtitle: Text(config.boot ?? 'Disk, Network, USB'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Use tablet for pointer"), value: config.tablet, defaultValue: true, @@ -75,6 +87,7 @@ class PveQemuOptions extends StatelessWidget { subtitle: Text(config.hotplug ?? 'disk,network,usb'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("ACPI support"), value: config.acpi, defaultValue: true, @@ -85,6 +98,7 @@ class PveQemuOptions extends StatelessWidget { bloc.events.add(RevertPendingQemuConfig('acpi')), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("KVM hardware virtualization"), value: config.kvm, defaultValue: true, @@ -95,6 +109,7 @@ class PveQemuOptions extends StatelessWidget { bloc.events.add(RevertPendingQemuConfig('kvm')), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Freeze CPU on startup"), value: config.freeze, defaultValue: false, @@ -105,6 +120,7 @@ class PveQemuOptions extends StatelessWidget { .add(RevertPendingQemuConfig('freeze')), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Use local time for RTC"), value: config.localtime, defaultValue: false, @@ -128,6 +144,7 @@ class PveQemuOptions extends StatelessWidget { subtitle: Text(config.agent ?? 'Default (disabled)'), ), PveConfigSwitchListTile( + disable: state.isOptionsLocked, title: const Text("Protection"), value: config.protection, defaultValue: false, diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart index 50f7382..473731c 100644 --- a/lib/widgets/pve_qemu_overview.dart +++ b/lib/widgets/pve_qemu_overview.dart @@ -257,7 +257,7 @@ class PveQemuOverview extends StatelessWidget { Route _createOptionsRoute(PveQemuOverviewBloc bloc) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => Provider.value( - value: bloc, + value: bloc..events.add(LockQemuOptions(true)), child: PveQemuOptions( guestID: guestID, ), -- 2.47.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel