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",
|
"continue": "Continue",
|
||||||
"alert": "Alert",
|
"alert": "Alert",
|
||||||
"copied_to_clipboard": "Copied to clipboard!",
|
"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": {
|
"more_page": {
|
||||||
"configuration_wizard": "Setup wizard",
|
"configuration_wizard": "Setup wizard",
|
||||||
|
@ -394,7 +395,8 @@
|
||||||
"could_not_add_ssh_key": "Couldn't add SSH key",
|
"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",
|
"username_rule": "Username must contain only lowercase latin letters, digits and underscores, should not start with a digit",
|
||||||
"email_login": "Email login",
|
"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": {
|
"initializing": {
|
||||||
"server_provider_description": "A place where your data and SelfPrivacy services will reside:",
|
"server_provider_description": "A place where your data and SelfPrivacy services will reside:",
|
||||||
|
@ -594,6 +596,7 @@
|
||||||
"service_turn_off": "Turn off",
|
"service_turn_off": "Turn off",
|
||||||
"service_turn_on": "Turn on",
|
"service_turn_on": "Turn on",
|
||||||
"job_added": "Job added",
|
"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",
|
"run_jobs": "Run jobs",
|
||||||
"reboot_success": "Server is rebooting",
|
"reboot_success": "Server is rebooting",
|
||||||
"reboot_failed": "Couldn't reboot the server. Check the app logs.",
|
"reboot_failed": "Couldn't reboot the server. Check the app logs.",
|
||||||
|
@ -606,7 +609,11 @@
|
||||||
"delete_ssh_key": "Delete SSH key for {}",
|
"delete_ssh_key": "Delete SSH key for {}",
|
||||||
"server_jobs": "Jobs on the server",
|
"server_jobs": "Jobs on the server",
|
||||||
"reset_user_password": "Reset password of user",
|
"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": {
|
"validations": {
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
|
|
|
@ -104,9 +104,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
),
|
),
|
||||||
BlocProvider(create: (final _) => volumesBloc),
|
BlocProvider(create: (final _) => volumesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => JobsCubit(
|
create: (final _) => JobsCubit(),
|
||||||
servicesBloc: servicesBloc,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
|
|
|
@ -52,6 +52,14 @@ mutation RunSystemRebuild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation RunSystemRebuildFallback {
|
||||||
|
system {
|
||||||
|
runSystemRebuild {
|
||||||
|
...basicMutationReturnFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation RunSystemRollback {
|
mutation RunSystemRollback {
|
||||||
system {
|
system {
|
||||||
runSystemRollback {
|
runSystemRollback {
|
||||||
|
@ -71,6 +79,14 @@ mutation RunSystemUpgrade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation RunSystemUpgradeFallback {
|
||||||
|
system {
|
||||||
|
runSystemUpgrade {
|
||||||
|
...basicMutationReturnFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation PullRepositoryChanges {
|
mutation PullRepositoryChanges {
|
||||||
system {
|
system {
|
||||||
pullRepositoryChanges {
|
pullRepositoryChanges {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,11 @@ mixin ServerActionsApi on GraphQLApiMap {
|
||||||
print(response.exception.toString());
|
print(response.exception.toString());
|
||||||
}
|
}
|
||||||
if (response.parsedData!.system.rebootSystem.success) {
|
if (response.parsedData!.system.rebootSystem.success) {
|
||||||
time = DateTime.now().toUtc();
|
return GenericResult(
|
||||||
|
data: time,
|
||||||
|
success: true,
|
||||||
|
message: response.parsedData!.system.rebootSystem.message,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -50,23 +54,94 @@ mixin ServerActionsApi on GraphQLApiMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> upgrade() async {
|
Future<GenericResult<ServerJob?>> upgrade() async {
|
||||||
try {
|
try {
|
||||||
final GraphQLClient client = await getClient();
|
final GraphQLClient client = await getClient();
|
||||||
return _commonBoolRequest(
|
final result = await client.mutate$RunSystemUpgrade();
|
||||||
() async => 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) {
|
} catch (e) {
|
||||||
return false;
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
message: e.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> apply() async {
|
Future<GenericResult<ServerJob?>> apply() async {
|
||||||
try {
|
try {
|
||||||
final GraphQLClient client = await getClient();
|
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) {
|
} catch (e) {
|
||||||
print(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>();
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
(final ApiData apiData) {
|
(final ApiData apiData) {
|
||||||
print('============');
|
|
||||||
print(apiData.devices.data);
|
|
||||||
add(
|
add(
|
||||||
DevicesListChanged(apiData.devices.data),
|
DevicesListChanged(apiData.devices.data),
|
||||||
);
|
);
|
||||||
|
@ -42,7 +40,6 @@ class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||||
if (state is DevicesDeleting) {
|
if (state is DevicesDeleting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
print(event.devices);
|
|
||||||
if (event.devices == null) {
|
if (event.devices == null) {
|
||||||
emit(DevicesError());
|
emit(DevicesError());
|
||||||
return;
|
return;
|
||||||
|
@ -103,7 +100,6 @@ class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||||
@override
|
@override
|
||||||
void onChange(final Change<DevicesState> change) {
|
void onChange(final Change<DevicesState> change) {
|
||||||
super.onChange(change);
|
super.onChange(change);
|
||||||
print(change);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,33 +1,55 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.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/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/job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
part 'client_jobs_state.dart';
|
part 'client_jobs_state.dart';
|
||||||
|
|
||||||
class JobsCubit extends Cubit<JobsState> {
|
class JobsCubit extends Cubit<JobsState> {
|
||||||
JobsCubit({
|
JobsCubit() : super(JobsStateEmpty()) {
|
||||||
required this.servicesBloc,
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
}) : super(JobsStateEmpty());
|
_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 ServerApi api = ServerApi();
|
||||||
final ServicesBloc servicesBloc;
|
|
||||||
|
|
||||||
void addJob(final ClientJob job) {
|
void _handleServerJobs(final List<ServerJob> jobs) {
|
||||||
final jobs = currentJobList;
|
if (state is! JobsStateLoading) {
|
||||||
if (job.canAddTo(jobs)) {
|
return;
|
||||||
_updateJobsState([
|
|
||||||
...jobs,
|
|
||||||
...[job],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
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) {
|
void removeJob(final String id) {
|
||||||
|
@ -35,61 +57,145 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
emit(newState);
|
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 {
|
Future<void> rebootServer() async {
|
||||||
emit(JobsStateLoading());
|
if (state is JobsStateEmpty) {
|
||||||
final rebootResult = await api.reboot();
|
emit(
|
||||||
if (rebootResult.success && rebootResult.data != null) {
|
JobsStateLoading(
|
||||||
getIt<NavigationService>().showSnackBar('jobs.reboot_success'.tr());
|
[RebootServerJob(status: JobStatusEnum.running)],
|
||||||
} else {
|
null,
|
||||||
getIt<NavigationService>().showSnackBar('jobs.reboot_failed'.tr());
|
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 {
|
Future<void> upgradeServer() async {
|
||||||
emit(JobsStateLoading());
|
if (state is JobsStateEmpty) {
|
||||||
final bool isPullSuccessful = await api.pullConfigurationUpdate();
|
emit(
|
||||||
final bool isSuccessful = await api.upgrade();
|
JobsStateLoading(
|
||||||
if (isSuccessful) {
|
[UpgradeServerJob(status: JobStatusEnum.running)],
|
||||||
if (!isPullSuccessful) {
|
null,
|
||||||
getIt<NavigationService>().showSnackBar('jobs.config_pull_failed'.tr());
|
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 {
|
} 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 {
|
Future<void> applyAll() async {
|
||||||
if (state is JobsStateWithJobs) {
|
if (state is JobsStateWithJobs) {
|
||||||
final List<ClientJob> jobs = (state as JobsStateWithJobs).clientJobList;
|
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) {
|
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();
|
if (!rebuildRequired) {
|
||||||
await api.apply();
|
emit((state as JobsStateLoading).finished());
|
||||||
servicesBloc.add(const ServicesReload());
|
return;
|
||||||
|
}
|
||||||
emit(JobsStateEmpty());
|
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';
|
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
|
@override
|
||||||
List<Object?> get props => [];
|
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 {
|
class JobsStateWithJobs extends JobsState {
|
||||||
JobsStateWithJobs(this.clientJobList);
|
JobsStateWithJobs(this.clientJobList);
|
||||||
final List<ClientJob> clientJobList;
|
final List<ClientJob> clientJobList;
|
||||||
|
|
||||||
|
bool get rebuildRequired =>
|
||||||
|
clientJobList.any((final job) => job.requiresRebuild);
|
||||||
|
|
||||||
JobsState removeById(final String id) {
|
JobsState removeById(final String id) {
|
||||||
final List<ClientJob> newJobsList =
|
final List<ClientJob> newJobsList =
|
||||||
clientJobList.where((final element) => element.id != id).toList();
|
clientJobList.where((final element) => element.id != id).toList();
|
||||||
|
@ -22,5 +37,118 @@ class JobsStateWithJobs extends JobsState {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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;
|
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 {
|
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;
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
if (loadedUsers == null) {
|
if (loadedUsers == null) {
|
||||||
return;
|
return (false, 'basis.network_error'.tr());
|
||||||
}
|
}
|
||||||
// If user exists on server, do nothing
|
// If user exists on server, do nothing
|
||||||
if (loadedUsers
|
if (loadedUsers
|
||||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||||
return;
|
return (false, 'users.user_already_exists'.tr());
|
||||||
}
|
}
|
||||||
final String? password = user.password;
|
final String? password = user.password;
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
getIt<NavigationService>()
|
return (false, 'users.could_not_create_user'.tr());
|
||||||
.showSnackBar('users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// If API returned error, do nothing
|
// If API returned error, do nothing
|
||||||
final GenericResult<User?> result =
|
final GenericResult<User?> result =
|
||||||
await api.createUser(user.login, password);
|
await api.createUser(user.login, password);
|
||||||
if (result.data == null) {
|
if (result.data == null) {
|
||||||
getIt<NavigationService>()
|
return (false, result.message ?? 'users.could_not_create_user'.tr());
|
||||||
.showSnackBar(result.message ?? 'users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_apiData.users.data?.add(result.data!);
|
_apiData.users.data?.add(result.data!);
|
||||||
_apiData.users.invalidate();
|
_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;
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
if (loadedUsers == null) {
|
if (loadedUsers == null) {
|
||||||
return;
|
return (false, 'basis.network_error'.tr());
|
||||||
}
|
}
|
||||||
// If user is primary or root, don't delete
|
// If user is primary or root, don't delete
|
||||||
if (user.type != UserType.normal) {
|
if (user.type != UserType.normal) {
|
||||||
getIt<NavigationService>()
|
return (false, 'users.could_not_delete_user'.tr());
|
||||||
.showSnackBar('users.could_not_delete_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
final GenericResult result = await api.deleteUser(user.login);
|
final GenericResult result = await api.deleteUser(user.login);
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
|
@ -117,19 +113,18 @@ class ApiConnectionRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.success || !result.data) {
|
if (!result.success || !result.data) {
|
||||||
getIt<NavigationService>()
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changeUserPassword(
|
Future<(bool, String)> changeUserPassword(
|
||||||
final User user,
|
final User user,
|
||||||
final String newPassword,
|
final String newPassword,
|
||||||
) async {
|
) async {
|
||||||
if (user.type == UserType.root) {
|
if (user.type == UserType.root) {
|
||||||
getIt<NavigationService>()
|
return (false, 'users.could_not_change_password'.tr());
|
||||||
.showSnackBar('users.could_not_change_password'.tr());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
final GenericResult<User?> result = await api.updateUser(
|
final GenericResult<User?> result = await api.updateUser(
|
||||||
user.login,
|
user.login,
|
||||||
|
@ -139,13 +134,21 @@ class ApiConnectionRepository {
|
||||||
getIt<NavigationService>().showSnackBar(
|
getIt<NavigationService>().showSnackBar(
|
||||||
result.message ?? 'users.could_not_change_password'.tr(),
|
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;
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
if (loadedUsers == null) {
|
if (loadedUsers == null) {
|
||||||
return;
|
return (false, 'basis.network_error'.tr());
|
||||||
}
|
}
|
||||||
final GenericResult<User?> result =
|
final GenericResult<User?> result =
|
||||||
await api.addSshKey(user.login, publicKey);
|
await api.addSshKey(user.login, publicKey);
|
||||||
|
@ -156,15 +159,19 @@ class ApiConnectionRepository {
|
||||||
loadedUsers[index] = updatedUser;
|
loadedUsers[index] = updatedUser;
|
||||||
_apiData.users.invalidate();
|
_apiData.users.invalidate();
|
||||||
} else {
|
} else {
|
||||||
getIt<NavigationService>()
|
return (false, result.message ?? 'users.could_not_add_ssh_key'.tr());
|
||||||
.showSnackBar(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;
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
if (loadedUsers == null) {
|
if (loadedUsers == null) {
|
||||||
return;
|
return (false, 'basis.network_error'.tr());
|
||||||
}
|
}
|
||||||
final GenericResult<User?> result =
|
final GenericResult<User?> result =
|
||||||
await api.removeSshKey(user.login, publicKey);
|
await api.removeSshKey(user.login, publicKey);
|
||||||
|
@ -175,9 +182,9 @@ class ApiConnectionRepository {
|
||||||
loadedUsers[index] = updatedUser;
|
loadedUsers[index] = updatedUser;
|
||||||
_apiData.users.invalidate();
|
_apiData.users.invalidate();
|
||||||
} else {
|
} else {
|
||||||
getIt<NavigationService>()
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
}
|
}
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -345,11 +352,8 @@ class ApiDataElement<T> {
|
||||||
final Function callback,
|
final Function callback,
|
||||||
) async {
|
) async {
|
||||||
if (VersionConstraint.parse(requiredApiVersion).allows(version)) {
|
if (VersionConstraint.parse(requiredApiVersion).allows(version)) {
|
||||||
print('Fetching data for $runtimeType');
|
|
||||||
if (isExpired) {
|
if (isExpired) {
|
||||||
print('Data is expired');
|
|
||||||
final newData = await fetchData();
|
final newData = await fetchData();
|
||||||
print(newData);
|
|
||||||
if (T is List) {
|
if (T is List) {
|
||||||
if (Object.hashAll(newData as Iterable<Object?>) !=
|
if (Object.hashAll(newData as Iterable<Object?>) !=
|
||||||
Object.hashAll(_data 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:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.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/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
|
@ -12,18 +14,31 @@ abstract class ClientJob extends Equatable {
|
||||||
ClientJob({
|
ClientJob({
|
||||||
required this.title,
|
required this.title,
|
||||||
final String? id,
|
final String? id,
|
||||||
|
this.requiresRebuild = true,
|
||||||
|
this.status = JobStatusEnum.created,
|
||||||
|
this.message,
|
||||||
}) : id = id ?? StringGenerators.simpleId();
|
}) : id = id ?? StringGenerators.simpleId();
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String id;
|
final String id;
|
||||||
|
final bool requiresRebuild;
|
||||||
|
|
||||||
|
final JobStatusEnum status;
|
||||||
|
final String? message;
|
||||||
|
|
||||||
bool canAddTo(final List<ClientJob> jobs) => true;
|
bool canAddTo(final List<ClientJob> jobs) => true;
|
||||||
void execute(final JobsCubit cubit);
|
Future<(bool, String)> execute(final JobsCubit cubit);
|
||||||
|
|
||||||
@override
|
@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 {
|
class RebuildServerJob extends ClientJob {
|
||||||
RebuildServerJob({
|
RebuildServerJob({
|
||||||
required super.title,
|
required super.title,
|
||||||
|
@ -35,47 +50,138 @@ class RebuildServerJob extends ClientJob {
|
||||||
!jobs.any((final job) => job is RebuildServerJob);
|
!jobs.any((final job) => job is RebuildServerJob);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await cubit.upgradeServer();
|
(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 {
|
class CreateUserJob extends ClientJob {
|
||||||
CreateUserJob({
|
CreateUserJob({
|
||||||
required this.user,
|
required this.user,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(title: '${"jobs.create_user".tr()} ${user.login}');
|
}) : super(title: '${"jobs.create_user".tr()} ${user.login}');
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await getIt<ApiConnectionRepository>().createUser(user);
|
getIt<ApiConnectionRepository>().createUser(user);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@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 {
|
class ResetUserPasswordJob extends ClientJob {
|
||||||
ResetUserPasswordJob({
|
ResetUserPasswordJob({
|
||||||
required this.user,
|
required this.user,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(title: '${"jobs.reset_user_password".tr()} ${user.login}');
|
}) : super(title: '${"jobs.reset_user_password".tr()} ${user.login}');
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await getIt<ApiConnectionRepository>()
|
getIt<ApiConnectionRepository>().changeUserPassword(user, user.password!);
|
||||||
.changeUserPassword(user, user.password!);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@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 {
|
class DeleteUserJob extends ClientJob {
|
||||||
DeleteUserJob({
|
DeleteUserJob({
|
||||||
required this.user,
|
required this.user,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(title: '${"jobs.delete_user".tr()} ${user.login}');
|
}) : super(title: '${"jobs.delete_user".tr()} ${user.login}');
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
|
@ -86,18 +192,32 @@ class DeleteUserJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await getIt<ApiConnectionRepository>().deleteUser(user);
|
getIt<ApiConnectionRepository>().deleteUser(user);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@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 {
|
class ServiceToggleJob extends ClientJob {
|
||||||
ServiceToggleJob({
|
ServiceToggleJob({
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.needToTurnOn,
|
required this.needToTurnOn,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(
|
}) : super(
|
||||||
title:
|
title:
|
||||||
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}',
|
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}',
|
||||||
|
@ -112,36 +232,68 @@ class ServiceToggleJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async {
|
||||||
await cubit.api.switchService(service.id, needToTurnOn);
|
await cubit.api.switchService(service.id, needToTurnOn);
|
||||||
|
return (true, 'Check not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [...super.props, service];
|
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 {
|
class CreateSSHKeyJob extends ClientJob {
|
||||||
CreateSSHKeyJob({
|
CreateSSHKeyJob({
|
||||||
required this.user,
|
required this.user,
|
||||||
required this.publicKey,
|
required this.publicKey,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login]));
|
}) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login]));
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
final String publicKey;
|
final String publicKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
|
getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@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 {
|
class DeleteSSHKeyJob extends ClientJob {
|
||||||
DeleteSSHKeyJob({
|
DeleteSSHKeyJob({
|
||||||
required this.user,
|
required this.user,
|
||||||
required this.publicKey,
|
required this.publicKey,
|
||||||
|
super.status,
|
||||||
|
super.message,
|
||||||
|
super.id,
|
||||||
}) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login]));
|
}) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login]));
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
|
@ -156,10 +308,114 @@ class DeleteSSHKeyJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute(final JobsCubit cubit) async =>
|
||||||
await getIt<ApiConnectionRepository>().deleteSshKey(user, publicKey);
|
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
|
@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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_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/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/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
@ -19,6 +19,32 @@ class JobsContent extends StatelessWidget {
|
||||||
|
|
||||||
final ScrollController controller;
|
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
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final List<ServerJob> serverJobs =
|
final List<ServerJob> serverJobs =
|
||||||
|
@ -68,8 +94,274 @@ class JobsContent extends StatelessWidget {
|
||||||
}
|
}
|
||||||
} else if (state is JobsStateLoading) {
|
} else if (state is JobsStateLoading) {
|
||||||
widgets = [
|
widgets = [
|
||||||
const SizedBox(height: 80),
|
...state.clientJobList.map(
|
||||||
BrandLoader.horizontal(),
|
(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) {
|
} else if (state is JobsStateWithJobs) {
|
||||||
widgets = [
|
widgets = [
|
||||||
|
@ -84,19 +376,31 @@ class JobsContent extends StatelessWidget {
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
vertical: 10,
|
vertical: 10,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Column(
|
||||||
j.title,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style:
|
children: [
|
||||||
Theme.of(context).textTheme.labelLarge?.copyWith(
|
Text(
|
||||||
|
j.title,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelLarge
|
||||||
|
?.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurfaceVariant,
|
.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if (j.message != null)
|
||||||
|
Text(
|
||||||
|
j.message!,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
@ -116,7 +420,7 @@ class JobsContent extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 16),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||||
text: 'jobs.start'.tr(),
|
text: 'jobs.start'.tr(),
|
||||||
|
@ -161,23 +465,25 @@ class JobsContent extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...serverJobs.map(
|
...serverJobs
|
||||||
(final job) => Dismissible(
|
.whereNot((final job) => job.uid == state.rebuildJobUid)
|
||||||
key: ValueKey(job.uid),
|
.map(
|
||||||
direction: job.status == JobStatusEnum.finished ||
|
(final job) => Dismissible(
|
||||||
job.status == JobStatusEnum.error
|
key: ValueKey(job.uid),
|
||||||
? DismissDirection.horizontal
|
direction: job.status == JobStatusEnum.finished ||
|
||||||
: DismissDirection.none,
|
job.status == JobStatusEnum.error
|
||||||
child: ServerJobCard(
|
? DismissDirection.horizontal
|
||||||
serverJob: job,
|
: 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),
|
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/metrics/metrics_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_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/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/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||||
|
|
|
@ -30,24 +30,32 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
||||||
value: allowAutoUpgrade ?? false,
|
value: allowAutoUpgrade ?? false,
|
||||||
onChanged: (final switched) {
|
onChanged: (final switched) {
|
||||||
context.read<JobsCubit>().addJob(
|
context.read<JobsCubit>().addJob(
|
||||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
ChangeAutoUpgradeSettingsJob(
|
||||||
);
|
|
||||||
context
|
|
||||||
.read<ServerDetailsCubit>()
|
|
||||||
.repository
|
|
||||||
.setAutoUpgradeSettings(
|
|
||||||
AutoUpgradeSettings(
|
|
||||||
enable: switched,
|
|
||||||
allowReboot: rebootAfterUpgrade ?? false,
|
allowReboot: rebootAfterUpgrade ?? false,
|
||||||
|
enable: switched,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
allowAutoUpgrade = switched;
|
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(
|
subtitle: Text(
|
||||||
'server.allow_autoupgrade_hint'.tr(),
|
'server.allow_autoupgrade_hint'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: allowAutoUpgrade !=
|
||||||
|
serverDetailsState.autoUpgradeSettings.enable
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
activeColor: Theme.of(context).colorScheme.primary,
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
@ -55,24 +63,32 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
||||||
value: rebootAfterUpgrade ?? false,
|
value: rebootAfterUpgrade ?? false,
|
||||||
onChanged: (final switched) {
|
onChanged: (final switched) {
|
||||||
context.read<JobsCubit>().addJob(
|
context.read<JobsCubit>().addJob(
|
||||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
ChangeAutoUpgradeSettingsJob(
|
||||||
);
|
|
||||||
context
|
|
||||||
.read<ServerDetailsCubit>()
|
|
||||||
.repository
|
|
||||||
.setAutoUpgradeSettings(
|
|
||||||
AutoUpgradeSettings(
|
|
||||||
enable: allowAutoUpgrade ?? false,
|
|
||||||
allowReboot: switched,
|
allowReboot: switched,
|
||||||
|
enable: allowAutoUpgrade ?? false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
rebootAfterUpgrade = switched;
|
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(
|
subtitle: Text(
|
||||||
'server.reboot_after_upgrade_hint'.tr(),
|
'server.reboot_after_upgrade_hint'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: rebootAfterUpgrade !=
|
||||||
|
serverDetailsState.autoUpgradeSettings.allowReboot
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
activeColor: Theme.of(context).colorScheme.primary,
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
@ -82,9 +98,6 @@ class _ServerSettingsState extends State<_ServerSettings> {
|
||||||
serverDetailsState.serverTimezone.toString(),
|
serverDetailsState.serverTimezone.toString(),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<JobsCubit>().addJob(
|
|
||||||
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
|
|
||||||
);
|
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
materialRoute(
|
materialRoute(
|
||||||
const SelectTimezone(),
|
const SelectTimezone(),
|
||||||
|
|
|
@ -140,8 +140,10 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
||||||
'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<ServerDetailsCubit>().repository.setTimezone(
|
context.read<JobsCubit>().addJob(
|
||||||
location.name,
|
ChangeServerTimezoneJob(
|
||||||
|
timezone: location.name,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
|
|
@ -202,7 +202,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
|
|
@ -13,6 +13,7 @@ dependencies:
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
bloc_concurrency: ^0.2.3
|
bloc_concurrency: ^0.2.3
|
||||||
crypt: ^4.3.1
|
crypt: ^4.3.1
|
||||||
|
collection: ^1.18.0
|
||||||
cubit_form: ^2.0.1
|
cubit_form: ^2.0.1
|
||||||
device_info_plus: ^9.1.1
|
device_info_plus: ^9.1.1
|
||||||
dio: ^5.4.0
|
dio: ^5.4.0
|
||||||
|
|
Loading…
Reference in a new issue