From: "Michael Köppl" <m.koeppl@proxmox.com>
To: "Proxmox VE development discussion" <pve-devel@lists.proxmox.com>
Cc: pve-devel <pve-devel-bounces@lists.proxmox.com>,
Thomas Lamprecht <t.lamprecht@proxmox.com>
Subject: Re: [pve-devel] [PATCH pve_flutter_frontend] feat: ui: add edit button in guests options page
Date: Fri, 29 Aug 2025 15:21:28 +0200 [thread overview]
Message-ID: <DCEXYH0L47T4.75CO4QIVVITU@proxmox.com> (raw)
In-Reply-To: <20250730123744.115219-1-s.shaji@proxmox.com>
It seems this patch does not apply anymore due to f263217.
On Wed Jul 30, 2025 at 2:37 PM CEST, Shan Shaji wrote:
> 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 <t.lamprecht@proxmox.com>
> Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
> ---
> 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<List<PveGuestRRDdataModel>> _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<List<PveGuestRRDdataModel>> _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<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..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<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,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 913f4b1..9165020 100644
> --- a/lib/widgets/pve_lxc_options_widget.dart
> +++ b/lib/widgets/pve_lxc_options_widget.dart
> @@ -4,6 +4,7 @@ import 'package:pve_flutter_frontend/states/pve_lxc_overview_state.dart';
> import 'package:pve_flutter_frontend/widgets/colored_safe_area.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;
> @@ -18,7 +19,17 @@ class PveLxcOptions extends StatelessWidget {
> if (config != null) {
> return ColoredSafeArea(
> child: 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: <Widget>[
> @@ -27,6 +38,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,
> @@ -49,6 +61,7 @@ class PveLxcOptions extends StatelessWidget {
> subtitle: Text("${config.arch}"),
> ),
> PveConfigSwitchListTile(
> + disable: state.isOptionsLocked,
> title: const Text("/dev/console"),
> value: config.console,
> defaultValue: true,
> @@ -67,6 +80,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 684dfb6..b49c6ea 100644
> --- a/lib/widgets/pve_lxc_overview.dart
> +++ b/lib/widgets/pve_lxc_overview.dart
> @@ -153,14 +153,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 bb1e11a..60b60b3 100644
> --- a/lib/widgets/pve_qemu_options_widget.dart
> +++ b/lib/widgets/pve_qemu_options_widget.dart
> @@ -5,6 +5,7 @@ import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart';
> import 'package:pve_flutter_frontend/widgets/colored_safe_area.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;
> @@ -26,6 +27,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(
> @@ -38,6 +48,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,
> @@ -63,6 +74,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,
> @@ -77,6 +89,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,
> @@ -87,6 +100,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,
> @@ -97,6 +111,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,
> @@ -107,6 +122,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,
> @@ -130,6 +146,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 aa91bcc..d347722 100644
> --- a/lib/widgets/pve_qemu_overview.dart
> +++ b/lib/widgets/pve_qemu_overview.dart
> @@ -259,7 +259,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,
> ),
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-08-29 13:21 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-30 12:37 Shan Shaji
2025-08-28 9:46 ` Shan Shaji
2025-08-29 13:21 ` Michael Köppl [this message]
2025-08-29 13:32 ` Shan Shaji
2025-08-29 15:58 ` Shan Shaji
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DCEXYH0L47T4.75CO4QIVVITU@proxmox.com \
--to=m.koeppl@proxmox.com \
--cc=pve-devel-bounces@lists.proxmox.com \
--cc=pve-devel@lists.proxmox.com \
--cc=t.lamprecht@proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox