mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-10-31 22:17:29 +00:00
refactor: Rework ClientJobs cubit so it doesn't depend on other cubits
Also implemented tracking of the jobs and rebuild status
This commit is contained in:
parent
fdb40fccd7
commit
16094a3257
|
@ -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",
|
||||
|
|
|
@ -104,9 +104,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
|||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(
|
||||
servicesBloc: servicesBloc,
|
||||
),
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
|
|
|
@ -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 {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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<bool> upgrade() async {
|
||||
Future<GenericResult<ServerJob?>> 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<void> apply() async {
|
||||
Future<GenericResult<ServerJob?>> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,6 @@ class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
|||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_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<DevicesEvent, DevicesState> {
|
|||
if (state is DevicesDeleting) {
|
||||
return;
|
||||
}
|
||||
print(event.devices);
|
||||
if (event.devices == null) {
|
||||
emit(DevicesError());
|
||||
return;
|
||||
|
@ -103,7 +100,6 @@ class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
|||
@override
|
||||
void onChange(final Change<DevicesState> change) {
|
||||
super.onChange(change);
|
||||
print(change);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -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<JobsState> {
|
||||
JobsCubit({
|
||||
required this.servicesBloc,
|
||||
}) : super(JobsStateEmpty());
|
||||
JobsCubit() : super(JobsStateEmpty()) {
|
||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||
_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<ServerJob> 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<JobsState> {
|
|||
emit(newState);
|
||||
}
|
||||
|
||||
List<ClientJob> get currentJobList {
|
||||
final List<ClientJob> jobs = <ClientJob>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
jobs.addAll((state as JobsStateWithJobs).clientJobList);
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
void _updateJobsState(final List<ClientJob> newJobs) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
emit(JobsStateWithJobs(newJobs));
|
||||
}
|
||||
|
||||
Future<void> rebootServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final rebootResult = await api.reboot();
|
||||
if (rebootResult.success && rebootResult.data != null) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.reboot_success'.tr());
|
||||
} else {
|
||||
getIt<NavigationService>().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<void> upgradeServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final bool isPullSuccessful = await api.pullConfigurationUpdate();
|
||||
final bool isSuccessful = await api.upgrade();
|
||||
if (isSuccessful) {
|
||||
if (!isPullSuccessful) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.config_pull_failed'.tr());
|
||||
if (state is JobsStateEmpty) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[UpgradeServerJob(status: JobStatusEnum.running)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
final result = await getIt<ApiConnectionRepository>().api.upgrade();
|
||||
if (result.success && result.data != null) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[UpgradeServerJob(status: JobStatusEnum.finished)],
|
||||
result.data!.uid,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.upgrade_success'.tr());
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[UpgradeServerJob(status: JobStatusEnum.error)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar('jobs.upgrade_failed'.tr());
|
||||
}
|
||||
emit(JobsStateEmpty());
|
||||
}
|
||||
|
||||
Future<void> applyAll() async {
|
||||
if (state is JobsStateWithJobs) {
|
||||
final List<ClientJob> jobs = (state as JobsStateWithJobs).clientJobList;
|
||||
emit(JobsStateLoading());
|
||||
emit(JobsStateLoading(jobs, null, const []));
|
||||
|
||||
await Future<void>.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<ApiConnectionRepository>().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<void> 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<ApiConnectionRepository>().removeServerJob(rebuildJobUid);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<JobsState> change) {
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_apiDataSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Object?> get props => [];
|
||||
}
|
||||
|
||||
class JobsStateLoading extends JobsState {}
|
||||
class JobsStateEmpty extends JobsState {
|
||||
@override
|
||||
JobsStateWithJobs addJob(final ClientJob job) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs([job]);
|
||||
}
|
||||
|
||||
class JobsStateEmpty extends JobsState {}
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class JobsStateWithJobs extends JobsState {
|
||||
JobsStateWithJobs(this.clientJobList);
|
||||
final List<ClientJob> clientJobList;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
JobsState removeById(final String id) {
|
||||
final List<ClientJob> newJobsList =
|
||||
clientJobList.where((final element) => element.id != id).toList();
|
||||
|
@ -22,5 +37,118 @@ class JobsStateWithJobs extends JobsState {
|
|||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => clientJobList;
|
||||
List<Object?> get props => [clientJobList];
|
||||
|
||||
@override
|
||||
JobsState addJob(final ClientJob job) {
|
||||
if (job is ReplaceableJob) {
|
||||
final List<ClientJob> newJobsList = clientJobList
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newJobsList);
|
||||
}
|
||||
if (job.canAddTo(clientJobList)) {
|
||||
final List<ClientJob> newJobsList = [...clientJobList, job];
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newJobsList);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class JobsStateLoading extends JobsState {
|
||||
JobsStateLoading(this.clientJobList, this.rebuildJobUid, this.postponedJobs);
|
||||
final List<ClientJob> clientJobList;
|
||||
@override
|
||||
final String? rebuildJobUid;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
final List<ClientJob> postponedJobs;
|
||||
|
||||
JobsStateLoading updateJobStatus(
|
||||
final String id,
|
||||
final JobStatusEnum status, {
|
||||
final String? message,
|
||||
}) {
|
||||
final List<ClientJob> 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<ClientJob>? clientJobList,
|
||||
final String? rebuildJobUid,
|
||||
final List<ClientJob>? postponedJobs,
|
||||
}) =>
|
||||
JobsStateLoading(
|
||||
clientJobList ?? this.clientJobList,
|
||||
rebuildJobUid ?? this.rebuildJobUid,
|
||||
postponedJobs ?? this.postponedJobs,
|
||||
);
|
||||
|
||||
JobsStateFinished finished() =>
|
||||
JobsStateFinished(clientJobList, rebuildJobUid, postponedJobs);
|
||||
|
||||
@override
|
||||
List<Object?> 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<ClientJob> newPostponedJobs = postponedJobs
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
newPostponedJobs.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_postponed'.tr());
|
||||
return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs);
|
||||
}
|
||||
if (job.canAddTo(postponedJobs)) {
|
||||
final List<ClientJob> newPostponedJobs = [...postponedJobs, job];
|
||||
getIt<NavigationService>().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<ClientJob> clientJobList;
|
||||
@override
|
||||
final String? rebuildJobUid;
|
||||
|
||||
bool get rebuildRequired =>
|
||||
clientJobList.any((final job) => job.requiresRebuild);
|
||||
|
||||
final List<ClientJob> postponedJobs;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [clientJobList, rebuildJobUid, postponedJobs];
|
||||
|
||||
@override
|
||||
JobsState addJob(final ClientJob job) {
|
||||
if (job is ReplaceableJob) {
|
||||
final List<ClientJob> newPostponedJobs = postponedJobs
|
||||
.where((final element) => element.runtimeType != job.runtimeType)
|
||||
.toList();
|
||||
newPostponedJobs.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newPostponedJobs);
|
||||
}
|
||||
if (job.canAddTo(postponedJobs)) {
|
||||
final List<ClientJob> newPostponedJobs = [...postponedJobs, job];
|
||||
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||
return JobsStateWithJobs(newPostponedJobs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,20 +40,6 @@ class ServerDetailsRepository {
|
|||
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<void> setAutoUpgradeSettings(
|
||||
final AutoUpgradeSettings settings,
|
||||
) async {
|
||||
await server.setAutoUpgradeSettings(settings);
|
||||
}
|
||||
|
||||
Future<void> setTimezone(
|
||||
final String timezone,
|
||||
) async {
|
||||
if (timezone.isNotEmpty) {
|
||||
await server.setTimezone(timezone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ServerDetailsRepositoryDto {
|
||||
|
|
|
@ -70,45 +70,41 @@ class ApiConnectionRepository {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> createUser(final User user) async {
|
||||
Future<(bool, String)> createUser(final User user) async {
|
||||
final List<User>? 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<NavigationService>()
|
||||
.showSnackBar('users.could_not_create_user'.tr());
|
||||
return;
|
||||
return (false, 'users.could_not_create_user'.tr());
|
||||
}
|
||||
// If API returned error, do nothing
|
||||
final GenericResult<User?> result =
|
||||
await api.createUser(user.login, password);
|
||||
if (result.data == null) {
|
||||
getIt<NavigationService>()
|
||||
.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<void> deleteUser(final User user) async {
|
||||
Future<(bool, String)> deleteUser(final User user) async {
|
||||
final List<User>? 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<NavigationService>()
|
||||
.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<NavigationService>()
|
||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||
}
|
||||
|
||||
return (true, result.message ?? 'basis.done'.tr());
|
||||
}
|
||||
|
||||
Future<void> changeUserPassword(
|
||||
Future<(bool, String)> changeUserPassword(
|
||||
final User user,
|
||||
final String newPassword,
|
||||
) async {
|
||||
if (user.type == UserType.root) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('users.could_not_change_password'.tr());
|
||||
return;
|
||||
return (false, 'users.could_not_change_password'.tr());
|
||||
}
|
||||
final GenericResult<User?> result = await api.updateUser(
|
||||
user.login,
|
||||
|
@ -139,13 +134,21 @@ class ApiConnectionRepository {
|
|||
getIt<NavigationService>().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<void> addSshKey(final User user, final String publicKey) async {
|
||||
Future<(bool, String)> addSshKey(
|
||||
final User user,
|
||||
final String publicKey,
|
||||
) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return;
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
final GenericResult<User?> result =
|
||||
await api.addSshKey(user.login, publicKey);
|
||||
|
@ -156,15 +159,19 @@ class ApiConnectionRepository {
|
|||
loadedUsers[index] = updatedUser;
|
||||
_apiData.users.invalidate();
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.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<void> deleteSshKey(final User user, final String publicKey) async {
|
||||
Future<(bool, String)> deleteSshKey(
|
||||
final User user,
|
||||
final String publicKey,
|
||||
) async {
|
||||
final List<User>? loadedUsers = _apiData.users.data;
|
||||
if (loadedUsers == null) {
|
||||
return;
|
||||
return (false, 'basis.network_error'.tr());
|
||||
}
|
||||
final GenericResult<User?> result =
|
||||
await api.removeSshKey(user.login, publicKey);
|
||||
|
@ -175,9 +182,9 @@ class ApiConnectionRepository {
|
|||
loadedUsers[index] = updatedUser;
|
||||
_apiData.users.invalidate();
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.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<T> {
|
|||
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?>) !=
|
||||
Object.hashAll(_data as Iterable<Object?>)) {
|
||||
|
|
|
@ -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<ClientJob> jobs) => true;
|
||||
void execute(final JobsCubit cubit);
|
||||
Future<(bool, String)> execute(final JobsCubit cubit);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title];
|
||||
List<Object> 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<ClientJob> 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<ClientJob> 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<ApiConnectionRepository>().createUser(user);
|
||||
}
|
||||
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||
getIt<ApiConnectionRepository>().createUser(user);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> 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<ApiConnectionRepository>()
|
||||
.changeUserPassword(user, user.password!);
|
||||
}
|
||||
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||
getIt<ApiConnectionRepository>().changeUserPassword(user, user.password!);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> 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<ApiConnectionRepository>().deleteUser(user);
|
||||
}
|
||||
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||
getIt<ApiConnectionRepository>().deleteUser(user);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user];
|
||||
List<Object> 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<Object> 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<ApiConnectionRepository>().addSshKey(user, publicKey);
|
||||
}
|
||||
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||
getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title, user, publicKey];
|
||||
List<Object> 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<ApiConnectionRepository>().deleteSshKey(user, publicKey);
|
||||
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||
getIt<ApiConnectionRepository>().deleteSshKey(user, publicKey);
|
||||
|
||||
@override
|
||||
List<Object> 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<ClientJob> 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<Object> get props => [id, title, user, publicKey];
|
||||
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||
// TODO: Finish this
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> 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<ApiConnectionRepository>().api.setTimezone(timezone);
|
||||
return (true, 'Check not implemented');
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||
// TODO: Finish this
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [...super.props, timezone];
|
||||
|
||||
@override
|
||||
ChangeServerTimezoneJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
ChangeServerTimezoneJob(
|
||||
timezone: timezone,
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<ServerJob> 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<JobsCubit>().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<JobsCubit>().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<ServerJobsBloc>().add(
|
||||
RemoveServerJob(job.uid),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissed: (final direction) {
|
||||
context.read<ServerJobsBloc>().add(
|
||||
RemoveServerJob(job.uid),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -30,24 +30,32 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
|||
value: allowAutoUpgrade ?? false,
|
||||
onChanged: (final switched) {
|
||||
context.read<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
context
|
||||
.read<ServerDetailsCubit>()
|
||||
.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<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
context
|
||||
.read<ServerDetailsCubit>()
|
||||
.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<JobsCubit>().addJob(
|
||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
||||
);
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const SelectTimezone(),
|
||||
|
|
|
@ -140,8 +140,10 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
|||
'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
||||
),
|
||||
onTap: () {
|
||||
context.read<ServerDetailsCubit>().repository.setTimezone(
|
||||
location.name,
|
||||
context.read<JobsCubit>().addJob(
|
||||
ChangeServerTimezoneJob(
|
||||
timezone: location.name,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
|
|
@ -202,7 +202,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.8.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue