public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page
@ 2025-09-04  9:25 Shan Shaji
  2025-09-17  8:06 ` Dominik Csapak
  0 siblings, 1 reply; 4+ messages in thread
From: Shan Shaji @ 2025-09-04  9:25 UTC (permalink / raw)
  To: pve-devel; +Cc: Thomas Lamprecht

On the options page for VMs and CTs it was easy to change the
configs by mistake. To avoid that, added a lock/unlock button
on top of the screen. The toggle buttons will only be enabled
if the button is clicked.

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
changes since v2:
 - Remove edit icon and text, instead used lock/unlock icon.
 - Allow users to lock the options again without closing the page. 
 - Update commit message.
 - Removed unintended formatting changes and rename `isLockOptions` to
   `isOptionsLocked` 

 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 |  4 ++-
 lib/widgets/pve_icon_button_widget.dart      | 38 ++++++++++++++++++++
 lib/widgets/pve_lxc_options_widget.dart      | 16 ++++++++-
 lib/widgets/pve_lxc_overview.dart            | 20 ++++++-----
 lib/widgets/pve_qemu_options_widget.dart     | 17 +++++++++
 lib/widgets/pve_qemu_overview.dart           |  2 +-
 10 files changed, 132 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..b339c08 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.isOptionsLocked);
+    }
   }
 
   Future<List<PveGuestRRDdataModel>> _preProcessRRDdata() async {
@@ -131,3 +136,9 @@ class RevertPendingLxcConfig extends PveLxcOverviewEvent {
 
   RevertPendingLxcConfig(this.cField);
 }
+
+class LockLxcOptions extends PveLxcOverviewEvent {
+  final bool isOptionsLocked;
+
+  LockLxcOptions(this.isOptionsLocked);
+}
diff --git a/lib/bloc/pve_qemu_overview_bloc.dart b/lib/bloc/pve_qemu_overview_bloc.dart
index 3d0fd0e..57a4c93 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.isOptionsLocked);
+    }
   }
 
   Future<List<PveGuestRRDdataModel>> _preProcessRRDdata() async {
@@ -136,3 +141,9 @@ class RevertPendingQemuConfig extends PveQemuOverviewEvent {
 
   RevertPendingQemuConfig(this.cField);
 }
+
+class LockQemuOptions extends PveQemuOverviewEvent {
+  final bool isOptionsLocked;
+
+  LockQemuOptions(this.isOptionsLocked);
+}
\ 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<PveLxcOverviewState, PveLxcOverviewStateBuilder> {
   // Fields
   String get nodeID;
+  bool get isOptionsLocked;
   PveNodesLxcStatusModel? get currentStatus;
   BuiltList<PveGuestRRDdataModel>? 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<PveQemuOverviewState, PveQemuOverviewStateBuilder> {
   String get nodeID;
+  bool get isOptionsLocked;
   PveQemuStatusModel? get currentStatus;
   BuiltList<PveGuestRRDdataModel>? 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..19ae13c 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<bool>? 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,7 @@ class PveConfigSwitchListTile extends StatelessWidget {
     return SwitchListTile(
       title: _getTitle(),
       value: pBool ?? value ?? defaultValue!,
-      onChanged: pending != null ? null : onChanged,
+      onChanged: disable || 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..8066cff
--- /dev/null
+++ b/lib/widgets/pve_icon_button_widget.dart
@@ -0,0 +1,38 @@
+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,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(
+      onPressed: onPressed,
+      child: Row(
+        spacing: 2,
+        children: [
+          Icon(
+            icon,
+            color: color ?? Theme.of(context).colorScheme.onPrimary,
+          ),
+          Text(
+            label,
+            style: TextStyle(
+              color: color ?? Theme.of(context).colorScheme.onPrimary,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart
index 7ad0224..32a0da9 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: [
+                    PveIconButton(
+                      onPressed: () => lxcBloc!.events.add(
+                        LockLxcOptions(!state.isOptionsLocked),
+                      ),
+                      icon: state.isOptionsLocked ? Icons.lock_outline : Icons.lock_open_outlined,
+                      label: state.isOptionsLocked ? 'Unlock' : 'Lock',
+                    )
+                  ],
+                ),
                 body: SingleChildScrollView(
                   child: Column(
                     children: <Widget>[
@@ -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..6ad2fa6 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -151,14 +151,18 @@ 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,
+                                () => 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..a7ddb0e 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: [
+                    PveIconButton(
+                      onPressed: () => bloc.events.add(
+                        LockQemuOptions(!state.isOptionsLocked),
+                      ),
+                      icon: state.isOptionsLocked ? Icons.lock_outline : Icons.lock_open_outlined,
+                      label: state.isOptionsLocked ? 'Unlock' : 'Lock',
+                    )
+                  ],
                 ),
                 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


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page
  2025-09-04  9:25 [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page Shan Shaji
@ 2025-09-17  8:06 ` Dominik Csapak
  2025-09-17  8:41   ` Thomas Lamprecht
  0 siblings, 1 reply; 4+ messages in thread
From: Dominik Csapak @ 2025-09-17  8:06 UTC (permalink / raw)
  To: Proxmox VE development discussion, Shan Shaji; +Cc: Thomas Lamprecht



On 9/4/25 11:25 AM, Shan Shaji wrote:
> On the options page for VMs and CTs it was easy to change the
> configs by mistake. To avoid that, added a lock/unlock button
> on top of the screen. The toggle buttons will only be enabled
> if the button is clicked.
> 
> Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
> Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
I did not apply this, as we're currently evaluating a different approach
for the yew mobile ui. If that works out, i'd prefer to have both uis
the same (or at least similar) approaches here


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page
  2025-09-17  8:06 ` Dominik Csapak
@ 2025-09-17  8:41   ` Thomas Lamprecht
  2025-09-18  8:44     ` Shan Shaji
  0 siblings, 1 reply; 4+ messages in thread
From: Thomas Lamprecht @ 2025-09-17  8:41 UTC (permalink / raw)
  To: Dominik Csapak, Proxmox VE development discussion, Shan Shaji

Am 17.09.25 um 10:06 schrieb Dominik Csapak:
> 
> 
> On 9/4/25 11:25 AM, Shan Shaji wrote:
>> On the options page for VMs and CTs it was easy to change the
>> configs by mistake. To avoid that, added a lock/unlock button
>> on top of the screen. The toggle buttons will only be enabled
>> if the button is clicked.
>>
>> Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
>> Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
> I did not apply this, as we're currently evaluating a different approach
> for the yew mobile ui. If that works out, i'd prefer to have both uis
> the same (or at least similar) approaches here

For the record: the idea is just using bottom sheets with an explicit
"update" save button for each property, i.e. both, single value ones
and more complex property strings.

As we do not plan to very actively extend the Apps capabilities to cover
the full option range for VMs/CTs that PVE allows and just keep the
simple options we got now I'm not really sure if syncing app and mobile
web is really something we have to do. That said, I have nothing against
using a bottom sheet there too, it's a common approach and works well
for most things, but I'm also fine with going a different route,
especially if we do not expand the availability of options and HW to
configure soon(ish) here anyway, we can still change this later should
that happen.


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page
  2025-09-17  8:41   ` Thomas Lamprecht
@ 2025-09-18  8:44     ` Shan Shaji
  0 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2025-09-18  8:44 UTC (permalink / raw)
  To: Thomas Lamprecht, Dominik Csapak, Proxmox VE development discussion

Thanks @Thomas and @Dominik for the review,  I will create another 
patch that will align with the mobile web UI which will be a bottom 
sheet with the explicit update or reset button. 

Also, the changes will only reflect on the existing editable options. 

On Wed Sep 17, 2025 at 10:41 AM CEST, Thomas Lamprecht wrote:
> Am 17.09.25 um 10:06 schrieb Dominik Csapak:
>> 
>> 
>> On 9/4/25 11:25 AM, Shan Shaji wrote:
>>> On the options page for VMs and CTs it was easy to change the
>>> configs by mistake. To avoid that, added a lock/unlock button
>>> on top of the screen. The toggle buttons will only be enabled
>>> if the button is clicked.
>>>
>>> Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
>>> Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
>> I did not apply this, as we're currently evaluating a different approach
>> for the yew mobile ui. If that works out, i'd prefer to have both uis
>> the same (or at least similar) approaches here
>
> For the record: the idea is just using bottom sheets with an explicit
> "update" save button for each property, i.e. both, single value ones
> and more complex property strings.
>
> As we do not plan to very actively extend the Apps capabilities to cover
> the full option range for VMs/CTs that PVE allows and just keep the
> simple options we got now I'm not really sure if syncing app and mobile
> web is really something we have to do. That said, I have nothing against
> using a bottom sheet there too, it's a common approach and works well
> for most things, but I'm also fine with going a different route,
> especially if we do not expand the availability of options and HW to
> configure soon(ish) here anyway, we can still change this later should
> that happen.



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-09-18  8:44 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-04  9:25 [pve-devel] [PATCH pve_flutter_frontend v3] feat: ui: add lock/unlock button in guests options page Shan Shaji
2025-09-17  8:06 ` Dominik Csapak
2025-09-17  8:41   ` Thomas Lamprecht
2025-09-18  8:44     ` Shan Shaji

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal