[pve-devel] [PATCH flutter_frontend 1/1] fix #4976: ui: Nodes offline
Michael Köppl
m.koeppl at proxmox.com
Tue Jun 24 14:53:46 CEST 2025
On 5/2/25 13:32, Michael Köppl wrote:
> On 4/29/25 15:16, Alexander Abraham wrote:
>> diff --git a/lib/bloc/pve_node_overview_bloc.dart b/lib/bloc/
>> pve_node_overview_bloc.dart
>> index 19d6563..98005fd 100644
>> --- a/lib/bloc/pve_node_overview_bloc.dart
>> +++ b/lib/bloc/pve_node_overview_bloc.dart
>> @@ -1,5 +1,5 @@
>> import 'dart:async';
>> -
>> +import 'package:flutter/material.dart';
>> import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
>> import 'package:pve_flutter_frontend/bloc/proxmox_base_bloc.dart';
>> import 'package:pve_flutter_frontend/states/
>> pve_node_overview_state.dart';
>> @@ -8,12 +8,13 @@ class PveNodeOverviewBloc
>> extends ProxmoxBaseBloc<PveNodeOverviewEvent,
>> PveNodeOverviewState> {
>> final ProxmoxApiClient apiClient;
>> final String nodeID;
>> + final BuildContext context;
>> final PveNodeOverviewState init;
>> @override
>> PveNodeOverviewState get initialState => init;
>> PveNodeOverviewBloc(
>> - {required this.apiClient, required this.nodeID, required
>> this.init});
>> + {required this.apiClient, required this.nodeID, required
>> this.init, required this.context});
>> Timer? sTimer;
>> @override
>> @@ -32,36 +33,56 @@ class PveNodeOverviewBloc
>> @override
>> Stream<PveNodeOverviewState> processEvents(
>> PveNodeOverviewEvent event) async* {
>> - if (event is UpdateNodeStatus) {
>> - final status = await apiClient.getNodeStatus(nodeID);
>> - yield latestState.rebuild((b) => b..status.replace(status));
>> - final rrdData =
>> - await apiClient.getNodeRRDdata(nodeID,
>> PveRRDTimeframeType.hour);
>> - yield latestState.rebuild((b) => b..rrdData.replace(rrdData));
>> - final services = await apiClient.getNodeServices(nodeID);
>> - yield latestState.rebuild((b) => b..services.replace(services));
>> - try {
>> - final updates = await apiClient.getNodeAptUpdate(nodeID);
>> - yield latestState.rebuild((b) => b..updates.replace(updates));
>> - yield latestState
>> - .rebuild((b) => b..updatesQueryPermissionFailure = false);
>> - } on ProxmoxApiException catch (e) {
>> - // only throw on non permission related errors
>> - if (e.statusCode != 403) {
>> - rethrow;
>> - } else {
>> + PveNodeStatusModel? status;
>> + try {
>> + status = await apiClient.getNodeStatus(nodeID);
>> + if (event is UpdateNodeStatus) {
>> + yield latestState.rebuild((b) => b..status.replace(status!));
>> + final rrdData =
>> + await apiClient.getNodeRRDdata(nodeID,
>> PveRRDTimeframeType.hour);
>> + yield latestState.rebuild((b) => b..rrdData.replace(rrdData));
>> + final services = await apiClient.getNodeServices(nodeID);
>> + yield latestState.rebuild((b) => b..services.replace(services));
>> + try {
>> + final updates = await apiClient.getNodeAptUpdate(nodeID);
>> + yield latestState.rebuild((b) => b..updates.replace(updates));
>> yield latestState
>> - .rebuild((b) => b..updatesQueryPermissionFailure = true);
>> + .rebuild((b) => b..updatesQueryPermissionFailure = false);
>> + }
>> + on ProxmoxApiException catch (e) {
>> + // only throw on non permission related errors
>> + if (e.statusCode != 403) {
>> + rethrow;
>> + }
>> + else {
>> + yield latestState
>> + .rebuild((b) => b..updatesQueryPermissionFailure =
>> true);
>> + }
>> + }
>> + 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;
>> }
>> }
>> - 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;
>> - }
>> + catch(e){
>> + yield latestState.rebuild(
>> + (b) => b
>> + ..updatesQueryPermissionFailure = true
>> + );
>> + if (context.mounted){
>> + Navigator.of(context).pop();
>
> Instead of navigating back immediately, I think it would make sense to
> instead display an AlertDialog, showing the error message to the user
> and letting them leave the screen through a "Close" button in the
> dialog. Otherwise, it feels a bit like the app is taking control away
> from the user. We use this approach in other places in the app (e.g. .
> What do you think?
Adding to this, I think you could use something similar to what the
PveGuestBackupWidget is using:
Future<bool?> _showConfirmDialog(
BuildContext context, String title, String body) async {
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(body),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text(
'Cancel',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
'Confirm',
style: TextStyle(fontWeight: FontWeight.bold),
),
)
],
),
);
}
You could then wrap the Navigator.of(context).pop() like this:
final confirm = (await _showConfirmDialog(
context,
"Node offline",
"Node is offline. Do you want to navigate back?",
))!;
if (confirm && context.mounted) {
Navigator.of(context).pop();
}
I think it would also make sense to have a utils method for opening an
AlertDialog for confirmation. But that could also be done separately in
another patch. I just noticed that we're using this in multiple widgets
already.
More information about the pve-devel
mailing list