public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH pve_flutter_frontend 0/3] fix: ui: clipping of ui elements in iPhone duo
@ 2026-10-23 11:40 Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 1/3] fix: ui being inconsistent in overview pages Shan Shaji
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Shan Shaji @ 2026-10-23 11:40 UTC (permalink / raw)
  To: pve-devel

While testing the app on iPhone duo simulator. The overview pages looks like it
was clipped just before app controls and camera. This was because of the SafeArea
widget wrapping the entire page. Please see the individual commit messages of
the patches for the rationale behind the changes. 

Shan Shaji (3):
  fix: ui being inconsistent in overview pages
  fix: ui: bottom overflow on apple's duo device
  cleanup: run dart format

 lib/utils/links.dart                          |   4 +-
 .../firstWelcomeScreen/pve_welcome_faq.dart   |   5 +-
 lib/widgets/pve_lxc_overview.dart             | 318 ++++----
 lib/widgets/pve_node_overview.dart            | 709 +++++++++---------
 lib/widgets/pve_qemu_overview.dart            | 305 ++++----
 lib/widgets/pve_rrd_chart_widget.dart         |  32 +-
 6 files changed, 683 insertions(+), 690 deletions(-)

-- 
2.50.1





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

* [PATCH pve_flutter_frontend 1/3] fix: ui being inconsistent in overview pages
  2026-10-23 11:40 [PATCH pve_flutter_frontend 0/3] fix: ui: clipping of ui elements in iPhone duo Shan Shaji
@ 2026-10-23 11:40 ` Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 2/3] fix: ui: bottom overflow on apple's duo device Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 3/3] cleanup: run dart format Shan Shaji
  2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-10-23 11:40 UTC (permalink / raw)
  To: pve-devel

For the new Apple's duo device, in the folded state the camera and the
application dock are on the right side of the device. However, this
depends on the orientation, so they are not always on the right.
Since these are system intrusions and we are using SafeArea to avoid
them, wrapping the overview page in SafeArea is causing clipping in the
UI. In order to avoid this, remove the SafeArea widget.

It is safe to remove SafeArea because the top AppBar avoids intrusions
by default. For the left and right sides, individual padding is given to
each widget. As for the bottom, a placeholder widget with a height of
viewPadding.bottom [1] has been added.

- [1] https://api.flutter.dev/flutter/dart-ui/FlutterView-class.html

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

diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart
index 7caa5cf..b0b210a 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -65,9 +65,7 @@ class PveLxcOverview extends StatelessWidget {
                 title: Text(config?.hostname ?? 'CT $guestID'),
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-              body: SafeArea(
-                bottom: !Platform.isIOS,
-                child: SingleChildScrollView(
+              body: SingleChildScrollView(
                   child: Column(
                     children: <Widget>[
                       PveGuestOverviewHeader(
@@ -247,11 +245,11 @@ class PveLxcOverview extends StatelessWidget {
                                 subtitle: const Text('DNS Server'),
                               ),
                             ]),
-                      ]
+                      ],
+                      SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
                     ],
                   ),
                 ),
-              ),
             );
           }),
     );
diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart
index 092bb54..4ecdede 100644
--- a/lib/widgets/pve_node_overview.dart
+++ b/lib/widgets/pve_node_overview.dart
@@ -62,9 +62,7 @@ class PveNodeOverview extends StatelessWidget {
             ),
           ),
           backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-          body: SafeArea(
-            bottom: !Platform.isIOS,
-            child: SingleChildScrollView(
+          body: SingleChildScrollView(
               child: Column(
                 children: <Widget>[
                   if (rrd.isNotEmpty)
@@ -446,10 +444,10 @@ class PveNodeOverview extends StatelessWidget {
                           .data!
                           .compareTo((b.title as Text).data!)),
                   ),
+                  SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
                 ],
               ),
             ),
-          ),
         );
       },
     );
diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart
index 12e3b30..2f366a8 100644
--- a/lib/widgets/pve_qemu_overview.dart
+++ b/lib/widgets/pve_qemu_overview.dart
@@ -66,9 +66,7 @@ class PveQemuOverview extends StatelessWidget {
                 title: Text(config?.name ?? 'VM $guestID'),
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
-              body: SafeArea(
-                bottom: !Platform.isIOS,
-                child: SingleChildScrollView(
+              body: SingleChildScrollView(
                     child: Column(
                   children: <Widget>[
                     PveGuestOverviewHeader(
@@ -236,9 +234,9 @@ class PveQemuOverview extends StatelessWidget {
                                 title: Text(net),
                               )
                           ]),
+                    SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
                   ],
                 )),
-              ),
             );
           }),
     );
-- 
2.50.1





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

* [PATCH pve_flutter_frontend 2/3] fix: ui: bottom overflow on apple's duo device
  2026-10-23 11:40 [PATCH pve_flutter_frontend 0/3] fix: ui: clipping of ui elements in iPhone duo Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 1/3] fix: ui being inconsistent in overview pages Shan Shaji
@ 2026-10-23 11:40 ` Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 3/3] cleanup: run dart format Shan Shaji
  2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-10-23 11:40 UTC (permalink / raw)
  To: pve-devel

Removing the SafeArea widget caused the overview header to throw a bottom
overflow error. This occurred because the ListTile text did not have
enough horizontal space to render, resulting in text wrapping onto
multiple lines. To resolve this, wrapped the title block in SafeArea
widget.

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 lib/widgets/pve_rrd_chart_widget.dart | 32 +++++++++++++++------------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/lib/widgets/pve_rrd_chart_widget.dart b/lib/widgets/pve_rrd_chart_widget.dart
index 0dc648a..1e71353 100644
--- a/lib/widgets/pve_rrd_chart_widget.dart
+++ b/lib/widgets/pve_rrd_chart_widget.dart
@@ -60,20 +60,24 @@ class _PveRRDChartState extends State<PveRRDChart> {
     return Column(
       crossAxisAlignment: widget.titleAlignment,
       children: <Widget>[
-        Container(
-          width: widget.titleWidth,
-          padding: widget.titlePadding,
-          child: ListTile(
-            leading: widget.icon,
-            title: Text(
-              widget.title!,
-              style: TextStyle(
-                  fontSize: 12, color: fgColor, fontWeight: FontWeight.bold),
-            ),
-            subtitle: Text(
-              '${widget.subtitle}',
-              style: TextStyle(
-                  fontSize: 20, color: fgColor, fontWeight: FontWeight.bold),
+        SafeArea(
+          bottom: false,
+          top: false,
+          child: Container(
+            padding: widget.titlePadding,
+            width: widget.titleWidth,
+            child: ListTile(
+              leading: widget.icon,
+              title: Text(
+                widget.title!,
+                style: TextStyle(
+                    fontSize: 12, color: fgColor, fontWeight: FontWeight.bold),
+              ),
+              subtitle: Text(
+                '${widget.subtitle}',
+                style: TextStyle(
+                    fontSize: 20, color: fgColor, fontWeight: FontWeight.bold),
+              ),
             ),
           ),
         ),
-- 
2.50.1





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

* [PATCH pve_flutter_frontend 3/3] cleanup: run dart format
  2026-10-23 11:40 [PATCH pve_flutter_frontend 0/3] fix: ui: clipping of ui elements in iPhone duo Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 1/3] fix: ui being inconsistent in overview pages Shan Shaji
  2026-10-23 11:40 ` [PATCH pve_flutter_frontend 2/3] fix: ui: bottom overflow on apple's duo device Shan Shaji
@ 2026-10-23 11:40 ` Shan Shaji
  2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-10-23 11:40 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 lib/utils/links.dart                          |   4 +-
 .../firstWelcomeScreen/pve_welcome_faq.dart   |   5 +-
 lib/widgets/pve_lxc_overview.dart             | 316 ++++----
 lib/widgets/pve_node_overview.dart            | 707 +++++++++---------
 lib/widgets/pve_qemu_overview.dart            | 301 ++++----
 5 files changed, 664 insertions(+), 669 deletions(-)

diff --git a/lib/utils/links.dart b/lib/utils/links.dart
index a161d36..708f732 100644
--- a/lib/utils/links.dart
+++ b/lib/utils/links.dart
@@ -9,8 +9,8 @@ Future<bool> tryLaunchUrl(Uri url) async {
 }
 
 class Links {
-  static final pvePricing =
-      Uri.parse('https://www.proxmox.com/en/products/proxmox-virtual-environment/pricing');
+  static final pvePricing = Uri.parse(
+      'https://www.proxmox.com/en/products/proxmox-virtual-environment/pricing');
   static final proxmoxForum = Uri.parse('https://forum.proxmox.com');
   static final pveUserList =
       Uri.parse('https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-user');
diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
index b34c4f5..0c76bbb 100644
--- a/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
+++ b/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
@@ -39,7 +39,7 @@ class PveWelcomePageFAQ extends StatelessWidget {
           ),
           const PveAnswer(
               text:
-                  "Spice is currently supported, and we also support VNC through noVNC." ),
+                  "Spice is currently supported, and we also support VNC through noVNC."),
           const PveQuestion(text: "Which Spice client works?"),
           const PveAnswer(
               text:
@@ -47,7 +47,8 @@ class PveWelcomePageFAQ extends StatelessWidget {
           Center(
             child: OutlinedButton(
               onPressed: () {
-                final link = Platform.isIOS ? Links.aSpiceClient : Links.opaqueApp;
+                final link =
+                    Platform.isIOS ? Links.aSpiceClient : Links.opaqueApp;
                 launchUrl(link);
               },
               style: OutlinedButton.styleFrom(
diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart
index b0b210a..d6cc721 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -66,190 +66,190 @@ class PveLxcOverview extends StatelessWidget {
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
               body: 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),
+                            ),
+                      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,
+                                  ),
                                 )
-                                  ..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))
-                                ActionCard.withIcon(
-                                    'Power Settings',
-                                    Icons.power_settings_new,
-                                    () => showPowerMenuBottomSheet(
-                                        context, lxcBloc)),
-                              if (!(status?.template ?? false))
-                                ActionCard.withIcon(
-                                    'Console',
-                                    Icons.queue_play_next,
-                                    () => showConsoleMenuBottomSheet(
-                                        context,
-                                        lxcBloc.apiClient,
-                                        guestID,
-                                        state.nodeID,
-                                        'lxc')),
+                                ..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))
+                              ActionCard.withIcon(
+                                  'Power Settings',
+                                  Icons.power_settings_new,
+                                  () => showPowerMenuBottomSheet(
+                                      context, lxcBloc)),
+                            if (!(status?.template ?? false))
                               ActionCard.withIcon(
-                                  'Options',
-                                  Icons.settings,
-                                  () => Navigator.of(context)
-                                      .push(MaterialPageRoute(
-                                          builder: (context) => PveLxcOptions(
-                                                lxcBloc: lxcBloc,
-                                              ),
-                                          fullscreenDialog: true))),
-                              if (!resourceBloc.latestState.isStandalone)
-                                ActionCard.withFaIcon(
-                                    'Migrate',
-                                    FontAwesomeIcons.paperPlane,
-                                    () => Navigator.of(context).push(
-                                        _createMigrationRoute(
-                                            guestID,
-                                            state.nodeID,
-                                            resourceBloc.apiClient!))),
+                                  'Console',
+                                  Icons.queue_play_next,
+                                  () => showConsoleMenuBottomSheet(
+                                      context,
+                                      lxcBloc.apiClient,
+                                      guestID,
+                                      state.nodeID,
+                                      'lxc')),
+                            ActionCard.withIcon(
+                                'Options',
+                                Icons.settings,
+                                () => Navigator.of(context)
+                                    .push(MaterialPageRoute(
+                                        builder: (context) => PveLxcOptions(
+                                              lxcBloc: lxcBloc,
+                                            ),
+                                        fullscreenDialog: true))),
+                            if (!resourceBloc.latestState.isStandalone)
                               ActionCard.withFaIcon(
-                                  'Backup',
-                                  FontAwesomeIcons.floppyDisk,
+                                  'Migrate',
+                                  FontAwesomeIcons.paperPlane,
                                   () => Navigator.of(context).push(
-                                      _createBackupRoute(guestID, state.nodeID,
+                                      _createMigrationRoute(
+                                          guestID,
+                                          state.nodeID,
                                           resourceBloc.apiClient!))),
-                            ],
+                            ActionCard.withFaIcon(
+                                '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 FaIcon(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 FaIcon(FontAwesomeIcons.hardDrive),
+                            dense: true,
+                            title: Text('${config.rootfs}'),
+                          ),
+                        ],
                       ),
-                      if (config != null) ...[
-                        PveResourceDataCardWidget(
+                      PveResourceDataCardWidget(
                           title: const Text(
-                            'Resources',
+                            'Network',
                             style: TextStyle(
                               fontWeight: FontWeight.bold,
                               fontSize: 20,
                             ),
                           ),
                           children: <Widget>[
-                            ListTile(
-                              leading: const FaIcon(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,
+                            for (var net in config.net!)
+                              ListTile(
+                                leading:
+                                    const FaIcon(FontAwesomeIcons.ethernet),
+                                dense: true,
+                                title: Text(net),
+                              ),
+                          ]),
+                      PveResourceDataCardWidget(
+                          title: const Text(
+                            'DNS',
+                            style: TextStyle(
+                              fontWeight: FontWeight.bold,
+                              fontSize: 20,
                             ),
+                          ),
+                          children: <Widget>[
                             ListTile(
-                              leading: const Icon(Icons.memory),
-                              title: Text('${config.cores ?? 'unlimited'}'),
-                              subtitle: const Text('Cores'),
+                              leading: const FaIcon(FontAwesomeIcons.globe),
                               dense: true,
+                              title: Text(
+                                  config.searchdomain ?? 'Use host settings'),
+                              subtitle: const Text('DNS Domain'),
                             ),
                             ListTile(
-                              leading: const FaIcon(FontAwesomeIcons.hardDrive),
+                              leading: const FaIcon(
+                                  FontAwesomeIcons.magnifyingGlass),
                               dense: true,
-                              title: Text('${config.rootfs}'),
+                              title: Text(
+                                  config.nameserver ?? 'Use host settings'),
+                              subtitle: const Text('DNS Server'),
                             ),
-                          ],
-                        ),
-                        PveResourceDataCardWidget(
-                            title: const Text(
-                              'Network',
-                              style: TextStyle(
-                                fontWeight: FontWeight.bold,
-                                fontSize: 20,
-                              ),
-                            ),
-                            children: <Widget>[
-                              for (var net in config.net!)
-                                ListTile(
-                                  leading:
-                                      const FaIcon(FontAwesomeIcons.ethernet),
-                                  dense: true,
-                                  title: Text(net),
-                                ),
-                            ]),
-                        PveResourceDataCardWidget(
-                            title: const Text(
-                              'DNS',
-                              style: TextStyle(
-                                fontWeight: FontWeight.bold,
-                                fontSize: 20,
-                              ),
-                            ),
-                            children: <Widget>[
-                              ListTile(
-                                leading: const FaIcon(FontAwesomeIcons.globe),
-                                dense: true,
-                                title: Text(
-                                    config.searchdomain ?? 'Use host settings'),
-                                subtitle: const Text('DNS Domain'),
-                              ),
-                              ListTile(
-                                leading: const FaIcon(
-                                    FontAwesomeIcons.magnifyingGlass),
-                                dense: true,
-                                title: Text(
-                                    config.nameserver ?? 'Use host settings'),
-                                subtitle: const Text('DNS Server'),
-                              ),
-                            ]),
-                      ],
-                      SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
+                          ]),
                     ],
-                  ),
+                    SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
+                  ],
                 ),
+              ),
             );
           }),
     );
diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart
index 4ecdede..b9cc4db 100644
--- a/lib/widgets/pve_node_overview.dart
+++ b/lib/widgets/pve_node_overview.dart
@@ -63,391 +63,388 @@ class PveNodeOverview extends StatelessWidget {
           ),
           backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
           body: 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,
-                              ),
-                            );
-                            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)} %',
-                                    ),
+            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: FaIcon(FontAwesomeIcons.memory,
-                                          color: fgColor),
-                                      bottomRight: pageIndicator,
-                                      dataRenderer: (data) =>
-                                          Renderers.formatSize(data),
-                                    ),
+                                ),
+                              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: FaIcon(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 == 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),
-                                    ),
+                                ),
+                              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(),
-                            ),
-                          ),
-                        );
-                      }
-                      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),
+                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(),
                           ),
-                          ActionCard(
-                            icon: const Icon(
-                              Icons.queue_play_next,
-                              size: 55,
-                              color: Colors.white24,
-                            ),
-                            title: 'Console',
-                            onTap: () => showConsoleMenuBottomSheet(
-                                context, nBloc.apiClient, null, nodeID, 'node'),
+                        ),
+                      );
+                    }
+                    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'),
                     ),
-                    titleTrailing: Text(Renderers.renderDuration(
-                        Duration(seconds: status?.uptime ?? 0))),
-                    subtitle: Text(status?.pveversion ?? ''),
-                    children: [
+                    if (status?.cpuinfo != null)
                       ListTile(
                         dense: true,
-                        title: Text(status?.kversion ?? 'unknown'),
-                        subtitle: const Text('Kernel'),
+                        title: Text(
+                            '${status!.cpuinfo.cpus} x ${status.cpuinfo.model}'),
+                        subtitle: Text(
+                            'CPU Information (Socket: ${status.cpuinfo.sockets})'),
                       ),
-                      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: FaIcon(
-                                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),
+                    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: FaIcon(
+                              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),
                           ),
                         ),
-                      ],
-                    ],
-                  ),
-                  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.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(),
-                      ],
+                  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,
                     ),
-                    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),
-                                    )
-                                  ],
-                                ),
+                  ),
+                  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!)),
+                ),
+                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: FaIcon(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),
-                                    )
-                                  ],
-                                ),
+                  ),
+                  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: FaIcon(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!)),
-                  ),
-                  SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
-                ],
-              ),
+                        ),
+                      )
+                      .toList()
+                    ..sort((a, b) => (a.title as Text)
+                        .data!
+                        .compareTo((b.title as Text).data!)),
+                ),
+                SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
+              ],
             ),
+          ),
         );
       },
     );
diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart
index 2f366a8..322fcf7 100644
--- a/lib/widgets/pve_qemu_overview.dart
+++ b/lib/widgets/pve_qemu_overview.dart
@@ -67,176 +67,173 @@ class PveQemuOverview extends StatelessWidget {
               ),
               backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
               body: SingleChildScrollView(
-                    child: Column(
-                  children: <Widget>[
-                    PveGuestOverviewHeader(
-                      background: !(status?.template ?? false)
-                          ? PveGuestHeaderRRDPageView(
-                              rrdData: rrdData,
-                            )
-                          : const Center(
-                              child: Text(
-                                "TEMPLATE",
-                                style: TextStyle(
-                                  color: Colors.white,
-                                ),
+                  child: Column(
+                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),
+                          ),
+                    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,
+                                ),
                               )
-                                ..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))
-                              ActionCard.withIcon(
-                                  'Power Settings',
-                                  Icons.power_settings_new,
-                                  () =>
-                                      showPowerMenuBottomSheet(context, bloc)),
-                            if (!(status?.template ?? false))
-                              ActionCard.withIcon(
-                                  'Console',
-                                  Icons.queue_play_next,
-                                  () => showConsoleMenuBottomSheet(
-                                        context,
-                                        bloc.apiClient,
-                                        guestID,
-                                        state.nodeID,
-                                        'qemu',
-                                        allowSpice: status?.spice ?? false,
-                                      )),
+                              ..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))
+                            ActionCard.withIcon(
+                                'Power Settings',
+                                Icons.power_settings_new,
+                                () => showPowerMenuBottomSheet(context, bloc)),
+                          if (!(status?.template ?? false))
                             ActionCard.withIcon(
-                                'Options',
-                                Icons.settings,
-                                () => Navigator.of(context)
-                                    .push(_createOptionsRoute(bloc))),
-                            if (!rBloc.latestState.isStandalone)
-                              ActionCard.withFaIcon(
-                                  'Migrate',
-                                  FontAwesomeIcons.paperPlane,
-                                  () => Navigator.of(context).push(
-                                      _createMigrationRoute(guestID,
-                                          state.nodeID, bloc.apiClient))),
+                                'Console',
+                                Icons.queue_play_next,
+                                () => showConsoleMenuBottomSheet(
+                                      context,
+                                      bloc.apiClient,
+                                      guestID,
+                                      state.nodeID,
+                                      'qemu',
+                                      allowSpice: status?.spice ?? false,
+                                    )),
+                          ActionCard.withIcon(
+                              'Options',
+                              Icons.settings,
+                              () => Navigator.of(context)
+                                  .push(_createOptionsRoute(bloc))),
+                          if (!rBloc.latestState.isStandalone)
                             ActionCard.withFaIcon(
-                                'Backup',
-                                FontAwesomeIcons.floppyDisk,
+                                'Migrate',
+                                FontAwesomeIcons.paperPlane,
                                 () => Navigator.of(context).push(
-                                    _createBackupRoute(guestID, state.nodeID,
+                                    _createMigrationRoute(guestID, state.nodeID,
                                         bloc.apiClient))),
-                          ],
-                        ),
+                          ActionCard.withFaIcon(
+                              '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 FaIcon(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,
-                            ),
+                        ),
+                        children: [
+                          ListTile(
+                            leading: const FaIcon(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 FaIcon(FontAwesomeIcons.microchip),
+                            title:
+                                Text(config.bios?.name ?? 'Default (SeaBIOS)'),
+                            subtitle: const Text('BIOS'),
+                            dense: true,
+                          ),
+                          ListTile(
+                            leading: const FaIcon(FontAwesomeIcons.gears),
+                            dense: true,
+                            title: Text(config.machine ?? 'Default (i440fx)'),
+                            subtitle: const Text('Machine Type'),
+                          ),
+                          ListTile(
+                            leading: const FaIcon(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 FaIcon(FontAwesomeIcons.microchip),
-                              title: Text(
-                                  config.bios?.name ?? 'Default (SeaBIOS)'),
-                              subtitle: const Text('BIOS'),
+                              leading:
+                                  const FaIcon(FontAwesomeIcons.compactDisc),
+                              title: Text(ide),
+                              subtitle: const Text('CD/DVD Drive'),
                               dense: true,
                             ),
+                          for (var scsi in config.scsi!)
                             ListTile(
-                              leading: const FaIcon(FontAwesomeIcons.gears),
+                              leading: const FaIcon(FontAwesomeIcons.hardDrive),
+                              title: Text(scsi),
+                              subtitle: const Text('Hard Disk'),
                               dense: true,
-                              title: Text(config.machine ?? 'Default (i440fx)'),
-                              subtitle: const Text('Machine Type'),
                             ),
+                          for (var net in config.net!)
                             ListTile(
-                              leading: const FaIcon(FontAwesomeIcons.database),
-                              title: Text(
-                                  config.scsihw?.name ?? 'Default (i440fx)'),
-                              subtitle: const Text('SCSI Controller'),
+                              leading: const FaIcon(FontAwesomeIcons.ethernet),
                               dense: true,
-                            ),
-                            for (var ide in config.ide!)
-                              ListTile(
-                                leading:
-                                    const FaIcon(FontAwesomeIcons.compactDisc),
-                                title: Text(ide),
-                                subtitle: const Text('CD/DVD Drive'),
-                                dense: true,
-                              ),
-                            for (var scsi in config.scsi!)
-                              ListTile(
-                                leading:
-                                    const FaIcon(FontAwesomeIcons.hardDrive),
-                                title: Text(scsi),
-                                subtitle: const Text('Hard Disk'),
-                                dense: true,
-                              ),
-                            for (var net in config.net!)
-                              ListTile(
-                                leading:
-                                    const FaIcon(FontAwesomeIcons.ethernet),
-                                dense: true,
-                                subtitle: const Text('Network Device'),
-                                title: Text(net),
-                              )
-                          ]),
-                    SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
-                  ],
-                )),
+                              subtitle: const Text('Network Device'),
+                              title: Text(net),
+                            )
+                        ]),
+                  SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
+                ],
+              )),
             );
           }),
     );
-- 
2.50.1





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

end of thread, other threads:[~2026-10-23 11:41 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 11:40 [PATCH pve_flutter_frontend 0/3] fix: ui: clipping of ui elements in iPhone duo Shan Shaji
2026-10-23 11:40 ` [PATCH pve_flutter_frontend 1/3] fix: ui being inconsistent in overview pages Shan Shaji
2026-10-23 11:40 ` [PATCH pve_flutter_frontend 2/3] fix: ui: bottom overflow on apple's duo device Shan Shaji
2026-10-23 11:40 ` [PATCH pve_flutter_frontend 3/3] cleanup: run dart format 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