From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 2B7259757F for ; Wed, 17 Apr 2024 08:46:19 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0BAFE36898 for ; Wed, 17 Apr 2024 08:45:49 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 17 Apr 2024 08:45:47 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id C42D746009 for ; Wed, 17 Apr 2024 08:45:47 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Date: Wed, 17 Apr 2024 08:45:46 +0200 Message-Id: <20240417064546.271430-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.014 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 Subject: [pve-devel] [PATCH pve-flutter-frontend v2] node overview: add power settings menu 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: , X-List-Received-Date: Wed, 17 Apr 2024 06:46:19 -0000 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 --- 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: {}); + 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.dart'; 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: [ + 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 showPowerMenuBottomSheet( + 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.dart'; + +class PveNodePowerSettings extends StatelessWidget { + const PveNodePowerSettings({ + super.key, + }); + @override + Widget build(BuildContext context) { + final bloc = Provider.of(context); + return ProxmoxStreamBuilder( + 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: [ + 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: [ + 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")) + ], + ); + })) { + bloc.events.add(PerformNodeAction(action)); + if (context.mounted) Navigator.of(context).pop(); + } + } +} -- 2.39.2