From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 359B51FF16B for ; Fri, 21 Nov 2025 16:45:35 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id CD86025E93; Fri, 21 Nov 2025 16:45:26 +0100 (CET) From: Shan Shaji To: pve-devel@lists.proxmox.com Date: Wed, 19 Nov 2025 13:57:11 +0100 Message-ID: <20251119125711.165181-3-s.shaji@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251119125711.165181-1-s.shaji@proxmox.com> References: <20251119125711.165181-1-s.shaji@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1763557010703 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.117 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-Mailman-Approved-At: Fri, 21 Nov 2025 16:45:23 +0100 Subject: [pve-devel] [PATCH pve_flutter_frontend 2/2] cleanup: run `dart format .` command to fix formatting X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Signed-off-by: Shan Shaji --- lib/widgets/pve_cd_selector_widget.dart | 96 +-- lib/widgets/pve_config_list_tile.dart | 65 +- lib/widgets/pve_lxc_options_widget.dart | 175 +++--- lib/widgets/pve_lxc_overview.dart | 340 +++++------ lib/widgets/pve_node_overview.dart | 720 ++++++++++++----------- lib/widgets/pve_qemu_options_widget.dart | 330 +++++------ lib/widgets/pve_qemu_overview.dart | 297 +++++----- 7 files changed, 1020 insertions(+), 1003 deletions(-) diff --git a/lib/widgets/pve_cd_selector_widget.dart b/lib/widgets/pve_cd_selector_widget.dart index b4591d0..8a67eac 100644 --- a/lib/widgets/pve_cd_selector_widget.dart +++ b/lib/widgets/pve_cd_selector_widget.dart @@ -23,53 +23,57 @@ class PveCdSelector extends StatelessWidget { if (snapshot.hasData) { final state = snapshot.data!; return RadioGroup( - groupValue: state.value, - onChanged: (value) => cdBloc.events.add(ChangeValue(value)), - child: Column(children: [ - RadioListTile( - title: const Text('Use CD/DVD disc image file (iso)'), - value: CdType.iso, - ), - if (state.value == CdType.iso) - OutlinedButton( - style: OutlinedButton.styleFrom( - side: state.hasError - ? const BorderSide(color: Colors.red) - : null, + groupValue: state.value, + onChanged: (value) => cdBloc.events.add(ChangeValue(value)), + child: Column(children: [ + RadioListTile( + title: const Text('Use CD/DVD disc image file (iso)'), + value: CdType.iso, ), - child: Text((state.file == null || state.file!.isEmpty) - ? "Choose File" - : state.file!), - onPressed: () async { - final PveNodesStorageContentModel? file = await showDialog( - context: context, - builder: (context) => PveFileSelector( - fBloc: PveFileSelectorBloc( - apiClient: client, - init: PveFileSelectorState.init().rebuild((b) => - b..fileType = PveStorageContentType.iso), - ), - sBloc: PveStorageSelectorBloc( - apiClient: client, - init: PveStorageSelectorState.init().rebuild( - (b) => - b..content = PveStorageContentType.iso), - )..events.add(LoadStoragesEvent()), - )); - if (file != null) { - cdBloc.events.add(FileSelected(file.volid)); - } - }, - ), - RadioListTile( - title: const Text('Use physical CD/DVD Drive'), - value: CdType.cdrom, - ), - RadioListTile( - title: const Text('Do not use any media'), - value: CdType.none, - ), - ])); + if (state.value == CdType.iso) + OutlinedButton( + style: OutlinedButton.styleFrom( + side: state.hasError + ? const BorderSide(color: Colors.red) + : null, + ), + child: Text((state.file == null || state.file!.isEmpty) + ? "Choose File" + : state.file!), + onPressed: () async { + final PveNodesStorageContentModel? file = + await showDialog( + context: context, + builder: (context) => PveFileSelector( + fBloc: PveFileSelectorBloc( + apiClient: client, + init: PveFileSelectorState.init() + .rebuild((b) => b + ..fileType = + PveStorageContentType.iso), + ), + sBloc: PveStorageSelectorBloc( + apiClient: client, + init: PveStorageSelectorState.init() + .rebuild((b) => b + ..content = + PveStorageContentType.iso), + )..events.add(LoadStoragesEvent()), + )); + if (file != null) { + cdBloc.events.add(FileSelected(file.volid)); + } + }, + ), + RadioListTile( + title: const Text('Use physical CD/DVD Drive'), + value: CdType.cdrom, + ), + RadioListTile( + title: const Text('Do not use any media'), + value: CdType.none, + ), + ])); } return Container(); diff --git a/lib/widgets/pve_config_list_tile.dart b/lib/widgets/pve_config_list_tile.dart index c937755..24eb1ec 100644 --- a/lib/widgets/pve_config_list_tile.dart +++ b/lib/widgets/pve_config_list_tile.dart @@ -131,40 +131,41 @@ class _PveConfigEditorFormState extends State<_PveConfigEditorForm> { @override Widget build(BuildContext context) { return SafeArea( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Column( - mainAxisSize: MainAxisSize.min, - spacing: 8, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - widget.title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), - ), - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Close'), - ), - ], - ), - widget.editorBuilder(context, _controller), - const SizedBox(height: 12), - Align( - alignment: Alignment.bottomRight, - child: FilledButton( - onPressed: _isDirty ? _submit : null, - child: const Text('Update'), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ], ), - ), - const SizedBox(height: 8), - ], + widget.editorBuilder(context, _controller), + const SizedBox(height: 12), + Align( + alignment: Alignment.bottomRight, + child: FilledButton( + onPressed: _isDirty ? _submit : null, + child: const Text('Update'), + ), + ), + const SizedBox(height: 8), + ], + ), ), - ),); + ); } } diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart index 21398d2..c43e435 100644 --- a/lib/widgets/pve_lxc_options_widget.dart +++ b/lib/widgets/pve_lxc_options_widget.dart @@ -23,102 +23,103 @@ class PveLxcOptions extends StatelessWidget { body: SafeArea( bottom: !Platform.isIOS, child: SingleChildScrollView( - child: Column( - children: [ - ListTile( - title: const Text("Name"), - subtitle: Text(config.hostname ?? 'undefined'), - ), - PveConfigListTile( - title: "Start on boot", - trailingText: config.onboot == true ? 'Yes' : 'No', - pending: config.getPending('onboot'), - onSubmit: (value) => lxcBloc!.events.add( - UpdateLxcConfigBool( - 'onboot', - value, + child: Column( + children: [ + ListTile( + title: const Text("Name"), + subtitle: Text(config.hostname ?? 'undefined'), + ), + PveConfigListTile( + title: "Start on boot", + trailingText: config.onboot == true ? 'Yes' : 'No', + pending: config.getPending('onboot'), + onSubmit: (value) => lxcBloc!.events.add( + UpdateLxcConfigBool( + 'onboot', + value, + ), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Start on boot", + value: config.onboot == true, + controller: controller, + ); + }, + onCancelEdit: () => lxcBloc!.events.add( + RevertPendingLxcConfig('onboot'), ), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Start on boot", - value: config.onboot == true, - controller: controller, - ); - }, - onCancelEdit: () => lxcBloc!.events.add( - RevertPendingLxcConfig('onboot'), + ListTile( + title: const Text("Start/Shutdown order"), + subtitle: Text(config.startup ?? "Default (any)"), ), - ), - ListTile( - title: const Text("Start/Shutdown order"), - subtitle: Text(config.startup ?? "Default (any)"), - ), - ListTile( - title: const Text("OS Type"), - subtitle: Text("${config.ostype}"), - ), - ListTile( - title: const Text("Architecture"), - subtitle: Text("${config.arch}"), - ), - PveConfigListTile( - title: "/dev/console", - trailingText: config.console == true ? 'Yes' : 'No', - pending: config.getPending('console'), - onSubmit: (v) => lxcBloc!.events.add( - UpdateLxcConfigBool('console', v), + ListTile( + title: const Text("OS Type"), + subtitle: Text("${config.ostype}"), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "/dev/console", - value: config.console == true, - controller: controller, - ); - }, - onCancelEdit: () => lxcBloc!.events.add( - RevertPendingLxcConfig('console'), + ListTile( + title: const Text("Architecture"), + subtitle: Text("${config.arch}"), ), - ), - ListTile( - title: const Text("TTY Count"), - subtitle: Text("${config.tty ?? 2}"), - ), - ListTile( - title: const Text("Console Mode"), - subtitle: Text(config.cmode?.name ?? 'tty'), - ), - PveConfigListTile( - title: "Protection", - trailingText: config.protection == true ? 'Yes' : 'No', - pending: config.getPending('protection'), - onSubmit: (v) => lxcBloc!.events.add( - UpdateLxcConfigBool('protection', v), + PveConfigListTile( + title: "/dev/console", + trailingText: config.console == true ? 'Yes' : 'No', + pending: config.getPending('console'), + onSubmit: (v) => lxcBloc!.events.add( + UpdateLxcConfigBool('console', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "/dev/console", + value: config.console == true, + controller: controller, + ); + }, + onCancelEdit: () => lxcBloc!.events.add( + RevertPendingLxcConfig('console'), + ), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Protection", - value: config.protection == true, - controller: controller, - ); - }, - onCancelEdit: () => lxcBloc!.events.add( - RevertPendingLxcConfig('protection'), + ListTile( + title: const Text("TTY Count"), + subtitle: Text("${config.tty ?? 2}"), ), - ), - ListTile( - title: const Text("Unprivileged"), - subtitle: - Text(config.unprivileged ?? false ? 'Yes' : 'No'), - ), - ListTile( - title: const Text("Features"), - subtitle: Text(config.features?.toString() ?? 'none'), - ), - ], + ListTile( + title: const Text("Console Mode"), + subtitle: Text(config.cmode?.name ?? 'tty'), + ), + PveConfigListTile( + title: "Protection", + trailingText: config.protection == true ? 'Yes' : 'No', + pending: config.getPending('protection'), + onSubmit: (v) => lxcBloc!.events.add( + UpdateLxcConfigBool('protection', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Protection", + value: config.protection == true, + controller: controller, + ); + }, + onCancelEdit: () => lxcBloc!.events.add( + RevertPendingLxcConfig('protection'), + ), + ), + ListTile( + title: const Text("Unprivileged"), + subtitle: + Text(config.unprivileged ?? false ? 'Yes' : 'No'), + ), + ListTile( + title: const Text("Features"), + subtitle: Text(config.features?.toString() ?? 'none'), + ), + ], + ), ), ), - ),); + ); } return const Center( child: CircularProgressIndicator(), diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart index 0f486d2..78ad853 100644 --- a/lib/widgets/pve_lxc_overview.dart +++ b/lib/widgets/pve_lxc_overview.dart @@ -80,189 +80,191 @@ class PveLxcOverview extends StatelessWidget { body: SafeArea( bottom: !Platform.isIOS, child: SingleChildScrollView( - child: Column( - children: [ - PveGuestOverviewHeader( - background: !(status?.template ?? false) - ? PveGuestHeaderRRDPageView( - rrdData: state.rrdData, - ) - : Center( - child: Text( - "TEMPLATE", - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, + child: Column( + children: [ + 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( - bloc: taskBloc, - builder: (context, taskState) { - if (taskState.tasks.isNotEmpty) { - return PveTaskExpansionTile( - task: taskState.tasks.first, - showMorePage: Provider( - create: (context) => PveTaskLogBloc( - apiClient: taskBloc.apiClient, - init: PveTaskLogState.init(state.nodeID), - ) - ..events.add( - FilterTasksByGuestID( - guestID: guestID, - ), + width: width, + guestID: guestID, + guestStatus: status?.getLxcStatus(), + guestName: config?.hostname ?? 'CT $guestID', + guestNodeID: state.nodeID, + guestType: 'lxc', + ha: status?.ha, + template: status?.template ?? false, + ), + ProxmoxStreamBuilder( + bloc: taskBloc, + builder: (context, taskState) { + if (taskState.tasks.isNotEmpty) { + return PveTaskExpansionTile( + task: taskState.tasks.first, + showMorePage: Provider( + create: (context) => PveTaskLogBloc( + apiClient: taskBloc.apiClient, + init: PveTaskLogState.init(state.nodeID), ) - ..events.add(LoadTasks()), - dispose: (context, bloc) => bloc.dispose(), - child: const PveTaskLog(), - ), - ); - } - return Container(); - }, - ), - SizedBox( - height: 130, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!(status?.template ?? false)) + ..events.add( + FilterTasksByGuestID( + guestID: guestID, + ), + ) + ..events.add(LoadTasks()), + dispose: (context, bloc) => bloc.dispose(), + child: const PveTaskLog(), + ), + ); + } + return Container(); + }, + ), + SizedBox( + height: 130, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!(status?.template ?? false)) + createActionCard( + 'Power Settings', + Icons.power_settings_new, + () => showPowerMenuBottomSheet( + context, lxcBloc)), + if (!(status?.template ?? false)) + createActionCard( + 'Console', + Icons.queue_play_next, + () => showConsoleMenuBottomSheet( + context, + lxcBloc.apiClient, + guestID, + state.nodeID, + 'lxc')), createActionCard( - 'Power Settings', - Icons.power_settings_new, - () => showPowerMenuBottomSheet( - context, lxcBloc)), - if (!(status?.template ?? false)) + 'Options', + Icons.settings, + () => Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => PveLxcOptions( + lxcBloc: lxcBloc, + ), + fullscreenDialog: true))), + if (!resourceBloc.latestState.isStandalone) + createActionCard( + 'Migrate', + FontAwesomeIcons.paperPlane, + () => Navigator.of(context).push( + _createMigrationRoute( + guestID, + state.nodeID, + resourceBloc.apiClient!))), createActionCard( - 'Console', - Icons.queue_play_next, - () => showConsoleMenuBottomSheet( - context, - lxcBloc.apiClient, - guestID, - state.nodeID, - 'lxc')), - createActionCard( - 'Options', - Icons.settings, - () => Navigator.of(context) - .push(MaterialPageRoute( - builder: (context) => PveLxcOptions( - lxcBloc: lxcBloc, - ), - fullscreenDialog: true))), - if (!resourceBloc.latestState.isStandalone) - createActionCard( - 'Migrate', - FontAwesomeIcons.paperPlane, + 'Backup', + FontAwesomeIcons.floppyDisk, () => Navigator.of(context).push( - _createMigrationRoute( - guestID, - state.nodeID, + _createBackupRoute(guestID, state.nodeID, resourceBloc.apiClient!))), - createActionCard( - 'Backup', - FontAwesomeIcons.floppyDisk, - () => Navigator.of(context).push( - _createBackupRoute(guestID, state.nodeID, - resourceBloc.apiClient!))), + ], + ), + ), + ), + if (config != null) ...[ + PveResourceDataCardWidget( + title: const Text( + 'Resources', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + children: [ + ListTile( + leading: const Icon(FontAwesomeIcons.memory), + title: Text('${config.memory}'), + subtitle: const Text('Memory'), + dense: true, + ), + ListTile( + leading: const Icon(Icons.cached), + title: Text('${config.swap}'), + subtitle: const Text('Swap'), + dense: true, + ), + ListTile( + leading: const Icon(Icons.memory), + title: Text('${config.cores ?? 'unlimited'}'), + subtitle: const Text('Cores'), + dense: true, + ), + ListTile( + leading: const Icon(FontAwesomeIcons.hardDrive), + dense: true, + title: Text('${config.rootfs}'), + ), ], ), - ), - ), - if (config != null) ...[ - PveResourceDataCardWidget( - title: const Text( - 'Resources', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - children: [ - ListTile( - leading: const Icon(FontAwesomeIcons.memory), - title: Text('${config.memory}'), - subtitle: const Text('Memory'), - dense: true, - ), - ListTile( - leading: const Icon(Icons.cached), - title: Text('${config.swap}'), - subtitle: const Text('Swap'), - dense: true, - ), - ListTile( - leading: const Icon(Icons.memory), - title: Text('${config.cores ?? 'unlimited'}'), - subtitle: const Text('Cores'), - dense: true, - ), - ListTile( - leading: const Icon(FontAwesomeIcons.hardDrive), - dense: true, - title: Text('${config.rootfs}'), - ), - ], - ), - PveResourceDataCardWidget( - title: const Text( - 'Network', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - children: [ - for (var net in config.net!) - ListTile( - leading: const Icon(FontAwesomeIcons.ethernet), - dense: true, - title: Text(net), + PveResourceDataCardWidget( + title: const Text( + 'Network', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, ), - ]), - PveResourceDataCardWidget( - title: const Text( - 'DNS', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, ), - ), - children: [ - ListTile( - leading: const Icon(FontAwesomeIcons.globe), - dense: true, - title: Text( - config.searchdomain ?? 'Use host settings'), - subtitle: const Text('DNS Domain'), + children: [ + for (var net in config.net!) + ListTile( + leading: + const Icon(FontAwesomeIcons.ethernet), + dense: true, + title: Text(net), + ), + ]), + PveResourceDataCardWidget( + title: const Text( + 'DNS', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), - ListTile( - leading: - const Icon(FontAwesomeIcons.magnifyingGlass), - dense: true, - title: Text( - config.nameserver ?? 'Use host settings'), - subtitle: const Text('DNS Server'), - ), - ]), - ] - ], + children: [ + ListTile( + leading: const Icon(FontAwesomeIcons.globe), + dense: true, + title: Text( + config.searchdomain ?? 'Use host settings'), + subtitle: const Text('DNS Domain'), + ), + ListTile( + leading: const Icon( + FontAwesomeIcons.magnifyingGlass), + dense: true, + title: Text( + config.nameserver ?? 'Use host settings'), + subtitle: const Text('DNS Server'), + ), + ]), + ] + ], + ), ), ), - ),); + ); }), ); } diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart index f0583a3..7236cf4 100644 --- a/lib/widgets/pve_node_overview.dart +++ b/lib/widgets/pve_node_overview.dart @@ -65,388 +65,392 @@ class PveNodeOverview extends StatelessWidget { body: SafeArea( bottom: !Platform.isIOS, child: SingleChildScrollView( - child: Column( - children: [ - if (rrd.isNotEmpty) - Container( - height: 200, - color: Theme.of(context).colorScheme.primary, - child: ScrollConfiguration( - behavior: PVEScrollBehavior(), - child: PageView.builder( - itemCount: 4, - itemBuilder: (context, item) { - final page = item + 1; - final pageIndicator = Text( - '$page of 4', - style: const TextStyle( - color: Colors.white54, - fontWeight: FontWeight.w500, + child: Column( + children: [ + if (rrd.isNotEmpty) + Container( + height: 200, + color: Theme.of(context).colorScheme.primary, + child: ScrollConfiguration( + behavior: PVEScrollBehavior(), + child: PageView.builder( + itemCount: 4, + itemBuilder: (context, item) { + final page = item + 1; + final pageIndicator = Text( + '$page of 4', + style: const TextStyle( + color: Colors.white54, + fontWeight: FontWeight.w500, + ), + ); + double? lastCpu = rrd.last.cpu; + String lastCpuText = lastCpu != null + ? "${(lastCpu * 100.0).toStringAsFixed(2)} %" + : ""; + return Column( + children: [ + if (item == 0) + Expanded( + child: PveRRDChart( + title: + 'CPU (${state.status?.cpuinfo.cpus ?? '-'})', + subtitle: lastCpuText, + data: rrd.where((e) => e.cpu != null).map( + (e) => Point( + e.time!.millisecondsSinceEpoch, + (e.cpu ?? 0) * 100.0)), + icon: Icon(Icons.memory, color: fgColor), + bottomRight: pageIndicator, + dataRenderer: (data) => + '${data.toStringAsFixed(2)} %', + ), + ), + if (item == 1) + Expanded( + child: PveRRDChart( + title: 'Memory', + subtitle: Renderers.formatSize( + rrd.last.memused ?? 0), + data: rrd.map((e) => Point( + e.time!.millisecondsSinceEpoch, + e.memused ?? 0)), + icon: Icon(FontAwesomeIcons.memory, + color: fgColor), + bottomRight: pageIndicator, + dataRenderer: (data) => + Renderers.formatSize(data), + ), + ), + if (item == 2) + Expanded( + child: PveRRDChart( + title: 'I/O wait', + subtitle: + rrd.last.iowait?.toStringAsFixed(2) ?? + '0', + data: rrd.map((e) => Point( + e.time!.millisecondsSinceEpoch, + e.iowait ?? 0)), + icon: Icon(Icons.timer, color: fgColor), + bottomRight: pageIndicator, + dataRenderer: (data) => + data.toStringAsFixed(3), + ), + ), + if (item == 3) + Expanded( + child: PveRRDChart( + title: 'Load', + subtitle: rrd.last.loadavg + ?.toStringAsFixed(2) ?? + '0', + data: rrd.map((e) => Point( + e.time!.millisecondsSinceEpoch, + e.loadavg ?? 0)), + icon: Icon(Icons.show_chart, + color: fgColor), + bottomRight: pageIndicator, + dataRenderer: (data) => + data.toStringAsFixed(2), + ), + ), + ], + ); + }, + ), + ), + ), + ProxmoxStreamBuilder( + 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( + create: (context) => PveTaskLogBloc( + apiClient: tBloc.apiClient, + init: PveTaskLogState.init(nodeID), + )..events.add(LoadTasks()), + dispose: (context, bloc) => bloc.dispose(), + child: const PveTaskLog(), ), - ); - double? lastCpu = rrd.last.cpu; - String lastCpuText = lastCpu != null - ? "${(lastCpu * 100.0).toStringAsFixed(2)} %" - : ""; - return Column( - children: [ - if (item == 0) - Expanded( - child: PveRRDChart( - title: - 'CPU (${state.status?.cpuinfo.cpus ?? '-'})', - subtitle: lastCpuText, - data: rrd.where((e) => e.cpu != null).map( - (e) => Point( - e.time!.millisecondsSinceEpoch, - (e.cpu ?? 0) * 100.0)), - icon: Icon(Icons.memory, color: fgColor), - bottomRight: pageIndicator, - dataRenderer: (data) => - '${data.toStringAsFixed(2)} %', - ), - ), - if (item == 1) - Expanded( - child: PveRRDChart( - title: 'Memory', - subtitle: Renderers.formatSize( - rrd.last.memused ?? 0), - data: rrd.map((e) => Point( - e.time!.millisecondsSinceEpoch, - e.memused ?? 0)), - icon: Icon(FontAwesomeIcons.memory, - color: fgColor), - bottomRight: pageIndicator, - dataRenderer: (data) => - Renderers.formatSize(data), - ), - ), - if (item == 2) - Expanded( - child: PveRRDChart( - title: 'I/O wait', - subtitle: - rrd.last.iowait?.toStringAsFixed(2) ?? - '0', - data: rrd.map((e) => Point( - e.time!.millisecondsSinceEpoch, - e.iowait ?? 0)), - icon: Icon(Icons.timer, color: fgColor), - bottomRight: pageIndicator, - dataRenderer: (data) => - data.toStringAsFixed(3), - ), - ), - if (item == 3) - Expanded( - child: PveRRDChart( - title: 'Load', - subtitle: - rrd.last.loadavg?.toStringAsFixed(2) ?? - '0', - data: rrd.map((e) => Point( - e.time!.millisecondsSinceEpoch, - e.loadavg ?? 0)), - icon: - Icon(Icons.show_chart, color: fgColor), - bottomRight: pageIndicator, - dataRenderer: (data) => - data.toStringAsFixed(2), - ), - ), - ], - ); - }, + ), + ); + } + return Container(); + }, + ), + SizedBox( + height: 130, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ActionCard( + icon: const Icon( + Icons.power_settings_new, + size: 55, + color: Colors.white24, + ), + title: 'Power Settings', + onTap: () => + showPowerMenuBottomSheet(context, nBloc), + ), + ActionCard( + icon: const Icon( + Icons.queue_play_next, + size: 55, + color: Colors.white24, + ), + title: 'Console', + onTap: () => showConsoleMenuBottomSheet( + context, nBloc.apiClient, null, nodeID, 'node'), + ), + ], ), ), ), - ProxmoxStreamBuilder( - 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( - 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: [ - ActionCard( - icon: const Icon( - Icons.power_settings_new, - size: 55, - color: Colors.white24, - ), - title: 'Power Settings', - onTap: () => showPowerMenuBottomSheet(context, nBloc), - ), - ActionCard( - icon: const Icon( - Icons.queue_play_next, - size: 55, - color: Colors.white24, - ), - title: 'Console', - onTap: () => showConsoleMenuBottomSheet( - context, nBloc.apiClient, null, nodeID, 'node'), - ), - ], + PveResourceDataCardWidget( + expandable: false, + showTitleTrailing: true, + title: const Text( + 'Summary', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), - ), - ), - PveResourceDataCardWidget( - expandable: false, - showTitleTrailing: true, - title: const Text( - 'Summary', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - titleTrailing: Text(Renderers.renderDuration( - Duration(seconds: status?.uptime ?? 0))), - subtitle: Text(status?.pveversion ?? ''), - children: [ - ListTile( - dense: true, - title: Text(status?.kversion ?? 'unknown'), - subtitle: const Text('Kernel'), - ), - if (status?.cpuinfo != null) + titleTrailing: Text(Renderers.renderDuration( + Duration(seconds: status?.uptime ?? 0))), + subtitle: Text(status?.pveversion ?? ''), + children: [ ListTile( dense: true, - title: Text( - '${status!.cpuinfo.cpus} x ${status.cpuinfo.model}'), - subtitle: Text( - 'CPU Information (Socket: ${status.cpuinfo.sockets})'), + title: Text(status?.kversion ?? 'unknown'), + subtitle: const Text('Kernel'), ), - if (status?.ksm.shared ?? false) - CheckboxListTile( - dense: true, - value: status?.ksm.shared ?? false, - title: const Text('Kernel same-page merging (KSM)', - style: TextStyle(color: Colors.black)), - onChanged: null, - ), - if (status?.rootfs != null) ...[ - const Divider( - indent: 10, - endIndent: 10, - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - title: const Text('HD space (root)'), - subtitle: ProxmoxCapacityIndicator( - icon: Icon( - FontAwesomeIcons.solidHardDrive, - color: Colors.blueGrey[300], + if (status?.cpuinfo != null) + ListTile( + dense: true, + title: Text( + '${status!.cpuinfo.cpus} x ${status.cpuinfo.model}'), + subtitle: Text( + 'CPU Information (Socket: ${status.cpuinfo.sockets})'), + ), + if (status?.ksm.shared ?? false) + CheckboxListTile( + dense: true, + value: status?.ksm.shared ?? false, + title: const Text('Kernel same-page merging (KSM)', + style: TextStyle(color: Colors.black)), + onChanged: null, + ), + if (status?.rootfs != null) ...[ + const Divider( + indent: 10, + endIndent: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ListTile( + title: const Text('HD space (root)'), + subtitle: ProxmoxCapacityIndicator( + icon: Icon( + FontAwesomeIcons.solidHardDrive, + color: Colors.blueGrey[300], + ), + usedValue: + Renderers.formatSize(status!.rootfs.used), + totalValue: + Renderers.formatSize(status.rootfs.total), + usedPercent: + (status.rootfs.used) / (status.rootfs.total), ), - usedValue: - Renderers.formatSize(status!.rootfs.used), - totalValue: - Renderers.formatSize(status.rootfs.total), - usedPercent: - (status.rootfs.used) / (status.rootfs.total), ), ), + ], + ], + ), + PveResourceDataCardWidget( + expandable: true, + showTitleTrailing: !state.allServicesRunning, + titleTrailing: const Icon(Icons.warning), + title: const Text( + 'Services', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, ), - ], - ], - ), - PveResourceDataCardWidget( - expandable: true, - showTitleTrailing: !state.allServicesRunning, - titleTrailing: const Icon(Icons.warning), - title: const Text( - 'Services', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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: [ + if (state.allServicesRunning) const Text('All running'), + if (!state.allServicesRunning) + const Text('One or more not running'), + const Divider(), + ], ), + children: state.services + .map( + (s) => ListTile( + dense: true, + title: Text(s.name), + subtitle: Text(s.desc), + trailing: getServiceStateIcon(context, s), + ), + ) + .toList() + ..sort((a, b) => (a.title as Text) + .data! + .compareTo((b.title as Text).data!)), ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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: [ - Text( - s.description ?? '', - style: const TextStyle(fontSize: 12), - ) - ], + PveResourceDataCardWidget( + expandable: !state.updatesQueryPermissionFailure, + showTitleTrailing: state.updates.isNotEmpty, + titleTrailing: const Icon(Icons.info_outline), + title: const Text( + 'Updates', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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: [ + Text( + s.description ?? '', + style: const TextStyle(fontSize: 12), + ) + ], + ), ), ), ), - ), - ) - .toList() - ..sort((a, b) => (a.title as Text) - .data! - .compareTo((b.title as Text).data!)), - ), - PveResourceDataCardWidget( - expandable: false, - title: const Text( - 'Disks', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), + ) + .toList() + ..sort((a, b) => (a.title as Text) + .data! + .compareTo((b.title as Text).data!)), ), - showTitleTrailing: !state.allDisksHealthy, - titleTrailing: const Icon(Icons.warning), - subtitle: state.allDisksHealthy - ? const Text('No health issues') - : const Text('Check disks, health error indicated!'), - children: state.disks - .map( - (d) => ListTile( - dense: true, - leading: Icon(FontAwesomeIcons.solidHardDrive, - color: state.isDiskHealthy(d) - ? Colors.grey - : Colors.red), - title: Text('${d.type!.toUpperCase()}: ${d.devPath}'), - subtitle: Text( - 'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'), - trailing: IconButton( - icon: const Icon( - Icons.info, - ), - onPressed: () => showDialog( - context: context, - builder: (context) => SimpleDialog( - title: const Text('Details'), - contentPadding: const EdgeInsets.all(24), - children: [ - ListTile( - title: const Text("Device"), - subtitle: Text(d.devPath!), - ), - ListTile( - title: const Text("Type"), - subtitle: Text(d.type!), - ), - ListTile( - title: const Text("Usage"), - subtitle: Text(d.used!), - ), - ListTile( - title: const Text("GPT"), - subtitle: Text(d.gpt.toString()), - ), - ListTile( - title: const Text("Model"), - subtitle: Text(d.model!), - ), - ListTile( - title: const Text("Serial"), - subtitle: Text(d.serial!), - ), - ListTile( - title: const Text("S.M.A.R.T"), - subtitle: Text(d.health!), - ), - ListTile( - title: const Text("Wearout"), - subtitle: Text(d.wearoutPercentage), - ) - ], + PveResourceDataCardWidget( + expandable: false, + title: const Text( + 'Disks', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + showTitleTrailing: !state.allDisksHealthy, + titleTrailing: const Icon(Icons.warning), + subtitle: state.allDisksHealthy + ? const Text('No health issues') + : const Text('Check disks, health error indicated!'), + children: state.disks + .map( + (d) => ListTile( + dense: true, + leading: Icon(FontAwesomeIcons.solidHardDrive, + color: state.isDiskHealthy(d) + ? Colors.grey + : Colors.red), + title: + Text('${d.type!.toUpperCase()}: ${d.devPath}'), + subtitle: Text( + 'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'), + trailing: IconButton( + icon: const Icon( + Icons.info, + ), + onPressed: () => showDialog( + context: context, + builder: (context) => SimpleDialog( + title: const Text('Details'), + contentPadding: const EdgeInsets.all(24), + children: [ + ListTile( + title: const Text("Device"), + subtitle: Text(d.devPath!), + ), + ListTile( + title: const Text("Type"), + subtitle: Text(d.type!), + ), + ListTile( + title: const Text("Usage"), + subtitle: Text(d.used!), + ), + ListTile( + title: const Text("GPT"), + subtitle: Text(d.gpt.toString()), + ), + ListTile( + title: const Text("Model"), + subtitle: Text(d.model!), + ), + ListTile( + title: const Text("Serial"), + subtitle: Text(d.serial!), + ), + ListTile( + title: const Text("S.M.A.R.T"), + subtitle: Text(d.health!), + ), + ListTile( + title: const Text("Wearout"), + subtitle: Text(d.wearoutPercentage), + ) + ], + ), ), ), ), - ), - ) - .toList() - ..sort((a, b) => (a.title as Text) - .data! - .compareTo((b.title as Text).data!)), - ), - ], + ) + .toList() + ..sort((a, b) => (a.title as Text) + .data! + .compareTo((b.title as Text).data!)), + ), + ], + ), ), ), - ),); + ); }, ); } diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart index 0f0d46a..b1900b5 100644 --- a/lib/widgets/pve_qemu_options_widget.dart +++ b/lib/widgets/pve_qemu_options_widget.dart @@ -31,187 +31,189 @@ class PveQemuOptions extends StatelessWidget { body: SafeArea( bottom: !Platform.isIOS, child: SingleChildScrollView( - child: Form( - key: _formKey, - onChanged: () {}, - child: Column( - children: [ - ListTile( - title: const Text("Name"), - subtitle: Text(config.name ?? 'VM$guestID'), - ), - PveConfigListTile( - title: "Start on boot", - trailingText: config.onboot == true ? 'Yes' : 'No', - pending: config.getPending('onboot'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('onboot', v), + child: Form( + key: _formKey, + onChanged: () {}, + child: Column( + children: [ + ListTile( + title: const Text("Name"), + subtitle: Text(config.name ?? 'VM$guestID'), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Start on boot", - value: config.onboot == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('onboot'), + PveConfigListTile( + title: "Start on boot", + trailingText: config.onboot == true ? 'Yes' : 'No', + pending: config.getPending('onboot'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('onboot', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Start on boot", + value: config.onboot == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('onboot'), + ), ), - ), - ListTile( - title: const Text("Start/Shutdown order"), - subtitle: Text(config.startup ?? "Default (any)"), - ), - ListTile( - title: const Text("OS Type"), - subtitle: Text(config.ostype != null - ? "${config.ostype!.type} ${config.ostype!.description}" - : "Other"), - ), - //TODO add better ui component e.g. collapseable - ListTile( - title: const Text("Boot Device"), - subtitle: Text(config.boot ?? 'Disk, Network, USB'), - ), - PveConfigListTile( - title: "Use tablet for pointer", - trailingText: config.tablet == true ? 'Yes' : 'No', - pending: config.getPending('tablet'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('tablet', v), + ListTile( + title: const Text("Start/Shutdown order"), + subtitle: Text(config.startup ?? "Default (any)"), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Use tablet for pointer", - value: config.tablet == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('tablet'), + ListTile( + title: const Text("OS Type"), + subtitle: Text(config.ostype != null + ? "${config.ostype!.type} ${config.ostype!.description}" + : "Other"), ), - ), - ListTile( - title: const Text("Hotplug"), - subtitle: Text(config.hotplug ?? 'disk,network,usb'), - ), - PveConfigListTile( - title: "ACPI support", - trailingText: config.acpi == true ? 'Yes' : 'No', - pending: config.getPending('acpi'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('acpi', v), + //TODO add better ui component e.g. collapseable + ListTile( + title: const Text("Boot Device"), + subtitle: Text(config.boot ?? 'Disk, Network, USB'), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "ACPI support", - value: config.acpi == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('acpi'), + PveConfigListTile( + title: "Use tablet for pointer", + trailingText: config.tablet == true ? 'Yes' : 'No', + pending: config.getPending('tablet'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('tablet', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Use tablet for pointer", + value: config.tablet == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('tablet'), + ), ), - ), - PveConfigListTile( - title: "KVM hardware virtualization", - trailingText: config.kvm == true ? 'Yes' : 'No', - pending: config.getPending('kvm'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('kvm', v), + ListTile( + title: const Text("Hotplug"), + subtitle: Text(config.hotplug ?? 'disk,network,usb'), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "KVM hardware virtualization", - value: config.kvm == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('kvm'), + PveConfigListTile( + title: "ACPI support", + trailingText: config.acpi == true ? 'Yes' : 'No', + pending: config.getPending('acpi'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('acpi', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "ACPI support", + value: config.acpi == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('acpi'), + ), ), - ), - PveConfigListTile( - title: "Freeze CPU on startup", - trailingText: config.freeze == true ? 'Yes' : 'No', - pending: config.getPending('freeze'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('freeze', v), + PveConfigListTile( + title: "KVM hardware virtualization", + trailingText: config.kvm == true ? 'Yes' : 'No', + pending: config.getPending('kvm'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('kvm', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "KVM hardware virtualization", + value: config.kvm == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('kvm'), + ), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Freeze CPU on startup", - value: config.freeze == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('freeze'), + PveConfigListTile( + title: "Freeze CPU on startup", + trailingText: config.freeze == true ? 'Yes' : 'No', + pending: config.getPending('freeze'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('freeze', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Freeze CPU on startup", + value: config.freeze == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('freeze'), + ), ), - ), - PveConfigListTile( - title: "Use local time for RTC", - trailingText: config.localtime == true ? 'Yes' : 'No', - pending: config.getPending('localtime'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('localtime', v), + PveConfigListTile( + title: "Use local time for RTC", + trailingText: config.localtime == true ? 'Yes' : 'No', + pending: config.getPending('localtime'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('localtime', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Use local time for RTC", + value: config.localtime == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('localtime'), + ), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Use local time for RTC", - value: config.localtime == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('localtime'), + ListTile( + title: const Text("RTC start date"), + subtitle: Text(config.startdate ?? 'now'), ), - ), - ListTile( - title: const Text("RTC start date"), - subtitle: Text(config.startdate ?? 'now'), - ), - ListTile( - title: const Text("SMBIOS settings (type1)"), - subtitle: Text(config.smbios1 ?? ''), - ), - //Todo enhance UI - ListTile( - title: const Text("QEMU Guest Agent"), - subtitle: Text(config.agent ?? 'Default (disabled)'), - ), - PveConfigListTile( - title: "Protection", - trailingText: config.protection == true ? 'Yes' : 'No', - pending: config.getPending('protection'), - onSubmit: (v) => bloc.events.add( - UpdateQemuConfigBool('protection', v), + ListTile( + title: const Text("SMBIOS settings (type1)"), + subtitle: Text(config.smbios1 ?? ''), ), - editorBuilder: (_, controller) { - return PveConfigSwitchForm( - title: "Protection", - value: config.protection == true, - controller: controller, - ); - }, - onCancelEdit: () => bloc.events.add( - RevertPendingQemuConfig('protection'), + //Todo enhance UI + ListTile( + title: const Text("QEMU Guest Agent"), + subtitle: Text(config.agent ?? 'Default (disabled)'), ), - ), - ListTile( - title: const Text("Spice Enhancements"), - subtitle: - Text(config.spiceEnhancements ?? 'No enhancements'), - ), - ListTile( - title: const Text("VM State Storage"), - subtitle: Text(config.vmstatestorage ?? 'Automatic'), - ), - ], + PveConfigListTile( + title: "Protection", + trailingText: + config.protection == true ? 'Yes' : 'No', + pending: config.getPending('protection'), + onSubmit: (v) => bloc.events.add( + UpdateQemuConfigBool('protection', v), + ), + editorBuilder: (_, controller) { + return PveConfigSwitchForm( + title: "Protection", + value: config.protection == true, + controller: controller, + ); + }, + onCancelEdit: () => bloc.events.add( + RevertPendingQemuConfig('protection'), + ), + ), + ListTile( + title: const Text("Spice Enhancements"), + subtitle: Text( + config.spiceEnhancements ?? 'No enhancements'), + ), + ListTile( + title: const Text("VM State Storage"), + subtitle: Text(config.vmstatestorage ?? 'Automatic'), + ), + ], + ), ), ), ), - ),); + ); } return const Center( child: CircularProgressIndicator(), diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart index a80a58a..3fb25ba 100644 --- a/lib/widgets/pve_qemu_overview.dart +++ b/lib/widgets/pve_qemu_overview.dart @@ -82,171 +82,174 @@ class PveQemuOverview extends StatelessWidget { bottom: !Platform.isIOS, child: SingleChildScrollView( child: Column( - children: [ - PveGuestOverviewHeader( - background: !(status?.template ?? false) - ? PveGuestHeaderRRDPageView( - rrdData: rrdData, - ) - : const Center( - child: Text( - "TEMPLATE", - style: TextStyle( - color: Colors.white, + children: [ + 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( - bloc: taskBloc, - builder: (context, taskState) { - if (taskState.tasks.isNotEmpty) { - return PveTaskExpansionTile( - headerColor: - Theme.of(context).colorScheme.onSurfaceVariant, - task: taskState.tasks.first, - showMorePage: Provider( - create: (context) => PveTaskLogBloc( - apiClient: taskBloc.apiClient, - init: PveTaskLogState.init(state.nodeID), - ) - ..events.add( - FilterTasksByGuestID( - guestID: guestID, - ), + width: width, + guestID: guestID, + guestStatus: status?.getQemuStatus(), + guestName: config?.name ?? 'VM $guestID', + guestNodeID: state.nodeID, + guestType: 'qemu', + ha: status?.ha, + template: status?.template ?? false, + ), + ProxmoxStreamBuilder( + bloc: taskBloc, + builder: (context, taskState) { + if (taskState.tasks.isNotEmpty) { + return PveTaskExpansionTile( + headerColor: + Theme.of(context).colorScheme.onSurfaceVariant, + task: taskState.tasks.first, + showMorePage: Provider( + create: (context) => PveTaskLogBloc( + apiClient: taskBloc.apiClient, + init: PveTaskLogState.init(state.nodeID), ) - ..events.add(LoadTasks()), - dispose: (context, bloc) => bloc.dispose(), - child: const PveTaskLog(), - ), - ); - } - return Container(); - }, - ), - SizedBox( - height: 130, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!(status?.template ?? false)) + ..events.add( + FilterTasksByGuestID( + guestID: guestID, + ), + ) + ..events.add(LoadTasks()), + dispose: (context, bloc) => bloc.dispose(), + child: const PveTaskLog(), + ), + ); + } + return Container(); + }, + ), + SizedBox( + height: 130, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!(status?.template ?? false)) + createActionCard( + 'Power Settings', + Icons.power_settings_new, + () => + showPowerMenuBottomSheet(context, bloc)), + if (!(status?.template ?? false)) + createActionCard( + 'Console', + Icons.queue_play_next, + () => showConsoleMenuBottomSheet( + context, + bloc.apiClient, + guestID, + state.nodeID, + 'qemu', + allowSpice: status?.spice ?? false, + )), createActionCard( - 'Power Settings', - Icons.power_settings_new, - () => showPowerMenuBottomSheet(context, bloc)), - if (!(status?.template ?? false)) + 'Options', + Icons.settings, + () => Navigator.of(context) + .push(_createOptionsRoute(bloc))), + if (!rBloc.latestState.isStandalone) + createActionCard( + 'Migrate', + FontAwesomeIcons.paperPlane, + () => Navigator.of(context).push( + _createMigrationRoute(guestID, + state.nodeID, bloc.apiClient))), createActionCard( - 'Console', - Icons.queue_play_next, - () => showConsoleMenuBottomSheet( - context, - bloc.apiClient, - guestID, - state.nodeID, - 'qemu', - allowSpice: status?.spice ?? false, - )), - createActionCard( - 'Options', - Icons.settings, - () => Navigator.of(context) - .push(_createOptionsRoute(bloc))), - if (!rBloc.latestState.isStandalone) - createActionCard( - 'Migrate', - FontAwesomeIcons.paperPlane, + 'Backup', + FontAwesomeIcons.floppyDisk, () => Navigator.of(context).push( - _createMigrationRoute(guestID, state.nodeID, + _createBackupRoute(guestID, state.nodeID, bloc.apiClient))), - createActionCard( - 'Backup', - FontAwesomeIcons.floppyDisk, - () => Navigator.of(context).push( - _createBackupRoute( - guestID, state.nodeID, bloc.apiClient))), - ], + ], + ), ), ), - ), - if (config != null) - PveResourceDataCardWidget( - expandable: false, - title: const Text( - 'Hardware', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, + if (config != null) + PveResourceDataCardWidget( + expandable: false, + title: const Text( + 'Hardware', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), - ), - children: [ - ListTile( - leading: const Icon(FontAwesomeIcons.memory), - title: Text('${config.memory}'), - subtitle: const Text('Memory'), - dense: true, - ), - ListTile( - leading: const Icon(Icons.memory), - title: Text( - '${config.cores} Cores ${config.sockets} Socket'), - subtitle: const Text('Processor'), - dense: true, - ), - ListTile( - leading: const Icon(FontAwesomeIcons.microchip), - title: - Text(config.bios?.name ?? 'Default (SeaBIOS)'), - subtitle: const Text('BIOS'), - dense: true, - ), - ListTile( - leading: const Icon(FontAwesomeIcons.gears), - dense: true, - title: Text(config.machine ?? 'Default (i440fx)'), - subtitle: const Text('Machine Type'), - ), - ListTile( - leading: const Icon(FontAwesomeIcons.database), - title: - Text(config.scsihw?.name ?? 'Default (i440fx)'), - subtitle: const Text('SCSI Controller'), - dense: true, - ), - for (var ide in config.ide!) + children: [ ListTile( - leading: const Icon(FontAwesomeIcons.compactDisc), - title: Text(ide), - subtitle: const Text('CD/DVD Drive'), + leading: const Icon(FontAwesomeIcons.memory), + title: Text('${config.memory}'), + subtitle: const Text('Memory'), dense: true, ), - for (var scsi in config.scsi!) ListTile( - leading: const Icon(FontAwesomeIcons.hardDrive), - title: Text(scsi), - subtitle: const Text('Hard Disk'), + leading: const Icon(Icons.memory), + title: Text( + '${config.cores} Cores ${config.sockets} Socket'), + subtitle: const Text('Processor'), dense: true, ), - for (var net in config.net!) ListTile( - leading: const Icon(FontAwesomeIcons.ethernet), + leading: const Icon(FontAwesomeIcons.microchip), + title: Text( + config.bios?.name ?? 'Default (SeaBIOS)'), + subtitle: const Text('BIOS'), dense: true, - subtitle: const Text('Network Device'), - title: Text(net), - ) - ]), - ], - )), - ),); + ), + ListTile( + leading: const Icon(FontAwesomeIcons.gears), + dense: true, + title: Text(config.machine ?? 'Default (i440fx)'), + subtitle: const Text('Machine Type'), + ), + ListTile( + leading: const Icon(FontAwesomeIcons.database), + title: Text( + config.scsihw?.name ?? 'Default (i440fx)'), + subtitle: const Text('SCSI Controller'), + dense: true, + ), + for (var ide in config.ide!) + ListTile( + leading: + const Icon(FontAwesomeIcons.compactDisc), + title: Text(ide), + subtitle: const Text('CD/DVD Drive'), + dense: true, + ), + for (var scsi in config.scsi!) + ListTile( + leading: const Icon(FontAwesomeIcons.hardDrive), + title: Text(scsi), + subtitle: const Text('Hard Disk'), + dense: true, + ), + for (var net in config.net!) + ListTile( + leading: const Icon(FontAwesomeIcons.ethernet), + dense: true, + subtitle: const Text('Network Device'), + title: Text(net), + ) + ]), + ], + )), + ), + ); }), ); } -- 2.47.3 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel