public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH pve_flutter_frontend 0/2] fix #7045: regression: add extra padding around the body to avoid system intrusions
@ 2025-11-19 12:57 Shan Shaji
  2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 1/2] fix #7045: regression: avoid system intrusion from navigation bar Shan Shaji
  2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 2/2] cleanup: run `dart format .` command to fix formatting Shan Shaji
  0 siblings, 2 replies; 3+ messages in thread
From: Shan Shaji @ 2025-11-19 12:57 UTC (permalink / raw)
  To: pve-devel

We had two reports [0][1] where the bottom system navigation bar were obscuring
with the app UI. Inorder to fix the issue wraped the qemu and lxc
overview pages inside `SafeArea`. The options config sheet UI were also
getting obscured due to the navigation bar. Fixed the same by wrapping
the content of the sheet inside a `SafeArea`. 

- [0] https://bugzilla.proxmox.com/show_bug.cgi?id=7034
- [1] https://bugzilla.proxmox.com/show_bug.cgi?id=7045

Shan Shaji (2):
  fix #7045: regression: avoid system intrusion from navigation bar
  cleanup: run `dart format .` command to fix formatting

 lib/widgets/pve_cd_selector_widget.dart  |  96 +--
 lib/widgets/pve_config_list_tile.dart    |  64 +-
 lib/widgets/pve_lxc_options_widget.dart  | 179 +++---
 lib/widgets/pve_lxc_overview.dart        | 344 +++++------
 lib/widgets/pve_node_overview.dart       | 724 ++++++++++++-----------
 lib/widgets/pve_qemu_options_widget.dart | 334 ++++++-----
 lib/widgets/pve_qemu_overview.dart       | 303 +++++-----
 7 files changed, 1041 insertions(+), 1003 deletions(-)

-- 
2.47.3



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


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

* [pve-devel] [PATCH pve_flutter_frontend 1/2] fix #7045: regression: avoid system intrusion from navigation bar
  2025-11-19 12:57 [pve-devel] [PATCH pve_flutter_frontend 0/2] fix #7045: regression: add extra padding around the body to avoid system intrusions Shan Shaji
@ 2025-11-19 12:57 ` Shan Shaji
  2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 2/2] cleanup: run `dart format .` command to fix formatting Shan Shaji
  1 sibling, 0 replies; 3+ messages in thread
From: Shan Shaji @ 2025-11-19 12:57 UTC (permalink / raw)
  To: pve-devel

In the following commit(f263217) the `ColoredSafeArea` was removed.
However the system intrusion from the system bottom navigation bar was
causing issues. Inorder to fix the issue the wrap the body of the
guest's overview and options page with `SafeArea` widget.

For iOS set the bottom padding to false as it feels like there is a
background present under the bottom navigation indicator.

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 lib/widgets/pve_config_list_tile.dart    |  5 +++--
 lib/widgets/pve_lxc_options_widget.dart  |  8 ++++++--
 lib/widgets/pve_lxc_overview.dart        |  8 ++++++--
 lib/widgets/pve_node_overview.dart       |  8 ++++++--
 lib/widgets/pve_qemu_options_widget.dart |  8 ++++++--
 lib/widgets/pve_qemu_overview.dart       | 10 +++++++---
 6 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/lib/widgets/pve_config_list_tile.dart b/lib/widgets/pve_config_list_tile.dart
index 712ff1a..c937755 100644
--- a/lib/widgets/pve_config_list_tile.dart
+++ b/lib/widgets/pve_config_list_tile.dart
@@ -130,7 +130,8 @@ class _PveConfigEditorFormState<T> extends State<_PveConfigEditorForm<T>> {
 
   @override
   Widget build(BuildContext context) {
-    return Container(
+    return SafeArea(
+        child: Container(
       padding: const EdgeInsets.symmetric(horizontal: 12),
       child: Column(
         mainAxisSize: MainAxisSize.min,
@@ -164,6 +165,6 @@ class _PveConfigEditorFormState<T> extends State<_PveConfigEditorForm<T>> {
           const SizedBox(height: 8),
         ],
       ),
-    );
+    ),);
   }
 }
diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart
index 4bd263e..21398d2 100644
--- a/lib/widgets/pve_lxc_options_widget.dart
+++ b/lib/widgets/pve_lxc_options_widget.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 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';
@@ -18,7 +20,9 @@ class PveLxcOptions extends StatelessWidget {
           if (config != null) {
             return Scaffold(
               appBar: AppBar(),
-              body: SingleChildScrollView(
+              body: SafeArea(
+                bottom: !Platform.isIOS,
+                child: SingleChildScrollView(
                 child: Column(
                   children: <Widget>[
                     ListTile(
@@ -114,7 +118,7 @@ class PveLxcOptions extends StatelessWidget {
                   ],
                 ),
               ),
-            );
+            ),);
           }
           return const Center(
             child: CircularProgressIndicator(),
diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart
index 1148452..0f486d2 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:font_awesome_flutter/font_awesome_flutter.dart';
 import 'package:provider/provider.dart';
@@ -75,7 +77,9 @@ class PveLxcOverview extends StatelessWidget {
                 title: Text(config?.hostname ?? 'CT $guestID'),
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-              body: SingleChildScrollView(
+              body: SafeArea(
+                bottom: !Platform.isIOS,
+                child: SingleChildScrollView(
                 child: Column(
                   children: <Widget>[
                     PveGuestOverviewHeader(
@@ -258,7 +262,7 @@ class PveLxcOverview extends StatelessWidget {
                   ],
                 ),
               ),
-            );
+            ),);
           }),
     );
   }
diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart
index f99ca53..f0583a3 100644
--- a/lib/widgets/pve_node_overview.dart
+++ b/lib/widgets/pve_node_overview.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:font_awesome_flutter/font_awesome_flutter.dart';
 import 'package:provider/provider.dart';
@@ -60,7 +62,9 @@ class PveNodeOverview extends StatelessWidget {
             ),
           ),
           backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-          body: SingleChildScrollView(
+          body: SafeArea(
+            bottom: !Platform.isIOS,
+            child: SingleChildScrollView(
             child: Column(
               children: <Widget>[
                 if (rrd.isNotEmpty)
@@ -442,7 +446,7 @@ class PveNodeOverview extends StatelessWidget {
               ],
             ),
           ),
-        );
+        ),);
       },
     );
   }
diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart
index f8cb000..0f0d46a 100644
--- a/lib/widgets/pve_qemu_options_widget.dart
+++ b/lib/widgets/pve_qemu_options_widget.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart';
@@ -26,7 +28,9 @@ class PveQemuOptions extends StatelessWidget {
                   onPressed: () => Navigator.of(context).pop(),
                 ),
               ),
-              body: SingleChildScrollView(
+              body: SafeArea(
+                bottom: !Platform.isIOS,
+                child: SingleChildScrollView(
                 child: Form(
                   key: _formKey,
                   onChanged: () {},
@@ -207,7 +211,7 @@ class PveQemuOptions extends StatelessWidget {
                   ),
                 ),
               ),
-            );
+            ),);
           }
           return const Center(
             child: CircularProgressIndicator(),
diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart
index 15ab409..a80a58a 100644
--- a/lib/widgets/pve_qemu_overview.dart
+++ b/lib/widgets/pve_qemu_overview.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:font_awesome_flutter/font_awesome_flutter.dart';
 import 'package:provider/provider.dart';
@@ -76,8 +78,10 @@ class PveQemuOverview extends StatelessWidget {
                 title: Text(config?.name ?? 'VM $guestID'),
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-              body: SingleChildScrollView(
-                  child: Column(
+              body: SafeArea(
+                bottom: !Platform.isIOS,
+                child: SingleChildScrollView(
+                    child: Column(
                 children: <Widget>[
                   PveGuestOverviewHeader(
                     background: !(status?.template ?? false)
@@ -242,7 +246,7 @@ class PveQemuOverview extends StatelessWidget {
                         ]),
                 ],
               )),
-            );
+            ),);
           }),
     );
   }
-- 
2.47.3



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


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

* [pve-devel] [PATCH pve_flutter_frontend 2/2] cleanup: run `dart format .` command to fix formatting
  2025-11-19 12:57 [pve-devel] [PATCH pve_flutter_frontend 0/2] fix #7045: regression: add extra padding around the body to avoid system intrusions Shan Shaji
  2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 1/2] fix #7045: regression: avoid system intrusion from navigation bar Shan Shaji
@ 2025-11-19 12:57 ` Shan Shaji
  1 sibling, 0 replies; 3+ messages in thread
From: Shan Shaji @ 2025-11-19 12:57 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 lib/widgets/pve_cd_selector_widget.dart  |  96 +--
 lib/widgets/pve_config_list_tile.dart    |  65 +-
 lib/widgets/pve_lxc_options_widget.dart  | 175 +++---
 lib/widgets/pve_lxc_overview.dart        | 340 +++++------
 lib/widgets/pve_node_overview.dart       | 720 ++++++++++++-----------
 lib/widgets/pve_qemu_options_widget.dart | 330 +++++------
 lib/widgets/pve_qemu_overview.dart       | 297 +++++-----
 7 files changed, 1020 insertions(+), 1003 deletions(-)

diff --git a/lib/widgets/pve_cd_selector_widget.dart b/lib/widgets/pve_cd_selector_widget.dart
index b4591d0..8a67eac 100644
--- a/lib/widgets/pve_cd_selector_widget.dart
+++ b/lib/widgets/pve_cd_selector_widget.dart
@@ -23,53 +23,57 @@ class PveCdSelector extends StatelessWidget {
           if (snapshot.hasData) {
             final state = snapshot.data!;
             return RadioGroup(
-              groupValue: state.value,
-              onChanged: (value) => cdBloc.events.add(ChangeValue(value)),
-              child: Column(children: [
-              RadioListTile<CdType>(
-                title: const Text('Use CD/DVD disc image file (iso)'),
-                value: CdType.iso,
-              ),
-              if (state.value == CdType.iso)
-                OutlinedButton(
-                  style: OutlinedButton.styleFrom(
-                    side: state.hasError
-                        ? const BorderSide(color: Colors.red)
-                        : null,
+                groupValue: state.value,
+                onChanged: (value) => cdBloc.events.add(ChangeValue(value)),
+                child: Column(children: [
+                  RadioListTile<CdType>(
+                    title: const Text('Use CD/DVD disc image file (iso)'),
+                    value: CdType.iso,
                   ),
-                  child: Text((state.file == null || state.file!.isEmpty)
-                      ? "Choose File"
-                      : state.file!),
-                  onPressed: () async {
-                    final PveNodesStorageContentModel? file = await showDialog(
-                        context: context,
-                        builder: (context) => PveFileSelector(
-                              fBloc: PveFileSelectorBloc(
-                                apiClient: client,
-                                init: PveFileSelectorState.init().rebuild((b) =>
-                                    b..fileType = PveStorageContentType.iso),
-                              ),
-                              sBloc: PveStorageSelectorBloc(
-                                apiClient: client,
-                                init: PveStorageSelectorState.init().rebuild(
-                                    (b) =>
-                                        b..content = PveStorageContentType.iso),
-                              )..events.add(LoadStoragesEvent()),
-                            ));
-                    if (file != null) {
-                      cdBloc.events.add(FileSelected(file.volid));
-                    }
-                  },
-                ),
-              RadioListTile<CdType>(
-                title: const Text('Use physical CD/DVD Drive'),
-                value: CdType.cdrom,
-              ),
-              RadioListTile<CdType>(
-                title: const Text('Do not use any media'),
-                value: CdType.none,
-              ),
-            ]));
+                  if (state.value == CdType.iso)
+                    OutlinedButton(
+                      style: OutlinedButton.styleFrom(
+                        side: state.hasError
+                            ? const BorderSide(color: Colors.red)
+                            : null,
+                      ),
+                      child: Text((state.file == null || state.file!.isEmpty)
+                          ? "Choose File"
+                          : state.file!),
+                      onPressed: () async {
+                        final PveNodesStorageContentModel? file =
+                            await showDialog(
+                                context: context,
+                                builder: (context) => PveFileSelector(
+                                      fBloc: PveFileSelectorBloc(
+                                        apiClient: client,
+                                        init: PveFileSelectorState.init()
+                                            .rebuild((b) => b
+                                              ..fileType =
+                                                  PveStorageContentType.iso),
+                                      ),
+                                      sBloc: PveStorageSelectorBloc(
+                                        apiClient: client,
+                                        init: PveStorageSelectorState.init()
+                                            .rebuild((b) => b
+                                              ..content =
+                                                  PveStorageContentType.iso),
+                                      )..events.add(LoadStoragesEvent()),
+                                    ));
+                        if (file != null) {
+                          cdBloc.events.add(FileSelected(file.volid));
+                        }
+                      },
+                    ),
+                  RadioListTile<CdType>(
+                    title: const Text('Use physical CD/DVD Drive'),
+                    value: CdType.cdrom,
+                  ),
+                  RadioListTile<CdType>(
+                    title: const Text('Do not use any media'),
+                    value: CdType.none,
+                  ),
+                ]));
           }
 
           return Container();
diff --git a/lib/widgets/pve_config_list_tile.dart b/lib/widgets/pve_config_list_tile.dart
index c937755..24eb1ec 100644
--- a/lib/widgets/pve_config_list_tile.dart
+++ b/lib/widgets/pve_config_list_tile.dart
@@ -131,40 +131,41 @@ class _PveConfigEditorFormState<T> extends State<_PveConfigEditorForm<T>> {
   @override
   Widget build(BuildContext context) {
     return SafeArea(
-        child: 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,
+      child: 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'),
+                TextButton(
+                  onPressed: () => Navigator.pop(context),
+                  child: const Text('Close'),
+                ),
+              ],
             ),
-          ),
-          const SizedBox(height: 8),
-        ],
+            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 21398d2..c43e435 100644
--- a/lib/widgets/pve_lxc_options_widget.dart
+++ b/lib/widgets/pve_lxc_options_widget.dart
@@ -23,102 +23,103 @@ class PveLxcOptions extends StatelessWidget {
               body: SafeArea(
                 bottom: !Platform.isIOS,
                 child: SingleChildScrollView(
-                child: Column(
-                  children: <Widget>[
-                    ListTile(
-                      title: const Text("Name"),
-                      subtitle: Text(config.hostname ?? 'undefined'),
-                    ),
-                    PveConfigListTile<bool>(
-                      title: "Start on boot",
-                      trailingText: config.onboot == true ? 'Yes' : 'No',
-                      pending: config.getPending('onboot'),
-                      onSubmit: (value) => lxcBloc!.events.add(
-                        UpdateLxcConfigBool(
-                          'onboot',
-                          value,
+                  child: Column(
+                    children: <Widget>[
+                      ListTile(
+                        title: const Text("Name"),
+                        subtitle: Text(config.hostname ?? 'undefined'),
+                      ),
+                      PveConfigListTile<bool>(
+                        title: "Start on boot",
+                        trailingText: config.onboot == true ? 'Yes' : 'No',
+                        pending: config.getPending('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'),
                         ),
                       ),
-                      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"),
+                        subtitle: Text(config.startup ?? "Default (any)"),
                       ),
-                    ),
-                    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}"),
-                    ),
-                    PveConfigListTile<bool>(
-                      title: "/dev/console",
-                      trailingText: config.console == true ? 'Yes' : 'No',
-                      pending: config.getPending('console'),
-                      onSubmit: (v) => lxcBloc!.events.add(
-                        UpdateLxcConfigBool('console', v),
+                      ListTile(
+                        title: const Text("OS Type"),
+                        subtitle: Text("${config.ostype}"),
                       ),
-                      editorBuilder: (_, controller) {
-                        return PveConfigSwitchForm(
-                          title: "/dev/console",
-                          value: config.console == true,
-                          controller: controller,
-                        );
-                      },
-                      onCancelEdit: () => lxcBloc!.events.add(
-                        RevertPendingLxcConfig('console'),
+                      ListTile(
+                        title: const Text("Architecture"),
+                        subtitle: Text("${config.arch}"),
                       ),
-                    ),
-                    ListTile(
-                      title: const Text("TTY Count"),
-                      subtitle: Text("${config.tty ?? 2}"),
-                    ),
-                    ListTile(
-                      title: const Text("Console Mode"),
-                      subtitle: Text(config.cmode?.name ?? 'tty'),
-                    ),
-                    PveConfigListTile<bool>(
-                      title: "Protection",
-                      trailingText: config.protection == true ? 'Yes' : 'No',
-                      pending: config.getPending('protection'),
-                      onSubmit: (v) => lxcBloc!.events.add(
-                        UpdateLxcConfigBool('protection', v),
+                      PveConfigListTile<bool>(
+                        title: "/dev/console",
+                        trailingText: config.console == true ? 'Yes' : 'No',
+                        pending: config.getPending('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'),
+                        ),
                       ),
-                      editorBuilder: (_, controller) {
-                        return PveConfigSwitchForm(
-                          title: "Protection",
-                          value: config.protection == true,
-                          controller: controller,
-                        );
-                      },
-                      onCancelEdit: () => lxcBloc!.events.add(
-                        RevertPendingLxcConfig('protection'),
+                      ListTile(
+                        title: const Text("TTY Count"),
+                        subtitle: Text("${config.tty ?? 2}"),
                       ),
-                    ),
-                    ListTile(
-                      title: const Text("Unprivileged"),
-                      subtitle:
-                          Text(config.unprivileged ?? false ? 'Yes' : 'No'),
-                    ),
-                    ListTile(
-                      title: const Text("Features"),
-                      subtitle: Text(config.features?.toString() ?? 'none'),
-                    ),
-                  ],
+                      ListTile(
+                        title: const Text("Console Mode"),
+                        subtitle: Text(config.cmode?.name ?? 'tty'),
+                      ),
+                      PveConfigListTile<bool>(
+                        title: "Protection",
+                        trailingText: config.protection == true ? 'Yes' : 'No',
+                        pending: config.getPending('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"),
+                        subtitle:
+                            Text(config.unprivileged ?? false ? 'Yes' : 'No'),
+                      ),
+                      ListTile(
+                        title: const Text("Features"),
+                        subtitle: Text(config.features?.toString() ?? 'none'),
+                      ),
+                    ],
+                  ),
                 ),
               ),
-            ),);
+            );
           }
           return const Center(
             child: CircularProgressIndicator(),
diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart
index 0f486d2..78ad853 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -80,189 +80,191 @@ class PveLxcOverview extends StatelessWidget {
               body: SafeArea(
                 bottom: !Platform.isIOS,
                 child: SingleChildScrollView(
-                child: Column(
-                  children: <Widget>[
-                    PveGuestOverviewHeader(
-                      background: !(status?.template ?? false)
-                          ? PveGuestHeaderRRDPageView(
-                              rrdData: state.rrdData,
-                            )
-                          : Center(
-                              child: Text(
-                                "TEMPLATE",
-                                style: TextStyle(
-                                  color: Theme.of(context)
-                                      .colorScheme
-                                      .onSurfaceVariant,
+                  child: Column(
+                    children: <Widget>[
+                      PveGuestOverviewHeader(
+                        background: !(status?.template ?? false)
+                            ? PveGuestHeaderRRDPageView(
+                                rrdData: state.rrdData,
+                              )
+                            : Center(
+                                child: Text(
+                                  "TEMPLATE",
+                                  style: TextStyle(
+                                    color: Theme.of(context)
+                                        .colorScheme
+                                        .onSurfaceVariant,
+                                  ),
                                 ),
                               ),
-                            ),
-                      width: width,
-                      guestID: guestID,
-                      guestStatus: status?.getLxcStatus(),
-                      guestName: config?.hostname ?? 'CT $guestID',
-                      guestNodeID: state.nodeID,
-                      guestType: 'lxc',
-                      ha: status?.ha,
-                      template: status?.template ?? false,
-                    ),
-                    ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
-                      bloc: taskBloc,
-                      builder: (context, taskState) {
-                        if (taskState.tasks.isNotEmpty) {
-                          return PveTaskExpansionTile(
-                            task: taskState.tasks.first,
-                            showMorePage: Provider<PveTaskLogBloc>(
-                              create: (context) => PveTaskLogBloc(
-                                apiClient: taskBloc.apiClient,
-                                init: PveTaskLogState.init(state.nodeID),
-                              )
-                                ..events.add(
-                                  FilterTasksByGuestID(
-                                    guestID: guestID,
-                                  ),
+                        width: width,
+                        guestID: guestID,
+                        guestStatus: status?.getLxcStatus(),
+                        guestName: config?.hostname ?? 'CT $guestID',
+                        guestNodeID: state.nodeID,
+                        guestType: 'lxc',
+                        ha: status?.ha,
+                        template: status?.template ?? false,
+                      ),
+                      ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
+                        bloc: taskBloc,
+                        builder: (context, taskState) {
+                          if (taskState.tasks.isNotEmpty) {
+                            return PveTaskExpansionTile(
+                              task: taskState.tasks.first,
+                              showMorePage: Provider<PveTaskLogBloc>(
+                                create: (context) => PveTaskLogBloc(
+                                  apiClient: taskBloc.apiClient,
+                                  init: PveTaskLogState.init(state.nodeID),
                                 )
-                                ..events.add(LoadTasks()),
-                              dispose: (context, bloc) => bloc.dispose(),
-                              child: const PveTaskLog(),
-                            ),
-                          );
-                        }
-                        return Container();
-                      },
-                    ),
-                    SizedBox(
-                      height: 130,
-                      child: SingleChildScrollView(
-                        scrollDirection: Axis.horizontal,
-                        child: Row(
-                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-                          children: <Widget>[
-                            if (!(status?.template ?? false))
+                                  ..events.add(
+                                    FilterTasksByGuestID(
+                                      guestID: guestID,
+                                    ),
+                                  )
+                                  ..events.add(LoadTasks()),
+                                dispose: (context, bloc) => bloc.dispose(),
+                                child: const PveTaskLog(),
+                              ),
+                            );
+                          }
+                          return Container();
+                        },
+                      ),
+                      SizedBox(
+                        height: 130,
+                        child: SingleChildScrollView(
+                          scrollDirection: Axis.horizontal,
+                          child: Row(
+                            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                            children: <Widget>[
+                              if (!(status?.template ?? false))
+                                createActionCard(
+                                    'Power Settings',
+                                    Icons.power_settings_new,
+                                    () => showPowerMenuBottomSheet(
+                                        context, lxcBloc)),
+                              if (!(status?.template ?? false))
+                                createActionCard(
+                                    'Console',
+                                    Icons.queue_play_next,
+                                    () => showConsoleMenuBottomSheet(
+                                        context,
+                                        lxcBloc.apiClient,
+                                        guestID,
+                                        state.nodeID,
+                                        'lxc')),
                               createActionCard(
-                                  'Power Settings',
-                                  Icons.power_settings_new,
-                                  () => showPowerMenuBottomSheet(
-                                      context, lxcBloc)),
-                            if (!(status?.template ?? false))
+                                  'Options',
+                                  Icons.settings,
+                                  () => Navigator.of(context)
+                                      .push(MaterialPageRoute(
+                                          builder: (context) => PveLxcOptions(
+                                                lxcBloc: lxcBloc,
+                                              ),
+                                          fullscreenDialog: true))),
+                              if (!resourceBloc.latestState.isStandalone)
+                                createActionCard(
+                                    'Migrate',
+                                    FontAwesomeIcons.paperPlane,
+                                    () => Navigator.of(context).push(
+                                        _createMigrationRoute(
+                                            guestID,
+                                            state.nodeID,
+                                            resourceBloc.apiClient!))),
                               createActionCard(
-                                  'Console',
-                                  Icons.queue_play_next,
-                                  () => showConsoleMenuBottomSheet(
-                                      context,
-                                      lxcBloc.apiClient,
-                                      guestID,
-                                      state.nodeID,
-                                      'lxc')),
-                            createActionCard(
-                                'Options',
-                                Icons.settings,
-                                () => Navigator.of(context)
-                                    .push(MaterialPageRoute(
-                                        builder: (context) => PveLxcOptions(
-                                              lxcBloc: lxcBloc,
-                                            ),
-                                        fullscreenDialog: true))),
-                            if (!resourceBloc.latestState.isStandalone)
-                              createActionCard(
-                                  'Migrate',
-                                  FontAwesomeIcons.paperPlane,
+                                  'Backup',
+                                  FontAwesomeIcons.floppyDisk,
                                   () => Navigator.of(context).push(
-                                      _createMigrationRoute(
-                                          guestID,
-                                          state.nodeID,
+                                      _createBackupRoute(guestID, state.nodeID,
                                           resourceBloc.apiClient!))),
-                            createActionCard(
-                                'Backup',
-                                FontAwesomeIcons.floppyDisk,
-                                () => Navigator.of(context).push(
-                                    _createBackupRoute(guestID, state.nodeID,
-                                        resourceBloc.apiClient!))),
+                            ],
+                          ),
+                        ),
+                      ),
+                      if (config != null) ...[
+                        PveResourceDataCardWidget(
+                          title: const Text(
+                            'Resources',
+                            style: TextStyle(
+                              fontWeight: FontWeight.bold,
+                              fontSize: 20,
+                            ),
+                          ),
+                          children: <Widget>[
+                            ListTile(
+                              leading: const Icon(FontAwesomeIcons.memory),
+                              title: Text('${config.memory}'),
+                              subtitle: const Text('Memory'),
+                              dense: true,
+                            ),
+                            ListTile(
+                              leading: const Icon(Icons.cached),
+                              title: Text('${config.swap}'),
+                              subtitle: const Text('Swap'),
+                              dense: true,
+                            ),
+                            ListTile(
+                              leading: const Icon(Icons.memory),
+                              title: Text('${config.cores ?? 'unlimited'}'),
+                              subtitle: const Text('Cores'),
+                              dense: true,
+                            ),
+                            ListTile(
+                              leading: const Icon(FontAwesomeIcons.hardDrive),
+                              dense: true,
+                              title: Text('${config.rootfs}'),
+                            ),
                           ],
                         ),
-                      ),
-                    ),
-                    if (config != null) ...[
-                      PveResourceDataCardWidget(
-                        title: const Text(
-                          'Resources',
-                          style: TextStyle(
-                            fontWeight: FontWeight.bold,
-                            fontSize: 20,
-                          ),
-                        ),
-                        children: <Widget>[
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.memory),
-                            title: Text('${config.memory}'),
-                            subtitle: const Text('Memory'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(Icons.cached),
-                            title: Text('${config.swap}'),
-                            subtitle: const Text('Swap'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(Icons.memory),
-                            title: Text('${config.cores ?? 'unlimited'}'),
-                            subtitle: const Text('Cores'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.hardDrive),
-                            dense: true,
-                            title: Text('${config.rootfs}'),
-                          ),
-                        ],
-                      ),
-                      PveResourceDataCardWidget(
-                          title: const Text(
-                            'Network',
-                            style: TextStyle(
-                              fontWeight: FontWeight.bold,
-                              fontSize: 20,
-                            ),
-                          ),
-                          children: <Widget>[
-                            for (var net in config.net!)
-                              ListTile(
-                                leading: const Icon(FontAwesomeIcons.ethernet),
-                                dense: true,
-                                title: Text(net),
+                        PveResourceDataCardWidget(
+                            title: const Text(
+                              'Network',
+                              style: TextStyle(
+                                fontWeight: FontWeight.bold,
+                                fontSize: 20,
                               ),
-                          ]),
-                      PveResourceDataCardWidget(
-                          title: const Text(
-                            'DNS',
-                            style: TextStyle(
-                              fontWeight: FontWeight.bold,
-                              fontSize: 20,
                             ),
-                          ),
-                          children: <Widget>[
-                            ListTile(
-                              leading: const Icon(FontAwesomeIcons.globe),
-                              dense: true,
-                              title: Text(
-                                  config.searchdomain ?? 'Use host settings'),
-                              subtitle: const Text('DNS Domain'),
+                            children: <Widget>[
+                              for (var net in config.net!)
+                                ListTile(
+                                  leading:
+                                      const Icon(FontAwesomeIcons.ethernet),
+                                  dense: true,
+                                  title: Text(net),
+                                ),
+                            ]),
+                        PveResourceDataCardWidget(
+                            title: const Text(
+                              'DNS',
+                              style: TextStyle(
+                                fontWeight: FontWeight.bold,
+                                fontSize: 20,
+                              ),
                             ),
-                            ListTile(
-                              leading:
-                                  const Icon(FontAwesomeIcons.magnifyingGlass),
-                              dense: true,
-                              title: Text(
-                                  config.nameserver ?? 'Use host settings'),
-                              subtitle: const Text('DNS Server'),
-                            ),
-                          ]),
-                    ]
-                  ],
+                            children: <Widget>[
+                              ListTile(
+                                leading: const Icon(FontAwesomeIcons.globe),
+                                dense: true,
+                                title: Text(
+                                    config.searchdomain ?? 'Use host settings'),
+                                subtitle: const Text('DNS Domain'),
+                              ),
+                              ListTile(
+                                leading: const Icon(
+                                    FontAwesomeIcons.magnifyingGlass),
+                                dense: true,
+                                title: Text(
+                                    config.nameserver ?? 'Use host settings'),
+                                subtitle: const Text('DNS Server'),
+                              ),
+                            ]),
+                      ]
+                    ],
+                  ),
                 ),
               ),
-            ),);
+            );
           }),
     );
   }
diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart
index f0583a3..7236cf4 100644
--- a/lib/widgets/pve_node_overview.dart
+++ b/lib/widgets/pve_node_overview.dart
@@ -65,388 +65,392 @@ class PveNodeOverview extends StatelessWidget {
           body: SafeArea(
             bottom: !Platform.isIOS,
             child: SingleChildScrollView(
-            child: Column(
-              children: <Widget>[
-                if (rrd.isNotEmpty)
-                  Container(
-                    height: 200,
-                    color: Theme.of(context).colorScheme.primary,
-                    child: ScrollConfiguration(
-                      behavior: PVEScrollBehavior(),
-                      child: PageView.builder(
-                        itemCount: 4,
-                        itemBuilder: (context, item) {
-                          final page = item + 1;
-                          final pageIndicator = Text(
-                            '$page of 4',
-                            style: const TextStyle(
-                              color: Colors.white54,
-                              fontWeight: FontWeight.w500,
+              child: Column(
+                children: <Widget>[
+                  if (rrd.isNotEmpty)
+                    Container(
+                      height: 200,
+                      color: Theme.of(context).colorScheme.primary,
+                      child: ScrollConfiguration(
+                        behavior: PVEScrollBehavior(),
+                        child: PageView.builder(
+                          itemCount: 4,
+                          itemBuilder: (context, item) {
+                            final page = item + 1;
+                            final pageIndicator = Text(
+                              '$page of 4',
+                              style: const TextStyle(
+                                color: Colors.white54,
+                                fontWeight: FontWeight.w500,
+                              ),
+                            );
+                            double? lastCpu = rrd.last.cpu;
+                            String lastCpuText = lastCpu != null
+                                ? "${(lastCpu * 100.0).toStringAsFixed(2)} %"
+                                : "";
+                            return Column(
+                              children: [
+                                if (item == 0)
+                                  Expanded(
+                                    child: PveRRDChart(
+                                      title:
+                                          'CPU (${state.status?.cpuinfo.cpus ?? '-'})',
+                                      subtitle: lastCpuText,
+                                      data: rrd.where((e) => e.cpu != null).map(
+                                          (e) => Point(
+                                              e.time!.millisecondsSinceEpoch,
+                                              (e.cpu ?? 0) * 100.0)),
+                                      icon: Icon(Icons.memory, color: fgColor),
+                                      bottomRight: pageIndicator,
+                                      dataRenderer: (data) =>
+                                          '${data.toStringAsFixed(2)} %',
+                                    ),
+                                  ),
+                                if (item == 1)
+                                  Expanded(
+                                    child: PveRRDChart(
+                                      title: 'Memory',
+                                      subtitle: Renderers.formatSize(
+                                          rrd.last.memused ?? 0),
+                                      data: rrd.map((e) => Point(
+                                          e.time!.millisecondsSinceEpoch,
+                                          e.memused ?? 0)),
+                                      icon: Icon(FontAwesomeIcons.memory,
+                                          color: fgColor),
+                                      bottomRight: pageIndicator,
+                                      dataRenderer: (data) =>
+                                          Renderers.formatSize(data),
+                                    ),
+                                  ),
+                                if (item == 2)
+                                  Expanded(
+                                    child: PveRRDChart(
+                                      title: 'I/O wait',
+                                      subtitle:
+                                          rrd.last.iowait?.toStringAsFixed(2) ??
+                                              '0',
+                                      data: rrd.map((e) => Point(
+                                          e.time!.millisecondsSinceEpoch,
+                                          e.iowait ?? 0)),
+                                      icon: Icon(Icons.timer, color: fgColor),
+                                      bottomRight: pageIndicator,
+                                      dataRenderer: (data) =>
+                                          data.toStringAsFixed(3),
+                                    ),
+                                  ),
+                                if (item == 3)
+                                  Expanded(
+                                    child: PveRRDChart(
+                                      title: 'Load',
+                                      subtitle: rrd.last.loadavg
+                                              ?.toStringAsFixed(2) ??
+                                          '0',
+                                      data: rrd.map((e) => Point(
+                                          e.time!.millisecondsSinceEpoch,
+                                          e.loadavg ?? 0)),
+                                      icon: Icon(Icons.show_chart,
+                                          color: fgColor),
+                                      bottomRight: pageIndicator,
+                                      dataRenderer: (data) =>
+                                          data.toStringAsFixed(2),
+                                    ),
+                                  ),
+                              ],
+                            );
+                          },
+                        ),
+                      ),
+                    ),
+                  ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
+                    bloc: tBloc,
+                    builder: (context, taskState) {
+                      if (taskState.tasks.isNotEmpty) {
+                        return Padding(
+                          padding: const EdgeInsets.all(4.0),
+                          child: PveTaskExpansionTile(
+                            task: taskState.tasks.first,
+                            showMorePage: Provider<PveTaskLogBloc>(
+                              create: (context) => PveTaskLogBloc(
+                                apiClient: tBloc.apiClient,
+                                init: PveTaskLogState.init(nodeID),
+                              )..events.add(LoadTasks()),
+                              dispose: (context, bloc) => bloc.dispose(),
+                              child: const PveTaskLog(),
                             ),
-                          );
-                          double? lastCpu = rrd.last.cpu;
-                          String lastCpuText = lastCpu != null
-                              ? "${(lastCpu * 100.0).toStringAsFixed(2)} %"
-                              : "";
-                          return Column(
-                            children: [
-                              if (item == 0)
-                                Expanded(
-                                  child: PveRRDChart(
-                                    title:
-                                        'CPU (${state.status?.cpuinfo.cpus ?? '-'})',
-                                    subtitle: lastCpuText,
-                                    data: rrd.where((e) => e.cpu != null).map(
-                                        (e) => Point(
-                                            e.time!.millisecondsSinceEpoch,
-                                            (e.cpu ?? 0) * 100.0)),
-                                    icon: Icon(Icons.memory, color: fgColor),
-                                    bottomRight: pageIndicator,
-                                    dataRenderer: (data) =>
-                                        '${data.toStringAsFixed(2)} %',
-                                  ),
-                                ),
-                              if (item == 1)
-                                Expanded(
-                                  child: PveRRDChart(
-                                    title: 'Memory',
-                                    subtitle: Renderers.formatSize(
-                                        rrd.last.memused ?? 0),
-                                    data: rrd.map((e) => Point(
-                                        e.time!.millisecondsSinceEpoch,
-                                        e.memused ?? 0)),
-                                    icon: Icon(FontAwesomeIcons.memory,
-                                        color: fgColor),
-                                    bottomRight: pageIndicator,
-                                    dataRenderer: (data) =>
-                                        Renderers.formatSize(data),
-                                  ),
-                                ),
-                              if (item == 2)
-                                Expanded(
-                                  child: PveRRDChart(
-                                    title: 'I/O wait',
-                                    subtitle:
-                                        rrd.last.iowait?.toStringAsFixed(2) ??
-                                            '0',
-                                    data: rrd.map((e) => Point(
-                                        e.time!.millisecondsSinceEpoch,
-                                        e.iowait ?? 0)),
-                                    icon: Icon(Icons.timer, color: fgColor),
-                                    bottomRight: pageIndicator,
-                                    dataRenderer: (data) =>
-                                        data.toStringAsFixed(3),
-                                  ),
-                                ),
-                              if (item == 3)
-                                Expanded(
-                                  child: PveRRDChart(
-                                    title: 'Load',
-                                    subtitle:
-                                        rrd.last.loadavg?.toStringAsFixed(2) ??
-                                            '0',
-                                    data: rrd.map((e) => Point(
-                                        e.time!.millisecondsSinceEpoch,
-                                        e.loadavg ?? 0)),
-                                    icon:
-                                        Icon(Icons.show_chart, color: fgColor),
-                                    bottomRight: pageIndicator,
-                                    dataRenderer: (data) =>
-                                        data.toStringAsFixed(2),
-                                  ),
-                                ),
-                            ],
-                          );
-                        },
+                          ),
+                        );
+                      }
+                      return Container();
+                    },
+                  ),
+                  SizedBox(
+                    height: 130,
+                    child: SingleChildScrollView(
+                      scrollDirection: Axis.horizontal,
+                      child: Row(
+                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                        children: <Widget>[
+                          ActionCard(
+                            icon: const Icon(
+                              Icons.power_settings_new,
+                              size: 55,
+                              color: Colors.white24,
+                            ),
+                            title: 'Power Settings',
+                            onTap: () =>
+                                showPowerMenuBottomSheet(context, nBloc),
+                          ),
+                          ActionCard(
+                            icon: const Icon(
+                              Icons.queue_play_next,
+                              size: 55,
+                              color: Colors.white24,
+                            ),
+                            title: 'Console',
+                            onTap: () => showConsoleMenuBottomSheet(
+                                context, nBloc.apiClient, null, nodeID, 'node'),
+                          ),
+                        ],
                       ),
                     ),
                   ),
-                ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
-                  bloc: tBloc,
-                  builder: (context, taskState) {
-                    if (taskState.tasks.isNotEmpty) {
-                      return Padding(
-                        padding: const EdgeInsets.all(4.0),
-                        child: PveTaskExpansionTile(
-                          task: taskState.tasks.first,
-                          showMorePage: Provider<PveTaskLogBloc>(
-                            create: (context) => PveTaskLogBloc(
-                              apiClient: tBloc.apiClient,
-                              init: PveTaskLogState.init(nodeID),
-                            )..events.add(LoadTasks()),
-                            dispose: (context, bloc) => bloc.dispose(),
-                            child: const PveTaskLog(),
-                          ),
-                        ),
-                      );
-                    }
-                    return Container();
-                  },
-                ),
-                SizedBox(
-                  height: 130,
-                  child: SingleChildScrollView(
-                    scrollDirection: Axis.horizontal,
-                    child: Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-                      children: <Widget>[
-                        ActionCard(
-                          icon: const Icon(
-                            Icons.power_settings_new,
-                            size: 55,
-                            color: Colors.white24,
-                          ),
-                          title: 'Power Settings',
-                          onTap: () => showPowerMenuBottomSheet(context, nBloc),
-                        ),
-                        ActionCard(
-                          icon: const Icon(
-                            Icons.queue_play_next,
-                            size: 55,
-                            color: Colors.white24,
-                          ),
-                          title: 'Console',
-                          onTap: () => showConsoleMenuBottomSheet(
-                              context, nBloc.apiClient, null, nodeID, 'node'),
-                        ),
-                      ],
+                  PveResourceDataCardWidget(
+                    expandable: false,
+                    showTitleTrailing: true,
+                    title: const Text(
+                      'Summary',
+                      style: TextStyle(
+                        fontWeight: FontWeight.bold,
+                        fontSize: 20,
+                      ),
                     ),
-                  ),
-                ),
-                PveResourceDataCardWidget(
-                  expandable: false,
-                  showTitleTrailing: true,
-                  title: const Text(
-                    'Summary',
-                    style: TextStyle(
-                      fontWeight: FontWeight.bold,
-                      fontSize: 20,
-                    ),
-                  ),
-                  titleTrailing: Text(Renderers.renderDuration(
-                      Duration(seconds: status?.uptime ?? 0))),
-                  subtitle: Text(status?.pveversion ?? ''),
-                  children: [
-                    ListTile(
-                      dense: true,
-                      title: Text(status?.kversion ?? 'unknown'),
-                      subtitle: const Text('Kernel'),
-                    ),
-                    if (status?.cpuinfo != null)
+                    titleTrailing: Text(Renderers.renderDuration(
+                        Duration(seconds: status?.uptime ?? 0))),
+                    subtitle: Text(status?.pveversion ?? ''),
+                    children: [
                       ListTile(
                         dense: true,
-                        title: Text(
-                            '${status!.cpuinfo.cpus} x ${status.cpuinfo.model}'),
-                        subtitle: Text(
-                            'CPU Information (Socket: ${status.cpuinfo.sockets})'),
+                        title: Text(status?.kversion ?? 'unknown'),
+                        subtitle: const Text('Kernel'),
                       ),
-                    if (status?.ksm.shared ?? false)
-                      CheckboxListTile(
-                        dense: true,
-                        value: status?.ksm.shared ?? false,
-                        title: const Text('Kernel same-page merging (KSM)',
-                            style: TextStyle(color: Colors.black)),
-                        onChanged: null,
-                      ),
-                    if (status?.rootfs != null) ...[
-                      const Divider(
-                        indent: 10,
-                        endIndent: 10,
-                      ),
-                      Padding(
-                        padding: const EdgeInsets.symmetric(vertical: 8.0),
-                        child: ListTile(
-                          title: const Text('HD space (root)'),
-                          subtitle: ProxmoxCapacityIndicator(
-                            icon: Icon(
-                              FontAwesomeIcons.solidHardDrive,
-                              color: Colors.blueGrey[300],
+                      if (status?.cpuinfo != null)
+                        ListTile(
+                          dense: true,
+                          title: Text(
+                              '${status!.cpuinfo.cpus} x ${status.cpuinfo.model}'),
+                          subtitle: Text(
+                              'CPU Information (Socket: ${status.cpuinfo.sockets})'),
+                        ),
+                      if (status?.ksm.shared ?? false)
+                        CheckboxListTile(
+                          dense: true,
+                          value: status?.ksm.shared ?? false,
+                          title: const Text('Kernel same-page merging (KSM)',
+                              style: TextStyle(color: Colors.black)),
+                          onChanged: null,
+                        ),
+                      if (status?.rootfs != null) ...[
+                        const Divider(
+                          indent: 10,
+                          endIndent: 10,
+                        ),
+                        Padding(
+                          padding: const EdgeInsets.symmetric(vertical: 8.0),
+                          child: ListTile(
+                            title: const Text('HD space (root)'),
+                            subtitle: ProxmoxCapacityIndicator(
+                              icon: Icon(
+                                FontAwesomeIcons.solidHardDrive,
+                                color: Colors.blueGrey[300],
+                              ),
+                              usedValue:
+                                  Renderers.formatSize(status!.rootfs.used),
+                              totalValue:
+                                  Renderers.formatSize(status.rootfs.total),
+                              usedPercent:
+                                  (status.rootfs.used) / (status.rootfs.total),
                             ),
-                            usedValue:
-                                Renderers.formatSize(status!.rootfs.used),
-                            totalValue:
-                                Renderers.formatSize(status.rootfs.total),
-                            usedPercent:
-                                (status.rootfs.used) / (status.rootfs.total),
                           ),
                         ),
+                      ],
+                    ],
+                  ),
+                  PveResourceDataCardWidget(
+                    expandable: true,
+                    showTitleTrailing: !state.allServicesRunning,
+                    titleTrailing: const Icon(Icons.warning),
+                    title: const Text(
+                      'Services',
+                      style: TextStyle(
+                        fontWeight: FontWeight.bold,
+                        fontSize: 20,
                       ),
-                    ],
-                  ],
-                ),
-                PveResourceDataCardWidget(
-                  expandable: true,
-                  showTitleTrailing: !state.allServicesRunning,
-                  titleTrailing: const Icon(Icons.warning),
-                  title: const Text(
-                    'Services',
-                    style: TextStyle(
-                      fontWeight: FontWeight.bold,
-                      fontSize: 20,
                     ),
-                  ),
-                  subtitle: Column(
-                    crossAxisAlignment: CrossAxisAlignment.start,
-                    children: <Widget>[
-                      if (state.allServicesRunning) const Text('All running'),
-                      if (!state.allServicesRunning)
-                        const Text('One or more not running'),
-                      const Divider(),
-                    ],
-                  ),
-                  children: state.services
-                      .map(
-                        (s) => ListTile(
-                          dense: true,
-                          title: Text(s.name),
-                          subtitle: Text(s.desc),
-                          trailing: getServiceStateIcon(context, s),
-                        ),
-                      )
-                      .toList()
-                    ..sort((a, b) => (a.title as Text)
-                        .data!
-                        .compareTo((b.title as Text).data!)),
-                ),
-                PveResourceDataCardWidget(
-                  expandable: !state.updatesQueryPermissionFailure,
-                  showTitleTrailing: state.updates.isNotEmpty,
-                  titleTrailing: const Icon(Icons.info_outline),
-                  title: const Text(
-                    'Updates',
-                    style: TextStyle(
-                      fontWeight: FontWeight.bold,
-                      fontSize: 20,
+                    subtitle: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: <Widget>[
+                        if (state.allServicesRunning) const Text('All running'),
+                        if (!state.allServicesRunning)
+                          const Text('One or more not running'),
+                        const Divider(),
+                      ],
                     ),
+                    children: state.services
+                        .map(
+                          (s) => ListTile(
+                            dense: true,
+                            title: Text(s.name),
+                            subtitle: Text(s.desc),
+                            trailing: getServiceStateIcon(context, s),
+                          ),
+                        )
+                        .toList()
+                      ..sort((a, b) => (a.title as Text)
+                          .data!
+                          .compareTo((b.title as Text).data!)),
                   ),
-                  subtitle: Column(
-                    crossAxisAlignment: CrossAxisAlignment.start,
-                    children: <Widget>[
-                      if (state.updatesQueryPermissionFailure)
-                        const Text('Lacking permission to query updates'),
-                      if (state.updates.isEmpty &&
-                          !state.updatesQueryPermissionFailure)
-                        const Text('No updates available'),
-                      if (state.updates.isNotEmpty)
-                        Text(
-                            '${state.updates.length} packages are ready to update'),
-                      if (!state.updatesQueryPermissionFailure) const Divider(),
-                    ],
-                  ),
-                  children: state.updates
-                      .map(
-                        (s) => ListTile(
-                          dense: true,
-                          title: Text(s.title),
-                          subtitle: Text(
-                              '${s.package}: ${s.oldVersion ?? ''} -> ${s.version}'),
-                          trailing: IconButton(
-                            icon: const Icon(
-                              Icons.info,
-                            ),
-                            onPressed: () => showDialog(
-                              context: context,
-                              builder: (context) => SimpleDialog(
-                                title: const Text('Description'),
-                                contentPadding: const EdgeInsets.all(24),
-                                children: <Widget>[
-                                  Text(
-                                    s.description ?? '',
-                                    style: const TextStyle(fontSize: 12),
-                                  )
-                                ],
+                  PveResourceDataCardWidget(
+                    expandable: !state.updatesQueryPermissionFailure,
+                    showTitleTrailing: state.updates.isNotEmpty,
+                    titleTrailing: const Icon(Icons.info_outline),
+                    title: const Text(
+                      'Updates',
+                      style: TextStyle(
+                        fontWeight: FontWeight.bold,
+                        fontSize: 20,
+                      ),
+                    ),
+                    subtitle: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: <Widget>[
+                        if (state.updatesQueryPermissionFailure)
+                          const Text('Lacking permission to query updates'),
+                        if (state.updates.isEmpty &&
+                            !state.updatesQueryPermissionFailure)
+                          const Text('No updates available'),
+                        if (state.updates.isNotEmpty)
+                          Text(
+                              '${state.updates.length} packages are ready to update'),
+                        if (!state.updatesQueryPermissionFailure)
+                          const Divider(),
+                      ],
+                    ),
+                    children: state.updates
+                        .map(
+                          (s) => ListTile(
+                            dense: true,
+                            title: Text(s.title),
+                            subtitle: Text(
+                                '${s.package}: ${s.oldVersion ?? ''} -> ${s.version}'),
+                            trailing: IconButton(
+                              icon: const Icon(
+                                Icons.info,
+                              ),
+                              onPressed: () => showDialog(
+                                context: context,
+                                builder: (context) => SimpleDialog(
+                                  title: const Text('Description'),
+                                  contentPadding: const EdgeInsets.all(24),
+                                  children: <Widget>[
+                                    Text(
+                                      s.description ?? '',
+                                      style: const TextStyle(fontSize: 12),
+                                    )
+                                  ],
+                                ),
                               ),
                             ),
                           ),
-                        ),
-                      )
-                      .toList()
-                    ..sort((a, b) => (a.title as Text)
-                        .data!
-                        .compareTo((b.title as Text).data!)),
-                ),
-                PveResourceDataCardWidget(
-                  expandable: false,
-                  title: const Text(
-                    'Disks',
-                    style: TextStyle(
-                      fontWeight: FontWeight.bold,
-                      fontSize: 20,
-                    ),
+                        )
+                        .toList()
+                      ..sort((a, b) => (a.title as Text)
+                          .data!
+                          .compareTo((b.title as Text).data!)),
                   ),
-                  showTitleTrailing: !state.allDisksHealthy,
-                  titleTrailing: const Icon(Icons.warning),
-                  subtitle: state.allDisksHealthy
-                      ? const Text('No health issues')
-                      : const Text('Check disks, health error indicated!'),
-                  children: state.disks
-                      .map(
-                        (d) => ListTile(
-                          dense: true,
-                          leading: Icon(FontAwesomeIcons.solidHardDrive,
-                              color: state.isDiskHealthy(d)
-                                  ? Colors.grey
-                                  : Colors.red),
-                          title: Text('${d.type!.toUpperCase()}: ${d.devPath}'),
-                          subtitle: Text(
-                              'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'),
-                          trailing: IconButton(
-                            icon: const Icon(
-                              Icons.info,
-                            ),
-                            onPressed: () => showDialog(
-                              context: context,
-                              builder: (context) => SimpleDialog(
-                                title: const Text('Details'),
-                                contentPadding: const EdgeInsets.all(24),
-                                children: <Widget>[
-                                  ListTile(
-                                    title: const Text("Device"),
-                                    subtitle: Text(d.devPath!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("Type"),
-                                    subtitle: Text(d.type!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("Usage"),
-                                    subtitle: Text(d.used!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("GPT"),
-                                    subtitle: Text(d.gpt.toString()),
-                                  ),
-                                  ListTile(
-                                    title: const Text("Model"),
-                                    subtitle: Text(d.model!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("Serial"),
-                                    subtitle: Text(d.serial!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("S.M.A.R.T"),
-                                    subtitle: Text(d.health!),
-                                  ),
-                                  ListTile(
-                                    title: const Text("Wearout"),
-                                    subtitle: Text(d.wearoutPercentage),
-                                  )
-                                ],
+                  PveResourceDataCardWidget(
+                    expandable: false,
+                    title: const Text(
+                      'Disks',
+                      style: TextStyle(
+                        fontWeight: FontWeight.bold,
+                        fontSize: 20,
+                      ),
+                    ),
+                    showTitleTrailing: !state.allDisksHealthy,
+                    titleTrailing: const Icon(Icons.warning),
+                    subtitle: state.allDisksHealthy
+                        ? const Text('No health issues')
+                        : const Text('Check disks, health error indicated!'),
+                    children: state.disks
+                        .map(
+                          (d) => ListTile(
+                            dense: true,
+                            leading: Icon(FontAwesomeIcons.solidHardDrive,
+                                color: state.isDiskHealthy(d)
+                                    ? Colors.grey
+                                    : Colors.red),
+                            title:
+                                Text('${d.type!.toUpperCase()}: ${d.devPath}'),
+                            subtitle: Text(
+                                'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'),
+                            trailing: IconButton(
+                              icon: const Icon(
+                                Icons.info,
+                              ),
+                              onPressed: () => showDialog(
+                                context: context,
+                                builder: (context) => SimpleDialog(
+                                  title: const Text('Details'),
+                                  contentPadding: const EdgeInsets.all(24),
+                                  children: <Widget>[
+                                    ListTile(
+                                      title: const Text("Device"),
+                                      subtitle: Text(d.devPath!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("Type"),
+                                      subtitle: Text(d.type!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("Usage"),
+                                      subtitle: Text(d.used!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("GPT"),
+                                      subtitle: Text(d.gpt.toString()),
+                                    ),
+                                    ListTile(
+                                      title: const Text("Model"),
+                                      subtitle: Text(d.model!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("Serial"),
+                                      subtitle: Text(d.serial!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("S.M.A.R.T"),
+                                      subtitle: Text(d.health!),
+                                    ),
+                                    ListTile(
+                                      title: const Text("Wearout"),
+                                      subtitle: Text(d.wearoutPercentage),
+                                    )
+                                  ],
+                                ),
                               ),
                             ),
                           ),
-                        ),
-                      )
-                      .toList()
-                    ..sort((a, b) => (a.title as Text)
-                        .data!
-                        .compareTo((b.title as Text).data!)),
-                ),
-              ],
+                        )
+                        .toList()
+                      ..sort((a, b) => (a.title as Text)
+                          .data!
+                          .compareTo((b.title as Text).data!)),
+                  ),
+                ],
+              ),
             ),
           ),
-        ),);
+        );
       },
     );
   }
diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart
index 0f0d46a..b1900b5 100644
--- a/lib/widgets/pve_qemu_options_widget.dart
+++ b/lib/widgets/pve_qemu_options_widget.dart
@@ -31,187 +31,189 @@ class PveQemuOptions extends StatelessWidget {
               body: SafeArea(
                 bottom: !Platform.isIOS,
                 child: SingleChildScrollView(
-                child: Form(
-                  key: _formKey,
-                  onChanged: () {},
-                  child: Column(
-                    children: <Widget>[
-                      ListTile(
-                        title: const Text("Name"),
-                        subtitle: Text(config.name ?? 'VM$guestID'),
-                      ),
-                      PveConfigListTile<bool>(
-                        title: "Start on boot",
-                        trailingText: config.onboot == true ? 'Yes' : 'No',
-                        pending: config.getPending('onboot'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('onboot', v),
+                  child: Form(
+                    key: _formKey,
+                    onChanged: () {},
+                    child: Column(
+                      children: <Widget>[
+                        ListTile(
+                          title: const Text("Name"),
+                          subtitle: Text(config.name ?? 'VM$guestID'),
                         ),
-                        editorBuilder: (_, controller) {
-                          return PveConfigSwitchForm(
-                            title: "Start on boot",
-                            value: config.onboot == true,
-                            controller: controller,
-                          );
-                        },
-                        onCancelEdit: () => bloc.events.add(
-                          RevertPendingQemuConfig('onboot'),
+                        PveConfigListTile<bool>(
+                          title: "Start on boot",
+                          trailingText: config.onboot == true ? 'Yes' : 'No',
+                          pending: config.getPending('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"),
-                        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'),
-                      ),
-                      PveConfigListTile<bool>(
-                        title: "Use tablet for pointer",
-                        trailingText: config.tablet == true ? 'Yes' : 'No',
-                        pending: config.getPending('tablet'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('tablet', v),
+                        ListTile(
+                          title: const Text("Start/Shutdown order"),
+                          subtitle: Text(config.startup ?? "Default (any)"),
                         ),
-                        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("OS Type"),
+                          subtitle: Text(config.ostype != null
+                              ? "${config.ostype!.type} ${config.ostype!.description}"
+                              : "Other"),
                         ),
-                      ),
-                      ListTile(
-                        title: const Text("Hotplug"),
-                        subtitle: Text(config.hotplug ?? 'disk,network,usb'),
-                      ),
-                      PveConfigListTile<bool>(
-                        title: "ACPI support",
-                        trailingText: config.acpi == true ? 'Yes' : 'No',
-                        pending: config.getPending('acpi'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('acpi', v),
+                        //TODO add better ui component e.g. collapseable
+                        ListTile(
+                          title: const Text("Boot Device"),
+                          subtitle: Text(config.boot ?? 'Disk, Network, USB'),
                         ),
-                        editorBuilder: (_, controller) {
-                          return PveConfigSwitchForm(
-                            title: "ACPI support",
-                            value: config.acpi == true,
-                            controller: controller,
-                          );
-                        },
-                        onCancelEdit: () => bloc.events.add(
-                          RevertPendingQemuConfig('acpi'),
+                        PveConfigListTile<bool>(
+                          title: "Use tablet for pointer",
+                          trailingText: config.tablet == true ? 'Yes' : 'No',
+                          pending: config.getPending('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'),
+                          ),
                         ),
-                      ),
-                      PveConfigListTile<bool>(
-                        title: "KVM hardware virtualization",
-                        trailingText: config.kvm == true ? 'Yes' : 'No',
-                        pending: config.getPending('kvm'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('kvm', v),
+                        ListTile(
+                          title: const Text("Hotplug"),
+                          subtitle: Text(config.hotplug ?? 'disk,network,usb'),
                         ),
-                        editorBuilder: (_, controller) {
-                          return PveConfigSwitchForm(
-                            title: "KVM hardware virtualization",
-                            value: config.kvm == true,
-                            controller: controller,
-                          );
-                        },
-                        onCancelEdit: () => bloc.events.add(
-                          RevertPendingQemuConfig('kvm'),
+                        PveConfigListTile<bool>(
+                          title: "ACPI support",
+                          trailingText: config.acpi == true ? 'Yes' : 'No',
+                          pending: config.getPending('acpi'),
+                          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: "Freeze CPU on startup",
-                        trailingText: config.freeze == true ? 'Yes' : 'No',
-                        pending: config.getPending('freeze'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('freeze', v),
+                        PveConfigListTile<bool>(
+                          title: "KVM hardware virtualization",
+                          trailingText: config.kvm == true ? 'Yes' : 'No',
+                          pending: config.getPending('kvm'),
+                          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'),
+                          ),
                         ),
-                        editorBuilder: (_, controller) {
-                          return PveConfigSwitchForm(
-                            title: "Freeze CPU on startup",
-                            value: config.freeze == true,
-                            controller: controller,
-                          );
-                        },
-                        onCancelEdit: () => bloc.events.add(
-                          RevertPendingQemuConfig('freeze'),
+                        PveConfigListTile<bool>(
+                          title: "Freeze CPU on startup",
+                          trailingText: config.freeze == true ? 'Yes' : 'No',
+                          pending: config.getPending('freeze'),
+                          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'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('localtime', v),
+                        PveConfigListTile<bool>(
+                          title: "Use local time for RTC",
+                          trailingText: config.localtime == true ? 'Yes' : 'No',
+                          pending: config.getPending('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'),
+                          ),
                         ),
-                        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"),
+                          subtitle: Text(config.startdate ?? 'now'),
                         ),
-                      ),
-                      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)'),
-                      ),
-                      PveConfigListTile<bool>(
-                        title: "Protection",
-                        trailingText: config.protection == true ? 'Yes' : 'No',
-                        pending: config.getPending('protection'),
-                        onSubmit: (v) => bloc.events.add(
-                          UpdateQemuConfigBool('protection', v),
+                        ListTile(
+                          title: const Text("SMBIOS settings (type1)"),
+                          subtitle: Text(config.smbios1 ?? ''),
                         ),
-                        editorBuilder: (_, controller) {
-                          return PveConfigSwitchForm(
-                            title: "Protection",
-                            value: config.protection == true,
-                            controller: controller,
-                          );
-                        },
-                        onCancelEdit: () => bloc.events.add(
-                          RevertPendingQemuConfig('protection'),
+                        //Todo enhance UI
+                        ListTile(
+                          title: const Text("QEMU Guest Agent"),
+                          subtitle: Text(config.agent ?? 'Default (disabled)'),
                         ),
-                      ),
-                      ListTile(
-                        title: const Text("Spice Enhancements"),
-                        subtitle:
-                            Text(config.spiceEnhancements ?? 'No enhancements'),
-                      ),
-                      ListTile(
-                        title: const Text("VM State Storage"),
-                        subtitle: Text(config.vmstatestorage ?? 'Automatic'),
-                      ),
-                    ],
+                        PveConfigListTile<bool>(
+                          title: "Protection",
+                          trailingText:
+                              config.protection == true ? 'Yes' : 'No',
+                          pending: config.getPending('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"),
+                          subtitle: Text(
+                              config.spiceEnhancements ?? 'No enhancements'),
+                        ),
+                        ListTile(
+                          title: const Text("VM State Storage"),
+                          subtitle: Text(config.vmstatestorage ?? 'Automatic'),
+                        ),
+                      ],
+                    ),
                   ),
                 ),
               ),
-            ),);
+            );
           }
           return const Center(
             child: CircularProgressIndicator(),
diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart
index a80a58a..3fb25ba 100644
--- a/lib/widgets/pve_qemu_overview.dart
+++ b/lib/widgets/pve_qemu_overview.dart
@@ -82,171 +82,174 @@ class PveQemuOverview extends StatelessWidget {
                 bottom: !Platform.isIOS,
                 child: SingleChildScrollView(
                     child: Column(
-                children: <Widget>[
-                  PveGuestOverviewHeader(
-                    background: !(status?.template ?? false)
-                        ? PveGuestHeaderRRDPageView(
-                            rrdData: rrdData,
-                          )
-                        : const Center(
-                            child: Text(
-                              "TEMPLATE",
-                              style: TextStyle(
-                                color: Colors.white,
+                  children: <Widget>[
+                    PveGuestOverviewHeader(
+                      background: !(status?.template ?? false)
+                          ? PveGuestHeaderRRDPageView(
+                              rrdData: rrdData,
+                            )
+                          : const Center(
+                              child: Text(
+                                "TEMPLATE",
+                                style: TextStyle(
+                                  color: Colors.white,
+                                ),
                               ),
                             ),
-                          ),
-                    width: width,
-                    guestID: guestID,
-                    guestStatus: status?.getQemuStatus(),
-                    guestName: config?.name ?? 'VM $guestID',
-                    guestNodeID: state.nodeID,
-                    guestType: 'qemu',
-                    ha: status?.ha,
-                    template: status?.template ?? false,
-                  ),
-                  ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
-                    bloc: taskBloc,
-                    builder: (context, taskState) {
-                      if (taskState.tasks.isNotEmpty) {
-                        return PveTaskExpansionTile(
-                          headerColor:
-                              Theme.of(context).colorScheme.onSurfaceVariant,
-                          task: taskState.tasks.first,
-                          showMorePage: Provider<PveTaskLogBloc>(
-                            create: (context) => PveTaskLogBloc(
-                              apiClient: taskBloc.apiClient,
-                              init: PveTaskLogState.init(state.nodeID),
-                            )
-                              ..events.add(
-                                FilterTasksByGuestID(
-                                  guestID: guestID,
-                                ),
+                      width: width,
+                      guestID: guestID,
+                      guestStatus: status?.getQemuStatus(),
+                      guestName: config?.name ?? 'VM $guestID',
+                      guestNodeID: state.nodeID,
+                      guestType: 'qemu',
+                      ha: status?.ha,
+                      template: status?.template ?? false,
+                    ),
+                    ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
+                      bloc: taskBloc,
+                      builder: (context, taskState) {
+                        if (taskState.tasks.isNotEmpty) {
+                          return PveTaskExpansionTile(
+                            headerColor:
+                                Theme.of(context).colorScheme.onSurfaceVariant,
+                            task: taskState.tasks.first,
+                            showMorePage: Provider<PveTaskLogBloc>(
+                              create: (context) => PveTaskLogBloc(
+                                apiClient: taskBloc.apiClient,
+                                init: PveTaskLogState.init(state.nodeID),
                               )
-                              ..events.add(LoadTasks()),
-                            dispose: (context, bloc) => bloc.dispose(),
-                            child: const PveTaskLog(),
-                          ),
-                        );
-                      }
-                      return Container();
-                    },
-                  ),
-                  SizedBox(
-                    height: 130,
-                    child: SingleChildScrollView(
-                      scrollDirection: Axis.horizontal,
-                      child: Row(
-                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-                        children: <Widget>[
-                          if (!(status?.template ?? false))
+                                ..events.add(
+                                  FilterTasksByGuestID(
+                                    guestID: guestID,
+                                  ),
+                                )
+                                ..events.add(LoadTasks()),
+                              dispose: (context, bloc) => bloc.dispose(),
+                              child: const PveTaskLog(),
+                            ),
+                          );
+                        }
+                        return Container();
+                      },
+                    ),
+                    SizedBox(
+                      height: 130,
+                      child: SingleChildScrollView(
+                        scrollDirection: Axis.horizontal,
+                        child: Row(
+                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                          children: <Widget>[
+                            if (!(status?.template ?? false))
+                              createActionCard(
+                                  'Power Settings',
+                                  Icons.power_settings_new,
+                                  () =>
+                                      showPowerMenuBottomSheet(context, bloc)),
+                            if (!(status?.template ?? false))
+                              createActionCard(
+                                  'Console',
+                                  Icons.queue_play_next,
+                                  () => showConsoleMenuBottomSheet(
+                                        context,
+                                        bloc.apiClient,
+                                        guestID,
+                                        state.nodeID,
+                                        'qemu',
+                                        allowSpice: status?.spice ?? false,
+                                      )),
                             createActionCard(
-                                'Power Settings',
-                                Icons.power_settings_new,
-                                () => showPowerMenuBottomSheet(context, bloc)),
-                          if (!(status?.template ?? false))
+                                'Options',
+                                Icons.settings,
+                                () => Navigator.of(context)
+                                    .push(_createOptionsRoute(bloc))),
+                            if (!rBloc.latestState.isStandalone)
+                              createActionCard(
+                                  'Migrate',
+                                  FontAwesomeIcons.paperPlane,
+                                  () => Navigator.of(context).push(
+                                      _createMigrationRoute(guestID,
+                                          state.nodeID, bloc.apiClient))),
                             createActionCard(
-                                'Console',
-                                Icons.queue_play_next,
-                                () => showConsoleMenuBottomSheet(
-                                      context,
-                                      bloc.apiClient,
-                                      guestID,
-                                      state.nodeID,
-                                      'qemu',
-                                      allowSpice: status?.spice ?? false,
-                                    )),
-                          createActionCard(
-                              'Options',
-                              Icons.settings,
-                              () => Navigator.of(context)
-                                  .push(_createOptionsRoute(bloc))),
-                          if (!rBloc.latestState.isStandalone)
-                            createActionCard(
-                                'Migrate',
-                                FontAwesomeIcons.paperPlane,
+                                'Backup',
+                                FontAwesomeIcons.floppyDisk,
                                 () => Navigator.of(context).push(
-                                    _createMigrationRoute(guestID, state.nodeID,
+                                    _createBackupRoute(guestID, state.nodeID,
                                         bloc.apiClient))),
-                          createActionCard(
-                              'Backup',
-                              FontAwesomeIcons.floppyDisk,
-                              () => Navigator.of(context).push(
-                                  _createBackupRoute(
-                                      guestID, state.nodeID, bloc.apiClient))),
-                        ],
+                          ],
+                        ),
                       ),
                     ),
-                  ),
-                  if (config != null)
-                    PveResourceDataCardWidget(
-                        expandable: false,
-                        title: const Text(
-                          'Hardware',
-                          style: TextStyle(
-                            fontWeight: FontWeight.bold,
-                            fontSize: 20,
+                    if (config != null)
+                      PveResourceDataCardWidget(
+                          expandable: false,
+                          title: const Text(
+                            'Hardware',
+                            style: TextStyle(
+                              fontWeight: FontWeight.bold,
+                              fontSize: 20,
+                            ),
                           ),
-                        ),
-                        children: [
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.memory),
-                            title: Text('${config.memory}'),
-                            subtitle: const Text('Memory'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(Icons.memory),
-                            title: Text(
-                                '${config.cores} Cores ${config.sockets} Socket'),
-                            subtitle: const Text('Processor'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.microchip),
-                            title:
-                                Text(config.bios?.name ?? 'Default (SeaBIOS)'),
-                            subtitle: const Text('BIOS'),
-                            dense: true,
-                          ),
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.gears),
-                            dense: true,
-                            title: Text(config.machine ?? 'Default (i440fx)'),
-                            subtitle: const Text('Machine Type'),
-                          ),
-                          ListTile(
-                            leading: const Icon(FontAwesomeIcons.database),
-                            title:
-                                Text(config.scsihw?.name ?? 'Default (i440fx)'),
-                            subtitle: const Text('SCSI Controller'),
-                            dense: true,
-                          ),
-                          for (var ide in config.ide!)
+                          children: [
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.compactDisc),
-                              title: Text(ide),
-                              subtitle: const Text('CD/DVD Drive'),
+                              leading: const Icon(FontAwesomeIcons.memory),
+                              title: Text('${config.memory}'),
+                              subtitle: const Text('Memory'),
                               dense: true,
                             ),
-                          for (var scsi in config.scsi!)
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.hardDrive),
-                              title: Text(scsi),
-                              subtitle: const Text('Hard Disk'),
+                              leading: const Icon(Icons.memory),
+                              title: Text(
+                                  '${config.cores} Cores ${config.sockets} Socket'),
+                              subtitle: const Text('Processor'),
                               dense: true,
                             ),
-                          for (var net in config.net!)
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.ethernet),
+                              leading: const Icon(FontAwesomeIcons.microchip),
+                              title: Text(
+                                  config.bios?.name ?? 'Default (SeaBIOS)'),
+                              subtitle: const Text('BIOS'),
                               dense: true,
-                              subtitle: const Text('Network Device'),
-                              title: Text(net),
-                            )
-                        ]),
-                ],
-              )),
-            ),);
+                            ),
+                            ListTile(
+                              leading: const Icon(FontAwesomeIcons.gears),
+                              dense: true,
+                              title: Text(config.machine ?? 'Default (i440fx)'),
+                              subtitle: const Text('Machine Type'),
+                            ),
+                            ListTile(
+                              leading: const Icon(FontAwesomeIcons.database),
+                              title: Text(
+                                  config.scsihw?.name ?? 'Default (i440fx)'),
+                              subtitle: const Text('SCSI Controller'),
+                              dense: true,
+                            ),
+                            for (var ide in config.ide!)
+                              ListTile(
+                                leading:
+                                    const Icon(FontAwesomeIcons.compactDisc),
+                                title: Text(ide),
+                                subtitle: const Text('CD/DVD Drive'),
+                                dense: true,
+                              ),
+                            for (var scsi in config.scsi!)
+                              ListTile(
+                                leading: const Icon(FontAwesomeIcons.hardDrive),
+                                title: Text(scsi),
+                                subtitle: const Text('Hard Disk'),
+                                dense: true,
+                              ),
+                            for (var net in config.net!)
+                              ListTile(
+                                leading: const Icon(FontAwesomeIcons.ethernet),
+                                dense: true,
+                                subtitle: const Text('Network Device'),
+                                title: Text(net),
+                              )
+                          ]),
+                  ],
+                )),
+              ),
+            );
           }),
     );
   }
-- 
2.47.3



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


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

end of thread, other threads:[~2025-11-21 15:45 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-19 12:57 [pve-devel] [PATCH pve_flutter_frontend 0/2] fix #7045: regression: add extra padding around the body to avoid system intrusions Shan Shaji
2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 1/2] fix #7045: regression: avoid system intrusion from navigation bar Shan Shaji
2025-11-19 12:57 ` [pve-devel] [PATCH pve_flutter_frontend 2/2] cleanup: run `dart format .` command to fix formatting 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