* [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs
@ 2025-09-23 12:23 Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 1/3] cleanup: run `dart format` on both qemu and lxc options page Shan Shaji
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Shan Shaji @ 2025-09-23 12:23 UTC (permalink / raw)
To: pve-devel
On the options page for VMs and CTs it was easy to change the
configs by mistake. To avoid that, all currently available
editable options will be shown inside a bottom sheet.
changes since v1: Thanks @Thomas
patch: https://lore.proxmox.com/pve-devel/20250923093629.119418-1-s.shaji@proxmox.com/T/#t
- Fixed file import error.
Shan Shaji (3):
cleanup: run `dart format` on both qemu and lxc options page
feat: ui: add bottom sheet to change VMs/CTs options
cleanup: remove `PveConfigSwitchListTile` in favor of
PveConfigListTile
.../pve_config_switch_form.dart | 54 +++
lib/widgets/pve_config_list_tile.dart | 176 ++++++++++
lib/widgets/pve_config_switch_list_tile.dart | 50 ---
lib/widgets/pve_lxc_options_widget.dart | 156 +++++----
lib/widgets/pve_qemu_options_widget.dart | 315 +++++++++++-------
5 files changed, 508 insertions(+), 243 deletions(-)
create mode 100644 lib/widgets/pve_config_forms/pve_config_switch_form.dart
create mode 100644 lib/widgets/pve_config_list_tile.dart
delete mode 100644 lib/widgets/pve_config_switch_list_tile.dart
--
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] 5+ messages in thread
* [pve-devel] [PATCH pve_flutter_frontend v2 1/3] cleanup: run `dart format` on both qemu and lxc options page
2025-09-23 12:23 [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Shan Shaji
@ 2025-09-23 12:23 ` Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 2/3] feat: ui: add bottom sheet to change VMs/CTs options Shan Shaji
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Shan Shaji @ 2025-09-23 12:23 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
lib/widgets/pve_lxc_options_widget.dart | 138 ++++++------
lib/widgets/pve_qemu_options_widget.dart | 256 +++++++++++------------
2 files changed, 197 insertions(+), 197 deletions(-)
diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart
index 7ad0224..5b6e009 100644
--- a/lib/widgets/pve_lxc_options_widget.dart
+++ b/lib/widgets/pve_lxc_options_widget.dart
@@ -16,76 +16,76 @@ class PveLxcOptions extends StatelessWidget {
final config = state.config;
if (config != null) {
return Scaffold(
- appBar: AppBar(),
- body: SingleChildScrollView(
- child: Column(
- children: <Widget>[
- ListTile(
- title: const Text("Name"),
- subtitle: Text(config.hostname ?? 'undefined'),
- ),
- PveConfigSwitchListTile(
- title: const Text("Start on boot"),
- value: config.onboot,
- defaultValue: false,
- pending: config.getPending('onboot'),
- onChanged: (v) => lxcBloc!.events
- .add(UpdateLxcConfigBool('onboot', v)),
- onDeleted: () => lxcBloc!.events
- .add(RevertPendingLxcConfig('onboot')),
- ),
- ListTile(
- title: const Text("Start/Shutdown order"),
- subtitle: Text(config.startup ?? "Default (any)"),
- ),
- ListTile(
- title: const Text("OS Type"),
- subtitle: Text("${config.ostype}"),
- ),
- ListTile(
- title: const Text("Architecture"),
- subtitle: Text("${config.arch}"),
- ),
- PveConfigSwitchListTile(
- title: const Text("/dev/console"),
- value: config.console,
- defaultValue: true,
- pending: config.getPending('console'),
- onChanged: (v) => lxcBloc!.events
- .add(UpdateLxcConfigBool('console', v)),
- onDeleted: () => lxcBloc!.events
- .add(RevertPendingLxcConfig('console')),
- ),
- ListTile(
- title: const Text("TTY Count"),
- subtitle: Text("${config.tty ?? 2}"),
- ),
- ListTile(
- title: const Text("Console Mode"),
- subtitle: Text(config.cmode?.name ?? 'tty'),
- ),
- PveConfigSwitchListTile(
- title: const Text("Protection"),
- value: config.protection,
- defaultValue: false,
- pending: config.getPending('protection'),
- onChanged: (v) => lxcBloc!.events
- .add(UpdateLxcConfigBool('protection', v)),
- onDeleted: () => lxcBloc!.events
- .add(RevertPendingLxcConfig('protection')),
- ),
- ListTile(
- title: const Text("Unprivileged"),
- subtitle:
- Text(config.unprivileged ?? false ? 'Yes' : 'No'),
- ),
- ListTile(
- title: const Text("Features"),
- subtitle: Text(config.features?.toString() ?? 'none'),
- ),
- ],
- ),
+ appBar: AppBar(),
+ body: SingleChildScrollView(
+ child: Column(
+ children: <Widget>[
+ ListTile(
+ title: const Text("Name"),
+ subtitle: Text(config.hostname ?? 'undefined'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Start on boot"),
+ value: config.onboot,
+ defaultValue: false,
+ pending: config.getPending('onboot'),
+ onChanged: (v) =>
+ lxcBloc!.events.add(UpdateLxcConfigBool('onboot', v)),
+ onDeleted: () =>
+ lxcBloc!.events.add(RevertPendingLxcConfig('onboot')),
+ ),
+ ListTile(
+ title: const Text("Start/Shutdown order"),
+ subtitle: Text(config.startup ?? "Default (any)"),
+ ),
+ ListTile(
+ title: const Text("OS Type"),
+ subtitle: Text("${config.ostype}"),
+ ),
+ ListTile(
+ title: const Text("Architecture"),
+ subtitle: Text("${config.arch}"),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("/dev/console"),
+ value: config.console,
+ defaultValue: true,
+ pending: config.getPending('console'),
+ onChanged: (v) => lxcBloc!.events
+ .add(UpdateLxcConfigBool('console', v)),
+ onDeleted: () => lxcBloc!.events
+ .add(RevertPendingLxcConfig('console')),
+ ),
+ ListTile(
+ title: const Text("TTY Count"),
+ subtitle: Text("${config.tty ?? 2}"),
+ ),
+ ListTile(
+ title: const Text("Console Mode"),
+ subtitle: Text(config.cmode?.name ?? 'tty'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Protection"),
+ value: config.protection,
+ defaultValue: false,
+ pending: config.getPending('protection'),
+ onChanged: (v) => lxcBloc!.events
+ .add(UpdateLxcConfigBool('protection', v)),
+ onDeleted: () => lxcBloc!.events
+ .add(RevertPendingLxcConfig('protection')),
+ ),
+ ListTile(
+ title: const Text("Unprivileged"),
+ subtitle:
+ Text(config.unprivileged ?? false ? 'Yes' : 'No'),
+ ),
+ ListTile(
+ title: const Text("Features"),
+ subtitle: Text(config.features?.toString() ?? 'none'),
+ ),
+ ],
),
+ ),
);
}
return const Center(
diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart
index 7ed0a3e..992a382 100644
--- a/lib/widgets/pve_qemu_options_widget.dart
+++ b/lib/widgets/pve_qemu_options_widget.dart
@@ -19,137 +19,137 @@ class PveQemuOptions extends StatelessWidget {
if (state.config != null) {
final config = state.config!;
return Scaffold(
- appBar: AppBar(
- leading: IconButton(
- icon: const Icon(Icons.close),
- onPressed: () => Navigator.of(context).pop(),
- ),
+ appBar: AppBar(
+ leading: IconButton(
+ icon: const Icon(Icons.close),
+ onPressed: () => Navigator.of(context).pop(),
),
- body: SingleChildScrollView(
- child: Form(
- key: _formKey,
- onChanged: () {},
- child: Column(
- children: <Widget>[
- ListTile(
- title: const Text("Name"),
- subtitle: Text(config.name ?? 'VM$guestID'),
- ),
- PveConfigSwitchListTile(
- title: const Text("Start on boot"),
- value: config.onboot,
- defaultValue: false,
- pending: config.getPending('onboot'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('onboot', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('onboot')),
- ),
- ListTile(
- title: const Text("Start/Shutdown order"),
- subtitle: Text(config.startup ?? "Default (any)"),
- ),
- ListTile(
- title: const Text("OS Type"),
- subtitle: Text(config.ostype != null
- ? "${config.ostype!.type} ${config.ostype!.description}"
- : "Other"),
- ),
- //TODO add better ui component e.g. collapseable
- ListTile(
- title: const Text("Boot Device"),
- subtitle: Text(config.boot ?? 'Disk, Network, USB'),
- ),
- PveConfigSwitchListTile(
- title: const Text("Use tablet for pointer"),
- value: config.tablet,
- defaultValue: true,
- pending: config.getPending('tablet'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('tablet', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('tablet')),
- ),
- ListTile(
- title: const Text("Hotplug"),
- subtitle: Text(config.hotplug ?? 'disk,network,usb'),
- ),
- PveConfigSwitchListTile(
- title: const Text("ACPI support"),
- value: config.acpi,
- defaultValue: true,
- pending: config.getPending('acpi'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('acpi', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('acpi')),
- ),
- PveConfigSwitchListTile(
- title: const Text("KVM hardware virtualization"),
- value: config.kvm,
- defaultValue: true,
- pending: config.getPending('kvm'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('kvm', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('kvm')),
- ),
- PveConfigSwitchListTile(
- title: const Text("Freeze CPU on startup"),
- value: config.freeze,
- defaultValue: false,
- pending: config.getPending('freeze'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('freeze', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('freeze')),
- ),
- PveConfigSwitchListTile(
- title: const Text("Use local time for RTC"),
- value: config.localtime,
- defaultValue: false,
- pending: config.getPending('localtime'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('localtime', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('localtime')),
- ),
- ListTile(
- title: const Text("RTC start date"),
- subtitle: Text(config.startdate ?? 'now'),
- ),
- ListTile(
- title: const Text("SMBIOS settings (type1)"),
- subtitle: Text(config.smbios1 ?? ''),
- ),
- //Todo enhance UI
- ListTile(
- title: const Text("QEMU Guest Agent"),
- subtitle: Text(config.agent ?? 'Default (disabled)'),
- ),
- PveConfigSwitchListTile(
- title: const Text("Protection"),
- value: config.protection,
- defaultValue: false,
- pending: config.getPending('protection'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('protection', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('protection')),
- ),
- ListTile(
- title: const Text("Spice Enhancements"),
- subtitle: Text(
- config.spiceEnhancements ?? 'No enhancements'),
- ),
- ListTile(
- title: const Text("VM State Storage"),
- subtitle: Text(config.vmstatestorage ?? 'Automatic'),
- ),
- ],
- ),
+ ),
+ body: SingleChildScrollView(
+ child: Form(
+ key: _formKey,
+ onChanged: () {},
+ child: Column(
+ children: <Widget>[
+ ListTile(
+ title: const Text("Name"),
+ subtitle: Text(config.name ?? 'VM$guestID'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Start on boot"),
+ value: config.onboot,
+ defaultValue: false,
+ pending: config.getPending('onboot'),
+ onChanged: (v) =>
+ bloc.events.add(UpdateQemuConfigBool('onboot', v)),
+ onDeleted: () =>
+ bloc.events.add(RevertPendingQemuConfig('onboot')),
+ ),
+ ListTile(
+ title: const Text("Start/Shutdown order"),
+ subtitle: Text(config.startup ?? "Default (any)"),
+ ),
+ ListTile(
+ title: const Text("OS Type"),
+ subtitle: Text(config.ostype != null
+ ? "${config.ostype!.type} ${config.ostype!.description}"
+ : "Other"),
+ ),
+ //TODO add better ui component e.g. collapseable
+ ListTile(
+ title: const Text("Boot Device"),
+ subtitle: Text(config.boot ?? 'Disk, Network, USB'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Use tablet for pointer"),
+ value: config.tablet,
+ defaultValue: true,
+ pending: config.getPending('tablet'),
+ onChanged: (v) =>
+ bloc.events.add(UpdateQemuConfigBool('tablet', v)),
+ onDeleted: () =>
+ bloc.events.add(RevertPendingQemuConfig('tablet')),
+ ),
+ ListTile(
+ title: const Text("Hotplug"),
+ subtitle: Text(config.hotplug ?? 'disk,network,usb'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("ACPI support"),
+ value: config.acpi,
+ defaultValue: true,
+ pending: config.getPending('acpi'),
+ onChanged: (v) =>
+ bloc.events.add(UpdateQemuConfigBool('acpi', v)),
+ onDeleted: () =>
+ bloc.events.add(RevertPendingQemuConfig('acpi')),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("KVM hardware virtualization"),
+ value: config.kvm,
+ defaultValue: true,
+ pending: config.getPending('kvm'),
+ onChanged: (v) =>
+ bloc.events.add(UpdateQemuConfigBool('kvm', v)),
+ onDeleted: () =>
+ bloc.events.add(RevertPendingQemuConfig('kvm')),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Freeze CPU on startup"),
+ value: config.freeze,
+ defaultValue: false,
+ pending: config.getPending('freeze'),
+ onChanged: (v) =>
+ bloc.events.add(UpdateQemuConfigBool('freeze', v)),
+ onDeleted: () =>
+ bloc.events.add(RevertPendingQemuConfig('freeze')),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Use local time for RTC"),
+ value: config.localtime,
+ defaultValue: false,
+ pending: config.getPending('localtime'),
+ onChanged: (v) => bloc.events
+ .add(UpdateQemuConfigBool('localtime', v)),
+ onDeleted: () => bloc.events
+ .add(RevertPendingQemuConfig('localtime')),
+ ),
+ ListTile(
+ title: const Text("RTC start date"),
+ subtitle: Text(config.startdate ?? 'now'),
+ ),
+ ListTile(
+ title: const Text("SMBIOS settings (type1)"),
+ subtitle: Text(config.smbios1 ?? ''),
+ ),
+ //Todo enhance UI
+ ListTile(
+ title: const Text("QEMU Guest Agent"),
+ subtitle: Text(config.agent ?? 'Default (disabled)'),
+ ),
+ PveConfigSwitchListTile(
+ title: const Text("Protection"),
+ value: config.protection,
+ defaultValue: false,
+ pending: config.getPending('protection'),
+ onChanged: (v) => bloc.events
+ .add(UpdateQemuConfigBool('protection', v)),
+ onDeleted: () => bloc.events
+ .add(RevertPendingQemuConfig('protection')),
+ ),
+ ListTile(
+ title: const Text("Spice Enhancements"),
+ subtitle:
+ Text(config.spiceEnhancements ?? 'No enhancements'),
+ ),
+ ListTile(
+ title: const Text("VM State Storage"),
+ subtitle: Text(config.vmstatestorage ?? 'Automatic'),
+ ),
+ ],
),
),
+ ),
);
}
return const Center(
--
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] 5+ messages in thread
* [pve-devel] [PATCH pve_flutter_frontend v2 2/3] feat: ui: add bottom sheet to change VMs/CTs options
2025-09-23 12:23 [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 1/3] cleanup: run `dart format` on both qemu and lxc options page Shan Shaji
@ 2025-09-23 12:23 ` Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 3/3] cleanup: remove `PveConfigSwitchListTile` in favor of PveConfigListTile Shan Shaji
2025-09-23 12:57 ` [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Thomas Lamprecht
3 siblings, 0 replies; 5+ messages in thread
From: Shan Shaji @ 2025-09-23 12:23 UTC (permalink / raw)
To: pve-devel
On the options page for VMs and CTs it was easy to change the configs by
mistake. To avoid add a bottom sheet to show the editable option.
The value will only be updated when the `update` button is pressed.
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
changes since v1
- Fixed file import error inside `pve_config_switch_form`
.../pve_config_switch_form.dart | 54 ++++++
lib/widgets/pve_config_list_tile.dart | 176 +++++++++++++++++
lib/widgets/pve_lxc_options_widget.dart | 78 +++++---
lib/widgets/pve_qemu_options_widget.dart | 177 ++++++++++++------
4 files changed, 400 insertions(+), 85 deletions(-)
create mode 100644 lib/widgets/pve_config_forms/pve_config_switch_form.dart
create mode 100644 lib/widgets/pve_config_list_tile.dart
diff --git a/lib/widgets/pve_config_forms/pve_config_switch_form.dart b/lib/widgets/pve_config_forms/pve_config_switch_form.dart
new file mode 100644
index 0000000..40bef8c
--- /dev/null
+++ b/lib/widgets/pve_config_forms/pve_config_switch_form.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:pve_flutter_frontend/widgets/pve_config_list_tile.dart';
+
+class PveConfigSwitchForm extends StatefulWidget {
+ const PveConfigSwitchForm({
+ super.key,
+ required this.value,
+ required this.title,
+ required this.controller,
+ });
+
+ final bool value;
+ final String title;
+ final PveConfigEditorController<bool> controller;
+
+ @override
+ State<PveConfigSwitchForm> createState() => _PveConfigSwitchFormState();
+}
+
+class _PveConfigSwitchFormState extends State<PveConfigSwitchForm> {
+ late bool _currentValue;
+
+ bool get _isDirty => _currentValue != widget.value;
+
+ @override
+ void initState() {
+ super.initState();
+ _currentValue = widget.value;
+
+ widget.controller
+ ..setValue(_currentValue)
+ ..setDirty(false);
+ }
+
+ void _handleChanged(bool value) {
+ setState(() {
+ _currentValue = value;
+ });
+
+ widget.controller
+ ..setValue(_currentValue)
+ ..setDirty(_isDirty);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SwitchListTile(
+ contentPadding: EdgeInsets.zero,
+ title: Text(widget.title),
+ value: _currentValue,
+ onChanged: _handleChanged,
+ );
+ }
+}
diff --git a/lib/widgets/pve_config_list_tile.dart b/lib/widgets/pve_config_list_tile.dart
new file mode 100644
index 0000000..84c4694
--- /dev/null
+++ b/lib/widgets/pve_config_list_tile.dart
@@ -0,0 +1,176 @@
+import 'package:flutter/material.dart';
+
+typedef PveConfigEditorBuilder<T> = Widget Function(
+ BuildContext context,
+ PveConfigEditorController<T> controller,
+);
+
+class PveConfigEditorController<T> {
+ PveConfigEditorController({
+ required void Function(bool) onDirtyChanged,
+ required void Function(T) onValueChanged,
+ }) : _onDirtyChanged = onDirtyChanged,
+ _onValueChanged = onValueChanged;
+
+ final void Function(bool) _onDirtyChanged;
+ final void Function(T) _onValueChanged;
+
+ void setDirty(bool value) => _onDirtyChanged(value);
+ void setValue(T value) => _onValueChanged(value);
+}
+
+class PveConfigListTile<T> extends StatelessWidget {
+ const PveConfigListTile({
+ super.key,
+ required this.title,
+ required this.editorBuilder,
+ required this.onSubmit,
+ this.subtitle,
+ this.trailingText,
+ this.pending,
+ this.onCancelEdit,
+ });
+
+ final String title;
+ final String? subtitle;
+ final String? trailingText;
+ final PveConfigEditorBuilder<T> editorBuilder;
+ final void Function(T value) onSubmit;
+ final VoidCallback? onCancelEdit;
+ final int? pending;
+
+ static void _showBottomSheet(
+ BuildContext context, {
+ required Widget Function(BuildContext) builder,
+ }) {
+ showModalBottomSheet(
+ context: context,
+ builder: builder,
+ useSafeArea: true,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ListTile(
+ enabled: pending == null,
+ title: pending != null
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(title),
+ Chip(
+ label: const Text('pending'),
+ backgroundColor: Colors.red,
+ onDeleted: onCancelEdit,
+ ),
+ ],
+ )
+ : Text(title),
+ subtitle: subtitle == null ? null : Text(subtitle!),
+ trailing: trailingText != null
+ ? Text(
+ trailingText!,
+ style: const TextStyle(fontSize: 14),
+ )
+ : null,
+ onTap: pending != null
+ ? null
+ : () {
+ _showBottomSheet(
+ context,
+ builder: (_) {
+ return _PveConfigEditorForm(
+ title: 'Edit: $title',
+ editorBuilder: editorBuilder,
+ onSubmit: onSubmit,
+ );
+ },
+ );
+ },
+ );
+ }
+}
+
+class _PveConfigEditorForm<T> extends StatefulWidget {
+ const _PveConfigEditorForm({
+ required this.title,
+ required this.editorBuilder,
+ required this.onSubmit,
+ });
+
+ final String title;
+ final PveConfigEditorBuilder<T> editorBuilder;
+ final void Function(T value) onSubmit;
+
+ @override
+ State<_PveConfigEditorForm<T>> createState() =>
+ _PveConfigEditorFormState<T>();
+}
+
+class _PveConfigEditorFormState<T> extends State<_PveConfigEditorForm<T>> {
+ bool _isDirty = false;
+ late T _currentValue;
+
+ late final PveConfigEditorController<T> _controller =
+ PveConfigEditorController<T>(
+ onDirtyChanged: _handleDirtyChanged,
+ onValueChanged: _handleValueChanged,
+ );
+
+ void _handleDirtyChanged(bool value) {
+ if (_isDirty != value) {
+ setState(() {
+ _isDirty = value;
+ });
+ }
+ }
+
+ void _handleValueChanged(T value) {
+ _currentValue = value;
+ }
+
+ void _submit() {
+ widget.onSubmit(_currentValue);
+ Navigator.of(context).pop();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 8,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ widget.title,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
+ ),
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('Close'),
+ ),
+ ],
+ ),
+ widget.editorBuilder(context, _controller),
+ const SizedBox(height: 12),
+ Align(
+ alignment: Alignment.bottomRight,
+ child: FilledButton(
+ onPressed: _isDirty ? _submit : null,
+ child: const Text('Update'),
+ ),
+ ),
+ const SizedBox(height: 8),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart
index 5b6e009..4bd263e 100644
--- a/lib/widgets/pve_lxc_options_widget.dart
+++ b/lib/widgets/pve_lxc_options_widget.dart
@@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
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_config_forms/pve_config_switch_form.dart';
+import 'package:pve_flutter_frontend/widgets/pve_config_list_tile.dart';
class PveLxcOptions extends StatelessWidget {
final PveLxcOverviewBloc? lxcBloc;
@@ -24,15 +25,26 @@ class PveLxcOptions extends StatelessWidget {
title: const Text("Name"),
subtitle: Text(config.hostname ?? 'undefined'),
),
- PveConfigSwitchListTile(
- title: const Text("Start on boot"),
- value: config.onboot,
- defaultValue: false,
+ PveConfigListTile<bool>(
+ title: "Start on boot",
+ trailingText: config.onboot == true ? 'Yes' : 'No',
pending: config.getPending('onboot'),
- onChanged: (v) =>
- lxcBloc!.events.add(UpdateLxcConfigBool('onboot', v)),
- onDeleted: () =>
- lxcBloc!.events.add(RevertPendingLxcConfig('onboot')),
+ onSubmit: (value) => lxcBloc!.events.add(
+ UpdateLxcConfigBool(
+ 'onboot',
+ value,
+ ),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Start on boot",
+ value: config.onboot == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => lxcBloc!.events.add(
+ RevertPendingLxcConfig('onboot'),
+ ),
),
ListTile(
title: const Text("Start/Shutdown order"),
@@ -46,15 +58,23 @@ class PveLxcOptions extends StatelessWidget {
title: const Text("Architecture"),
subtitle: Text("${config.arch}"),
),
- PveConfigSwitchListTile(
- title: const Text("/dev/console"),
- value: config.console,
- defaultValue: true,
+ PveConfigListTile<bool>(
+ title: "/dev/console",
+ trailingText: config.console == true ? 'Yes' : 'No',
pending: config.getPending('console'),
- onChanged: (v) => lxcBloc!.events
- .add(UpdateLxcConfigBool('console', v)),
- onDeleted: () => lxcBloc!.events
- .add(RevertPendingLxcConfig('console')),
+ onSubmit: (v) => lxcBloc!.events.add(
+ UpdateLxcConfigBool('console', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "/dev/console",
+ value: config.console == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => lxcBloc!.events.add(
+ RevertPendingLxcConfig('console'),
+ ),
),
ListTile(
title: const Text("TTY Count"),
@@ -64,15 +84,23 @@ class PveLxcOptions extends StatelessWidget {
title: const Text("Console Mode"),
subtitle: Text(config.cmode?.name ?? 'tty'),
),
- PveConfigSwitchListTile(
- title: const Text("Protection"),
- value: config.protection,
- defaultValue: false,
+ PveConfigListTile<bool>(
+ title: "Protection",
+ trailingText: config.protection == true ? 'Yes' : 'No',
pending: config.getPending('protection'),
- onChanged: (v) => lxcBloc!.events
- .add(UpdateLxcConfigBool('protection', v)),
- onDeleted: () => lxcBloc!.events
- .add(RevertPendingLxcConfig('protection')),
+ onSubmit: (v) => lxcBloc!.events.add(
+ UpdateLxcConfigBool('protection', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Protection",
+ value: config.protection == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => lxcBloc!.events.add(
+ RevertPendingLxcConfig('protection'),
+ ),
),
ListTile(
title: const Text("Unprivileged"),
diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart
index 992a382..f8cb000 100644
--- a/lib/widgets/pve_qemu_options_widget.dart
+++ b/lib/widgets/pve_qemu_options_widget.dart
@@ -3,7 +3,8 @@ import 'package:provider/provider.dart';
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_config_forms/pve_config_switch_form.dart';
+import 'package:pve_flutter_frontend/widgets/pve_config_list_tile.dart';
class PveQemuOptions extends StatelessWidget {
final String guestID;
@@ -35,15 +36,23 @@ class PveQemuOptions extends StatelessWidget {
title: const Text("Name"),
subtitle: Text(config.name ?? 'VM$guestID'),
),
- PveConfigSwitchListTile(
- title: const Text("Start on boot"),
- value: config.onboot,
- defaultValue: false,
+ PveConfigListTile<bool>(
+ title: "Start on boot",
+ trailingText: config.onboot == true ? 'Yes' : 'No',
pending: config.getPending('onboot'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('onboot', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('onboot')),
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('onboot', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Start on boot",
+ value: config.onboot == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('onboot'),
+ ),
),
ListTile(
title: const Text("Start/Shutdown order"),
@@ -60,59 +69,99 @@ class PveQemuOptions extends StatelessWidget {
title: const Text("Boot Device"),
subtitle: Text(config.boot ?? 'Disk, Network, USB'),
),
- PveConfigSwitchListTile(
- title: const Text("Use tablet for pointer"),
- value: config.tablet,
- defaultValue: true,
+ PveConfigListTile<bool>(
+ title: "Use tablet for pointer",
+ trailingText: config.tablet == true ? 'Yes' : 'No',
pending: config.getPending('tablet'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('tablet', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('tablet')),
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('tablet', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Use tablet for pointer",
+ value: config.tablet == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('tablet'),
+ ),
),
ListTile(
title: const Text("Hotplug"),
subtitle: Text(config.hotplug ?? 'disk,network,usb'),
),
- PveConfigSwitchListTile(
- title: const Text("ACPI support"),
- value: config.acpi,
- defaultValue: true,
+ PveConfigListTile<bool>(
+ title: "ACPI support",
+ trailingText: config.acpi == true ? 'Yes' : 'No',
pending: config.getPending('acpi'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('acpi', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('acpi')),
- ),
- PveConfigSwitchListTile(
- title: const Text("KVM hardware virtualization"),
- value: config.kvm,
- defaultValue: true,
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('acpi', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "ACPI support",
+ value: config.acpi == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('acpi'),
+ ),
+ ),
+ PveConfigListTile<bool>(
+ title: "KVM hardware virtualization",
+ trailingText: config.kvm == true ? 'Yes' : 'No',
pending: config.getPending('kvm'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('kvm', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('kvm')),
- ),
- PveConfigSwitchListTile(
- title: const Text("Freeze CPU on startup"),
- value: config.freeze,
- defaultValue: false,
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('kvm', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "KVM hardware virtualization",
+ value: config.kvm == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('kvm'),
+ ),
+ ),
+ PveConfigListTile<bool>(
+ title: "Freeze CPU on startup",
+ trailingText: config.freeze == true ? 'Yes' : 'No',
pending: config.getPending('freeze'),
- onChanged: (v) =>
- bloc.events.add(UpdateQemuConfigBool('freeze', v)),
- onDeleted: () =>
- bloc.events.add(RevertPendingQemuConfig('freeze')),
- ),
- PveConfigSwitchListTile(
- title: const Text("Use local time for RTC"),
- value: config.localtime,
- defaultValue: false,
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('freeze', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Freeze CPU on startup",
+ value: config.freeze == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('freeze'),
+ ),
+ ),
+ PveConfigListTile<bool>(
+ title: "Use local time for RTC",
+ trailingText: config.localtime == true ? 'Yes' : 'No',
pending: config.getPending('localtime'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('localtime', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('localtime')),
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('localtime', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Use local time for RTC",
+ value: config.localtime == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('localtime'),
+ ),
),
ListTile(
title: const Text("RTC start date"),
@@ -127,15 +176,23 @@ class PveQemuOptions extends StatelessWidget {
title: const Text("QEMU Guest Agent"),
subtitle: Text(config.agent ?? 'Default (disabled)'),
),
- PveConfigSwitchListTile(
- title: const Text("Protection"),
- value: config.protection,
- defaultValue: false,
+ PveConfigListTile<bool>(
+ title: "Protection",
+ trailingText: config.protection == true ? 'Yes' : 'No',
pending: config.getPending('protection'),
- onChanged: (v) => bloc.events
- .add(UpdateQemuConfigBool('protection', v)),
- onDeleted: () => bloc.events
- .add(RevertPendingQemuConfig('protection')),
+ onSubmit: (v) => bloc.events.add(
+ UpdateQemuConfigBool('protection', v),
+ ),
+ editorBuilder: (_, controller) {
+ return PveConfigSwitchForm(
+ title: "Protection",
+ value: config.protection == true,
+ controller: controller,
+ );
+ },
+ onCancelEdit: () => bloc.events.add(
+ RevertPendingQemuConfig('protection'),
+ ),
),
ListTile(
title: const Text("Spice Enhancements"),
--
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] 5+ messages in thread
* [pve-devel] [PATCH pve_flutter_frontend v2 3/3] cleanup: remove `PveConfigSwitchListTile` in favor of PveConfigListTile
2025-09-23 12:23 [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 1/3] cleanup: run `dart format` on both qemu and lxc options page Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 2/3] feat: ui: add bottom sheet to change VMs/CTs options Shan Shaji
@ 2025-09-23 12:23 ` Shan Shaji
2025-09-23 12:57 ` [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Thomas Lamprecht
3 siblings, 0 replies; 5+ messages in thread
From: Shan Shaji @ 2025-09-23 12:23 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
lib/widgets/pve_config_switch_list_tile.dart | 50 --------------------
1 file changed, 50 deletions(-)
delete mode 100644 lib/widgets/pve_config_switch_list_tile.dart
diff --git a/lib/widgets/pve_config_switch_list_tile.dart b/lib/widgets/pve_config_switch_list_tile.dart
deleted file mode 100644
index c209fbe..0000000
--- a/lib/widgets/pve_config_switch_list_tile.dart
+++ /dev/null
@@ -1,50 +0,0 @@
-import 'package:flutter/material.dart';
-
-class PveConfigSwitchListTile extends StatelessWidget {
- final bool? value;
- final int? pending;
- final bool? defaultValue;
- final Widget? title;
- final ValueChanged<bool>? onChanged;
- final VoidCallback? onDeleted;
-
- const PveConfigSwitchListTile({
- super.key,
- this.value,
- this.pending,
- this.defaultValue,
- this.title,
- this.onChanged,
- this.onDeleted,
- });
- @override
- Widget build(BuildContext context) {
- bool? pBool;
- if (pending != null) {
- pBool = pending == 0 ? false : true;
- }
- return SwitchListTile(
- title: _getTitle(),
- value: pBool ?? value ?? defaultValue!,
- onChanged: pending != null ? null : onChanged,
- );
- }
-
- Widget? _getTitle() {
- if (pending != null) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- title!,
- Chip(
- label: const Text('pending'),
- backgroundColor: Colors.red,
- onDeleted: onDeleted,
- )
- ],
- );
- } else {
- return title;
- }
- }
-}
--
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] 5+ messages in thread
* Re: [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs
2025-09-23 12:23 [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Shan Shaji
` (2 preceding siblings ...)
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 3/3] cleanup: remove `PveConfigSwitchListTile` in favor of PveConfigListTile Shan Shaji
@ 2025-09-23 12:57 ` Thomas Lamprecht
3 siblings, 0 replies; 5+ messages in thread
From: Thomas Lamprecht @ 2025-09-23 12:57 UTC (permalink / raw)
To: pve-devel, Shan Shaji
On Tue, 23 Sep 2025 14:23:31 +0200, Shan Shaji wrote:
> On the options page for VMs and CTs it was easy to change the
> configs by mistake. To avoid that, all currently available
> editable options will be shown inside a bottom sheet.
>
> changes since v1: Thanks @Thomas
> patch: https://lore.proxmox.com/pve-devel/20250923093629.119418-1-s.shaji@proxmox.com/T/#t
> - Fixed file import error.
>
> [...]
Applied, thanks!
I changed the background color of the "pending" chip to orange in a follow up
commit though, red looked rather like an error to me.
[1/3] cleanup: run `dart format` on both qemu and lxc options page
commit: 9665ea62f85adcbd8e3ca2835cd1a65b661cb670
[2/3] feat: ui: add bottom sheet to change VMs/CTs options
commit: 3456772270bd26e20544f35c9881f68f0e6687c7
[3/3] cleanup: remove `PveConfigSwitchListTile` in favor of PveConfigListTile
commit: ada9279f4ae762a8473954b1a0c564b0b0c8f644
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-09-23 12:57 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-23 12:23 [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 1/3] cleanup: run `dart format` on both qemu and lxc options page Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 2/3] feat: ui: add bottom sheet to change VMs/CTs options Shan Shaji
2025-09-23 12:23 ` [pve-devel] [PATCH pve_flutter_frontend v2 3/3] cleanup: remove `PveConfigSwitchListTile` in favor of PveConfigListTile Shan Shaji
2025-09-23 12:57 ` [pve-devel] [PATCH pve_flutter_frontend v2 0/3] feat: ui: add bottom sheet to show editable options of VMs/CTs Thomas Lamprecht
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.