* [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