[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