mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-11 18:39:45 +00:00
Service migrations
This commit is contained in:
parent
d6d7a0dcb6
commit
10891881ae
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ 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),
|
||||||
|
if (serverJobs.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue