From: Folke Gleumes <f.gleumes@proxmox.com>
To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Subject: Re: [pve-devel] [PATCH pve-flutter-frontend v2] node overview: add power settings menu
Date: Wed, 17 Apr 2024 10:19:59 +0200 [thread overview]
Message-ID: <044ddf67407c9e2ef69e527b4d28c6685be96f6b.camel@proxmox.com> (raw)
In-Reply-To: <20240417064546.271430-1-d.csapak@proxmox.com>
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.csapak@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.gleumes@proxmox.com>
[0] https://m2.material.io/components/dialogs#actions
next prev parent reply other threads:[~2024-04-17 8:20 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-17 6:45 Dominik Csapak
2024-04-17 8:19 ` Folke Gleumes [this message]
2024-04-17 8:33 ` Dominik Csapak
2024-04-17 8:48 ` [pve-devel] applied: " Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=044ddf67407c9e2ef69e527b4d28c6685be96f6b.camel@proxmox.com \
--to=f.gleumes@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.