On Wed, 2024-04-17 at 08:45 +0200, Dominik Csapak wrote: > similar to how it works for qemu, but add a confirmation dialog > so one does not accidentally shutdown or reboot a node. > > Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> > --- > changes from v1: > * add an AlertDialog as confirmation before executing the action > > lib/bloc/pve_node_overview_bloc.dart | 11 +++ > lib/widgets/pve_node_overview.dart | 24 ++++++ > .../pve_node_power_settings_widget.dart | 84 > +++++++++++++++++++ > 3 files changed, 119 insertions(+) > create mode 100644 lib/widgets/pve_node_power_settings_widget.dart > > diff --git a/lib/bloc/pve_node_overview_bloc.dart > b/lib/bloc/pve_node_overview_bloc.dart > index d14ff79..19d6563 100644 > --- a/lib/bloc/pve_node_overview_bloc.dart > +++ b/lib/bloc/pve_node_overview_bloc.dart > @@ -57,9 +57,20 @@ class PveNodeOverviewBloc > final disks = await apiClient.getNodeDisksList(nodeID); > yield latestState.rebuild((b) => b..disks.replace(disks)); > } > + if (event is PerformNodeAction) { > + await apiClient.doResourceAction(nodeID, '', 'node', > event.action, > + parameters: <String, String>{}); > + yield latestState; > + } > } > } > > abstract class PveNodeOverviewEvent {} > > class UpdateNodeStatus extends PveNodeOverviewEvent {} > + > +class PerformNodeAction extends PveNodeOverviewEvent { > + final PveClusterResourceAction action; > + > + PerformNodeAction(this.action); > +} > diff --git a/lib/widgets/pve_node_overview.dart > b/lib/widgets/pve_node_overview.dart > index 7b65c0e..ad9a3b2 100644 > --- a/lib/widgets/pve_node_overview.dart > +++ b/lib/widgets/pve_node_overview.dart > @@ -8,6 +8,7 @@ import > 'package:pve_flutter_frontend/states/pve_node_overview_state.dart'; > import > 'package:pve_flutter_frontend/states/pve_task_log_state.dart'; > import 'package:pve_flutter_frontend/utils/renderers.dart'; > import 'package:pve_flutter_frontend/utils/utils.dart'; > +import > 'package:pve_flutter_frontend/widgets/pve_node_power_settings_widget. > dart'; > import > 'package:pve_flutter_frontend/widgets/proxmox_capacity_indicator.dart > '; > import > 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.d > art'; > import > 'package:pve_flutter_frontend/widgets/pve_action_card_widget.dart'; > @@ -189,6 +190,16 @@ class PveNodeOverview extends StatelessWidget { > 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, > @@ -443,4 +454,17 @@ class PveNodeOverview extends StatelessWidget { > }, > ); > } > + > + Future<T?> showPowerMenuBottomSheet<T>( > + BuildContext context, PveNodeOverviewBloc nodeBloc) async { > + return showModalBottomSheet( > + shape: const RoundedRectangleBorder( > + borderRadius: BorderRadius.vertical(top: > Radius.circular(10))), > + context: context, > + builder: (context) => Provider.value( > + value: nodeBloc, > + child: const PveNodePowerSettings(), > + ), > + ); > + } > } > diff --git a/lib/widgets/pve_node_power_settings_widget.dart > b/lib/widgets/pve_node_power_settings_widget.dart > new file mode 100644 > index 0000000..621ac68 > --- /dev/null > +++ b/lib/widgets/pve_node_power_settings_widget.dart > @@ -0,0 +1,84 @@ > +import 'package:flutter/material.dart'; > +import 'package:provider/provider.dart'; > +import > 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'; > +import > 'package:pve_flutter_frontend/bloc/pve_node_overview_bloc.dart'; > +import > 'package:pve_flutter_frontend/states/pve_node_overview_state.dart'; > +import > 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.d > art'; > + > +class PveNodePowerSettings extends StatelessWidget { > + const PveNodePowerSettings({ > + super.key, > + }); > + @override > + Widget build(BuildContext context) { > + final bloc = Provider.of<PveNodeOverviewBloc>(context); > + return ProxmoxStreamBuilder<PveNodeOverviewBloc, > PveNodeOverviewState>( > + bloc: bloc, > + builder: (context, state) { > + return SafeArea( > + child: SingleChildScrollView( > + child: Container( > + constraints: BoxConstraints( > + minHeight: MediaQuery.of(context).size.height / > 3), > + child: Column( > + mainAxisSize: MainAxisSize.min, > + children: <Widget>[ > + ListTile( > + leading: const Icon(Icons.autorenew), > + title: const Text( > + "Reboot", > + style: TextStyle(fontWeight: > FontWeight.bold), > + ), > + subtitle: const Text("Reboot Node"), > + onTap: () => action(context, > + PveClusterResourceAction.reboot, bloc, > "reboot"), > + ), > + ListTile( > + leading: const Icon(Icons.power_settings_new), > + title: const Text( > + "Shutdown", > + style: TextStyle(fontWeight: > FontWeight.bold), > + ), > + subtitle: const Text("Shutdown Node"), > + onTap: () => action(context, > + PveClusterResourceAction.shutdown, bloc, > "shutdown"), > + ), > + ], > + ), > + ), > + ), > + ); > + }); > + } > + > + void action(BuildContext context, PveClusterResourceAction action, > + PveNodeOverviewBloc bloc, String actionText) async { > + if (await showDialog( > + context: context, > + builder: (context) { > + return AlertDialog( > + contentPadding: const EdgeInsets.fromLTRB(24.0, 12.0, > 24.0, 16.0), > + title: const Row( > + mainAxisAlignment: MainAxisAlignment.spaceBetween, > + children: <Widget>[ > + Text('Confirm'), > + Icon(Icons.warning), > + ], > + ), > + content: Text( > + "Are you sure you want to $actionText node > '${bloc.nodeID}'?"), > + actions: [ > + TextButton( > + onPressed: () => Navigator.of(context).pop(true), > + child: const Text("Yes")), > + TextButton( > + onPressed: () => Navigator.of(context).pop(false), > + child: const Text("No")) > + ],
The order of buttons probably should be reversed. According to material guidelines a dismissive action has to be placed to the left of confirming actions [0]. In practice, I accidentally shut down my test VM because I intuitively went for the leftmost button to abort. To make the actions even more obvious, they could be phrased as Cancel and Shutdown/Reboot. > + ); > + })) { > + bloc.events.add(PerformNodeAction(action)); > + if (context.mounted) Navigator.of(context).pop(); > + } > + } > +} Besides the button ordering, everything worked well, so if those are changed, consider this: Tested-by: Folke Gleumes <f.gleu...@proxmox.com> [0] https://m2.material.io/components/dialogs#actions _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel