The following commit `f263217` [0] skipped running the `dart format` command on changed files to avoid missing the changes. To fix the issue ran the command again on master.
[0] - https://lore.proxmox.com/pve-devel/20250804110634.64816-1-s.sh...@proxmox.com/T/#t Signed-off-by: Shan Shaji <s.sh...@proxmox.com> --- lib/widgets/pve_lxc_options_widget.dart | 138 ++--- lib/widgets/pve_lxc_overview.dart | 325 +++++----- lib/widgets/pve_node_overview.dart | 724 +++++++++++------------ lib/widgets/pve_qemu_options_widget.dart | 256 ++++---- lib/widgets/pve_qemu_overview.dart | 335 +++++------ lib/widgets/pve_task_log_widget.dart | 228 +++---- 6 files changed, 997 insertions(+), 1009 deletions(-) diff --git a/lib/widgets/pve_lxc_options_widget.dart b/lib/widgets/pve_lxc_options_widget.dart index 7ad0224..5b6e009 100644 --- a/lib/widgets/pve_lxc_options_widget.dart +++ b/lib/widgets/pve_lxc_options_widget.dart @@ -16,76 +16,76 @@ class PveLxcOptions extends StatelessWidget { final config = state.config; if (config != null) { return Scaffold( - appBar: AppBar(), - body: SingleChildScrollView( - child: Column( - children: <Widget>[ - ListTile( - title: const Text("Name"), - subtitle: Text(config.hostname ?? 'undefined'), - ), - PveConfigSwitchListTile( - title: const Text("Start on boot"), - value: config.onboot, - defaultValue: false, - pending: config.getPending('onboot'), - onChanged: (v) => lxcBloc!.events - .add(UpdateLxcConfigBool('onboot', v)), - onDeleted: () => lxcBloc!.events - .add(RevertPendingLxcConfig('onboot')), - ), - ListTile( - title: const Text("Start/Shutdown order"), - subtitle: Text(config.startup ?? "Default (any)"), - ), - ListTile( - title: const Text("OS Type"), - subtitle: Text("${config.ostype}"), - ), - ListTile( - title: const Text("Architecture"), - subtitle: Text("${config.arch}"), - ), - PveConfigSwitchListTile( - title: const Text("/dev/console"), - value: config.console, - defaultValue: true, - pending: config.getPending('console'), - onChanged: (v) => lxcBloc!.events - .add(UpdateLxcConfigBool('console', v)), - onDeleted: () => lxcBloc!.events - .add(RevertPendingLxcConfig('console')), - ), - ListTile( - title: const Text("TTY Count"), - subtitle: Text("${config.tty ?? 2}"), - ), - ListTile( - title: const Text("Console Mode"), - subtitle: Text(config.cmode?.name ?? 'tty'), - ), - PveConfigSwitchListTile( - title: const Text("Protection"), - value: config.protection, - defaultValue: false, - pending: config.getPending('protection'), - onChanged: (v) => lxcBloc!.events - .add(UpdateLxcConfigBool('protection', v)), - onDeleted: () => lxcBloc!.events - .add(RevertPendingLxcConfig('protection')), - ), - ListTile( - title: const Text("Unprivileged"), - subtitle: - Text(config.unprivileged ?? false ? 'Yes' : 'No'), - ), - ListTile( - title: const Text("Features"), - subtitle: Text(config.features?.toString() ?? 'none'), - ), - ], - ), + appBar: AppBar(), + body: SingleChildScrollView( + child: Column( + children: <Widget>[ + ListTile( + title: const Text("Name"), + subtitle: Text(config.hostname ?? 'undefined'), + ), + PveConfigSwitchListTile( + title: const Text("Start on boot"), + value: config.onboot, + defaultValue: false, + pending: config.getPending('onboot'), + onChanged: (v) => + lxcBloc!.events.add(UpdateLxcConfigBool('onboot', v)), + onDeleted: () => + lxcBloc!.events.add(RevertPendingLxcConfig('onboot')), + ), + ListTile( + title: const Text("Start/Shutdown order"), + subtitle: Text(config.startup ?? "Default (any)"), + ), + ListTile( + title: const Text("OS Type"), + subtitle: Text("${config.ostype}"), + ), + ListTile( + title: const Text("Architecture"), + subtitle: Text("${config.arch}"), + ), + PveConfigSwitchListTile( + title: const Text("/dev/console"), + value: config.console, + defaultValue: true, + pending: config.getPending('console'), + onChanged: (v) => lxcBloc!.events + .add(UpdateLxcConfigBool('console', v)), + onDeleted: () => lxcBloc!.events + .add(RevertPendingLxcConfig('console')), + ), + ListTile( + title: const Text("TTY Count"), + subtitle: Text("${config.tty ?? 2}"), + ), + ListTile( + title: const Text("Console Mode"), + subtitle: Text(config.cmode?.name ?? 'tty'), + ), + PveConfigSwitchListTile( + title: const Text("Protection"), + value: config.protection, + defaultValue: false, + pending: config.getPending('protection'), + onChanged: (v) => lxcBloc!.events + .add(UpdateLxcConfigBool('protection', v)), + onDeleted: () => lxcBloc!.events + .add(RevertPendingLxcConfig('protection')), + ), + ListTile( + title: const Text("Unprivileged"), + subtitle: + Text(config.unprivileged ?? false ? 'Yes' : 'No'), + ), + ListTile( + title: const Text("Features"), + subtitle: Text(config.features?.toString() ?? 'none'), + ), + ], ), + ), ); } return const Center( diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart index fe43a26..1148452 100644 --- a/lib/widgets/pve_lxc_overview.dart +++ b/lib/widgets/pve_lxc_overview.dart @@ -71,194 +71,193 @@ class PveLxcOverview extends StatelessWidget { final status = state.currentStatus; final config = state.config; return Scaffold( - appBar: PveAppBar( - title: Text(config?.hostname ?? 'CT $guestID'), - ), - 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, - ), + appBar: PveAppBar( + title: Text(config?.hostname ?? 'CT $guestID'), + ), + 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, ), ), - 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)) - 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')), + ..events.add(LoadTasks()), + dispose: (context, bloc) => bloc.dispose(), + child: const PveTaskLog(), + ), + ); + } + return Container(); + }, + ), + SizedBox( + height: 130, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + if (!(status?.template ?? false)) + createActionCard( + 'Power Settings', + Icons.power_settings_new, + () => showPowerMenuBottomSheet( + context, lxcBloc)), + if (!(status?.template ?? false)) createActionCard( - '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!))), + '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( - 'Backup', - FontAwesomeIcons.floppyDisk, + 'Migrate', + FontAwesomeIcons.paperPlane, () => Navigator.of(context).push( - _createBackupRoute(guestID, state.nodeID, + _createMigrationRoute( + guestID, + state.nodeID, resourceBloc.apiClient!))), - ], + createActionCard( + 'Backup', + FontAwesomeIcons.floppyDisk, + () => Navigator.of(context).push( + _createBackupRoute(guestID, state.nodeID, + resourceBloc.apiClient!))), + ], + ), + ), + ), + if (config != null) ...[ + PveResourceDataCardWidget( + title: const Text( + 'Resources', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, ), ), + children: <Widget>[ + ListTile( + leading: const Icon(FontAwesomeIcons.memory), + title: Text('${config.memory}'), + subtitle: const Text('Memory'), + dense: true, + ), + ListTile( + leading: const Icon(Icons.cached), + title: Text('${config.swap}'), + subtitle: const Text('Swap'), + dense: true, + ), + ListTile( + leading: const Icon(Icons.memory), + title: Text('${config.cores ?? 'unlimited'}'), + subtitle: const Text('Cores'), + dense: true, + ), + ListTile( + leading: const Icon(FontAwesomeIcons.hardDrive), + dense: true, + title: Text('${config.rootfs}'), + ), + ], ), - if (config != null) ...[ - PveResourceDataCardWidget( + PveResourceDataCardWidget( title: const Text( - 'Resources', + 'Network', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, ), ), children: <Widget>[ - ListTile( - leading: const Icon(FontAwesomeIcons.memory), - title: Text('${config.memory}'), - subtitle: const Text('Memory'), - dense: true, - ), - ListTile( - leading: const Icon(Icons.cached), - title: Text('${config.swap}'), - subtitle: const Text('Swap'), - dense: true, + 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, ), + ), + children: <Widget>[ ListTile( - leading: const Icon(Icons.memory), - title: Text('${config.cores ?? 'unlimited'}'), - subtitle: const Text('Cores'), + leading: const Icon(FontAwesomeIcons.globe), dense: true, + title: Text( + config.searchdomain ?? 'Use host settings'), + subtitle: const Text('DNS Domain'), ), ListTile( - leading: const Icon(FontAwesomeIcons.hardDrive), + leading: + const Icon(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 Icon(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(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 cc58df9..f99ca53 100644 --- a/lib/widgets/pve_node_overview.dart +++ b/lib/widgets/pve_node_overview.dart @@ -51,401 +51,397 @@ class PveNodeOverview extends StatelessWidget { final fgColor = Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.75); return Scaffold( - appBar: PveAppBar( - title: Text( - "Node $nodeID", - style: - const TextStyle(fontWeight: FontWeight.bold, fontSize: 25), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + appBar: PveAppBar( + title: Text( + "Node $nodeID", + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 25), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - 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)} %', - ), + ), + 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)} %', ), - 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 == 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 == 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: 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), + 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), ), ), - ], - ], - ), - 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: Icon(FontAwesomeIcons.solidHardDrive, - color: state.isDiskHealthy(d) - ? Colors.grey - : Colors.red), - title: - Text('${d.type!.toUpperCase()}: ${d.devPath}'), - subtitle: Text( - 'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'), - trailing: IconButton( - icon: const Icon( - Icons.info, - ), - onPressed: () => showDialog( - context: context, - builder: (context) => SimpleDialog( - title: const Text('Details'), - contentPadding: const EdgeInsets.all(24), - children: <Widget>[ - ListTile( - title: const Text("Device"), - subtitle: Text(d.devPath!), - ), - ListTile( - title: const Text("Type"), - subtitle: Text(d.type!), - ), - ListTile( - title: const Text("Usage"), - subtitle: Text(d.used!), - ), - ListTile( - title: const Text("GPT"), - subtitle: Text(d.gpt.toString()), - ), - ListTile( - title: const Text("Model"), - subtitle: Text(d.model!), - ), - ListTile( - title: const Text("Serial"), - subtitle: Text(d.serial!), - ), - ListTile( - title: const Text("S.M.A.R.T"), - subtitle: Text(d.health!), - ), - ListTile( - title: const Text("Wearout"), - subtitle: Text(d.wearoutPercentage), - ) - ], - ), + ), + showTitleTrailing: !state.allDisksHealthy, + titleTrailing: const Icon(Icons.warning), + subtitle: state.allDisksHealthy + ? const Text('No health issues') + : const Text('Check disks, health error indicated!'), + children: state.disks + .map( + (d) => ListTile( + dense: true, + leading: Icon(FontAwesomeIcons.solidHardDrive, + color: state.isDiskHealthy(d) + ? Colors.grey + : Colors.red), + title: Text('${d.type!.toUpperCase()}: ${d.devPath}'), + subtitle: Text( + 'Usage: ${d.used} ${Renderers.formatSize(d.size ?? 0)}'), + trailing: IconButton( + icon: const Icon( + Icons.info, + ), + onPressed: () => showDialog( + context: context, + builder: (context) => SimpleDialog( + title: const Text('Details'), + contentPadding: const EdgeInsets.all(24), + children: <Widget>[ + ListTile( + title: const Text("Device"), + subtitle: Text(d.devPath!), + ), + ListTile( + title: const Text("Type"), + subtitle: Text(d.type!), + ), + ListTile( + title: const Text("Usage"), + subtitle: Text(d.used!), + ), + ListTile( + title: const Text("GPT"), + subtitle: Text(d.gpt.toString()), + ), + ListTile( + title: const Text("Model"), + subtitle: Text(d.model!), + ), + ListTile( + title: const Text("Serial"), + subtitle: Text(d.serial!), + ), + ListTile( + title: const Text("S.M.A.R.T"), + subtitle: Text(d.health!), + ), + ListTile( + title: const Text("Wearout"), + subtitle: Text(d.wearoutPercentage), + ) + ], ), ), ), - ) - .toList() - ..sort((a, b) => (a.title as Text) - .data! - .compareTo((b.title as Text).data!)), - ), - ], - ), + ), + ) + .toList() + ..sort((a, b) => (a.title as Text) + .data! + .compareTo((b.title as Text).data!)), + ), + ], ), + ), ); }, ); diff --git a/lib/widgets/pve_qemu_options_widget.dart b/lib/widgets/pve_qemu_options_widget.dart index 7ed0a3e..992a382 100644 --- a/lib/widgets/pve_qemu_options_widget.dart +++ b/lib/widgets/pve_qemu_options_widget.dart @@ -19,137 +19,137 @@ class PveQemuOptions extends StatelessWidget { if (state.config != null) { final config = state.config!; return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), ), - body: SingleChildScrollView( - child: Form( - key: _formKey, - onChanged: () {}, - child: Column( - children: <Widget>[ - ListTile( - title: const Text("Name"), - subtitle: Text(config.name ?? 'VM$guestID'), - ), - PveConfigSwitchListTile( - title: const Text("Start on boot"), - value: config.onboot, - defaultValue: false, - pending: config.getPending('onboot'), - onChanged: (v) => bloc.events - .add(UpdateQemuConfigBool('onboot', v)), - onDeleted: () => bloc.events - .add(RevertPendingQemuConfig('onboot')), - ), - ListTile( - title: const Text("Start/Shutdown order"), - subtitle: Text(config.startup ?? "Default (any)"), - ), - ListTile( - title: const Text("OS Type"), - subtitle: Text(config.ostype != null - ? "${config.ostype!.type} ${config.ostype!.description}" - : "Other"), - ), - //TODO add better ui component e.g. collapseable - ListTile( - title: const Text("Boot Device"), - subtitle: Text(config.boot ?? 'Disk, Network, USB'), - ), - PveConfigSwitchListTile( - title: const Text("Use tablet for pointer"), - value: config.tablet, - defaultValue: true, - pending: config.getPending('tablet'), - onChanged: (v) => bloc.events - .add(UpdateQemuConfigBool('tablet', v)), - onDeleted: () => bloc.events - .add(RevertPendingQemuConfig('tablet')), - ), - ListTile( - title: const Text("Hotplug"), - subtitle: Text(config.hotplug ?? 'disk,network,usb'), - ), - PveConfigSwitchListTile( - title: const Text("ACPI support"), - value: config.acpi, - defaultValue: true, - pending: config.getPending('acpi'), - onChanged: (v) => - bloc.events.add(UpdateQemuConfigBool('acpi', v)), - onDeleted: () => - bloc.events.add(RevertPendingQemuConfig('acpi')), - ), - PveConfigSwitchListTile( - title: const Text("KVM hardware virtualization"), - value: config.kvm, - defaultValue: true, - pending: config.getPending('kvm'), - onChanged: (v) => - bloc.events.add(UpdateQemuConfigBool('kvm', v)), - onDeleted: () => - bloc.events.add(RevertPendingQemuConfig('kvm')), - ), - PveConfigSwitchListTile( - title: const Text("Freeze CPU on startup"), - value: config.freeze, - defaultValue: false, - pending: config.getPending('freeze'), - onChanged: (v) => bloc.events - .add(UpdateQemuConfigBool('freeze', v)), - onDeleted: () => bloc.events - .add(RevertPendingQemuConfig('freeze')), - ), - PveConfigSwitchListTile( - title: const Text("Use local time for RTC"), - value: config.localtime, - defaultValue: false, - pending: config.getPending('localtime'), - onChanged: (v) => bloc.events - .add(UpdateQemuConfigBool('localtime', v)), - onDeleted: () => bloc.events - .add(RevertPendingQemuConfig('localtime')), - ), - ListTile( - title: const Text("RTC start date"), - subtitle: Text(config.startdate ?? 'now'), - ), - ListTile( - title: const Text("SMBIOS settings (type1)"), - subtitle: Text(config.smbios1 ?? ''), - ), - //Todo enhance UI - ListTile( - title: const Text("QEMU Guest Agent"), - subtitle: Text(config.agent ?? 'Default (disabled)'), - ), - PveConfigSwitchListTile( - title: const Text("Protection"), - value: config.protection, - defaultValue: false, - pending: config.getPending('protection'), - onChanged: (v) => bloc.events - .add(UpdateQemuConfigBool('protection', v)), - onDeleted: () => bloc.events - .add(RevertPendingQemuConfig('protection')), - ), - ListTile( - title: const Text("Spice Enhancements"), - subtitle: Text( - config.spiceEnhancements ?? 'No enhancements'), - ), - ListTile( - title: const Text("VM State Storage"), - subtitle: Text(config.vmstatestorage ?? 'Automatic'), - ), - ], - ), + ), + body: SingleChildScrollView( + child: Form( + key: _formKey, + onChanged: () {}, + child: Column( + children: <Widget>[ + ListTile( + title: const Text("Name"), + subtitle: Text(config.name ?? 'VM$guestID'), + ), + PveConfigSwitchListTile( + title: const Text("Start on boot"), + value: config.onboot, + defaultValue: false, + pending: config.getPending('onboot'), + onChanged: (v) => + bloc.events.add(UpdateQemuConfigBool('onboot', v)), + onDeleted: () => + bloc.events.add(RevertPendingQemuConfig('onboot')), + ), + ListTile( + title: const Text("Start/Shutdown order"), + subtitle: Text(config.startup ?? "Default (any)"), + ), + ListTile( + title: const Text("OS Type"), + subtitle: Text(config.ostype != null + ? "${config.ostype!.type} ${config.ostype!.description}" + : "Other"), + ), + //TODO add better ui component e.g. collapseable + ListTile( + title: const Text("Boot Device"), + subtitle: Text(config.boot ?? 'Disk, Network, USB'), + ), + PveConfigSwitchListTile( + title: const Text("Use tablet for pointer"), + value: config.tablet, + defaultValue: true, + pending: config.getPending('tablet'), + onChanged: (v) => + bloc.events.add(UpdateQemuConfigBool('tablet', v)), + onDeleted: () => + bloc.events.add(RevertPendingQemuConfig('tablet')), + ), + ListTile( + title: const Text("Hotplug"), + subtitle: Text(config.hotplug ?? 'disk,network,usb'), + ), + PveConfigSwitchListTile( + title: const Text("ACPI support"), + value: config.acpi, + defaultValue: true, + pending: config.getPending('acpi'), + onChanged: (v) => + bloc.events.add(UpdateQemuConfigBool('acpi', v)), + onDeleted: () => + bloc.events.add(RevertPendingQemuConfig('acpi')), + ), + PveConfigSwitchListTile( + title: const Text("KVM hardware virtualization"), + value: config.kvm, + defaultValue: true, + pending: config.getPending('kvm'), + onChanged: (v) => + bloc.events.add(UpdateQemuConfigBool('kvm', v)), + onDeleted: () => + bloc.events.add(RevertPendingQemuConfig('kvm')), + ), + PveConfigSwitchListTile( + title: const Text("Freeze CPU on startup"), + value: config.freeze, + defaultValue: false, + pending: config.getPending('freeze'), + onChanged: (v) => + bloc.events.add(UpdateQemuConfigBool('freeze', v)), + onDeleted: () => + bloc.events.add(RevertPendingQemuConfig('freeze')), + ), + PveConfigSwitchListTile( + title: const Text("Use local time for RTC"), + value: config.localtime, + defaultValue: false, + pending: config.getPending('localtime'), + onChanged: (v) => bloc.events + .add(UpdateQemuConfigBool('localtime', v)), + onDeleted: () => bloc.events + .add(RevertPendingQemuConfig('localtime')), + ), + ListTile( + title: const Text("RTC start date"), + subtitle: Text(config.startdate ?? 'now'), + ), + ListTile( + title: const Text("SMBIOS settings (type1)"), + subtitle: Text(config.smbios1 ?? ''), + ), + //Todo enhance UI + ListTile( + title: const Text("QEMU Guest Agent"), + subtitle: Text(config.agent ?? 'Default (disabled)'), + ), + PveConfigSwitchListTile( + title: const Text("Protection"), + value: config.protection, + defaultValue: false, + pending: config.getPending('protection'), + onChanged: (v) => bloc.events + .add(UpdateQemuConfigBool('protection', v)), + onDeleted: () => bloc.events + .add(RevertPendingQemuConfig('protection')), + ), + ListTile( + title: const Text("Spice Enhancements"), + subtitle: + Text(config.spiceEnhancements ?? 'No enhancements'), + ), + ListTile( + title: const Text("VM State Storage"), + subtitle: Text(config.vmstatestorage ?? 'Automatic'), + ), + ], ), ), + ), ); } return const Center( diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart index 50f7382..15ab409 100644 --- a/lib/widgets/pve_qemu_overview.dart +++ b/lib/widgets/pve_qemu_overview.dart @@ -72,183 +72,176 @@ class PveQemuOverview extends StatelessWidget { final rrdData = state.rrdData; return Scaffold( - appBar: PveAppBar( - title: Text(config?.name ?? 'VM $guestID'), + appBar: PveAppBar( + title: Text(config?.name ?? 'VM $guestID'), + ), + 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, + ), + ), + ), + width: width, + guestID: guestID, + guestStatus: status?.getQemuStatus(), + guestName: config?.name ?? 'VM $guestID', + guestNodeID: state.nodeID, + guestType: 'qemu', + ha: status?.ha, + template: status?.template ?? false, ), - 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, - ), + ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>( + bloc: taskBloc, + builder: (context, taskState) { + if (taskState.tasks.isNotEmpty) { + return PveTaskExpansionTile( + headerColor: + Theme.of(context).colorScheme.onSurfaceVariant, + task: taskState.tasks.first, + showMorePage: Provider<PveTaskLogBloc>( + create: (context) => PveTaskLogBloc( + apiClient: taskBloc.apiClient, + init: PveTaskLogState.init(state.nodeID), + ) + ..events.add( + FilterTasksByGuestID( + guestID: guestID, ), - ), - width: width, - guestID: guestID, - guestStatus: status?.getQemuStatus(), - guestName: config?.name ?? 'VM $guestID', - guestNodeID: state.nodeID, - guestType: 'qemu', - ha: status?.ha, - template: status?.template ?? false, - ), - ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>( - bloc: taskBloc, - builder: (context, taskState) { - if (taskState.tasks.isNotEmpty) { - return PveTaskExpansionTile( - headerColor: Theme.of(context) - .colorScheme - .onSurfaceVariant, - task: taskState.tasks.first, - showMorePage: Provider<PveTaskLogBloc>( - create: (context) => PveTaskLogBloc( - apiClient: taskBloc.apiClient, - init: PveTaskLogState.init(state.nodeID), - ) - ..events.add( - FilterTasksByGuestID( - guestID: guestID, - ), - ) - ..events.add(LoadTasks()), - dispose: (context, bloc) => bloc.dispose(), - child: const PveTaskLog(), - ), - ); - } - return Container(); - }, + ) + ..events.add(LoadTasks()), + dispose: (context, bloc) => bloc.dispose(), + child: const PveTaskLog(), + ), + ); + } + return Container(); + }, + ), + SizedBox( + height: 130, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + if (!(status?.template ?? false)) + createActionCard( + 'Power Settings', + Icons.power_settings_new, + () => showPowerMenuBottomSheet(context, bloc)), + if (!(status?.template ?? false)) + createActionCard( + 'Console', + Icons.queue_play_next, + () => showConsoleMenuBottomSheet( + context, + bloc.apiClient, + guestID, + state.nodeID, + 'qemu', + allowSpice: status?.spice ?? false, + )), + createActionCard( + '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( + 'Backup', + FontAwesomeIcons.floppyDisk, + () => Navigator.of(context).push( + _createBackupRoute( + guestID, state.nodeID, bloc.apiClient))), + ], ), - SizedBox( - height: 130, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: <Widget>[ - if (!(status?.template ?? false)) - createActionCard( - 'Power Settings', - Icons.power_settings_new, - () => showPowerMenuBottomSheet( - context, bloc)), - if (!(status?.template ?? false)) - createActionCard( - 'Console', - Icons.queue_play_next, - () => showConsoleMenuBottomSheet( - context, - bloc.apiClient, - guestID, - state.nodeID, - 'qemu', - allowSpice: status?.spice ?? false, - )), - createActionCard( - '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( - '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!) + ListTile( + leading: const Icon(FontAwesomeIcons.compactDisc), + title: Text(ide), + subtitle: const Text('CD/DVD Drive'), + dense: true, ), - 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!) - 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), - ) - ]), - ], - )), + 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), + ) + ]), + ], + )), ); }), ); diff --git a/lib/widgets/pve_task_log_widget.dart b/lib/widgets/pve_task_log_widget.dart index 7efb522..292e499 100644 --- a/lib/widgets/pve_task_log_widget.dart +++ b/lib/widgets/pve_task_log_widget.dart @@ -37,126 +37,126 @@ class _PveTaskLogState extends State<PveTaskLog> { bloc: bloc, builder: (context, state) { return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), - actions: <Widget>[ - IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () => _scaffoldKey.currentState?.openEndDrawer(), - ) - ], + key: _scaffoldKey, + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), ), - endDrawer: Drawer( + actions: <Widget>[ + IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () => _scaffoldKey.currentState?.openEndDrawer(), + ) + ], + ), + endDrawer: Drawer( child: SafeArea( - child: Padding( - padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: <Widget>[ - Text( - 'Filters', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox( - height: 20, - ), - TextFormField( - decoration: const InputDecoration( - labelText: 'by user', - filled: true, - prefixIcon: Icon(Icons.person)), - onChanged: (newValue) { - bloc.events.add(FilterTasksByUser(newValue)); - bloc.events.add(LoadTasks()); - }, - controller: _userFilterController, - ), - const SizedBox( - height: 20, - ), - TextFormField( - decoration: const InputDecoration( - labelText: 'by type', - filled: true, - prefixIcon: Icon(Icons.description)), - onChanged: (newValue) { - bloc.events.add(FilterTasksByType(newValue)); - bloc.events.add(LoadTasks()); - }, - controller: _typeFilterController, - ), - const SizedBox( - height: 20, - ), - DropdownButtonFormField<String>( - decoration: const InputDecoration(labelText: 'Source'), - value: state.source, - icon: const Icon(Icons.arrow_downward), - iconSize: 24, - elevation: 16, - onChanged: (String? newValue) { - bloc.events.add(FilterTasksBySource(newValue)); - bloc.events.add(LoadTasks()); - }, - items: <String>[ - 'all', - 'active', - 'archive', - ].map<DropdownMenuItem<String>>((String value) { - return DropdownMenuItem<String>( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox( - height: 20, + child: Padding( + padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + Text( + 'Filters', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox( + height: 20, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'by user', + filled: true, + prefixIcon: Icon(Icons.person)), + onChanged: (newValue) { + bloc.events.add(FilterTasksByUser(newValue)); + bloc.events.add(LoadTasks()); + }, + controller: _userFilterController, + ), + const SizedBox( + height: 20, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'by type', + filled: true, + prefixIcon: Icon(Icons.description)), + onChanged: (newValue) { + bloc.events.add(FilterTasksByType(newValue)); + bloc.events.add(LoadTasks()); + }, + controller: _typeFilterController, + ), + const SizedBox( + height: 20, + ), + DropdownButtonFormField<String>( + decoration: const InputDecoration(labelText: 'Source'), + value: state.source, + icon: const Icon(Icons.arrow_downward), + iconSize: 24, + elevation: 16, + onChanged: (String? newValue) { + bloc.events.add(FilterTasksBySource(newValue)); + bloc.events.add(LoadTasks()); + }, + items: <String>[ + 'all', + 'active', + 'archive', + ].map<DropdownMenuItem<String>>((String value) { + return DropdownMenuItem<String>( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox( + height: 20, + ), + FormField( + builder: (FormFieldState<bool> formFieldState) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + const Text("Only errors"), + Checkbox( + value: state.onlyErrors, + onChanged: (value) { + formFieldState.didChange(value); + bloc.events.add(FilterTasksByError()); + bloc.events.add(LoadTasks()); + }, + ), + ], ), - FormField( - builder: (FormFieldState<bool> formFieldState) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: <Widget>[ - const Text("Only errors"), - Checkbox( - value: state.onlyErrors, - onChanged: (value) { - formFieldState.didChange(value); - bloc.events.add(FilterTasksByError()); - bloc.events.add(LoadTasks()); - }, - ), - ], - ), - ) - ], - ), + ) + ], ), - )), - body: NotificationListener<ScrollNotification>( - onNotification: (ScrollNotification scrollInfo) { - if (scrollInfo.metrics.pixels >= - (0.8 * scrollInfo.metrics.maxScrollExtent)) { - if (!state.isLoading) { - bloc.events.add(LoadMoreTasks()); - } + ), + )), + body: NotificationListener<ScrollNotification>( + onNotification: (ScrollNotification scrollInfo) { + if (scrollInfo.metrics.pixels >= + (0.8 * scrollInfo.metrics.maxScrollExtent)) { + if (!state.isLoading) { + bloc.events.add(LoadMoreTasks()); } - return false; - }, - child: state.tasks.isNotEmpty - ? ListView.builder( - itemCount: state.tasks.length, - itemBuilder: (context, index) => PveTaskExpansionTile( - task: state.tasks[index], - ), - ) - : const Center( - child: Text("No tasks found"), + } + return false; + }, + child: state.tasks.isNotEmpty + ? ListView.builder( + itemCount: state.tasks.length, + itemBuilder: (context, index) => PveTaskExpansionTile( + task: state.tasks[index], ), - ), + ) + : const Center( + child: Text("No tasks found"), + ), + ), ); }); } -- 2.50.1 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel