Service migrations

Inex Code 2022-09-19 03:21:08 +03:00
parent d6d7a0dcb6
commit 10891881ae
7 changed files with 94 additions and 19 deletions

View File

@ -11,8 +11,11 @@ class ServerJobsState extends ServerInstallationDependendState {
late final List<ServerJob> _serverJobList; late final List<ServerJob> _serverJobList;
final String? migrationJobUid; final String? migrationJobUid;
List<ServerJob> get serverJobList => List<ServerJob> get serverJobList {
_serverJobList.where((final ServerJob job) => !job.isHidden).toList(); final List<ServerJob> list = _serverJobList;
list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt));
return list;
}
@override @override
List<Object?> get props => [migrationJobUid, ..._serverJobList]; List<Object?> get props => [migrationJobUid, ..._serverJobList];

View File

@ -19,6 +19,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
emit( emit(
ServicesState( ServicesState(
services: services, services: services,
lockedServices: const [],
), ),
); );
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true)); timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
@ -28,7 +29,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
Future<void> reload({final bool useTimer = false}) async { Future<void> reload({final bool useTimer = false}) async {
final List<Service> services = await api.getAllServices(); final List<Service> services = await api.getAllServices();
emit( emit(
ServicesState( state.copyWith(
services: services, services: services,
), ),
); );
@ -38,7 +39,26 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
} }
Future<void> restart(final String serviceId) async { Future<void> restart(final String serviceId) async {
emit(state.copyWith(lockedServices: [...state.lockedServices, serviceId]));
await api.restartService(serviceId); await api.restartService(serviceId);
await Future.delayed(const Duration(seconds: 2));
reload();
await Future.delayed(const Duration(seconds: 10));
emit(
state.copyWith(
lockedServices: state.lockedServices
.where((final element) => element != serviceId)
.toList(),
),
);
reload();
}
Future<void> moveService(
final String serviceId,
final String destination,
) async {
await api.moveService(serviceId, destination);
} }
@override @override

View File

@ -3,11 +3,18 @@ part of 'services_cubit.dart';
class ServicesState extends ServerInstallationDependendState { class ServicesState extends ServerInstallationDependendState {
const ServicesState({ const ServicesState({
required this.services, required this.services,
required this.lockedServices,
}); });
const ServicesState.empty() : this(services: const []); const ServicesState.empty()
: this(services: const [], lockedServices: const []);
final List<Service> services; final List<Service> services;
final List<String> lockedServices;
bool isServiceLocked(final String serviceId) =>
lockedServices.contains(serviceId);
bool get isPasswordManagerEnable => services bool get isPasswordManagerEnable => services
.firstWhere( .firstWhere(
(final service) => service.id == 'bitwarden', (final service) => service.id == 'bitwarden',
@ -53,6 +60,7 @@ class ServicesState extends ServerInstallationDependendState {
@override @override
List<Object> get props => [ List<Object> get props => [
services, services,
lockedServices,
]; ];
bool isEnableByType(final ServiceTypes type) { bool isEnableByType(final ServiceTypes type) {
@ -71,4 +79,13 @@ class ServicesState extends ServerInstallationDependendState {
throw Exception('wrong state'); throw Exception('wrong state');
} }
} }
ServicesState copyWith({
final List<Service>? services,
final List<String>? lockedServices,
}) =>
ServicesState(
services: services ?? this.services,
lockedServices: lockedServices ?? this.lockedServices,
);
} }

View File

@ -126,13 +126,14 @@ class JobsContent extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
const Divider(height: 0), const Divider(height: 0),
const SizedBox(height: 8), const SizedBox(height: 8),
Padding( if (serverJobs.isNotEmpty)
padding: const EdgeInsets.all(8.0), Padding(
child: Text( padding: const EdgeInsets.all(8.0),
'jobs.server_jobs'.tr(), child: Text(
style: Theme.of(context).textTheme.titleMedium, 'jobs.server_jobs'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
), ),
),
...serverJobs.map( ...serverJobs.map(
(final job) => Dismissible( (final job) => Dismissible(
key: ValueKey(job.uid), key: ValueKey(job.uid),

View File

@ -10,7 +10,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.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/pages/devices/devices.dart'; import 'package:selfprivacy/ui/pages/devices/devices.dart';
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart'; import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/data_to_binds_migration.dart'; import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
import 'package:selfprivacy/ui/pages/setup/initializing.dart'; import 'package:selfprivacy/ui/pages/setup/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:selfprivacy/ui/pages/root_route.dart'; import 'package:selfprivacy/ui/pages/root_route.dart';
@ -50,7 +50,7 @@ class MorePage extends StatelessWidget {
_MoreMenuItem( _MoreMenuItem(
title: 'providers.storage.start_migration_button'.tr(), title: 'providers.storage.start_migration_button'.tr(),
iconData: Icons.drive_file_move_outline, iconData: Icons.drive_file_move_outline,
goTo: DataToBindsMigrationPage( goTo: ServicesMigrationPage(
diskStatus: context diskStatus: context
.watch<ApiServerVolumeCubit>() .watch<ApiServerVolumeCubit>()
.state .state
@ -68,6 +68,7 @@ class MorePage extends StatelessWidget {
service.id == 'nextcloud', service.id == 'nextcloud',
) )
.toList(), .toList(),
isMigration: true,
), ),
subtitle: 'not_ready_card.in_menu'.tr(), subtitle: 'not_ready_card.in_menu'.tr(),
accent: true, accent: true,

View File

@ -1,6 +1,7 @@
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:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
@ -15,22 +16,23 @@ import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/ui/pages/root_route.dart'; import 'package:selfprivacy/ui/pages/root_route.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
class DataToBindsMigrationPage extends StatefulWidget { class ServicesMigrationPage extends StatefulWidget {
const DataToBindsMigrationPage({ const ServicesMigrationPage({
required this.services, required this.services,
required this.diskStatus, required this.diskStatus,
required this.isMigration,
final super.key, final super.key,
}); });
final DiskStatus diskStatus; final DiskStatus diskStatus;
final List<Service> services; final List<Service> services;
final bool isMigration;
@override @override
State<DataToBindsMigrationPage> createState() => State<ServicesMigrationPage> createState() => _ServicesMigrationPageState();
_DataToBindsMigrationPageState();
} }
class _DataToBindsMigrationPageState extends State<DataToBindsMigrationPage> { class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
/// Service id to target migration disk name /// Service id to target migration disk name
final Map<String, String> serviceToDisk = {}; final Map<String, String> serviceToDisk = {};
@ -164,7 +166,20 @@ class _DataToBindsMigrationPageState extends State<DataToBindsMigrationPage> {
FilledButton( FilledButton(
title: 'providers.storage.start_migration_button'.tr(), title: 'providers.storage.start_migration_button'.tr(),
onPressed: () { onPressed: () {
context.read<ServerJobsCubit>().migrateToBinds(serviceToDisk); if (widget.isMigration) {
context.read<ServerJobsCubit>().migrateToBinds(
serviceToDisk,
);
} else {
for (final service in widget.services) {
if (serviceToDisk[service.id] != null) {
context.read<ServicesCubit>().moveService(
service.id,
serviceToDisk[service.id]!,
);
}
}
}
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).pushAndRemoveUntil(
materialRoute(const RootPage()), materialRoute(const RootPage()),
(final predicate) => false, (final predicate) => false,

View File

@ -9,6 +9,8 @@ import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class ServicePage extends StatefulWidget { class ServicePage extends StatefulWidget {
@ -40,6 +42,9 @@ class _ServicePageState extends State<ServicePage> {
final bool serviceDisabled = service.status == ServiceStatus.inactive || final bool serviceDisabled = service.status == ServiceStatus.inactive ||
service.status == ServiceStatus.off; service.status == ServiceStatus.off;
final bool serviceLocked =
context.watch<ServicesCubit>().state.isServiceLocked(service.id);
return BrandHeroScreen( return BrandHeroScreen(
hasBackButton: true, hasBackButton: true,
children: [ children: [
@ -90,6 +95,7 @@ class _ServicePageState extends State<ServicePage> {
'services.service_page.restart'.tr(), 'services.service_page.restart'.tr(),
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
enabled: !serviceDisabled && !serviceLocked,
), ),
ListTile( ListTile(
iconColor: Theme.of(context).colorScheme.onBackground, iconColor: Theme.of(context).colorScheme.onBackground,
@ -108,11 +114,22 @@ class _ServicePageState extends State<ServicePage> {
: 'services.service_page.disable'.tr(), : 'services.service_page.disable'.tr(),
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
enabled: !serviceLocked,
), ),
if (service.isMovable) if (service.isMovable)
ListTile( ListTile(
iconColor: Theme.of(context).colorScheme.onBackground, iconColor: Theme.of(context).colorScheme.onBackground,
onTap: () => {}, // Open page ServicesMigrationPage
onTap: () => Navigator.of(context).push(
materialRoute(
ServicesMigrationPage(
services: [service],
diskStatus:
context.read<ApiServerVolumeCubit>().state.diskStatus,
isMigration: false,
),
),
),
leading: const Icon(Icons.drive_file_move_outlined), leading: const Icon(Icons.drive_file_move_outlined),
title: Text( title: Text(
'services.service_page.move'.tr(), 'services.service_page.move'.tr(),
@ -131,6 +148,7 @@ class _ServicePageState extends State<ServicePage> {
), ),
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
), ),
enabled: !serviceDisabled && !serviceLocked,
), ),
], ],
); );