Allow removing completed and failed server jobs

pull/116/head
Inex Code 2022-09-18 23:12:09 +03:00
parent 19aab4b57f
commit e330878e6d
6 changed files with 194 additions and 116 deletions

View File

@ -482,7 +482,8 @@
"upgradeServer": "Обновить сервер",
"rebootServer": "Перезагрузить сервер",
"create_ssh_key": "Создать SSH ключ для {}",
"delete_ssh_key": "Удалить SSH ключ для {}"
"delete_ssh_key": "Удалить SSH ключ для {}",
"server_jobs": "Задачи на сервере"
},
"validations": {
"required": "Обязательное поле.",

View File

@ -22,14 +22,24 @@ mixin JobsApi on ApiMap {
return jobsList;
}
Future<void> removeApiJob(final String uid) async {
Future<GenericMutationResult> removeApiJob(final String uid) async {
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$RemoveJob(jobId: uid);
final mutation = Options$Mutation$RemoveJob(variables: variables);
await client.mutate$RemoveJob(mutation);
final response = await client.mutate$RemoveJob(mutation);
return GenericMutationResult(
success: response.parsedData?.removeJob.success ?? false,
code: response.parsedData?.removeJob.code ?? 0,
message: response.parsedData?.removeJob.message,
);
} catch (e) {
print(e);
return GenericMutationResult(
success: false,
code: 0,
message: e.toString(),
);
}
}
}

View File

@ -14,7 +14,7 @@ class ServerJobsCubit
ServerJobsCubit(final ServerInstallationCubit serverInstallationCubit)
: super(
serverInstallationCubit,
const ServerJobsState(),
ServerJobsState(),
);
Timer? timer;
@ -23,7 +23,7 @@ class ServerJobsCubit
@override
void clear() async {
emit(
const ServerJobsState(),
ServerJobsState(),
);
if (timer != null && timer!.isActive) {
timer!.cancel();
@ -40,7 +40,7 @@ class ServerJobsCubit
serverJobList: jobs,
),
);
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
}
}
@ -72,6 +72,44 @@ class ServerJobsCubit
return job;
}
/// Get the job object and change its isHidden to true.
/// Emit the new state.
/// Call the api to remove the job.
/// If the api call fails, change the isHidden to false and emit the new state.
/// If the api call succeeds, remove the job from the list and emit the new state.
Future<void> removeServerJob(final String uid) async {
final ServerJob? job = getServerJobByUid(uid);
if (job == null) {
return;
}
job.isHidden = true;
emit(
ServerJobsState(
serverJobList: state.serverJobList,
),
);
final result = await api.removeApiJob(uid);
if (!result.success) {
job.isHidden = false;
emit(
ServerJobsState(
serverJobList: state.serverJobList,
),
);
getIt<NavigationService>().showSnackBar(result.message!);
return;
}
state.serverJobList.remove(job);
emit(
ServerJobsState(
serverJobList: state.serverJobList,
),
);
}
Future<void> reload({final bool useTimer = false}) async {
final List<ServerJob> jobs = await api.getServerJobs();
emit(
@ -80,7 +118,7 @@ class ServerJobsCubit
),
);
if (useTimer) {
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
}
}
}

View File

@ -1,22 +1,28 @@
part of 'server_jobs_cubit.dart';
class ServerJobsState extends ServerInstallationDependendState {
const ServerJobsState({
this.serverJobList = const [],
ServerJobsState({
final serverJobList = const <ServerJob>[],
this.migrationJobUid,
});
final List<ServerJob> serverJobList;
}) {
_serverJobList = serverJobList;
}
late final List<ServerJob> _serverJobList;
final String? migrationJobUid;
List<ServerJob> get serverJobList =>
_serverJobList.where((final ServerJob job) => !job.isHidden).toList();
@override
List<Object?> get props => [migrationJobUid, ...serverJobList];
List<Object?> get props => [migrationJobUid, ..._serverJobList];
ServerJobsState copyWith({
final List<ServerJob>? serverJobList,
final String? migrationJobUid,
}) =>
ServerJobsState(
serverJobList: serverJobList ?? this.serverJobList,
serverJobList: serverJobList ?? _serverJobList,
migrationJobUid: migrationJobUid ?? this.migrationJobUid,
);
}

View File

@ -47,6 +47,7 @@ class ServerJob {
final String? result;
final String? statusText;
final DateTime? finishedAt;
bool isHidden = false;
}
enum JobStatusEnum {

View File

@ -6,6 +6,7 @@ import 'package:selfprivacy/config/get_it_config.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_jobs/server_jobs_cubit.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart';
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -18,118 +19,139 @@ class JobsContent extends StatelessWidget {
const JobsContent({final super.key});
@override
Widget build(final BuildContext context) => BlocBuilder<JobsCubit, JobsState>(
builder: (final context, final state) {
late List<Widget> widgets;
final ServerInstallationState installationState =
context.read<ServerInstallationCubit>().state;
if (state is JobsStateEmpty) {
widgets = [
const SizedBox(height: 80),
Center(child: BrandText.body1('jobs.empty'.tr())),
];
Widget build(final BuildContext context) {
final List<ServerJob> serverJobs =
context.watch<ServerJobsCubit>().state.serverJobList;
if (installationState is ServerInstallationFinished) {
widgets = [
...widgets,
const SizedBox(height: 80),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().upgradeServer(),
text: 'jobs.upgradeServer'.tr(),
),
const SizedBox(height: 10),
BrandButton.text(
onPressed: () {
final NavigationService nav = getIt<NavigationService>();
nav.showPopUpDialog(
BrandAlert(
title: 'jobs.rebootServer'.tr(),
contentText: 'modals.3'.tr(),
actions: [
ActionButton(
text: 'basis.cancel'.tr(),
),
ActionButton(
onPressed: () =>
{context.read<JobsCubit>().rebootServer()},
text: 'modals.9'.tr(),
)
],
),
);
},
title: 'jobs.rebootServer'.tr(),
),
];
}
} else if (state is JobsStateLoading) {
return BlocBuilder<JobsCubit, JobsState>(
builder: (final context, final state) {
late List<Widget> widgets;
final ServerInstallationState installationState =
context.read<ServerInstallationCubit>().state;
if (state is JobsStateEmpty) {
widgets = [
const SizedBox(height: 80),
Center(child: BrandText.body1('jobs.empty'.tr())),
];
if (installationState is ServerInstallationFinished) {
widgets = [
...widgets,
const SizedBox(height: 80),
BrandLoader.horizontal(),
];
} else if (state is JobsStateWithJobs) {
widgets = [
...state.clientJobList
.map(
(final j) => Row(
children: [
Expanded(
child: BrandCards.small(
child: Text(j.title),
),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().upgradeServer(),
text: 'jobs.upgradeServer'.tr(),
),
const SizedBox(height: 10),
BrandButton.text(
onPressed: () {
final NavigationService nav = getIt<NavigationService>();
nav.showPopUpDialog(
BrandAlert(
title: 'jobs.rebootServer'.tr(),
contentText: 'modals.3'.tr(),
actions: [
ActionButton(
text: 'basis.cancel'.tr(),
),
const SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary:
Theme.of(context).colorScheme.errorContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
ActionButton(
onPressed: () =>
context.read<JobsCubit>().removeJob(j.id),
child: Text(
'basis.remove'.tr(),
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onErrorContainer,
),
),
),
{context.read<JobsCubit>().rebootServer()},
text: 'modals.9'.tr(),
)
],
),
)
.toList(),
const SizedBox(height: 20),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().applyAll(),
text: 'jobs.start'.tr(),
);
},
title: 'jobs.rebootServer'.tr(),
),
];
}
return ListView(
padding: paddingH15V0,
children: [
const SizedBox(height: 15),
Center(
child: BrandText.h2(
'jobs.title'.tr(),
),
),
const SizedBox(height: 20),
...widgets,
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
...context.read<ServerJobsCubit>().state.serverJobList.map(
(final job) => ServerJobCard(
serverJob: job,
),
} else if (state is JobsStateLoading) {
widgets = [
const SizedBox(height: 80),
BrandLoader.horizontal(),
];
} else if (state is JobsStateWithJobs) {
widgets = [
...state.clientJobList
.map(
(final j) => Row(
children: [
Expanded(
child: BrandCards.small(
child: Text(j.title),
),
),
const SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).colorScheme.errorContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () =>
context.read<JobsCubit>().removeJob(j.id),
child: Text(
'basis.remove'.tr(),
style: TextStyle(
color:
Theme.of(context).colorScheme.onErrorContainer,
),
),
),
],
),
],
);
},
);
)
.toList(),
const SizedBox(height: 20),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().applyAll(),
text: 'jobs.start'.tr(),
),
];
}
return ListView(
padding: paddingH15V0,
children: [
const SizedBox(height: 15),
Center(
child: BrandText.h2(
'jobs.title'.tr(),
),
),
const SizedBox(height: 20),
...widgets,
const SizedBox(height: 8),
const Divider(height: 0),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'jobs.server_jobs'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
),
...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,
),
onDismissed: (final direction) {
context.read<ServerJobsCubit>().removeServerJob(job.uid);
},
),
),
const SizedBox(height: 24),
],
);
},
);
}
}