From 16094a3257d13fa16788f81db5ff79287f2b7c61 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 19:33:24 +0300 Subject: [PATCH] refactor: Rework ClientJobs cubit so it doesn't depend on other cubits Also implemented tracking of the jobs and rebuild status --- assets/translations/en.json | 13 +- lib/config/bloc_config.dart | 4 +- .../graphql_maps/schema/server_api.graphql | 16 + .../schema/server_api.graphql.dart | 1228 +++++++++++++++++ .../server_api/server_actions_api.dart | 91 +- lib/logic/bloc/devices/devices_bloc.dart | 4 - .../cubit/client_jobs/client_jobs_cubit.dart | 206 ++- .../cubit/client_jobs/client_jobs_state.dart | 136 +- .../server_detailed_info_repository.dart | 14 - .../get_it/api_connection_repository.dart | 66 +- lib/logic/models/job.dart | 306 +++- .../components/jobs_content/jobs_content.dart | 356 ++++- .../server_details/server_details_screen.dart | 1 - .../pages/server_details/server_settings.dart | 55 +- .../server_details/time_zone/time_zone.dart | 6 +- pubspec.lock | 2 +- pubspec.yaml | 1 + 17 files changed, 2313 insertions(+), 192 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 1410a950..14861d32 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -36,7 +36,8 @@ "continue": "Continue", "alert": "Alert", "copied_to_clipboard": "Copied to clipboard!", - "please_connect": "Please connect your server, domain and DNS provider to dive in!" + "please_connect": "Please connect your server, domain and DNS provider to dive in!", + "network_error": "Network error" }, "more_page": { "configuration_wizard": "Setup wizard", @@ -394,7 +395,8 @@ "could_not_add_ssh_key": "Couldn't add SSH key", "username_rule": "Username must contain only lowercase latin letters, digits and underscores, should not start with a digit", "email_login": "Email login", - "no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon." + "no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon.", + "user_already_exists": "User with such username already exists" }, "initializing": { "server_provider_description": "A place where your data and SelfPrivacy services will reside:", @@ -594,6 +596,7 @@ "service_turn_off": "Turn off", "service_turn_on": "Turn on", "job_added": "Job added", + "job_postponed": "Job added, but you will be able to launch it after current jobs are finished", "run_jobs": "Run jobs", "reboot_success": "Server is rebooting", "reboot_failed": "Couldn't reboot the server. Check the app logs.", @@ -606,7 +609,11 @@ "delete_ssh_key": "Delete SSH key for {}", "server_jobs": "Jobs on the server", "reset_user_password": "Reset password of user", - "generic_error": "Couldn't connect to the server!" + "generic_error": "Couldn't connect to the server!", + "rebuild_system": "Rebuild system", + "start_server_upgrade": "Start the server upgrade", + "change_auto_upgrade_settings": "Change auto-upgrade settings", + "change_server_timezone": "Change server timezone" }, "validations": { "required": "Required", diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 4f5baeed..06cb7244 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -104,9 +104,7 @@ class BlocAndProviderConfigState extends State { ), BlocProvider(create: (final _) => volumesBloc), BlocProvider( - create: (final _) => JobsCubit( - servicesBloc: servicesBloc, - ), + create: (final _) => JobsCubit(), ), ], child: widget.child, diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql index e66d8143..d8e21485 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -52,6 +52,14 @@ mutation RunSystemRebuild { } } +mutation RunSystemRebuildFallback { + system { + runSystemRebuild { + ...basicMutationReturnFields + } + } +} + mutation RunSystemRollback { system { runSystemRollback { @@ -71,6 +79,14 @@ mutation RunSystemUpgrade { } } +mutation RunSystemUpgradeFallback { + system { + runSystemUpgrade { + ...basicMutationReturnFields + } + } +} + mutation PullRepositoryChanges { system { pullRepositoryChanges { diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart index 5241a6c9..7fcc3d4d 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart @@ -5174,6 +5174,620 @@ class _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild CopyWith$Fragment$basicApiJobsFields.stub(_res); } +class Mutation$RunSystemRebuildFallback { + Mutation$RunSystemRebuildFallback({ + required this.system, + this.$__typename = 'Mutation', + }); + + factory Mutation$RunSystemRebuildFallback.fromJson( + Map json) { + final l$system = json['system']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback( + system: Mutation$RunSystemRebuildFallback$system.fromJson( + (l$system as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemRebuildFallback$system system; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$system = system; + _resultData['system'] = l$system.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$system = system; + final l$$__typename = $__typename; + return Object.hashAll([ + l$system, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback) || + runtimeType != other.runtimeType) { + return false; + } + final l$system = system; + final lOther$system = other.system; + if (l$system != lOther$system) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback + on Mutation$RunSystemRebuildFallback { + CopyWith$Mutation$RunSystemRebuildFallback + get copyWith => CopyWith$Mutation$RunSystemRebuildFallback( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback { + factory CopyWith$Mutation$RunSystemRebuildFallback( + Mutation$RunSystemRebuildFallback instance, + TRes Function(Mutation$RunSystemRebuildFallback) then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback; + + factory CopyWith$Mutation$RunSystemRebuildFallback.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback; + + TRes call({ + Mutation$RunSystemRebuildFallback$system? system, + String? $__typename, + }); + CopyWith$Mutation$RunSystemRebuildFallback$system get system; +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback + implements CopyWith$Mutation$RunSystemRebuildFallback { + _CopyWithImpl$Mutation$RunSystemRebuildFallback( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback) _then; + + static const _undefined = {}; + + TRes call({ + Object? system = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback( + system: system == _undefined || system == null + ? _instance.system + : (system as Mutation$RunSystemRebuildFallback$system), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemRebuildFallback$system get system { + final local$system = _instance.system; + return CopyWith$Mutation$RunSystemRebuildFallback$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback + implements CopyWith$Mutation$RunSystemRebuildFallback { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback(this._res); + + TRes _res; + + call({ + Mutation$RunSystemRebuildFallback$system? system, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemRebuildFallback$system get system => + CopyWith$Mutation$RunSystemRebuildFallback$system.stub(_res); +} + +const documentNodeMutationRunSystemRebuildFallback = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'RunSystemRebuildFallback'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'system'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'runSystemRebuild'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicMutationReturnFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionbasicMutationReturnFields, +]); +Mutation$RunSystemRebuildFallback _parserFn$Mutation$RunSystemRebuildFallback( + Map data) => + Mutation$RunSystemRebuildFallback.fromJson(data); +typedef OnMutationCompleted$Mutation$RunSystemRebuildFallback = FutureOr + Function( + Map?, + Mutation$RunSystemRebuildFallback?, +); + +class Options$Mutation$RunSystemRebuildFallback + extends graphql.MutationOptions { + Options$Mutation$RunSystemRebuildFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemRebuildFallback? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$RunSystemRebuildFallback? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$RunSystemRebuildFallback(data), + ), + update: update, + onError: onError, + document: documentNodeMutationRunSystemRebuildFallback, + parserFn: _parserFn$Mutation$RunSystemRebuildFallback, + ); + + final OnMutationCompleted$Mutation$RunSystemRebuildFallback? + onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$RunSystemRebuildFallback + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$RunSystemRebuildFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemRebuildFallback? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationRunSystemRebuildFallback, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$RunSystemRebuildFallback, + ); +} + +extension ClientExtension$Mutation$RunSystemRebuildFallback + on graphql.GraphQLClient { + Future> + mutate$RunSystemRebuildFallback( + [Options$Mutation$RunSystemRebuildFallback? options]) async => + await this + .mutate(options ?? Options$Mutation$RunSystemRebuildFallback()); + graphql.ObservableQuery + watchMutation$RunSystemRebuildFallback( + [WatchOptions$Mutation$RunSystemRebuildFallback? options]) => + this.watchMutation( + options ?? WatchOptions$Mutation$RunSystemRebuildFallback()); +} + +class Mutation$RunSystemRebuildFallback$system { + Mutation$RunSystemRebuildFallback$system({ + required this.runSystemRebuild, + this.$__typename = 'SystemMutations', + }); + + factory Mutation$RunSystemRebuildFallback$system.fromJson( + Map json) { + final l$runSystemRebuild = json['runSystemRebuild']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback$system( + runSystemRebuild: + Mutation$RunSystemRebuildFallback$system$runSystemRebuild.fromJson( + (l$runSystemRebuild as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemRebuildFallback$system$runSystemRebuild + runSystemRebuild; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$runSystemRebuild = runSystemRebuild; + _resultData['runSystemRebuild'] = l$runSystemRebuild.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$runSystemRebuild = runSystemRebuild; + final l$$__typename = $__typename; + return Object.hashAll([ + l$runSystemRebuild, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback$system) || + runtimeType != other.runtimeType) { + return false; + } + final l$runSystemRebuild = runSystemRebuild; + final lOther$runSystemRebuild = other.runSystemRebuild; + if (l$runSystemRebuild != lOther$runSystemRebuild) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback$system + on Mutation$RunSystemRebuildFallback$system { + CopyWith$Mutation$RunSystemRebuildFallback$system< + Mutation$RunSystemRebuildFallback$system> + get copyWith => CopyWith$Mutation$RunSystemRebuildFallback$system( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback$system { + factory CopyWith$Mutation$RunSystemRebuildFallback$system( + Mutation$RunSystemRebuildFallback$system instance, + TRes Function(Mutation$RunSystemRebuildFallback$system) then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback$system; + + factory CopyWith$Mutation$RunSystemRebuildFallback$system.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system; + + TRes call({ + Mutation$RunSystemRebuildFallback$system$runSystemRebuild? runSystemRebuild, + String? $__typename, + }); + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild; +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback$system + implements CopyWith$Mutation$RunSystemRebuildFallback$system { + _CopyWithImpl$Mutation$RunSystemRebuildFallback$system( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback$system _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback$system) _then; + + static const _undefined = {}; + + TRes call({ + Object? runSystemRebuild = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback$system( + runSystemRebuild: runSystemRebuild == _undefined || + runSystemRebuild == null + ? _instance.runSystemRebuild + : (runSystemRebuild + as Mutation$RunSystemRebuildFallback$system$runSystemRebuild), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild { + final local$runSystemRebuild = _instance.runSystemRebuild; + return CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + local$runSystemRebuild, (e) => call(runSystemRebuild: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system + implements CopyWith$Mutation$RunSystemRebuildFallback$system { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system(this._res); + + TRes _res; + + call({ + Mutation$RunSystemRebuildFallback$system$runSystemRebuild? runSystemRebuild, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild => + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + .stub(_res); +} + +class Mutation$RunSystemRebuildFallback$system$runSystemRebuild + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { + Mutation$RunSystemRebuildFallback$system$runSystemRebuild({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'GenericJobMutationReturn', + }); + + factory Mutation$RunSystemRebuildFallback$system$runSystemRebuild.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback$system$runSystemRebuild) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + on Mutation$RunSystemRebuildFallback$system$runSystemRebuild { + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + Mutation$RunSystemRebuildFallback$system$runSystemRebuild> + get copyWith => + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + factory CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + Mutation$RunSystemRebuildFallback$system$runSystemRebuild instance, + TRes Function(Mutation$RunSystemRebuildFallback$system$runSystemRebuild) + then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild; + + factory CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + }); +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> + implements + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback$system$runSystemRebuild _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback$system$runSystemRebuild) + _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> + implements + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + }) => + _res; +} + class Mutation$RunSystemRollback { Mutation$RunSystemRollback({ required this.system, @@ -6429,6 +7043,620 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade CopyWith$Fragment$basicApiJobsFields.stub(_res); } +class Mutation$RunSystemUpgradeFallback { + Mutation$RunSystemUpgradeFallback({ + required this.system, + this.$__typename = 'Mutation', + }); + + factory Mutation$RunSystemUpgradeFallback.fromJson( + Map json) { + final l$system = json['system']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback( + system: Mutation$RunSystemUpgradeFallback$system.fromJson( + (l$system as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemUpgradeFallback$system system; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$system = system; + _resultData['system'] = l$system.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$system = system; + final l$$__typename = $__typename; + return Object.hashAll([ + l$system, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback) || + runtimeType != other.runtimeType) { + return false; + } + final l$system = system; + final lOther$system = other.system; + if (l$system != lOther$system) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback + on Mutation$RunSystemUpgradeFallback { + CopyWith$Mutation$RunSystemUpgradeFallback + get copyWith => CopyWith$Mutation$RunSystemUpgradeFallback( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback { + factory CopyWith$Mutation$RunSystemUpgradeFallback( + Mutation$RunSystemUpgradeFallback instance, + TRes Function(Mutation$RunSystemUpgradeFallback) then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback; + + factory CopyWith$Mutation$RunSystemUpgradeFallback.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback; + + TRes call({ + Mutation$RunSystemUpgradeFallback$system? system, + String? $__typename, + }); + CopyWith$Mutation$RunSystemUpgradeFallback$system get system; +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback + implements CopyWith$Mutation$RunSystemUpgradeFallback { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback) _then; + + static const _undefined = {}; + + TRes call({ + Object? system = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback( + system: system == _undefined || system == null + ? _instance.system + : (system as Mutation$RunSystemUpgradeFallback$system), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemUpgradeFallback$system get system { + final local$system = _instance.system; + return CopyWith$Mutation$RunSystemUpgradeFallback$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback + implements CopyWith$Mutation$RunSystemUpgradeFallback { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback(this._res); + + TRes _res; + + call({ + Mutation$RunSystemUpgradeFallback$system? system, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemUpgradeFallback$system get system => + CopyWith$Mutation$RunSystemUpgradeFallback$system.stub(_res); +} + +const documentNodeMutationRunSystemUpgradeFallback = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'RunSystemUpgradeFallback'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'system'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'runSystemUpgrade'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicMutationReturnFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionbasicMutationReturnFields, +]); +Mutation$RunSystemUpgradeFallback _parserFn$Mutation$RunSystemUpgradeFallback( + Map data) => + Mutation$RunSystemUpgradeFallback.fromJson(data); +typedef OnMutationCompleted$Mutation$RunSystemUpgradeFallback = FutureOr + Function( + Map?, + Mutation$RunSystemUpgradeFallback?, +); + +class Options$Mutation$RunSystemUpgradeFallback + extends graphql.MutationOptions { + Options$Mutation$RunSystemUpgradeFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemUpgradeFallback? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$RunSystemUpgradeFallback? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$RunSystemUpgradeFallback(data), + ), + update: update, + onError: onError, + document: documentNodeMutationRunSystemUpgradeFallback, + parserFn: _parserFn$Mutation$RunSystemUpgradeFallback, + ); + + final OnMutationCompleted$Mutation$RunSystemUpgradeFallback? + onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$RunSystemUpgradeFallback + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$RunSystemUpgradeFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemUpgradeFallback? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationRunSystemUpgradeFallback, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$RunSystemUpgradeFallback, + ); +} + +extension ClientExtension$Mutation$RunSystemUpgradeFallback + on graphql.GraphQLClient { + Future> + mutate$RunSystemUpgradeFallback( + [Options$Mutation$RunSystemUpgradeFallback? options]) async => + await this + .mutate(options ?? Options$Mutation$RunSystemUpgradeFallback()); + graphql.ObservableQuery + watchMutation$RunSystemUpgradeFallback( + [WatchOptions$Mutation$RunSystemUpgradeFallback? options]) => + this.watchMutation( + options ?? WatchOptions$Mutation$RunSystemUpgradeFallback()); +} + +class Mutation$RunSystemUpgradeFallback$system { + Mutation$RunSystemUpgradeFallback$system({ + required this.runSystemUpgrade, + this.$__typename = 'SystemMutations', + }); + + factory Mutation$RunSystemUpgradeFallback$system.fromJson( + Map json) { + final l$runSystemUpgrade = json['runSystemUpgrade']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback$system( + runSystemUpgrade: + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.fromJson( + (l$runSystemUpgrade as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + runSystemUpgrade; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$runSystemUpgrade = runSystemUpgrade; + _resultData['runSystemUpgrade'] = l$runSystemUpgrade.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$runSystemUpgrade = runSystemUpgrade; + final l$$__typename = $__typename; + return Object.hashAll([ + l$runSystemUpgrade, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback$system) || + runtimeType != other.runtimeType) { + return false; + } + final l$runSystemUpgrade = runSystemUpgrade; + final lOther$runSystemUpgrade = other.runSystemUpgrade; + if (l$runSystemUpgrade != lOther$runSystemUpgrade) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback$system + on Mutation$RunSystemUpgradeFallback$system { + CopyWith$Mutation$RunSystemUpgradeFallback$system< + Mutation$RunSystemUpgradeFallback$system> + get copyWith => CopyWith$Mutation$RunSystemUpgradeFallback$system( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback$system { + factory CopyWith$Mutation$RunSystemUpgradeFallback$system( + Mutation$RunSystemUpgradeFallback$system instance, + TRes Function(Mutation$RunSystemUpgradeFallback$system) then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system; + + factory CopyWith$Mutation$RunSystemUpgradeFallback$system.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system; + + TRes call({ + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade? runSystemUpgrade, + String? $__typename, + }); + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade; +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system + implements CopyWith$Mutation$RunSystemUpgradeFallback$system { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback$system _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback$system) _then; + + static const _undefined = {}; + + TRes call({ + Object? runSystemUpgrade = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback$system( + runSystemUpgrade: runSystemUpgrade == _undefined || + runSystemUpgrade == null + ? _instance.runSystemUpgrade + : (runSystemUpgrade + as Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade { + final local$runSystemUpgrade = _instance.runSystemUpgrade; + return CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + local$runSystemUpgrade, (e) => call(runSystemUpgrade: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system + implements CopyWith$Mutation$RunSystemUpgradeFallback$system { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system(this._res); + + TRes _res; + + call({ + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade? runSystemUpgrade, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade => + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + .stub(_res); +} + +class Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'GenericJobMutationReturn', + }); + + factory Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + on Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade { + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade> + get copyWith => + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + factory CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade instance, + TRes Function(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) + then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade; + + factory CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + }); +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> + implements + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) + _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> + implements + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + }) => + _res; +} + class Mutation$PullRepositoryChanges { Mutation$PullRepositoryChanges({ required this.system, diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart index 1d357b06..f568a064 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart @@ -29,7 +29,11 @@ mixin ServerActionsApi on GraphQLApiMap { print(response.exception.toString()); } if (response.parsedData!.system.rebootSystem.success) { - time = DateTime.now().toUtc(); + return GenericResult( + data: time, + success: true, + message: response.parsedData!.system.rebootSystem.message, + ); } } catch (e) { print(e); @@ -50,23 +54,94 @@ mixin ServerActionsApi on GraphQLApiMap { } } - Future upgrade() async { + Future> upgrade() async { try { final GraphQLClient client = await getClient(); - return _commonBoolRequest( - () async => client.mutate$RunSystemUpgrade(), - ); + final result = await client.mutate$RunSystemUpgrade(); + if (result.hasException) { + final fallbackResult = await client.mutate$RunSystemUpgradeFallback(); + if (fallbackResult.parsedData!.system.runSystemUpgrade.success) { + return GenericResult( + success: true, + data: null, + message: fallbackResult.parsedData!.system.runSystemUpgrade.message, + ); + } else { + return GenericResult( + success: false, + message: fallbackResult.parsedData!.system.runSystemUpgrade.message, + data: null, + ); + } + } else if (result.parsedData!.system.runSystemUpgrade.success && + result.parsedData!.system.runSystemUpgrade.job != null) { + return GenericResult( + success: true, + data: ServerJob.fromGraphQL( + result.parsedData!.system.runSystemUpgrade.job!, + ), + message: result.parsedData!.system.runSystemUpgrade.message, + ); + } else { + return GenericResult( + success: false, + message: result.parsedData!.system.runSystemUpgrade.message, + data: null, + ); + } } catch (e) { - return false; + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } - Future apply() async { + Future> apply() async { try { final GraphQLClient client = await getClient(); - await client.mutate$RunSystemRebuild(); + final result = await client.mutate$RunSystemRebuild(); + if (result.hasException) { + final fallbackResult = await client.mutate$RunSystemRebuildFallback(); + if (fallbackResult.parsedData!.system.runSystemRebuild.success) { + return GenericResult( + success: true, + data: null, + message: fallbackResult.parsedData!.system.runSystemRebuild.message, + ); + } else { + return GenericResult( + success: false, + message: fallbackResult.parsedData!.system.runSystemRebuild.message, + data: null, + ); + } + } else { + if (result.parsedData!.system.runSystemRebuild.success && + result.parsedData!.system.runSystemRebuild.job != null) { + return GenericResult( + success: true, + data: ServerJob.fromGraphQL( + result.parsedData!.system.runSystemRebuild.job!, + ), + message: result.parsedData!.system.runSystemRebuild.message, + ); + } else { + return GenericResult( + success: false, + message: result.parsedData!.system.runSystemRebuild.message, + data: null, + ); + } + } } catch (e) { print(e); + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } } diff --git a/lib/logic/bloc/devices/devices_bloc.dart b/lib/logic/bloc/devices/devices_bloc.dart index e1d248c7..fdba66e9 100644 --- a/lib/logic/bloc/devices/devices_bloc.dart +++ b/lib/logic/bloc/devices/devices_bloc.dart @@ -24,8 +24,6 @@ class DevicesBloc extends Bloc { final apiConnectionRepository = getIt(); _apiDataSubscription = apiConnectionRepository.dataStream.listen( (final ApiData apiData) { - print('============'); - print(apiData.devices.data); add( DevicesListChanged(apiData.devices.data), ); @@ -42,7 +40,6 @@ class DevicesBloc extends Bloc { if (state is DevicesDeleting) { return; } - print(event.devices); if (event.devices == null) { emit(DevicesError()); return; @@ -103,7 +100,6 @@ class DevicesBloc extends Bloc { @override void onChange(final Change change) { super.onChange(change); - print(change); } @override diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 16b87c77..25ed3642 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -1,33 +1,55 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; export 'package:provider/provider.dart'; part 'client_jobs_state.dart'; class JobsCubit extends Cubit { - JobsCubit({ - required this.servicesBloc, - }) : super(JobsStateEmpty()); + JobsCubit() : super(JobsStateEmpty()) { + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.serverJobs.data != null && + apiData.serverJobs.data!.isNotEmpty) { + _handleServerJobs(apiData.serverJobs.data!); + } + }, + ); + } + + StreamSubscription? _apiDataSubscription; final ServerApi api = ServerApi(); - final ServicesBloc servicesBloc; - void addJob(final ClientJob job) { - final jobs = currentJobList; - if (job.canAddTo(jobs)) { - _updateJobsState([ - ...jobs, - ...[job], - ]); + void _handleServerJobs(final List jobs) { + if (state is! JobsStateLoading) { + return; } + if (state.rebuildJobUid == null) { + return; + } + // Find a job with the uid of the rebuild job + final ServerJob? rebuildJob = jobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + if (rebuildJob == null || + rebuildJob.status == JobStatusEnum.error || + rebuildJob.status == JobStatusEnum.finished) { + emit((state as JobsStateLoading).finished()); + } + } + + void addJob(final ClientJob job) async { + emit(state.addJob(job)); } void removeJob(final String id) { @@ -35,61 +57,145 @@ class JobsCubit extends Cubit { emit(newState); } - List get currentJobList { - final List jobs = []; - if (state is JobsStateWithJobs) { - jobs.addAll((state as JobsStateWithJobs).clientJobList); - } - - return jobs; - } - - void _updateJobsState(final List newJobs) { - getIt().showSnackBar('jobs.job_added'.tr()); - emit(JobsStateWithJobs(newJobs)); - } - Future rebootServer() async { - emit(JobsStateLoading()); - final rebootResult = await api.reboot(); - if (rebootResult.success && rebootResult.data != null) { - getIt().showSnackBar('jobs.reboot_success'.tr()); - } else { - getIt().showSnackBar('jobs.reboot_failed'.tr()); + if (state is JobsStateEmpty) { + emit( + JobsStateLoading( + [RebootServerJob(status: JobStatusEnum.running)], + null, + const [], + ), + ); + final rebootResult = await api.reboot(); + if (rebootResult.success && rebootResult.data != null) { + emit( + JobsStateFinished( + [ + RebootServerJob( + status: JobStatusEnum.finished, + message: rebootResult.message, + ), + ], + null, + const [], + ), + ); + } else { + emit( + JobsStateFinished( + [RebootServerJob(status: JobStatusEnum.error)], + null, + const [], + ), + ); + } } - emit(JobsStateEmpty()); } Future upgradeServer() async { - emit(JobsStateLoading()); - final bool isPullSuccessful = await api.pullConfigurationUpdate(); - final bool isSuccessful = await api.upgrade(); - if (isSuccessful) { - if (!isPullSuccessful) { - getIt().showSnackBar('jobs.config_pull_failed'.tr()); + if (state is JobsStateEmpty) { + emit( + JobsStateLoading( + [UpgradeServerJob(status: JobStatusEnum.running)], + null, + const [], + ), + ); + final result = await getIt().api.upgrade(); + if (result.success && result.data != null) { + emit( + JobsStateLoading( + [UpgradeServerJob(status: JobStatusEnum.finished)], + result.data!.uid, + const [], + ), + ); } else { - getIt().showSnackBar('jobs.upgrade_success'.tr()); + emit( + JobsStateFinished( + [UpgradeServerJob(status: JobStatusEnum.error)], + null, + const [], + ), + ); } - } else { - getIt().showSnackBar('jobs.upgrade_failed'.tr()); } - emit(JobsStateEmpty()); } Future applyAll() async { if (state is JobsStateWithJobs) { final List jobs = (state as JobsStateWithJobs).clientJobList; - emit(JobsStateLoading()); + emit(JobsStateLoading(jobs, null, const [])); + + await Future.delayed(Duration.zero); + + final rebuildRequired = jobs.any((final job) => job.requiresRebuild); for (final ClientJob job in jobs) { - job.execute(this); + emit( + (state as JobsStateLoading) + .updateJobStatus(job.id, JobStatusEnum.running), + ); + final (result, message) = await job.execute(this); + if (result) { + emit( + (state as JobsStateLoading).updateJobStatus( + job.id, + JobStatusEnum.finished, + message: message, + ), + ); + } else { + emit( + (state as JobsStateLoading) + .updateJobStatus(job.id, JobStatusEnum.error, message: message), + ); + } } - await api.pullConfigurationUpdate(); - await api.apply(); - servicesBloc.add(const ServicesReload()); - - emit(JobsStateEmpty()); + if (!rebuildRequired) { + emit((state as JobsStateLoading).finished()); + return; + } + final rebuildResult = await getIt().api.apply(); + if (rebuildResult.success) { + if (rebuildResult.data != null) { + emit( + (state as JobsStateLoading) + .copyWith(rebuildJobUid: rebuildResult.data!.uid), + ); + } else { + emit((state as JobsStateLoading).finished()); + } + } else { + emit((state as JobsStateLoading).finished()); + } } } + + Future acknowledgeFinished() async { + if (state is! JobsStateFinished) { + return; + } + final rebuildJobUid = state.rebuildJobUid; + if ((state as JobsStateFinished).postponedJobs.isNotEmpty) { + emit(JobsStateWithJobs((state as JobsStateFinished).postponedJobs)); + } else { + emit(JobsStateEmpty()); + } + if (rebuildJobUid != null) { + await getIt().removeServerJob(rebuildJobUid); + } + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } } diff --git a/lib/logic/cubit/client_jobs/client_jobs_state.dart b/lib/logic/cubit/client_jobs/client_jobs_state.dart index 2bb31856..4f7244f5 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_state.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_state.dart @@ -1,17 +1,32 @@ part of 'client_jobs_cubit.dart'; -abstract class JobsState extends Equatable { +sealed class JobsState extends Equatable { + String? get rebuildJobUid => null; + + JobsState addJob(final ClientJob job); + @override List get props => []; } -class JobsStateLoading extends JobsState {} +class JobsStateEmpty extends JobsState { + @override + JobsStateWithJobs addJob(final ClientJob job) { + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs([job]); + } -class JobsStateEmpty extends JobsState {} + @override + List get props => []; +} class JobsStateWithJobs extends JobsState { JobsStateWithJobs(this.clientJobList); final List clientJobList; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + JobsState removeById(final String id) { final List newJobsList = clientJobList.where((final element) => element.id != id).toList(); @@ -22,5 +37,118 @@ class JobsStateWithJobs extends JobsState { } @override - List get props => clientJobList; + List get props => [clientJobList]; + + @override + JobsState addJob(final ClientJob job) { + if (job is ReplaceableJob) { + final List newJobsList = clientJobList + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newJobsList.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newJobsList); + } + if (job.canAddTo(clientJobList)) { + final List newJobsList = [...clientJobList, job]; + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newJobsList); + } + return this; + } +} + +class JobsStateLoading extends JobsState { + JobsStateLoading(this.clientJobList, this.rebuildJobUid, this.postponedJobs); + final List clientJobList; + @override + final String? rebuildJobUid; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + + final List postponedJobs; + + JobsStateLoading updateJobStatus( + final String id, + final JobStatusEnum status, { + final String? message, + }) { + final List newJobsList = clientJobList.map((final job) { + if (job.id == id) { + return job.copyWithNewStatus(status: status, message: message); + } + return job; + }).toList(); + return JobsStateLoading(newJobsList, rebuildJobUid, postponedJobs); + } + + JobsStateLoading copyWith({ + final List? clientJobList, + final String? rebuildJobUid, + final List? postponedJobs, + }) => + JobsStateLoading( + clientJobList ?? this.clientJobList, + rebuildJobUid ?? this.rebuildJobUid, + postponedJobs ?? this.postponedJobs, + ); + + JobsStateFinished finished() => + JobsStateFinished(clientJobList, rebuildJobUid, postponedJobs); + + @override + List get props => [clientJobList, rebuildJobUid, postponedJobs]; + + @override + JobsState addJob(final ClientJob job) { + // Do the same, but add jobs to the postponed list + if (job is ReplaceableJob) { + final List newPostponedJobs = postponedJobs + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_postponed'.tr()); + return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs); + } + if (job.canAddTo(postponedJobs)) { + final List newPostponedJobs = [...postponedJobs, job]; + getIt().showSnackBar('jobs.job_postponed'.tr()); + return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs); + } + return this; + } +} + +class JobsStateFinished extends JobsState { + JobsStateFinished(this.clientJobList, this.rebuildJobUid, this.postponedJobs); + final List clientJobList; + @override + final String? rebuildJobUid; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + + final List postponedJobs; + + @override + List get props => [clientJobList, rebuildJobUid, postponedJobs]; + + @override + JobsState addJob(final ClientJob job) { + if (job is ReplaceableJob) { + final List newPostponedJobs = postponedJobs + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newPostponedJobs); + } + if (job.canAddTo(postponedJobs)) { + final List newPostponedJobs = [...postponedJobs, job]; + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newPostponedJobs); + } + return this; + } } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index 0d2d80e3..54540e5f 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -40,20 +40,6 @@ class ServerDetailsRepository { return data; } - - Future setAutoUpgradeSettings( - final AutoUpgradeSettings settings, - ) async { - await server.setAutoUpgradeSettings(settings); - } - - Future setTimezone( - final String timezone, - ) async { - if (timezone.isNotEmpty) { - await server.setTimezone(timezone); - } - } } class ServerDetailsRepositoryDto { diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 13229442..eb818f35 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -70,45 +70,41 @@ class ApiConnectionRepository { ); } - Future createUser(final User user) async { + Future<(bool, String)> createUser(final User user) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } // If user exists on server, do nothing if (loadedUsers .any((final User u) => u.login == user.login && u.isFoundOnServer)) { - return; + return (false, 'users.user_already_exists'.tr()); } final String? password = user.password; if (password == null) { - getIt() - .showSnackBar('users.could_not_create_user'.tr()); - return; + return (false, 'users.could_not_create_user'.tr()); } // If API returned error, do nothing final GenericResult result = await api.createUser(user.login, password); if (result.data == null) { - getIt() - .showSnackBar(result.message ?? 'users.could_not_create_user'.tr()); - return; + return (false, result.message ?? 'users.could_not_create_user'.tr()); } _apiData.users.data?.add(result.data!); _apiData.users.invalidate(); + + return (true, result.message ?? 'basis.done'.tr()); } - Future deleteUser(final User user) async { + Future<(bool, String)> deleteUser(final User user) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } // If user is primary or root, don't delete if (user.type != UserType.normal) { - getIt() - .showSnackBar('users.could_not_delete_user'.tr()); - return; + return (false, 'users.could_not_delete_user'.tr()); } final GenericResult result = await api.deleteUser(user.login); if (result.success && result.data) { @@ -117,19 +113,18 @@ class ApiConnectionRepository { } if (!result.success || !result.data) { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + return (false, result.message ?? 'jobs.generic_error'.tr()); } + + return (true, result.message ?? 'basis.done'.tr()); } - Future changeUserPassword( + Future<(bool, String)> changeUserPassword( final User user, final String newPassword, ) async { if (user.type == UserType.root) { - getIt() - .showSnackBar('users.could_not_change_password'.tr()); - return; + return (false, 'users.could_not_change_password'.tr()); } final GenericResult result = await api.updateUser( user.login, @@ -139,13 +134,21 @@ class ApiConnectionRepository { getIt().showSnackBar( result.message ?? 'users.could_not_change_password'.tr(), ); + return ( + false, + result.message ?? 'users.could_not_change_password'.tr(), + ); } + return (true, result.message ?? 'basis.done'.tr()); } - Future addSshKey(final User user, final String publicKey) async { + Future<(bool, String)> addSshKey( + final User user, + final String publicKey, + ) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } final GenericResult result = await api.addSshKey(user.login, publicKey); @@ -156,15 +159,19 @@ class ApiConnectionRepository { loadedUsers[index] = updatedUser; _apiData.users.invalidate(); } else { - getIt() - .showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr()); + return (false, result.message ?? 'users.could_not_add_ssh_key'.tr()); } + + return (true, result.message ?? 'basis.done'.tr()); } - Future deleteSshKey(final User user, final String publicKey) async { + Future<(bool, String)> deleteSshKey( + final User user, + final String publicKey, + ) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } final GenericResult result = await api.removeSshKey(user.login, publicKey); @@ -175,9 +182,9 @@ class ApiConnectionRepository { loadedUsers[index] = updatedUser; _apiData.users.invalidate(); } else { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + return (false, result.message ?? 'jobs.generic_error'.tr()); } + return (true, result.message ?? 'basis.done'.tr()); } void dispose() { @@ -345,11 +352,8 @@ class ApiDataElement { final Function callback, ) async { if (VersionConstraint.parse(requiredApiVersion).allows(version)) { - print('Fetching data for $runtimeType'); if (isExpired) { - print('Data is expired'); final newData = await fetchData(); - print(newData); if (T is List) { if (Object.hashAll(newData as Iterable) != Object.hashAll(_data as Iterable)) { diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index 6e3d2a1d..b88d759c 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -3,7 +3,9 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/utils/password_generator.dart'; @@ -12,18 +14,31 @@ abstract class ClientJob extends Equatable { ClientJob({ required this.title, final String? id, + this.requiresRebuild = true, + this.status = JobStatusEnum.created, + this.message, }) : id = id ?? StringGenerators.simpleId(); final String title; final String id; + final bool requiresRebuild; + + final JobStatusEnum status; + final String? message; bool canAddTo(final List jobs) => true; - void execute(final JobsCubit cubit); + Future<(bool, String)> execute(final JobsCubit cubit); @override - List get props => [id, title]; + List get props => [id, title, status]; + + ClientJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }); } +@Deprecated('Jobs bloc should handle it itself') class RebuildServerJob extends ClientJob { RebuildServerJob({ required super.title, @@ -35,47 +50,138 @@ class RebuildServerJob extends ClientJob { !jobs.any((final job) => job is RebuildServerJob); @override - void execute(final JobsCubit cubit) async { - await cubit.upgradeServer(); + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + RebuildServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) { + throw UnimplementedError(); } } +class UpgradeServerJob extends ClientJob { + UpgradeServerJob({ + super.status, + super.message, + super.id, + }) : super(title: 'jobs.start_server_upgrade'.tr()); + + @override + bool canAddTo(final List jobs) => + !jobs.any((final job) => job is UpgradeServerJob); + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + UpgradeServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + UpgradeServerJob( + status: status, + message: message, + id: id, + ); +} + +class RebootServerJob extends ClientJob { + RebootServerJob({ + super.status, + super.message, + super.id, + }) : super(title: 'jobs.reboot_server'.tr(), requiresRebuild: false); + + @override + bool canAddTo(final List jobs) => + !jobs.any((final job) => job is RebootServerJob); + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + RebootServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + RebootServerJob( + status: status, + message: message, + id: id, + ); +} + class CreateUserJob extends ClientJob { CreateUserJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.create_user".tr()} ${user.login}'); final User user; @override - void execute(final JobsCubit cubit) async { - await getIt().createUser(user); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().createUser(user); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + CreateUserJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + CreateUserJob( + user: user, + status: status, + message: message, + id: id, + ); } class ResetUserPasswordJob extends ClientJob { ResetUserPasswordJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.reset_user_password".tr()} ${user.login}'); final User user; @override - void execute(final JobsCubit cubit) async { - await getIt() - .changeUserPassword(user, user.password!); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().changeUserPassword(user, user.password!); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + ResetUserPasswordJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ResetUserPasswordJob( + user: user, + status: status, + message: message, + id: id, + ); } class DeleteUserJob extends ClientJob { DeleteUserJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.delete_user".tr()} ${user.login}'); final User user; @@ -86,18 +192,32 @@ class DeleteUserJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await getIt().deleteUser(user); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().deleteUser(user); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + DeleteUserJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + DeleteUserJob( + user: user, + status: status, + message: message, + id: id, + ); } class ServiceToggleJob extends ClientJob { ServiceToggleJob({ required this.service, required this.needToTurnOn, + super.status, + super.message, + super.id, }) : super( title: '${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}', @@ -112,36 +232,68 @@ class ServiceToggleJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { + Future<(bool, String)> execute(final JobsCubit cubit) async { await cubit.api.switchService(service.id, needToTurnOn); + return (true, 'Check not implemented'); } @override List get props => [...super.props, service]; + + @override + ServiceToggleJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ServiceToggleJob( + service: service, + needToTurnOn: needToTurnOn, + status: status, + message: message, + id: id, + ); } class CreateSSHKeyJob extends ClientJob { CreateSSHKeyJob({ required this.user, required this.publicKey, + super.status, + super.message, + super.id, }) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login])); final User user; final String publicKey; @override - void execute(final JobsCubit cubit) async { - await getIt().addSshKey(user, publicKey); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().addSshKey(user, publicKey); @override - List get props => [id, title, user, publicKey]; + List get props => [...super.props, user, publicKey]; + + @override + CreateSSHKeyJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + CreateSSHKeyJob( + user: user, + publicKey: publicKey, + status: status, + message: message, + id: id, + ); } class DeleteSSHKeyJob extends ClientJob { DeleteSSHKeyJob({ required this.user, required this.publicKey, + super.status, + super.message, + super.id, }) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login])); final User user; @@ -156,10 +308,114 @@ class DeleteSSHKeyJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await getIt().deleteSshKey(user, publicKey); + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().deleteSshKey(user, publicKey); + + @override + List get props => [...super.props, user, publicKey]; + + @override + DeleteSSHKeyJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + DeleteSSHKeyJob( + user: user, + publicKey: publicKey, + status: status, + message: message, + id: id, + ); +} + +abstract class ReplaceableJob extends ClientJob { + ReplaceableJob({ + required super.title, + super.id, + super.status, + super.message, + }); + + bool shouldRemoveInsteadOfAdd(final List jobs) => false; +} + +class ChangeAutoUpgradeSettingsJob extends ReplaceableJob { + ChangeAutoUpgradeSettingsJob({ + required this.enable, + required this.allowReboot, + super.status, + super.message, + super.id, + }) : super(title: 'jobs.change_auto_upgrade_settings'.tr()); + + final bool enable; + final bool allowReboot; + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async { + await cubit.api.setAutoUpgradeSettings( + AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), + ); + return (true, 'Check not implemented'); } @override - List get props => [id, title, user, publicKey]; + bool shouldRemoveInsteadOfAdd(final List jobs) { + // TODO: Finish this + throw UnimplementedError(); + } + + @override + List get props => [...super.props, enable, allowReboot]; + + @override + ChangeAutoUpgradeSettingsJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ChangeAutoUpgradeSettingsJob( + enable: enable, + allowReboot: allowReboot, + status: status, + message: message, + id: id, + ); +} + +class ChangeServerTimezoneJob extends ReplaceableJob { + ChangeServerTimezoneJob({ + required this.timezone, + super.status, + super.message, + super.id, + }) : super(title: 'jobs.change_server_timezone'.tr()); + + final String timezone; + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async { + await getIt().api.setTimezone(timezone); + return (true, 'Check not implemented'); + } + + @override + bool shouldRemoveInsteadOfAdd(final List jobs) { + // TODO: Finish this + throw UnimplementedError(); + } + + @override + List get props => [...super.props, timezone]; + + @override + ChangeServerTimezoneJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ChangeServerTimezoneJob( + timezone: timezone, + status: status, + message: message, + id: id, + ); } diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index d583e039..e3c1c9de 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -6,7 +7,6 @@ import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; -import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; @@ -19,6 +19,32 @@ class JobsContent extends StatelessWidget { final ScrollController controller; + IconData _getIcon(final JobStatusEnum status) { + switch (status) { + case JobStatusEnum.created: + return Icons.query_builder_outlined; + case JobStatusEnum.running: + return Icons.pending_outlined; + case JobStatusEnum.finished: + return Icons.check_circle_outline; + case JobStatusEnum.error: + return Icons.error_outline; + } + } + + Color _getColor(final JobStatusEnum status, final BuildContext context) { + switch (status) { + case JobStatusEnum.created: + return Theme.of(context).colorScheme.secondary; + case JobStatusEnum.running: + return Theme.of(context).colorScheme.tertiary; + case JobStatusEnum.finished: + return Theme.of(context).colorScheme.primary; + case JobStatusEnum.error: + return Theme.of(context).colorScheme.error; + } + } + @override Widget build(final BuildContext context) { final List serverJobs = @@ -68,8 +94,274 @@ class JobsContent extends StatelessWidget { } } else if (state is JobsStateLoading) { widgets = [ - const SizedBox(height: 80), - BrandLoader.horizontal(), + ...state.clientJobList.map( + (final j) => Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(j.status), + color: _getColor(j.status, context), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ), + ), + if (state.rebuildRequired) + Builder( + builder: (final context) { + final rebuildJob = serverJobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + return Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(rebuildJob?.status ?? JobStatusEnum.created), + color: _getColor( + rebuildJob?.status ?? JobStatusEnum.created, + context, + ), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + rebuildJob?.name ?? + 'jobs.rebuild_system'.tr(), + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (rebuildJob?.description != null) + Text( + rebuildJob!.description, + style: + Theme.of(context).textTheme.labelSmall, + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: rebuildJob?.progress == null + ? 0.0 + : ((rebuildJob!.progress ?? 0) < 1) + ? null + : rebuildJob.progress! / 100.0, + color: _getColor( + rebuildJob?.status ?? JobStatusEnum.created, + context, + ), + backgroundColor: Theme.of(context) + .colorScheme + .surfaceVariant, + minHeight: 7.0, + borderRadius: BorderRadius.circular(7.0), + ), + const SizedBox(height: 8), + if (rebuildJob?.error != null || + rebuildJob?.result != null || + rebuildJob?.statusText != null) + Text( + rebuildJob?.error ?? + rebuildJob?.result ?? + rebuildJob?.statusText ?? + '', + style: + Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ]; + } else if (state is JobsStateFinished) { + widgets = [ + ...state.clientJobList.map( + (final j) => Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(j.status), + color: _getColor(j.status, context), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ), + ), + if (state.rebuildRequired) + Builder( + builder: (final context) { + final rebuildJob = serverJobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + if (rebuildJob == null) { + return const SizedBox(); + } + return Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(rebuildJob.status), + color: _getColor( + rebuildJob.status, + context, + ), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + rebuildJob.name, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + Text( + rebuildJob.description, + style: Theme.of(context).textTheme.labelSmall, + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: rebuildJob.progress == null + ? 0.0 + : ((rebuildJob.progress ?? 0) < 1) + ? null + : rebuildJob.progress! / 100.0, + color: _getColor( + rebuildJob.status, + context, + ), + backgroundColor: Theme.of(context) + .colorScheme + .surfaceVariant, + minHeight: 7.0, + borderRadius: BorderRadius.circular(7.0), + ), + const SizedBox(height: 8), + if (rebuildJob.error != null || + rebuildJob.result != null || + rebuildJob.statusText != null) + Text( + rebuildJob.error ?? + rebuildJob.result ?? + rebuildJob.statusText ?? + '', + style: + Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + const SizedBox(height: 16), + BrandButton.rised( + onPressed: () => context.read().acknowledgeFinished(), + text: 'basis.done'.tr(), + ), ]; } else if (state is JobsStateWithJobs) { widgets = [ @@ -84,19 +376,31 @@ class JobsContent extends StatelessWidget { horizontal: 15, vertical: 10, ), - child: Text( - j.title, - style: - Theme.of(context).textTheme.labelLarge?.copyWith( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( color: Theme.of(context) .colorScheme .onSurfaceVariant, ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], ), ), ), ), - const SizedBox(width: 10), + const SizedBox(width: 8), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: @@ -116,7 +420,7 @@ class JobsContent extends StatelessWidget { ], ), ), - const SizedBox(height: 20), + const SizedBox(height: 16), BrandButton.rised( onPressed: () => context.read().applyAll(), text: 'jobs.start'.tr(), @@ -161,23 +465,25 @@ class JobsContent extends StatelessWidget { ], ), ), - ...serverJobs.map( - (final job) => Dismissible( - key: ValueKey(job.uid), - direction: job.status == JobStatusEnum.finished || - job.status == JobStatusEnum.error - ? DismissDirection.horizontal - : DismissDirection.none, - child: ServerJobCard( - serverJob: job, + ...serverJobs + .whereNot((final job) => job.uid == state.rebuildJobUid) + .map( + (final job) => Dismissible( + key: ValueKey(job.uid), + direction: job.status == JobStatusEnum.finished || + job.status == JobStatusEnum.error + ? DismissDirection.horizontal + : DismissDirection.none, + child: ServerJobCard( + serverJob: job, + ), + onDismissed: (final direction) { + context.read().add( + RemoveServerJob(job.uid), + ); + }, + ), ), - onDismissed: (final direction) { - context.read().add( - RemoveServerJob(job.uid), - ); - }, - ), - ), const SizedBox(height: 24), ], ); diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 1b66a21b..ab1f64d3 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -8,7 +8,6 @@ import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; diff --git a/lib/ui/pages/server_details/server_settings.dart b/lib/ui/pages/server_details/server_settings.dart index a9facd5d..c6381a3e 100644 --- a/lib/ui/pages/server_details/server_settings.dart +++ b/lib/ui/pages/server_details/server_settings.dart @@ -30,24 +30,32 @@ class _ServerSettingsState extends State<_ServerSettings> { value: allowAutoUpgrade ?? false, onChanged: (final switched) { context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); - context - .read() - .repository - .setAutoUpgradeSettings( - AutoUpgradeSettings( - enable: switched, + ChangeAutoUpgradeSettingsJob( allowReboot: rebootAfterUpgrade ?? false, + enable: switched, ), ); setState(() { allowAutoUpgrade = switched; }); }, - title: Text('server.allow_autoupgrade'.tr()), + title: Text( + 'server.allow_autoupgrade'.tr(), + style: TextStyle( + fontStyle: allowAutoUpgrade != + serverDetailsState.autoUpgradeSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), + ), subtitle: Text( 'server.allow_autoupgrade_hint'.tr(), + style: TextStyle( + fontStyle: allowAutoUpgrade != + serverDetailsState.autoUpgradeSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), ), activeColor: Theme.of(context).colorScheme.primary, ), @@ -55,24 +63,32 @@ class _ServerSettingsState extends State<_ServerSettings> { value: rebootAfterUpgrade ?? false, onChanged: (final switched) { context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); - context - .read() - .repository - .setAutoUpgradeSettings( - AutoUpgradeSettings( - enable: allowAutoUpgrade ?? false, + ChangeAutoUpgradeSettingsJob( allowReboot: switched, + enable: allowAutoUpgrade ?? false, ), ); setState(() { rebootAfterUpgrade = switched; }); }, - title: Text('server.reboot_after_upgrade'.tr()), + title: Text( + 'server.reboot_after_upgrade'.tr(), + style: TextStyle( + fontStyle: rebootAfterUpgrade != + serverDetailsState.autoUpgradeSettings.allowReboot + ? FontStyle.italic + : FontStyle.normal, + ), + ), subtitle: Text( 'server.reboot_after_upgrade_hint'.tr(), + style: TextStyle( + fontStyle: rebootAfterUpgrade != + serverDetailsState.autoUpgradeSettings.allowReboot + ? FontStyle.italic + : FontStyle.normal, + ), ), activeColor: Theme.of(context).colorScheme.primary, ), @@ -82,9 +98,6 @@ class _ServerSettingsState extends State<_ServerSettings> { serverDetailsState.serverTimezone.toString(), ), onTap: () { - context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); Navigator.of(context).push( materialRoute( const SelectTimezone(), diff --git a/lib/ui/pages/server_details/time_zone/time_zone.dart b/lib/ui/pages/server_details/time_zone/time_zone.dart index 5eb369bc..2bb2608f 100644 --- a/lib/ui/pages/server_details/time_zone/time_zone.dart +++ b/lib/ui/pages/server_details/time_zone/time_zone.dart @@ -140,8 +140,10 @@ class _SelectTimezoneState extends State { 'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}', ), onTap: () { - context.read().repository.setTimezone( - location.name, + context.read().addJob( + ChangeServerTimezoneJob( + timezone: location.name, + ), ); Navigator.of(context).pop(); }, diff --git a/pubspec.lock b/pubspec.lock index 4c2cee42..f71810c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,7 +202,7 @@ packages: source: hosted version: "4.8.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a diff --git a/pubspec.yaml b/pubspec.yaml index 289888d7..dabc5cff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: auto_size_text: ^3.0.0 bloc_concurrency: ^0.2.3 crypt: ^4.3.1 + collection: ^1.18.0 cubit_form: ^2.0.1 device_info_plus: ^9.1.1 dio: ^5.4.0