mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-09 17:39:42 +00:00
Merge branch 'master' into move-title-in-cards
This commit is contained in:
commit
b8b8ac43ea
7
.github/workflows/windows.yml
vendored
7
.github/workflows/windows.yml
vendored
|
@ -1,6 +1,9 @@
|
||||||
name: Windows Builder
|
name: Windows Builder
|
||||||
|
|
||||||
on: tag
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*.*.*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-windows:
|
build-windows:
|
||||||
|
@ -14,7 +17,7 @@ jobs:
|
||||||
# Install Flutter
|
# Install Flutter
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.3.10'
|
flutter-version: '3.16.1'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
|
||||||
# Build Windows artifact
|
# Build Windows artifact
|
||||||
|
|
|
@ -10,7 +10,7 @@ AppDir:
|
||||||
id: org.selfprivacy.app
|
id: org.selfprivacy.app
|
||||||
name: SelfPrivacy
|
name: SelfPrivacy
|
||||||
icon: org.selfprivacy.app
|
icon: org.selfprivacy.app
|
||||||
version: 0.10.0
|
version: 0.10.1
|
||||||
exec: selfprivacy
|
exec: selfprivacy
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
### Пра нас
|
|
||||||
|
|
||||||
Усё больш арганізацый жадаюць валодаць нашымі дадзенымі
|
|
||||||
Праект дазваляе толькі Вам у поўнай меры распараджацца ўласнымі **дадзенымі** на сваім сэрвэры.
|
|
||||||
|
|
||||||
### Наша місія
|
|
||||||
|
|
||||||
Лічбавая незалежнасць і прыватнасць, даступныя кожнаму
|
|
||||||
|
|
||||||
### Мэта
|
|
||||||
|
|
||||||
Распрацаваць праграму, якая дазволіць кожнаму разгарнуць свае прыватныя паслугі для сябе і сваіх суседзяў.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### O nás
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Naše poslání
|
|
||||||
|
|
||||||
Digitální nezávislost a soukromí dostupné všem
|
|
||||||
|
|
||||||
### Cíl
|
|
||||||
|
|
||||||
Rozvíjet program, který umožní každému nasadit své soukromé služby pro sebe a své sousedy.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### Über uns
|
|
||||||
|
|
||||||
Immer mehr Unternehmen wollen unsere Daten kontrollieren.
|
|
||||||
Wir wollen selbst die volle Kontrolle über unsere **data** haben.
|
|
||||||
|
|
||||||
### Unsere Mission
|
|
||||||
|
|
||||||
Digitale Unabhängigkeit und Privatsphäre für alle verfügbar
|
|
||||||
|
|
||||||
### Ziel
|
|
||||||
|
|
||||||
Das Programm entwickeln, das es jedem ermöglicht, seine privaten Dienste für sich und seine Nachbarn einzusetzen.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Misja projektu
|
|
||||||
|
|
||||||
Niezależność i prywatność cyfrowa dostępna dla wszystkich
|
|
||||||
|
|
||||||
### Cel
|
|
||||||
|
|
||||||
Opracuj program, dzięki któremu każdy będzie mógł stworzyć prywatne usługi dla siebie i swoich bliskich.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### О проекте
|
|
||||||
|
|
||||||
Всё больше организаций хотят владеть нашими данными
|
|
||||||
Проект позволяет только Вам в полной мере распоряжаться собственными **данными** на своём сервере.
|
|
||||||
|
|
||||||
### Миссия проекта
|
|
||||||
|
|
||||||
Цифровая независимость и приватность, доступная каждому
|
|
||||||
|
|
||||||
### Цель
|
|
||||||
|
|
||||||
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### O nás
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Naše poslanie
|
|
||||||
|
|
||||||
Digitálna nezávislosť a súkromie dostupné pre každého
|
|
||||||
|
|
||||||
### Cieľ
|
|
||||||
|
|
||||||
Vytvorte program, ktorý umožní každému vytvoriť súkromné služby pre seba a svojich blízkych.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### About us
|
|
||||||
|
|
||||||
More and more corporations want to control our data.
|
|
||||||
We want to have full control of our **data** on our own.
|
|
||||||
|
|
||||||
### Our mission
|
|
||||||
|
|
||||||
Digital independence and privacy, available to everyone
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours.
|
|
|
@ -1,12 +0,0 @@
|
||||||
### Про нас
|
|
||||||
|
|
||||||
Все більше корпорацій хочуть контролювати свої дані.
|
|
||||||
Ми хочемо мати повний контроль над нашими.
|
|
||||||
|
|
||||||
### Наша місія
|
|
||||||
|
|
||||||
Цифрова незалежність і конфіденційність доступні кожному
|
|
||||||
|
|
||||||
### Ціль
|
|
||||||
|
|
||||||
Розробити програму, яка дозволить кожному розгорнути свої приватні послуги для себе та їх сусідів.
|
|
|
@ -36,29 +36,41 @@
|
||||||
"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",
|
||||||
"about_project": "About us",
|
|
||||||
"about_application": "About",
|
|
||||||
"onboarding": "Onboarding",
|
"onboarding": "Onboarding",
|
||||||
"create_ssh_key": "Superuser SSH keys",
|
"create_ssh_key": "Superuser SSH keys"
|
||||||
"console": "Console",
|
|
||||||
"application_settings": "Application settings"
|
|
||||||
},
|
},
|
||||||
"console_page": {
|
"console_page": {
|
||||||
"title": "Console",
|
"title": "Console",
|
||||||
"waiting": "Waiting for initialization…",
|
"waiting": "Waiting for initialization…",
|
||||||
"copy": "Copy"
|
"copy": "Copy"
|
||||||
},
|
},
|
||||||
"about_us_page": {
|
|
||||||
"title": "About us"
|
|
||||||
},
|
|
||||||
"about_application_page": {
|
"about_application_page": {
|
||||||
"title": "About",
|
"title": "About & support",
|
||||||
"application_version_text": "Application version {}",
|
"versions": "Versions",
|
||||||
"api_version_text": "Server API version {}",
|
"application_version_text": "Application version",
|
||||||
|
"api_version_text": "Server API version",
|
||||||
|
"open_source_licenses": "Open source licenses",
|
||||||
|
"links": "Links",
|
||||||
|
"website": "Our website",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"matrix_channel": "Matrix channel",
|
||||||
|
"telegram_channel": "Telegram channel",
|
||||||
|
"get_support": "Get support",
|
||||||
|
"matrix_support_chat": "Matrix support chat",
|
||||||
|
"telegram_support_chat": "Telegram support chat",
|
||||||
|
"email_support": "Email support",
|
||||||
|
"contribute": "Contribute",
|
||||||
|
"source_code": "Source code",
|
||||||
|
"bug_report": "Report a bug",
|
||||||
|
"bug_report_subtitle": "Due to spam, manual account confirmation is required. Contact us in the support chat to activate your account.",
|
||||||
|
"help_translate": "Help us translate",
|
||||||
|
"matrix_contributors_chat": "Matrix contributors chat",
|
||||||
|
"telegram_contributors_chat": "Telegram contributors chat",
|
||||||
"privacy_policy": "Privacy policy"
|
"privacy_policy": "Privacy policy"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
|
@ -305,6 +317,10 @@
|
||||||
"extending_volume_description": "Resizing volume will allow you to store more data on your server without extending the server itself. Volume can only be extended: shrinking is not possible.",
|
"extending_volume_description": "Resizing volume will allow you to store more data on your server without extending the server itself. Volume can only be extended: shrinking is not possible.",
|
||||||
"extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.",
|
"extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.",
|
||||||
"extending_volume_error": "Couldn't initialize volume extending.",
|
"extending_volume_error": "Couldn't initialize volume extending.",
|
||||||
|
"extending_volume_started": "Volume extending started",
|
||||||
|
"extending_volume_provider_waiting": "Provider volume resized, waiting 10 seconds…",
|
||||||
|
"extending_volume_server_waiting": "Server volume resized, waiting 20 seconds…",
|
||||||
|
"extending_volume_rebooting": "Rebooting server…",
|
||||||
"extending_volume_modal_description": "Upgrade to {} for {} plan per month.",
|
"extending_volume_modal_description": "Upgrade to {} for {} plan per month.",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"price": "Price",
|
"price": "Price",
|
||||||
|
@ -390,7 +406,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:",
|
||||||
|
@ -590,6 +607,8 @@
|
||||||
"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",
|
||||||
|
"job_removed": "Job removed",
|
||||||
"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.",
|
||||||
|
@ -602,7 +621,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",
|
||||||
|
|
15
fastlane/metadata/android/en-US/changelogs/0.10.1.txt
Normal file
15
fastlane/metadata/android/en-US/changelogs/0.10.1.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Enabled the following languages:
|
||||||
|
- Azerbaijani
|
||||||
|
- Belarusian
|
||||||
|
- Hebrew
|
||||||
|
- Latvian
|
||||||
|
- Macedonian
|
||||||
|
- Slovak
|
||||||
|
- Slovenian
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **Hetzner**: Fixed an issue where could not resize a volume on Hetzner ([#456](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/456), resolves [#455](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/455))
|
||||||
|
- **DNS**: Make sure that we notice domain ownership lost ([#441](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/441), resolves [#390](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/390))
|
|
@ -1,43 +1,64 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.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/devices/devices_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_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/cubit/server_jobs/server_jobs_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
|
|
||||||
class BlocAndProviderConfig extends StatelessWidget {
|
class BlocAndProviderConfig extends StatefulWidget {
|
||||||
const BlocAndProviderConfig({super.key, this.child});
|
const BlocAndProviderConfig({super.key, this.child});
|
||||||
|
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BlocAndProviderConfigState createState() => BlocAndProviderConfigState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
|
late final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
late final SupportSystemCubit supportSystemCubit;
|
||||||
|
late final UsersBloc usersBloc;
|
||||||
|
late final ServicesBloc servicesBloc;
|
||||||
|
late final BackupsBloc backupsBloc;
|
||||||
|
late final DnsRecordsCubit dnsRecordsCubit;
|
||||||
|
late final RecoveryKeyBloc recoveryKeyBloc;
|
||||||
|
late final DevicesBloc devicesBloc;
|
||||||
|
late final ServerJobsBloc serverJobsBloc;
|
||||||
|
late final ConnectionStatusBloc connectionStatusBloc;
|
||||||
|
late final ServerDetailsCubit serverDetailsCubit;
|
||||||
|
late final VolumesBloc volumesBloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
serverInstallationCubit = ServerInstallationCubit()..load();
|
||||||
|
supportSystemCubit = SupportSystemCubit();
|
||||||
|
usersBloc = UsersBloc();
|
||||||
|
servicesBloc = ServicesBloc();
|
||||||
|
backupsBloc = BackupsBloc();
|
||||||
|
dnsRecordsCubit = DnsRecordsCubit();
|
||||||
|
recoveryKeyBloc = RecoveryKeyBloc();
|
||||||
|
devicesBloc = DevicesBloc();
|
||||||
|
serverJobsBloc = ServerJobsBloc();
|
||||||
|
connectionStatusBloc = ConnectionStatusBloc();
|
||||||
|
serverDetailsCubit = ServerDetailsCubit();
|
||||||
|
volumesBloc = VolumesBloc();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
const isDark = false;
|
const isDark = false;
|
||||||
const isAutoDark = true;
|
const isAutoDark = true;
|
||||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
|
||||||
final supportSystemCubit = SupportSystemCubit();
|
|
||||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
|
||||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
|
||||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
|
||||||
final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit);
|
|
||||||
final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit);
|
|
||||||
final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit);
|
|
||||||
final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit);
|
|
||||||
final apiServerVolumesCubit =
|
|
||||||
ApiServerVolumeCubit(serverInstallationCubit, apiVolumesCubit);
|
|
||||||
final serverJobsCubit = ServerJobsCubit(serverInstallationCubit);
|
|
||||||
final serverDetailsCubit = ServerDetailsCubit(serverInstallationCubit);
|
|
||||||
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -56,49 +77,37 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
lazy: false,
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => ProvidersCubit(),
|
create: (final _) => usersBloc,
|
||||||
),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => usersCubit..load(),
|
|
||||||
lazy: false,
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => servicesCubit..load(),
|
create: (final _) => servicesBloc,
|
||||||
lazy: false,
|
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => backupsCubit..load(),
|
create: (final _) => backupsBloc,
|
||||||
lazy: false,
|
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => dnsRecordsCubit..load(),
|
create: (final _) => dnsRecordsCubit,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => recoveryKeyCubit..load(),
|
create: (final _) => recoveryKeyBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => apiDevicesCubit..load(),
|
create: (final _) => devicesBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => apiVolumesCubit..load(),
|
create: (final _) => serverJobsBloc,
|
||||||
),
|
),
|
||||||
|
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => apiServerVolumesCubit..load(),
|
create: (final _) => serverDetailsCubit,
|
||||||
),
|
),
|
||||||
|
BlocProvider(create: (final _) => volumesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverJobsCubit..load(),
|
create: (final _) => JobsCubit(),
|
||||||
),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => serverDetailsCubit..load(),
|
|
||||||
),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => JobsCubit(
|
|
||||||
usersCubit: usersCubit,
|
|
||||||
servicesCubit: servicesCubit,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
import 'package:selfprivacy/logic/get_it/console.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/timer.dart';
|
|
||||||
|
|
||||||
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
|
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/console.dart';
|
export 'package:selfprivacy/logic/get_it/console.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/timer.dart';
|
|
||||||
|
|
||||||
final GetIt getIt = GetIt.instance;
|
final GetIt getIt = GetIt.instance;
|
||||||
|
|
||||||
|
@ -15,8 +15,11 @@ Future<void> getItSetup() async {
|
||||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||||
|
|
||||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
|
||||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||||
|
|
||||||
|
getIt.registerSingleton<ApiConnectionRepository>(
|
||||||
|
ApiConnectionRepository()..init(),
|
||||||
|
);
|
||||||
|
|
||||||
await getIt.allReady();
|
await getIt.allReady();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class HiveConfig {
|
||||||
Hive.registerAdapter(ServerDomainAdapter());
|
Hive.registerAdapter(ServerDomainAdapter());
|
||||||
Hive.registerAdapter(BackupsCredentialAdapter());
|
Hive.registerAdapter(BackupsCredentialAdapter());
|
||||||
Hive.registerAdapter(BackblazeBucketAdapter());
|
Hive.registerAdapter(BackblazeBucketAdapter());
|
||||||
Hive.registerAdapter(ServerVolumeAdapter());
|
Hive.registerAdapter(ServerProviderVolumeAdapter());
|
||||||
Hive.registerAdapter(UserTypeAdapter());
|
Hive.registerAdapter(UserTypeAdapter());
|
||||||
Hive.registerAdapter(DnsProviderTypeAdapter());
|
Hive.registerAdapter(DnsProviderTypeAdapter());
|
||||||
Hive.registerAdapter(ServerProviderTypeAdapter());
|
Hive.registerAdapter(ServerProviderTypeAdapter());
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -17,10 +17,6 @@ class StrayDeerPainter extends CustomPainter {
|
||||||
final Color deerSkin =
|
final Color deerSkin =
|
||||||
const Color(0xffe0ac9c).harmonizeWith(colorScheme.primary);
|
const Color(0xffe0ac9c).harmonizeWith(colorScheme.primary);
|
||||||
|
|
||||||
print('deerSkin: $deerSkin');
|
|
||||||
print('colorScheme.primary: ${colorScheme.primary}');
|
|
||||||
print('colorPalette.tertiary.get(10): ${colorPalette.tertiary.get(50)}');
|
|
||||||
|
|
||||||
final Path path0 = Path();
|
final Path path0 = Path();
|
||||||
path0.moveTo(size.width * 0.6099773, size.height * 0.6719577);
|
path0.moveTo(size.width * 0.6099773, size.height * 0.6719577);
|
||||||
path0.lineTo(size.width * 0.6088435, size.height * 0.6719577);
|
path0.lineTo(size.width * 0.6088435, size.height * 0.6719577);
|
||||||
|
|
|
@ -150,9 +150,9 @@ type DnsRecord {
|
||||||
recordType: String!
|
recordType: String!
|
||||||
name: String!
|
name: String!
|
||||||
content: String!
|
content: String!
|
||||||
displayName: String!
|
|
||||||
ttl: Int!
|
ttl: Int!
|
||||||
priority: Int
|
priority: Int
|
||||||
|
displayName: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type GenericBackupConfigReturn implements MutationReturnInterface {
|
type GenericBackupConfigReturn implements MutationReturnInterface {
|
||||||
|
@ -272,6 +272,19 @@ enum RestoreStrategy {
|
||||||
DOWNLOAD_VERIFY_OVERWRITE
|
DOWNLOAD_VERIFY_OVERWRITE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input SSHSettingsInput {
|
||||||
|
enable: Boolean!
|
||||||
|
passwordAuthentication: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHSettingsMutationReturn implements MutationReturnInterface {
|
||||||
|
success: Boolean!
|
||||||
|
message: String!
|
||||||
|
code: Int!
|
||||||
|
enable: Boolean!
|
||||||
|
passwordAuthentication: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
enum ServerProvider {
|
enum ServerProvider {
|
||||||
HETZNER
|
HETZNER
|
||||||
DIGITALOCEAN
|
DIGITALOCEAN
|
||||||
|
@ -424,9 +437,10 @@ type SystemInfo {
|
||||||
type SystemMutations {
|
type SystemMutations {
|
||||||
changeTimezone(timezone: String!): TimezoneMutationReturn!
|
changeTimezone(timezone: String!): TimezoneMutationReturn!
|
||||||
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
|
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
|
||||||
runSystemRebuild: GenericMutationReturn!
|
changeSshSettings(settings: SSHSettingsInput!): SSHSettingsMutationReturn!
|
||||||
|
runSystemRebuild: GenericJobMutationReturn!
|
||||||
runSystemRollback: GenericMutationReturn!
|
runSystemRollback: GenericMutationReturn!
|
||||||
runSystemUpgrade: GenericMutationReturn!
|
runSystemUpgrade: GenericJobMutationReturn!
|
||||||
rebootSystem: GenericMutationReturn!
|
rebootSystem: GenericMutationReturn!
|
||||||
pullRepositoryChanges: GenericMutationReturn!
|
pullRepositoryChanges: GenericMutationReturn!
|
||||||
}
|
}
|
||||||
|
|
|
@ -982,6 +982,135 @@ class _CopyWithStubImpl$Input$RecoveryKeyLimitsInput<TRes>
|
||||||
_res;
|
_res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Input$SSHSettingsInput {
|
||||||
|
factory Input$SSHSettingsInput({
|
||||||
|
required bool enable,
|
||||||
|
required bool passwordAuthentication,
|
||||||
|
}) =>
|
||||||
|
Input$SSHSettingsInput._({
|
||||||
|
r'enable': enable,
|
||||||
|
r'passwordAuthentication': passwordAuthentication,
|
||||||
|
});
|
||||||
|
|
||||||
|
Input$SSHSettingsInput._(this._$data);
|
||||||
|
|
||||||
|
factory Input$SSHSettingsInput.fromJson(Map<String, dynamic> data) {
|
||||||
|
final result$data = <String, dynamic>{};
|
||||||
|
final l$enable = data['enable'];
|
||||||
|
result$data['enable'] = (l$enable as bool);
|
||||||
|
final l$passwordAuthentication = data['passwordAuthentication'];
|
||||||
|
result$data['passwordAuthentication'] = (l$passwordAuthentication as bool);
|
||||||
|
return Input$SSHSettingsInput._(result$data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$data;
|
||||||
|
|
||||||
|
bool get enable => (_$data['enable'] as bool);
|
||||||
|
|
||||||
|
bool get passwordAuthentication => (_$data['passwordAuthentication'] as bool);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final result$data = <String, dynamic>{};
|
||||||
|
final l$enable = enable;
|
||||||
|
result$data['enable'] = l$enable;
|
||||||
|
final l$passwordAuthentication = passwordAuthentication;
|
||||||
|
result$data['passwordAuthentication'] = l$passwordAuthentication;
|
||||||
|
return result$data;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyWith$Input$SSHSettingsInput<Input$SSHSettingsInput> get copyWith =>
|
||||||
|
CopyWith$Input$SSHSettingsInput(
|
||||||
|
this,
|
||||||
|
(i) => i,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(other is Input$SSHSettingsInput) ||
|
||||||
|
runtimeType != other.runtimeType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final l$enable = enable;
|
||||||
|
final lOther$enable = other.enable;
|
||||||
|
if (l$enable != lOther$enable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final l$passwordAuthentication = passwordAuthentication;
|
||||||
|
final lOther$passwordAuthentication = other.passwordAuthentication;
|
||||||
|
if (l$passwordAuthentication != lOther$passwordAuthentication) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
final l$enable = enable;
|
||||||
|
final l$passwordAuthentication = passwordAuthentication;
|
||||||
|
return Object.hashAll([
|
||||||
|
l$enable,
|
||||||
|
l$passwordAuthentication,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CopyWith$Input$SSHSettingsInput<TRes> {
|
||||||
|
factory CopyWith$Input$SSHSettingsInput(
|
||||||
|
Input$SSHSettingsInput instance,
|
||||||
|
TRes Function(Input$SSHSettingsInput) then,
|
||||||
|
) = _CopyWithImpl$Input$SSHSettingsInput;
|
||||||
|
|
||||||
|
factory CopyWith$Input$SSHSettingsInput.stub(TRes res) =
|
||||||
|
_CopyWithStubImpl$Input$SSHSettingsInput;
|
||||||
|
|
||||||
|
TRes call({
|
||||||
|
bool? enable,
|
||||||
|
bool? passwordAuthentication,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithImpl$Input$SSHSettingsInput<TRes>
|
||||||
|
implements CopyWith$Input$SSHSettingsInput<TRes> {
|
||||||
|
_CopyWithImpl$Input$SSHSettingsInput(
|
||||||
|
this._instance,
|
||||||
|
this._then,
|
||||||
|
);
|
||||||
|
|
||||||
|
final Input$SSHSettingsInput _instance;
|
||||||
|
|
||||||
|
final TRes Function(Input$SSHSettingsInput) _then;
|
||||||
|
|
||||||
|
static const _undefined = <dynamic, dynamic>{};
|
||||||
|
|
||||||
|
TRes call({
|
||||||
|
Object? enable = _undefined,
|
||||||
|
Object? passwordAuthentication = _undefined,
|
||||||
|
}) =>
|
||||||
|
_then(Input$SSHSettingsInput._({
|
||||||
|
..._instance._$data,
|
||||||
|
if (enable != _undefined && enable != null) 'enable': (enable as bool),
|
||||||
|
if (passwordAuthentication != _undefined &&
|
||||||
|
passwordAuthentication != null)
|
||||||
|
'passwordAuthentication': (passwordAuthentication as bool),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithStubImpl$Input$SSHSettingsInput<TRes>
|
||||||
|
implements CopyWith$Input$SSHSettingsInput<TRes> {
|
||||||
|
_CopyWithStubImpl$Input$SSHSettingsInput(this._res);
|
||||||
|
|
||||||
|
TRes _res;
|
||||||
|
|
||||||
|
call({
|
||||||
|
bool? enable,
|
||||||
|
bool? passwordAuthentication,
|
||||||
|
}) =>
|
||||||
|
_res;
|
||||||
|
}
|
||||||
|
|
||||||
class Input$SshMutationInput {
|
class Input$SshMutationInput {
|
||||||
factory Input$SshMutationInput({
|
factory Input$SshMutationInput({
|
||||||
required String username,
|
required String username,
|
||||||
|
@ -1928,6 +2057,7 @@ const possibleTypesMap = <String, Set<String>>{
|
||||||
'GenericBackupConfigReturn',
|
'GenericBackupConfigReturn',
|
||||||
'GenericJobMutationReturn',
|
'GenericJobMutationReturn',
|
||||||
'GenericMutationReturn',
|
'GenericMutationReturn',
|
||||||
|
'SSHSettingsMutationReturn',
|
||||||
'ServiceJobMutationReturn',
|
'ServiceJobMutationReturn',
|
||||||
'ServiceMutationReturn',
|
'ServiceMutationReturn',
|
||||||
'TimezoneMutationReturn',
|
'TimezoneMutationReturn',
|
||||||
|
|
|
@ -42,6 +42,17 @@ mutation RemoveJob($jobId: String!) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mutation RunSystemRebuild {
|
mutation RunSystemRebuild {
|
||||||
|
system {
|
||||||
|
runSystemRebuild {
|
||||||
|
...basicMutationReturnFields
|
||||||
|
job {
|
||||||
|
...basicApiJobsFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation RunSystemRebuildFallback {
|
||||||
system {
|
system {
|
||||||
runSystemRebuild {
|
runSystemRebuild {
|
||||||
...basicMutationReturnFields
|
...basicMutationReturnFields
|
||||||
|
@ -58,6 +69,17 @@ mutation RunSystemRollback {
|
||||||
}
|
}
|
||||||
|
|
||||||
mutation RunSystemUpgrade {
|
mutation RunSystemUpgrade {
|
||||||
|
system {
|
||||||
|
runSystemUpgrade {
|
||||||
|
...basicMutationReturnFields
|
||||||
|
job {
|
||||||
|
...basicApiJobsFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation RunSystemUpgradeFallback {
|
||||||
system {
|
system {
|
||||||
runSystemUpgrade {
|
runSystemUpgrade {
|
||||||
...basicMutationReturnFields
|
...basicMutationReturnFields
|
||||||
|
|
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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,24 +132,55 @@ class ServerApi extends GraphQLApiMap
|
||||||
return usesBinds;
|
return usesBinds;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> switchService(final String uid, final bool needTurnOn) async {
|
Future<GenericResult> switchService(
|
||||||
|
final String uid,
|
||||||
|
final bool needTurnOn,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final GraphQLClient client = await getClient();
|
final GraphQLClient client = await getClient();
|
||||||
if (needTurnOn) {
|
if (needTurnOn) {
|
||||||
final variables = Variables$Mutation$EnableService(serviceId: uid);
|
final variables = Variables$Mutation$EnableService(serviceId: uid);
|
||||||
final mutation = Options$Mutation$EnableService(variables: variables);
|
final mutation = Options$Mutation$EnableService(variables: variables);
|
||||||
await client.mutate$EnableService(mutation);
|
final result = await client.mutate$EnableService(mutation);
|
||||||
|
if (result.hasException) {
|
||||||
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
message: result.exception.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GenericResult(
|
||||||
|
success: result.parsedData?.services.enableService.success ?? false,
|
||||||
|
message: result.parsedData?.services.enableService.message,
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
final variables = Variables$Mutation$DisableService(serviceId: uid);
|
final variables = Variables$Mutation$DisableService(serviceId: uid);
|
||||||
final mutation = Options$Mutation$DisableService(variables: variables);
|
final mutation = Options$Mutation$DisableService(variables: variables);
|
||||||
await client.mutate$DisableService(mutation);
|
final result = await client.mutate$DisableService(mutation);
|
||||||
|
if (result.hasException) {
|
||||||
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
message: result.exception.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GenericResult(
|
||||||
|
success: result.parsedData?.services.disableService.success ?? false,
|
||||||
|
message: result.parsedData?.services.disableService.message,
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
return GenericResult(
|
||||||
|
success: false,
|
||||||
|
message: e.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAutoUpgradeSettings(
|
Future<GenericResult<AutoUpgradeSettings?>> setAutoUpgradeSettings(
|
||||||
final AutoUpgradeSettings settings,
|
final AutoUpgradeSettings settings,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
|
@ -164,13 +195,38 @@ class ServerApi extends GraphQLApiMap
|
||||||
final mutation = Options$Mutation$ChangeAutoUpgradeSettings(
|
final mutation = Options$Mutation$ChangeAutoUpgradeSettings(
|
||||||
variables: variables,
|
variables: variables,
|
||||||
);
|
);
|
||||||
await client.mutate$ChangeAutoUpgradeSettings(mutation);
|
final result = await client.mutate$ChangeAutoUpgradeSettings(mutation);
|
||||||
|
if (result.hasException) {
|
||||||
|
return GenericResult<AutoUpgradeSettings?>(
|
||||||
|
success: false,
|
||||||
|
message: result.exception.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GenericResult<AutoUpgradeSettings?>(
|
||||||
|
success: result.parsedData?.system.changeAutoUpgradeSettings.success ??
|
||||||
|
false,
|
||||||
|
message: result.parsedData?.system.changeAutoUpgradeSettings.message,
|
||||||
|
data: result.parsedData == null
|
||||||
|
? null
|
||||||
|
: AutoUpgradeSettings(
|
||||||
|
allowReboot: result
|
||||||
|
.parsedData!.system.changeAutoUpgradeSettings.allowReboot,
|
||||||
|
enable: result.parsedData!.system.changeAutoUpgradeSettings
|
||||||
|
.enableAutoUpgrade,
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
return GenericResult<AutoUpgradeSettings?>(
|
||||||
|
success: false,
|
||||||
|
message: e.toString(),
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setTimezone(final String timezone) async {
|
Future<GenericResult<String?>> setTimezone(final String timezone) async {
|
||||||
try {
|
try {
|
||||||
final GraphQLClient client = await getClient();
|
final GraphQLClient client = await getClient();
|
||||||
final variables = Variables$Mutation$ChangeTimezone(
|
final variables = Variables$Mutation$ChangeTimezone(
|
||||||
|
@ -179,9 +235,26 @@ class ServerApi extends GraphQLApiMap
|
||||||
final mutation = Options$Mutation$ChangeTimezone(
|
final mutation = Options$Mutation$ChangeTimezone(
|
||||||
variables: variables,
|
variables: variables,
|
||||||
);
|
);
|
||||||
await client.mutate$ChangeTimezone(mutation);
|
final result = await client.mutate$ChangeTimezone(mutation);
|
||||||
|
if (result.hasException) {
|
||||||
|
return GenericResult<String>(
|
||||||
|
success: false,
|
||||||
|
message: result.exception.toString(),
|
||||||
|
data: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GenericResult<String?>(
|
||||||
|
success: result.parsedData?.system.changeTimezone.success ?? false,
|
||||||
|
message: result.parsedData?.system.changeTimezone.message,
|
||||||
|
data: result.parsedData?.system.changeTimezone.timezone,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
return GenericResult<String?>(
|
||||||
|
success: false,
|
||||||
|
message: e.toString(),
|
||||||
|
data: '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ mixin VolumeApi on GraphQLApiMap {
|
||||||
if (response.hasException) {
|
if (response.hasException) {
|
||||||
print(response.exception.toString());
|
print(response.exception.toString());
|
||||||
}
|
}
|
||||||
|
// TODO: Rewrite to use fromGraphQL
|
||||||
volumes = response.data!['storage']['volumes']
|
volumes = response.data!['storage']['volumes']
|
||||||
.map<ServerDiskVolume>((final e) => ServerDiskVolume.fromJson(e))
|
.map<ServerDiskVolume>((final e) => ServerDiskVolume.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -59,17 +60,18 @@ mixin VolumeApi on GraphQLApiMap {
|
||||||
|
|
||||||
Future<GenericResult<String?>> migrateToBinds(
|
Future<GenericResult<String?>> migrateToBinds(
|
||||||
final Map<String, String> serviceToDisk,
|
final Map<String, String> serviceToDisk,
|
||||||
|
final String fallbackDrive,
|
||||||
) async {
|
) async {
|
||||||
GenericResult<String?>? mutation;
|
GenericResult<String?>? mutation;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final GraphQLClient client = await getClient();
|
final GraphQLClient client = await getClient();
|
||||||
final input = Input$MigrateToBindsInput(
|
final input = Input$MigrateToBindsInput(
|
||||||
bitwardenBlockDevice: serviceToDisk['bitwarden']!,
|
bitwardenBlockDevice: serviceToDisk['bitwarden'] ?? fallbackDrive,
|
||||||
emailBlockDevice: serviceToDisk['mailserver']!,
|
emailBlockDevice: serviceToDisk['email'] ?? fallbackDrive,
|
||||||
giteaBlockDevice: serviceToDisk['gitea']!,
|
giteaBlockDevice: serviceToDisk['gitea'] ?? fallbackDrive,
|
||||||
nextcloudBlockDevice: serviceToDisk['nextcloud']!,
|
nextcloudBlockDevice: serviceToDisk['nextcloud'] ?? fallbackDrive,
|
||||||
pleromaBlockDevice: serviceToDisk['pleroma']!,
|
pleromaBlockDevice: serviceToDisk['pleroma'] ?? fallbackDrive,
|
||||||
);
|
);
|
||||||
final variables = Variables$Mutation$MigrateToBinds(input: input);
|
final variables = Variables$Mutation$MigrateToBinds(input: input);
|
||||||
final migrateMutation =
|
final migrateMutation =
|
||||||
|
|
|
@ -547,7 +547,7 @@ class HetznerApi extends RestApiMap {
|
||||||
resizeVolumeResponse = await client.post(
|
resizeVolumeResponse = await client.post(
|
||||||
'/volumes/${volume.id}/actions/resize',
|
'/volumes/${volume.id}/actions/resize',
|
||||||
data: {
|
data: {
|
||||||
'size': size.gibibyte,
|
'size': size.gibibyte.floor(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
success =
|
success =
|
||||||
|
|
408
lib/logic/bloc/backups/backups_bloc.dart
Normal file
408
lib/logic/bloc/backups/backups_bloc.dart
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
|
||||||
|
part 'backups_event.dart';
|
||||||
|
part 'backups_state.dart';
|
||||||
|
|
||||||
|
class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
|
||||||
|
BackupsBloc() : super(BackupsInitial()) {
|
||||||
|
on<BackupsServerLoaded>(
|
||||||
|
_loadState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<BackupsServerReset>(
|
||||||
|
_resetState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<BackupsStateChanged>(
|
||||||
|
_updateState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<InitializeBackupsRepository>(
|
||||||
|
_initializeRepository,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<ForceSnapshotListUpdate>(
|
||||||
|
_forceSnapshotListUpdate,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<CreateBackups>(
|
||||||
|
_createBackups,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<RestoreBackup>(
|
||||||
|
_restoreBackup,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<SetAutobackupPeriod>(
|
||||||
|
_setAutobackupPeriod,
|
||||||
|
transformer: restartable(),
|
||||||
|
);
|
||||||
|
on<SetAutobackupQuotas>(
|
||||||
|
_setAutobackupQuotas,
|
||||||
|
transformer: restartable(),
|
||||||
|
);
|
||||||
|
on<ForgetSnapshot>(
|
||||||
|
_forgetSnapshot,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
_apiStatusSubscription = connectionRepository.connectionStatusStream
|
||||||
|
.listen((final ConnectionStatus connectionStatus) {
|
||||||
|
switch (connectionStatus) {
|
||||||
|
case ConnectionStatus.nonexistent:
|
||||||
|
add(const BackupsServerReset());
|
||||||
|
isLoaded = false;
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (!isLoaded) {
|
||||||
|
add(const BackupsServerLoaded());
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
if (apiData.backups.data == null || apiData.backupConfig.data == null) {
|
||||||
|
add(const BackupsServerReset());
|
||||||
|
isLoaded = false;
|
||||||
|
} else {
|
||||||
|
add(
|
||||||
|
BackupsStateChanged(
|
||||||
|
apiData.backups.data!,
|
||||||
|
apiData.backupConfig.data,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (connectionRepository.connectionStatus == ConnectionStatus.connected) {
|
||||||
|
add(const BackupsServerLoaded());
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final BackblazeApi backblaze = BackblazeApi();
|
||||||
|
|
||||||
|
Future<void> _loadState(
|
||||||
|
final BackupsServerLoaded event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||||
|
final backups = getIt<ApiConnectionRepository>().apiData.backups;
|
||||||
|
final backupConfig = getIt<ApiConnectionRepository>().apiData.backupConfig;
|
||||||
|
if (backupConfig.data == null || backups.data == null) {
|
||||||
|
emit(BackupsLoading());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bucket != null &&
|
||||||
|
backupConfig.data!.encryptionKey != bucket.encryptionKey) {
|
||||||
|
bucket = bucket.copyWith(
|
||||||
|
encryptionKey: backupConfig.data!.encryptionKey,
|
||||||
|
);
|
||||||
|
await getIt<ApiConfigModel>().setBackblazeBucket(bucket);
|
||||||
|
}
|
||||||
|
if (backupConfig.data!.isInitialized) {
|
||||||
|
emit(
|
||||||
|
BackupsInitialized(
|
||||||
|
backblazeBucket: bucket,
|
||||||
|
backupConfig: backupConfig.data,
|
||||||
|
backups: backups.data ?? [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resetState(
|
||||||
|
final BackupsServerReset event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
emit(BackupsInitial());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeRepository(
|
||||||
|
final InitializeBackupsRepository event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
if (state is! BackupsUnititialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(BackupsInitializing());
|
||||||
|
final String? encryptionKey = getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.backupConfig
|
||||||
|
.data
|
||||||
|
?.encryptionKey;
|
||||||
|
if (encryptionKey == null) {
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar("Couldn't get encryption key from your server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BackblazeBucket bucket;
|
||||||
|
|
||||||
|
if (state.backblazeBucket == null) {
|
||||||
|
final String domain = getIt<ApiConnectionRepository>()
|
||||||
|
.serverDomain!
|
||||||
|
.domainName
|
||||||
|
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||||
|
final int serverId = getIt<ApiConnectionRepository>().serverDetails!.id;
|
||||||
|
String bucketName =
|
||||||
|
'${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain';
|
||||||
|
if (bucketName.length > 49) {
|
||||||
|
bucketName = bucketName.substring(0, 49);
|
||||||
|
}
|
||||||
|
final String bucketId = await backblaze.createBucket(bucketName);
|
||||||
|
|
||||||
|
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
||||||
|
bucket = BackblazeBucket(
|
||||||
|
bucketId: bucketId,
|
||||||
|
bucketName: bucketName,
|
||||||
|
applicationKey: key.applicationKey,
|
||||||
|
applicationKeyId: key.applicationKeyId,
|
||||||
|
encryptionKey: encryptionKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
await getIt<ApiConfigModel>().setBackblazeBucket(bucket);
|
||||||
|
emit(state.copyWith(backblazeBucket: bucket));
|
||||||
|
} else {
|
||||||
|
bucket = state.backblazeBucket!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final GenericResult result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.initializeRepository(
|
||||||
|
InitializeRepositoryInput(
|
||||||
|
provider: BackupsProviderType.backblaze,
|
||||||
|
locationId: bucket.bucketId,
|
||||||
|
locationName: bucket.bucketName,
|
||||||
|
login: bucket.applicationKeyId,
|
||||||
|
password: bucket.applicationKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
result.message ?? "Couldn't initialize repository on your server.",
|
||||||
|
);
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backupConfig.invalidate();
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backups.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'Backups repository is now initializing. It may take a while.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateState(
|
||||||
|
final BackupsStateChanged event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.backupConfiguration == null ||
|
||||||
|
event.backupConfiguration!.isInitialized == false) {
|
||||||
|
emit(BackupsUnititialized());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||||
|
emit(
|
||||||
|
BackupsInitialized(
|
||||||
|
backblazeBucket: bucket,
|
||||||
|
backupConfig: event.backupConfiguration,
|
||||||
|
backups: event.backups,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _forceSnapshotListUpdate(
|
||||||
|
final ForceSnapshotListUpdate event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
|
||||||
|
await getIt<ApiConnectionRepository>().api.forceBackupListReload();
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backups.invalidate();
|
||||||
|
emit(currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createBackups(
|
||||||
|
final CreateBackups event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
for (final service in event.services) {
|
||||||
|
final GenericResult<ServerJob?> result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.startBackup(
|
||||||
|
service.id,
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
if (result.data != null) {
|
||||||
|
getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.serverJobs
|
||||||
|
.data
|
||||||
|
?.add(result.data!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(currentState);
|
||||||
|
getIt<ApiConnectionRepository>().emitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restoreBackup(
|
||||||
|
final RestoreBackup event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
final GenericResult result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.restoreBackup(
|
||||||
|
event.backupId,
|
||||||
|
event.restoreStrategy,
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
emit(currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setAutobackupPeriod(
|
||||||
|
final SetAutobackupPeriod event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
final GenericResult result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.setAutobackupPeriod(
|
||||||
|
period: event.period?.inMinutes,
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
if (result.success == true) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backupConfig.data =
|
||||||
|
getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.backupConfig
|
||||||
|
.data
|
||||||
|
?.copyWith(
|
||||||
|
autobackupPeriod: event.period,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
emit(currentState);
|
||||||
|
getIt<ApiConnectionRepository>().emitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setAutobackupQuotas(
|
||||||
|
final SetAutobackupQuotas event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
final GenericResult result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.setAutobackupQuotas(
|
||||||
|
event.quotas,
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
if (result.success == true) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backupConfig.data =
|
||||||
|
getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.backupConfig
|
||||||
|
.data
|
||||||
|
?.copyWith(
|
||||||
|
autobackupQuotas: event.quotas,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
emit(currentState);
|
||||||
|
getIt<ApiConnectionRepository>().emitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _forgetSnapshot(
|
||||||
|
final ForgetSnapshot event,
|
||||||
|
final Emitter<BackupsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is BackupsInitialized) {
|
||||||
|
// Optimistically remove the snapshot from the list
|
||||||
|
getIt<ApiConnectionRepository>().apiData.backups.data =
|
||||||
|
getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.backups
|
||||||
|
.data
|
||||||
|
?.where((final Backup backup) => backup.id != event.backupId)
|
||||||
|
.toList();
|
||||||
|
emit(BackupsBusy.fromState(currentState));
|
||||||
|
final GenericResult result =
|
||||||
|
await getIt<ApiConnectionRepository>().api.forgetSnapshot(
|
||||||
|
event.backupId,
|
||||||
|
);
|
||||||
|
if (result.success == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
} else if (result.data == false) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('backup.forget_snapshot_error'.tr());
|
||||||
|
}
|
||||||
|
emit(currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiStatusSubscription.cancel();
|
||||||
|
_apiDataSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<BackupsState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription _apiStatusSubscription;
|
||||||
|
late StreamSubscription _apiDataSubscription;
|
||||||
|
bool isLoaded = false;
|
||||||
|
}
|
89
lib/logic/bloc/backups/backups_event.dart
Normal file
89
lib/logic/bloc/backups/backups_event.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
part of 'backups_bloc.dart';
|
||||||
|
|
||||||
|
sealed class BackupsEvent extends Equatable {
|
||||||
|
const BackupsEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsServerLoaded extends BackupsEvent {
|
||||||
|
const BackupsServerLoaded();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsServerReset extends BackupsEvent {
|
||||||
|
const BackupsServerReset();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitializeBackupsRepository extends BackupsEvent {
|
||||||
|
const InitializeBackupsRepository();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsStateChanged extends BackupsEvent {
|
||||||
|
const BackupsStateChanged(this.backups, this.backupConfiguration);
|
||||||
|
|
||||||
|
final List<Backup> backups;
|
||||||
|
final BackupConfiguration? backupConfiguration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [backups, backupConfiguration];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForceSnapshotListUpdate extends BackupsEvent {
|
||||||
|
const ForceSnapshotListUpdate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateBackups extends BackupsEvent {
|
||||||
|
const CreateBackups(this.services);
|
||||||
|
|
||||||
|
final List<Service> services;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [services];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreBackup extends BackupsEvent {
|
||||||
|
const RestoreBackup(this.backupId, this.restoreStrategy);
|
||||||
|
|
||||||
|
final String backupId;
|
||||||
|
final BackupRestoreStrategy restoreStrategy;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [backupId, restoreStrategy];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetAutobackupPeriod extends BackupsEvent {
|
||||||
|
const SetAutobackupPeriod(this.period);
|
||||||
|
|
||||||
|
final Duration? period;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [period];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetAutobackupQuotas extends BackupsEvent {
|
||||||
|
const SetAutobackupQuotas(this.quotas);
|
||||||
|
|
||||||
|
final AutobackupQuotas quotas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [quotas];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForgetSnapshot extends BackupsEvent {
|
||||||
|
const ForgetSnapshot(this.backupId);
|
||||||
|
|
||||||
|
final String backupId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [backupId];
|
||||||
|
}
|
170
lib/logic/bloc/backups/backups_state.dart
Normal file
170
lib/logic/bloc/backups/backups_state.dart
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
part of 'backups_bloc.dart';
|
||||||
|
|
||||||
|
sealed class BackupsState extends Equatable {
|
||||||
|
BackupsState({
|
||||||
|
this.backblazeBucket,
|
||||||
|
});
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
final BackblazeBucket? backblazeBucket;
|
||||||
|
|
||||||
|
@Deprecated('Infer the initializations status from state')
|
||||||
|
bool get isInitialized => false;
|
||||||
|
|
||||||
|
@Deprecated('Infer the loading status from state')
|
||||||
|
bool get refreshing => false;
|
||||||
|
|
||||||
|
@Deprecated('Infer the prevent actions status from state')
|
||||||
|
bool get preventActions => true;
|
||||||
|
|
||||||
|
List<Backup> get backups => [];
|
||||||
|
|
||||||
|
List<Backup> serviceBackups(final String serviceId) => [];
|
||||||
|
|
||||||
|
Duration? get autobackupPeriod => null;
|
||||||
|
|
||||||
|
AutobackupQuotas? get autobackupQuotas => null;
|
||||||
|
|
||||||
|
BackupsState copyWith({required final BackblazeBucket backblazeBucket});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsInitial extends BackupsState {
|
||||||
|
BackupsInitial({
|
||||||
|
super.backblazeBucket,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsInitial copyWith({
|
||||||
|
final BackblazeBucket? backblazeBucket,
|
||||||
|
}) =>
|
||||||
|
BackupsInitial(backblazeBucket: backblazeBucket ?? this.backblazeBucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsLoading extends BackupsState {
|
||||||
|
BackupsLoading({
|
||||||
|
super.backblazeBucket,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
@Deprecated('Infer the loading status from state')
|
||||||
|
bool get refreshing => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsLoading copyWith({
|
||||||
|
final BackblazeBucket? backblazeBucket,
|
||||||
|
}) =>
|
||||||
|
BackupsLoading(backblazeBucket: backblazeBucket ?? this.backblazeBucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsUnititialized extends BackupsState {
|
||||||
|
BackupsUnititialized({
|
||||||
|
super.backblazeBucket,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsUnititialized copyWith({
|
||||||
|
final BackblazeBucket? backblazeBucket,
|
||||||
|
}) =>
|
||||||
|
BackupsUnititialized(
|
||||||
|
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsInitializing extends BackupsState {
|
||||||
|
BackupsInitializing({
|
||||||
|
super.backblazeBucket,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsInitializing copyWith({
|
||||||
|
final BackblazeBucket? backblazeBucket,
|
||||||
|
}) =>
|
||||||
|
BackupsInitializing(
|
||||||
|
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsInitialized extends BackupsState {
|
||||||
|
BackupsInitialized({
|
||||||
|
final List<Backup> backups = const [],
|
||||||
|
final BackupConfiguration? backupConfig,
|
||||||
|
super.backblazeBucket,
|
||||||
|
}) : _backupsHashCode = Object.hashAll(backups),
|
||||||
|
_backupConfigHashCode = Object.hashAll([backupConfig]);
|
||||||
|
|
||||||
|
final int _backupsHashCode;
|
||||||
|
final int _backupConfigHashCode;
|
||||||
|
|
||||||
|
List<Backup> get _backupList =>
|
||||||
|
apiConnectionRepository.apiData.backups.data ?? [];
|
||||||
|
|
||||||
|
BackupConfiguration? get _backupConfig =>
|
||||||
|
apiConnectionRepository.apiData.backupConfig.data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutobackupQuotas? get autobackupQuotas => _backupConfig?.autobackupQuotas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration? get autobackupPeriod =>
|
||||||
|
_backupConfig?.autobackupPeriod?.inMinutes == 0
|
||||||
|
? null
|
||||||
|
: _backupConfig?.autobackupPeriod;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@Deprecated('Infer the initializations status from state')
|
||||||
|
bool get isInitialized => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@Deprecated('Infer the prevent actions status from state')
|
||||||
|
bool get preventActions => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Backup> get backups {
|
||||||
|
try {
|
||||||
|
final List<Backup> list = _backupList;
|
||||||
|
list.sort((final a, final b) => b.time.compareTo(a.time));
|
||||||
|
return list;
|
||||||
|
} catch (_) {
|
||||||
|
return _backupList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Backup> serviceBackups(final String serviceId) => backups
|
||||||
|
.where((final backup) => backup.serviceId == serviceId)
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_backupsHashCode, _backupConfigHashCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
BackupsState copyWith({required final BackblazeBucket backblazeBucket}) =>
|
||||||
|
BackupsInitialized(
|
||||||
|
backups: backups,
|
||||||
|
backupConfig: _backupConfig,
|
||||||
|
backblazeBucket: backblazeBucket,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupsBusy extends BackupsInitialized {
|
||||||
|
BackupsBusy.fromState(final BackupsInitialized state)
|
||||||
|
: super(
|
||||||
|
backups: state.backups,
|
||||||
|
backupConfig: state._backupConfig,
|
||||||
|
backblazeBucket: state.backblazeBucket,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@Deprecated('Infer the prevent actions status from state')
|
||||||
|
bool get preventActions => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
39
lib/logic/bloc/connection_status/connection_status_bloc.dart
Normal file
39
lib/logic/bloc/connection_status/connection_status_bloc.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
|
||||||
|
part 'connection_status_event.dart';
|
||||||
|
part 'connection_status_state.dart';
|
||||||
|
|
||||||
|
class ConnectionStatusBloc
|
||||||
|
extends Bloc<ConnectionStatusEvent, ConnectionStatusState> {
|
||||||
|
ConnectionStatusBloc()
|
||||||
|
: super(
|
||||||
|
const ConnectionStatusState(
|
||||||
|
connectionStatus: ConnectionStatus.nonexistent,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
on<ConnectionStatusChanged>((final event, final emit) {
|
||||||
|
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
||||||
|
});
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiConnectionStatusSubscription =
|
||||||
|
apiConnectionRepository.connectionStatusStream.listen(
|
||||||
|
(final ConnectionStatus connectionStatus) {
|
||||||
|
add(
|
||||||
|
ConnectionStatusChanged(connectionStatus),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiConnectionStatusSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiConnectionStatusSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
part of 'connection_status_bloc.dart';
|
||||||
|
|
||||||
|
sealed class ConnectionStatusEvent extends Equatable {
|
||||||
|
const ConnectionStatusEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionStatusChanged extends ConnectionStatusEvent {
|
||||||
|
const ConnectionStatusChanged(this.connectionStatus);
|
||||||
|
|
||||||
|
final ConnectionStatus connectionStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [connectionStatus];
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
part of 'connection_status_bloc.dart';
|
||||||
|
|
||||||
|
class ConnectionStatusState extends Equatable {
|
||||||
|
const ConnectionStatusState({
|
||||||
|
required this.connectionStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ConnectionStatus connectionStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [connectionStatus];
|
||||||
|
}
|
110
lib/logic/bloc/devices/devices_bloc.dart
Normal file
110
lib/logic/bloc/devices/devices_bloc.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
|
|
||||||
|
part 'devices_event.dart';
|
||||||
|
part 'devices_state.dart';
|
||||||
|
|
||||||
|
class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||||
|
DevicesBloc() : super(DevicesInitial()) {
|
||||||
|
on<DevicesListChanged>(
|
||||||
|
_mapDevicesListChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<DeleteDevice>(
|
||||||
|
_mapDeleteDeviceToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
DevicesListChanged(apiData.devices.data),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
|
||||||
|
Future<void> _mapDevicesListChangedToState(
|
||||||
|
final DevicesListChanged event,
|
||||||
|
final Emitter<DevicesState> emit,
|
||||||
|
) async {
|
||||||
|
if (state is DevicesDeleting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.devices == null) {
|
||||||
|
emit(DevicesError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(DevicesLoaded(devices: event.devices!));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapDeleteDeviceToState(
|
||||||
|
final DeleteDevice event,
|
||||||
|
final Emitter<DevicesState> emit,
|
||||||
|
) async {
|
||||||
|
// Optimistically remove the device from the list
|
||||||
|
emit(
|
||||||
|
DevicesDeleting(
|
||||||
|
devices: state.devices
|
||||||
|
.where((final d) => d.name != event.device.name)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final GenericResult<void> response = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.deleteApiToken(event.device.name);
|
||||||
|
if (response.success) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||||
|
emit(
|
||||||
|
DevicesLoaded(
|
||||||
|
devices: state.devices
|
||||||
|
.where((final d) => d.name != event.device.name)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(response.message ?? 'Error deleting device');
|
||||||
|
emit(DevicesLoaded(devices: state.devices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getNewDeviceKey() async {
|
||||||
|
final GenericResult<String> response =
|
||||||
|
await getIt<ApiConnectionRepository>().api.createDeviceToken();
|
||||||
|
if (response.success) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
response.message ?? 'Error getting new device key',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<DevicesState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
23
lib/logic/bloc/devices/devices_event.dart
Normal file
23
lib/logic/bloc/devices/devices_event.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
part of 'devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DevicesEvent extends Equatable {
|
||||||
|
const DevicesEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesListChanged extends DevicesEvent {
|
||||||
|
const DevicesListChanged(this.devices);
|
||||||
|
|
||||||
|
final List<ApiToken>? devices;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteDevice extends DevicesEvent {
|
||||||
|
const DeleteDevice(this.device);
|
||||||
|
|
||||||
|
final ApiToken device;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [device];
|
||||||
|
}
|
53
lib/logic/bloc/devices/devices_state.dart
Normal file
53
lib/logic/bloc/devices/devices_state.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
part of 'devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DevicesState extends Equatable {
|
||||||
|
DevicesState({
|
||||||
|
required final List<ApiToken> devices,
|
||||||
|
}) : _hashCode = Object.hashAll(devices);
|
||||||
|
|
||||||
|
final int _hashCode;
|
||||||
|
|
||||||
|
List<ApiToken> get _devices =>
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.data ?? const [];
|
||||||
|
|
||||||
|
List<ApiToken> get devices => _devices;
|
||||||
|
ApiToken get thisDevice => _devices.firstWhere(
|
||||||
|
(final device) => device.isCaller,
|
||||||
|
orElse: () => ApiToken(
|
||||||
|
name: 'Error fetching device',
|
||||||
|
isCaller: true,
|
||||||
|
date: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ApiToken> get otherDevices =>
|
||||||
|
_devices.where((final device) => !device.isCaller).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesInitial extends DevicesState {
|
||||||
|
DevicesInitial() : super(devices: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesLoaded extends DevicesState {
|
||||||
|
DevicesLoaded({required super.devices});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesError extends DevicesState {
|
||||||
|
DevicesError() : super(devices: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesDeleting extends DevicesState {
|
||||||
|
DevicesDeleting({required super.devices});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
88
lib/logic/bloc/recovery_key/recovery_key_bloc.dart
Normal file
88
lib/logic/bloc/recovery_key/recovery_key_bloc.dart
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||||
|
|
||||||
|
part 'recovery_key_event.dart';
|
||||||
|
part 'recovery_key_state.dart';
|
||||||
|
|
||||||
|
class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||||
|
RecoveryKeyBloc() : super(RecoveryKeyInitial()) {
|
||||||
|
on<RecoveryKeyStatusChanged>(
|
||||||
|
_mapRecoveryKeyStatusChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<RecoveryKeyStatusRefresh>(
|
||||||
|
_mapRecoveryKeyStatusRefreshToState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
RecoveryKeyStatusChanged(apiData.recoveryKeyStatus.data),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
|
||||||
|
Future<void> _mapRecoveryKeyStatusChangedToState(
|
||||||
|
final RecoveryKeyStatusChanged event,
|
||||||
|
final Emitter<RecoveryKeyState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.recoveryKeyStatus == null) {
|
||||||
|
emit(RecoveryKeyError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> generateRecoveryKey({
|
||||||
|
final DateTime? expirationDate,
|
||||||
|
final int? numberOfUses,
|
||||||
|
}) async {
|
||||||
|
final GenericResult<String> response =
|
||||||
|
await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.generateRecoveryToken(expirationDate, numberOfUses);
|
||||||
|
if (response.success) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
|
||||||
|
unawaited(getIt<ApiConnectionRepository>().reload(null));
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
throw GenerationError(response.message ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapRecoveryKeyStatusRefreshToState(
|
||||||
|
final RecoveryKeyEvent event,
|
||||||
|
final Emitter<RecoveryKeyState> emit,
|
||||||
|
) async {
|
||||||
|
emit(RecoveryKeyRefreshing(keyStatus: state._status));
|
||||||
|
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<RecoveryKeyState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenerationError extends Error {
|
||||||
|
GenerationError(this.message);
|
||||||
|
final String message;
|
||||||
|
}
|
21
lib/logic/bloc/recovery_key/recovery_key_event.dart
Normal file
21
lib/logic/bloc/recovery_key/recovery_key_event.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
part of 'recovery_key_bloc.dart';
|
||||||
|
|
||||||
|
sealed class RecoveryKeyEvent extends Equatable {
|
||||||
|
const RecoveryKeyEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyStatusChanged extends RecoveryKeyEvent {
|
||||||
|
const RecoveryKeyStatusChanged(this.recoveryKeyStatus);
|
||||||
|
|
||||||
|
final RecoveryKeyStatus? recoveryKeyStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [recoveryKeyStatus];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyStatusRefresh extends RecoveryKeyEvent {
|
||||||
|
const RecoveryKeyStatusRefresh();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
56
lib/logic/bloc/recovery_key/recovery_key_state.dart
Normal file
56
lib/logic/bloc/recovery_key/recovery_key_state.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
part of 'recovery_key_bloc.dart';
|
||||||
|
|
||||||
|
sealed class RecoveryKeyState extends Equatable {
|
||||||
|
RecoveryKeyState({
|
||||||
|
required final RecoveryKeyStatus? keyStatus,
|
||||||
|
}) : _hashCode = keyStatus.hashCode;
|
||||||
|
|
||||||
|
final int _hashCode;
|
||||||
|
|
||||||
|
RecoveryKeyStatus get _status =>
|
||||||
|
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.data ??
|
||||||
|
const RecoveryKeyStatus(exists: false, valid: false);
|
||||||
|
|
||||||
|
bool get exists => _status.exists;
|
||||||
|
bool get isValid => _status.valid;
|
||||||
|
DateTime? get generatedAt => _status.date;
|
||||||
|
DateTime? get expiresAt => _status.expiration;
|
||||||
|
int? get usesLeft => _status.usesLeft;
|
||||||
|
|
||||||
|
bool get isInvalidBecauseExpired =>
|
||||||
|
_status.expiration != null &&
|
||||||
|
_status.expiration!.isBefore(DateTime.now());
|
||||||
|
|
||||||
|
bool get isInvalidBecauseUsed =>
|
||||||
|
_status.usesLeft != null && _status.usesLeft == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyInitial extends RecoveryKeyState {
|
||||||
|
RecoveryKeyInitial()
|
||||||
|
: super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false));
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyRefreshing extends RecoveryKeyState {
|
||||||
|
RecoveryKeyRefreshing({required super.keyStatus});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyLoaded extends RecoveryKeyState {
|
||||||
|
RecoveryKeyLoaded({required super.keyStatus});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveryKeyError extends RecoveryKeyState {
|
||||||
|
RecoveryKeyError()
|
||||||
|
: super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false));
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
101
lib/logic/bloc/server_jobs/server_jobs_bloc.dart
Normal file
101
lib/logic/bloc/server_jobs/server_jobs_bloc.dart
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
|
||||||
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
part 'server_jobs_state.dart';
|
||||||
|
part 'server_jobs_event.dart';
|
||||||
|
|
||||||
|
class ServerJobsBloc extends Bloc<ServerJobsEvent, ServerJobsState> {
|
||||||
|
ServerJobsBloc()
|
||||||
|
: super(
|
||||||
|
ServerJobsInitialState(),
|
||||||
|
) {
|
||||||
|
on<ServerJobsListChanged>(
|
||||||
|
_mapServerJobsListChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<RemoveServerJob>(
|
||||||
|
_mapRemoveServerJobToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<RemoveAllFinishedJobs>(
|
||||||
|
_mapRemoveAllFinishedJobsToState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
ServerJobsListChanged([...apiData.serverJobs.data ?? []]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
|
||||||
|
Future<void> _mapServerJobsListChangedToState(
|
||||||
|
final ServerJobsListChanged event,
|
||||||
|
final Emitter<ServerJobsState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.serverJobList.isEmpty) {
|
||||||
|
emit(ServerJobsListEmptyState());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final newState =
|
||||||
|
ServerJobsListWithJobsState(serverJobList: event.serverJobList);
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapRemoveServerJobToState(
|
||||||
|
final RemoveServerJob event,
|
||||||
|
final Emitter<ServerJobsState> emit,
|
||||||
|
) async {
|
||||||
|
await getIt<ApiConnectionRepository>().removeServerJob(event.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapRemoveAllFinishedJobsToState(
|
||||||
|
final RemoveAllFinishedJobs event,
|
||||||
|
final Emitter<ServerJobsState> emit,
|
||||||
|
) async {
|
||||||
|
await getIt<ApiConnectionRepository>().removeAllFinishedServerJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> migrateToBinds(final Map<String, String> serviceToDisk) async {
|
||||||
|
final fallbackDrive = getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.volumes
|
||||||
|
.data
|
||||||
|
?.where((final drive) => drive.root)
|
||||||
|
.first
|
||||||
|
.name ??
|
||||||
|
'sda1';
|
||||||
|
final result = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.migrateToBinds(serviceToDisk, fallbackDrive);
|
||||||
|
if (result.data == null) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message!, behavior: SnackBarBehavior.floating);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<ServerJobsState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
28
lib/logic/bloc/server_jobs/server_jobs_event.dart
Normal file
28
lib/logic/bloc/server_jobs/server_jobs_event.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
part of 'server_jobs_bloc.dart';
|
||||||
|
|
||||||
|
sealed class ServerJobsEvent extends Equatable {
|
||||||
|
const ServerJobsEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerJobsListChanged extends ServerJobsEvent {
|
||||||
|
const ServerJobsListChanged(this.serverJobList);
|
||||||
|
|
||||||
|
final List<ServerJob> serverJobList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [serverJobList];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveServerJob extends ServerJobsEvent {
|
||||||
|
const RemoveServerJob(this.uid);
|
||||||
|
|
||||||
|
final String uid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [uid];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveAllFinishedJobs extends ServerJobsEvent {}
|
55
lib/logic/bloc/server_jobs/server_jobs_state.dart
Normal file
55
lib/logic/bloc/server_jobs/server_jobs_state.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
part of 'server_jobs_bloc.dart';
|
||||||
|
|
||||||
|
sealed class ServerJobsState extends Equatable {
|
||||||
|
ServerJobsState({
|
||||||
|
final int? hashCode,
|
||||||
|
}) : _hashCode = hashCode ?? Object.hashAll([]);
|
||||||
|
|
||||||
|
final int? _hashCode;
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
List<ServerJob> get _serverJobList =>
|
||||||
|
apiConnectionRepository.apiData.serverJobs.data ?? [];
|
||||||
|
|
||||||
|
List<ServerJob> get serverJobList {
|
||||||
|
try {
|
||||||
|
final List<ServerJob> list = _serverJobList;
|
||||||
|
list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
return list;
|
||||||
|
} on UnsupportedError {
|
||||||
|
return _serverJobList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ServerJob> get backupJobList => serverJobList
|
||||||
|
.where(
|
||||||
|
// The backup jobs has the format of 'service.<service_id>.backup'
|
||||||
|
(final job) =>
|
||||||
|
job.typeId.contains('backup') || job.typeId.contains('restore'),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
bool get hasRemovableJobs => serverJobList.any(
|
||||||
|
(final job) =>
|
||||||
|
job.status == JobStatusEnum.finished ||
|
||||||
|
job.status == JobStatusEnum.error,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerJobsInitialState extends ServerJobsState {
|
||||||
|
ServerJobsInitialState() : super(hashCode: Object.hashAll([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerJobsListEmptyState extends ServerJobsState {
|
||||||
|
ServerJobsListEmptyState() : super(hashCode: Object.hashAll([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerJobsListWithJobsState extends ServerJobsState {
|
||||||
|
ServerJobsListWithJobsState({
|
||||||
|
required final List<ServerJob> serverJobList,
|
||||||
|
}) : super(hashCode: Object.hashAll([...serverJobList]));
|
||||||
|
}
|
149
lib/logic/bloc/services/services_bloc.dart
Normal file
149
lib/logic/bloc/services/services_bloc.dart
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
|
||||||
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
|
||||||
|
part 'services_event.dart';
|
||||||
|
part 'services_state.dart';
|
||||||
|
|
||||||
|
class ServicesBloc extends Bloc<ServicesEvent, ServicesState> {
|
||||||
|
ServicesBloc() : super(ServicesInitial()) {
|
||||||
|
on<ServicesListUpdate>(
|
||||||
|
_updateList,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<ServicesReload>(
|
||||||
|
_reload,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<ServiceRestart>(
|
||||||
|
_restart,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<ServiceMove>(
|
||||||
|
_move,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
ServicesListUpdate([...apiData.services.data ?? []]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (connectionRepository.connectionStatus == ConnectionStatus.connected) {
|
||||||
|
add(
|
||||||
|
ServicesListUpdate(
|
||||||
|
[...connectionRepository.apiData.services.data ?? []],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateList(
|
||||||
|
final ServicesListUpdate event,
|
||||||
|
final Emitter<ServicesState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.services.isEmpty) {
|
||||||
|
emit(ServicesInitial());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final newState = ServicesLoaded(
|
||||||
|
services: event.services,
|
||||||
|
lockedServices: state._lockedServices,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reload(
|
||||||
|
final ServicesReload event,
|
||||||
|
final Emitter<ServicesState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is ServicesLoaded) {
|
||||||
|
emit(ServicesReloading.fromState(currentState));
|
||||||
|
getIt<ApiConnectionRepository>().apiData.services.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> awaitReload() async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is ServicesLoaded) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.services.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restart(
|
||||||
|
final ServiceRestart event,
|
||||||
|
final Emitter<ServicesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
lockedServices: [
|
||||||
|
...state._lockedServices,
|
||||||
|
ServiceLock(
|
||||||
|
serviceId: event.service.id,
|
||||||
|
lockDuration: const Duration(seconds: 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final result = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.restartService(event.service.id);
|
||||||
|
if (!result.success) {
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.data) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _move(
|
||||||
|
final ServiceMove event,
|
||||||
|
final Emitter<ServicesState> emit,
|
||||||
|
) async {
|
||||||
|
final migrationJob = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.moveService(event.service.id, event.destination);
|
||||||
|
if (!migrationJob.success) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(migrationJob.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
if (migrationJob.data != null) {
|
||||||
|
getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.serverJobs
|
||||||
|
.data
|
||||||
|
?.add(migrationJob.data!);
|
||||||
|
getIt<ApiConnectionRepository>().emitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription _apiDataSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<ServicesState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
40
lib/logic/bloc/services/services_event.dart
Normal file
40
lib/logic/bloc/services/services_event.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
part of 'services_bloc.dart';
|
||||||
|
|
||||||
|
sealed class ServicesEvent extends Equatable {
|
||||||
|
const ServicesEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServicesListUpdate extends ServicesEvent {
|
||||||
|
const ServicesListUpdate(this.services);
|
||||||
|
|
||||||
|
final List<Service> services;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [services];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServicesReload extends ServicesEvent {
|
||||||
|
const ServicesReload();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceRestart extends ServicesEvent {
|
||||||
|
const ServiceRestart(this.service);
|
||||||
|
|
||||||
|
final Service service;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [service];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceMove extends ServicesEvent {
|
||||||
|
const ServiceMove(this.service, this.destination);
|
||||||
|
|
||||||
|
final Service service;
|
||||||
|
final String destination;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [service, destination];
|
||||||
|
}
|
115
lib/logic/bloc/services/services_state.dart
Normal file
115
lib/logic/bloc/services/services_state.dart
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
part of 'services_bloc.dart';
|
||||||
|
|
||||||
|
sealed class ServicesState extends Equatable {
|
||||||
|
ServicesState({final List<ServiceLock> lockedServices = const []})
|
||||||
|
: _lockedServices =
|
||||||
|
lockedServices.where((final lock) => lock.isLocked).toList();
|
||||||
|
final List<ServiceLock> _lockedServices;
|
||||||
|
List<Service> get services;
|
||||||
|
List<String> get lockedServices => _lockedServices
|
||||||
|
.where((final lock) => lock.isLocked)
|
||||||
|
.map((final lock) => lock.serviceId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Service> get servicesThatCanBeBackedUp => services
|
||||||
|
.where(
|
||||||
|
(final service) => service.canBeBackedUp,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
bool isServiceLocked(final String serviceId) =>
|
||||||
|
lockedServices.contains(serviceId);
|
||||||
|
|
||||||
|
Service? getServiceById(final String id) {
|
||||||
|
final service = services.firstWhere(
|
||||||
|
(final service) => service.id == id,
|
||||||
|
orElse: () => Service.empty,
|
||||||
|
);
|
||||||
|
if (service.id == 'empty') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServicesState copyWith({
|
||||||
|
final List<Service>? services,
|
||||||
|
final List<ServiceLock>? lockedServices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceLock extends Equatable {
|
||||||
|
ServiceLock({
|
||||||
|
required this.serviceId,
|
||||||
|
required this.lockDuration,
|
||||||
|
}) : lockTime = DateTime.now();
|
||||||
|
|
||||||
|
final String serviceId;
|
||||||
|
final Duration lockDuration;
|
||||||
|
final DateTime lockTime;
|
||||||
|
|
||||||
|
bool get isLocked => DateTime.now().isBefore(lockTime.add(lockDuration));
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [serviceId, lockDuration, lockTime];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServicesInitial extends ServicesState {
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Service> get services => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServicesState copyWith({
|
||||||
|
final List<Service>? services,
|
||||||
|
final List<ServiceLock>? lockedServices,
|
||||||
|
}) =>
|
||||||
|
ServicesInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServicesLoaded extends ServicesState {
|
||||||
|
ServicesLoaded({
|
||||||
|
required final List<Service> services,
|
||||||
|
required super.lockedServices,
|
||||||
|
}) : _servicesHachCode = Object.hashAll([...services]);
|
||||||
|
|
||||||
|
final int _servicesHachCode;
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
List<Service> get _services =>
|
||||||
|
apiConnectionRepository.apiData.services.data ?? [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Service> get services => _services;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [_servicesHachCode, _lockedServices];
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServicesLoaded copyWith({
|
||||||
|
final List<Service>? services,
|
||||||
|
final List<ServiceLock>? lockedServices,
|
||||||
|
}) =>
|
||||||
|
ServicesLoaded(
|
||||||
|
services: services ?? this.services,
|
||||||
|
lockedServices: lockedServices ?? _lockedServices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServicesReloading extends ServicesLoaded {
|
||||||
|
ServicesReloading({
|
||||||
|
required super.services,
|
||||||
|
required super.lockedServices,
|
||||||
|
});
|
||||||
|
|
||||||
|
ServicesReloading.fromState(final ServicesLoaded state)
|
||||||
|
: super(
|
||||||
|
services: state.services,
|
||||||
|
lockedServices: state._lockedServices,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [services, lockedServices];
|
||||||
|
}
|
105
lib/logic/bloc/users/users_bloc.dart
Normal file
105
lib/logic/bloc/users/users_bloc.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
|
||||||
|
part 'users_event.dart';
|
||||||
|
part 'users_state.dart';
|
||||||
|
|
||||||
|
class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||||
|
UsersBloc() : super(UsersInitial()) {
|
||||||
|
on<UsersListChanged>(
|
||||||
|
_updateList,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<UsersListRefresh>(
|
||||||
|
_reload,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<UsersConnectionStatusChanged>(
|
||||||
|
_mapConnectionStatusChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiConnectionStatusSubscription =
|
||||||
|
apiConnectionRepository.connectionStatusStream.listen(
|
||||||
|
(final ConnectionStatus connectionStatus) {
|
||||||
|
add(
|
||||||
|
UsersConnectionStatusChanged(connectionStatus),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
UsersListChanged(apiData.users.data ?? []),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateList(
|
||||||
|
final UsersListChanged event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.users.isEmpty) {
|
||||||
|
emit(UsersInitial());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final newState = UsersLoaded(
|
||||||
|
users: event.users,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.users.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reload(
|
||||||
|
final UsersListRefresh event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
emit(UsersRefreshing(users: state.users));
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapConnectionStatusChangedToState(
|
||||||
|
final UsersConnectionStatusChanged event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
switch (event.connectionStatus) {
|
||||||
|
case ConnectionStatus.nonexistent:
|
||||||
|
emit(UsersInitial());
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (state is! UsersLoaded) {
|
||||||
|
emit(UsersRefreshing(users: state.users));
|
||||||
|
}
|
||||||
|
case ConnectionStatus.reconnecting:
|
||||||
|
case ConnectionStatus.offline:
|
||||||
|
case ConnectionStatus.unauthorized:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
StreamSubscription? _apiConnectionStatusSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<UsersState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
_apiConnectionStatusSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
30
lib/logic/bloc/users/users_event.dart
Normal file
30
lib/logic/bloc/users/users_event.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
part of 'users_bloc.dart';
|
||||||
|
|
||||||
|
sealed class UsersEvent extends Equatable {
|
||||||
|
const UsersEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersListChanged extends UsersEvent {
|
||||||
|
const UsersListChanged(this.users);
|
||||||
|
|
||||||
|
final List<User> users;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [users];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersListRefresh extends UsersEvent {
|
||||||
|
const UsersListRefresh();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersConnectionStatusChanged extends UsersEvent {
|
||||||
|
const UsersConnectionStatusChanged(this.connectionStatus);
|
||||||
|
|
||||||
|
final ConnectionStatus connectionStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [connectionStatus];
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
part of 'users_cubit.dart';
|
part of 'users_bloc.dart';
|
||||||
|
|
||||||
class UsersState extends ServerInstallationDependendState {
|
sealed class UsersState extends Equatable {
|
||||||
const UsersState(this.users, this.isLoading);
|
UsersState({
|
||||||
|
required final List<User> users,
|
||||||
|
}) : _hashCode = Object.hashAll(users);
|
||||||
|
|
||||||
final List<User> users;
|
final int _hashCode;
|
||||||
final bool isLoading;
|
|
||||||
|
List<User> get users =>
|
||||||
|
getIt<ApiConnectionRepository>().apiData.users.data ?? const [];
|
||||||
|
|
||||||
User get rootUser =>
|
User get rootUser =>
|
||||||
users.firstWhere((final user) => user.type == UserType.root);
|
users.firstWhere((final user) => user.type == UserType.root);
|
||||||
|
@ -15,9 +19,6 @@ class UsersState extends ServerInstallationDependendState {
|
||||||
List<User> get normalUsers =>
|
List<User> get normalUsers =>
|
||||||
users.where((final user) => user.type == UserType.normal).toList();
|
users.where((final user) => user.type == UserType.normal).toList();
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [users, isLoading];
|
|
||||||
|
|
||||||
/// Makes a copy of existing users list, but places 'primary'
|
/// Makes a copy of existing users list, but places 'primary'
|
||||||
/// to the beginning and sorts the rest alphabetically
|
/// to the beginning and sorts the rest alphabetically
|
||||||
///
|
///
|
||||||
|
@ -44,17 +45,29 @@ class UsersState extends ServerInstallationDependendState {
|
||||||
return primaryUser == null ? normalUsers : [primaryUser] + normalUsers;
|
return primaryUser == null ? normalUsers : [primaryUser] + normalUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
UsersState copyWith({
|
|
||||||
final List<User>? users,
|
|
||||||
final bool? isLoading,
|
|
||||||
}) =>
|
|
||||||
UsersState(
|
|
||||||
users ?? this.users,
|
|
||||||
isLoading ?? this.isLoading,
|
|
||||||
);
|
|
||||||
|
|
||||||
bool isLoginRegistered(final String login) =>
|
bool isLoginRegistered(final String login) =>
|
||||||
users.any((final User user) => user.login == login);
|
users.any((final User user) => user.login == login);
|
||||||
|
|
||||||
bool get isEmpty => users.isEmpty;
|
bool get isEmpty => users.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsersInitial extends UsersState {
|
||||||
|
UsersInitial() : super(users: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersRefreshing extends UsersState {
|
||||||
|
UsersRefreshing({required super.users});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersLoaded extends UsersState {
|
||||||
|
UsersLoaded({required super.users});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
246
lib/logic/bloc/volumes/volumes_bloc.dart
Normal file
246
lib/logic/bloc/volumes/volumes_bloc.dart
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||||
|
|
||||||
|
part 'volumes_event.dart';
|
||||||
|
part 'volumes_state.dart';
|
||||||
|
|
||||||
|
class VolumesBloc extends Bloc<VolumesEvent, VolumesState> {
|
||||||
|
VolumesBloc() : super(VolumesInitial()) {
|
||||||
|
on<VolumesServerLoaded>(
|
||||||
|
_loadState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<VolumesServerReset>(
|
||||||
|
_resetState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<VolumesServerStateChanged>(
|
||||||
|
_updateState,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<VolumeResize>(
|
||||||
|
_resizeVolume,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
_apiStatusSubscription = connectionRepository.connectionStatusStream
|
||||||
|
.listen((final ConnectionStatus connectionStatus) {
|
||||||
|
switch (connectionStatus) {
|
||||||
|
case ConnectionStatus.nonexistent:
|
||||||
|
add(const VolumesServerReset());
|
||||||
|
isLoaded = false;
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (!isLoaded) {
|
||||||
|
add(const VolumesServerLoaded());
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_apiDataSubscription = connectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
if (apiData.volumes.data == null) {
|
||||||
|
add(const VolumesServerReset());
|
||||||
|
} else {
|
||||||
|
add(
|
||||||
|
VolumesServerStateChanged(
|
||||||
|
apiData.volumes.data!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription _apiStatusSubscription;
|
||||||
|
late StreamSubscription _apiDataSubscription;
|
||||||
|
bool isLoaded = false;
|
||||||
|
|
||||||
|
Future<Price?> getPricePerGb() async {
|
||||||
|
if (ProvidersController.currentServerProvider == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Price? price;
|
||||||
|
final pricingResult =
|
||||||
|
await ProvidersController.currentServerProvider!.getAdditionalPricing();
|
||||||
|
if (pricingResult.data == null || !pricingResult.success) {
|
||||||
|
getIt<NavigationService>().showSnackBar('server.pricing_error'.tr());
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
price = pricingResult.data!.perVolumeGb;
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadState(
|
||||||
|
final VolumesServerLoaded event,
|
||||||
|
final Emitter<VolumesState> emit,
|
||||||
|
) async {
|
||||||
|
if (ProvidersController.currentServerProvider == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(VolumesLoading());
|
||||||
|
|
||||||
|
final volumesResult =
|
||||||
|
await ProvidersController.currentServerProvider!.getVolumes();
|
||||||
|
|
||||||
|
if (!volumesResult.success || volumesResult.data.isEmpty) {
|
||||||
|
emit(VolumesInitial());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final serverVolumes = getIt<ApiConnectionRepository>().apiData.volumes.data;
|
||||||
|
|
||||||
|
if (serverVolumes == null) {
|
||||||
|
emit(VolumesLoading(providerVolumes: volumesResult.data));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
emit(
|
||||||
|
VolumesLoaded(
|
||||||
|
diskStatus: DiskStatus.fromVolumes(
|
||||||
|
serverVolumes,
|
||||||
|
volumesResult.data,
|
||||||
|
),
|
||||||
|
providerVolumes: volumesResult.data,
|
||||||
|
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resetState(
|
||||||
|
final VolumesServerReset event,
|
||||||
|
final Emitter<VolumesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(VolumesInitial());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<VolumesState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await _apiStatusSubscription.cancel();
|
||||||
|
await _apiDataSubscription.cancel();
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> invalidateCache() async {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.volumes.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateState(
|
||||||
|
final VolumesServerStateChanged event,
|
||||||
|
final Emitter<VolumesState> emit,
|
||||||
|
) async {
|
||||||
|
final serverVolumes = event.volumes;
|
||||||
|
final providerVolumes = state.providerVolumes;
|
||||||
|
if (state is VolumesLoading) {
|
||||||
|
emit(
|
||||||
|
VolumesLoaded(
|
||||||
|
diskStatus: DiskStatus.fromVolumes(
|
||||||
|
serverVolumes,
|
||||||
|
providerVolumes,
|
||||||
|
),
|
||||||
|
providerVolumes: providerVolumes,
|
||||||
|
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
diskStatus: DiskStatus.fromVolumes(
|
||||||
|
serverVolumes,
|
||||||
|
providerVolumes,
|
||||||
|
),
|
||||||
|
providerVolumes: providerVolumes,
|
||||||
|
serverVolumesHashCode: Object.hashAll(serverVolumes),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resizeVolume(
|
||||||
|
final VolumeResize event,
|
||||||
|
final Emitter<VolumesState> emit,
|
||||||
|
) async {
|
||||||
|
if (state is! VolumesLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'storage.extending_volume_started'.tr(),
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
VolumesResizing(
|
||||||
|
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||||
|
diskStatus: state.diskStatus,
|
||||||
|
providerVolumes: state.providerVolumes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final resizedResult =
|
||||||
|
await ProvidersController.currentServerProvider!.resizeVolume(
|
||||||
|
event.volume.providerVolume!,
|
||||||
|
event.newSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resizedResult.success || !resizedResult.data) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'storage.extending_volume_error'.tr(),
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
VolumesLoaded(
|
||||||
|
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||||
|
diskStatus: state.diskStatus,
|
||||||
|
providerVolumes: state.providerVolumes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'storage.extending_volume_waiting'.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
|
||||||
|
await getIt<ApiConnectionRepository>().api.resizeVolume(event.volume.name);
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'storage.extending_volume_server_waiting'.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(seconds: 20));
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'storage.extending_volume_rebooting'.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
VolumesLoaded(
|
||||||
|
serverVolumesHashCode: state._serverVolumesHashCode,
|
||||||
|
diskStatus: state.diskStatus,
|
||||||
|
providerVolumes: state.providerVolumes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await getIt<ApiConnectionRepository>().api.reboot();
|
||||||
|
}
|
||||||
|
}
|
43
lib/logic/bloc/volumes/volumes_event.dart
Normal file
43
lib/logic/bloc/volumes/volumes_event.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
part of 'volumes_bloc.dart';
|
||||||
|
|
||||||
|
sealed class VolumesEvent extends Equatable {
|
||||||
|
const VolumesEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesServerLoaded extends VolumesEvent {
|
||||||
|
const VolumesServerLoaded();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesServerReset extends VolumesEvent {
|
||||||
|
const VolumesServerReset();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesServerStateChanged extends VolumesEvent {
|
||||||
|
const VolumesServerStateChanged(
|
||||||
|
this.volumes,
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<ServerDiskVolume> volumes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [volumes];
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumeResize extends VolumesEvent {
|
||||||
|
const VolumeResize(
|
||||||
|
this.volume,
|
||||||
|
this.newSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
final DiskVolume volume;
|
||||||
|
final DiskSize newSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [volume, newSize];
|
||||||
|
}
|
122
lib/logic/bloc/volumes/volumes_state.dart
Normal file
122
lib/logic/bloc/volumes/volumes_state.dart
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
part of 'volumes_bloc.dart';
|
||||||
|
|
||||||
|
sealed class VolumesState extends Equatable {
|
||||||
|
const VolumesState({
|
||||||
|
required this.diskStatus,
|
||||||
|
required final serverVolumesHashCode,
|
||||||
|
this.providerVolumes = const [],
|
||||||
|
}) : _serverVolumesHashCode = serverVolumesHashCode;
|
||||||
|
|
||||||
|
final DiskStatus diskStatus;
|
||||||
|
final List<ServerProviderVolume> providerVolumes;
|
||||||
|
List<DiskVolume> get volumes => diskStatus.diskVolumes;
|
||||||
|
final int? _serverVolumesHashCode;
|
||||||
|
|
||||||
|
DiskVolume getVolume(final String volumeName) => volumes.firstWhere(
|
||||||
|
(final volume) => volume.name == volumeName,
|
||||||
|
orElse: () => DiskVolume(),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool get isProviderVolumesLoaded => providerVolumes.isNotEmpty;
|
||||||
|
|
||||||
|
VolumesState copyWith({
|
||||||
|
required final int? serverVolumesHashCode,
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesInitial extends VolumesState {
|
||||||
|
VolumesInitial()
|
||||||
|
: super(
|
||||||
|
diskStatus: DiskStatus(),
|
||||||
|
serverVolumesHashCode: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumesInitial copyWith({
|
||||||
|
required final int? serverVolumesHashCode,
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
}) =>
|
||||||
|
VolumesInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesLoading extends VolumesState {
|
||||||
|
VolumesLoading({
|
||||||
|
super.serverVolumesHashCode,
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
}) : super(
|
||||||
|
diskStatus: diskStatus ?? DiskStatus(),
|
||||||
|
providerVolumes: providerVolumes ?? const [],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumesLoading copyWith({
|
||||||
|
required final int? serverVolumesHashCode,
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
}) =>
|
||||||
|
VolumesLoading(
|
||||||
|
diskStatus: diskStatus ?? this.diskStatus,
|
||||||
|
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||||
|
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesLoaded extends VolumesState {
|
||||||
|
const VolumesLoaded({
|
||||||
|
required super.serverVolumesHashCode,
|
||||||
|
required super.diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
}) : super(
|
||||||
|
providerVolumes: providerVolumes ?? const [],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumesLoaded copyWith({
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
final int? serverVolumesHashCode,
|
||||||
|
}) =>
|
||||||
|
VolumesLoaded(
|
||||||
|
diskStatus: diskStatus ?? this.diskStatus,
|
||||||
|
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||||
|
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VolumesResizing extends VolumesState {
|
||||||
|
const VolumesResizing({
|
||||||
|
required super.serverVolumesHashCode,
|
||||||
|
required super.diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
}) : super(
|
||||||
|
providerVolumes: providerVolumes ?? const [],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [providerVolumes, _serverVolumesHashCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumesResizing copyWith({
|
||||||
|
final DiskStatus? diskStatus,
|
||||||
|
final List<ServerProviderVolume>? providerVolumes,
|
||||||
|
final int? serverVolumesHashCode,
|
||||||
|
}) =>
|
||||||
|
VolumesResizing(
|
||||||
|
diskStatus: diskStatus ?? this.diskStatus,
|
||||||
|
providerVolumes: providerVolumes ?? this.providerVolumes,
|
||||||
|
serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!,
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
|
||||||
|
|
||||||
export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
|
||||||
|
|
||||||
part 'authentication_dependend_state.dart';
|
|
||||||
|
|
||||||
abstract class ServerInstallationDependendCubit<
|
|
||||||
T extends ServerInstallationDependendState> extends Cubit<T> {
|
|
||||||
ServerInstallationDependendCubit(
|
|
||||||
this.serverInstallationCubit,
|
|
||||||
final T initState,
|
|
||||||
) : super(initState) {
|
|
||||||
authCubitSubscription =
|
|
||||||
serverInstallationCubit.stream.listen(checkAuthStatus);
|
|
||||||
checkAuthStatus(serverInstallationCubit.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkAuthStatus(final ServerInstallationState state) {
|
|
||||||
if (state is ServerInstallationFinished) {
|
|
||||||
load();
|
|
||||||
} else if (state is ServerInstallationEmpty) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
late StreamSubscription authCubitSubscription;
|
|
||||||
final ServerInstallationCubit serverInstallationCubit;
|
|
||||||
|
|
||||||
void load();
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
authCubitSubscription.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,279 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.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/rest_maps/backblaze.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/backup.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
|
||||||
|
|
||||||
part 'backups_state.dart';
|
|
||||||
|
|
||||||
class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
|
||||||
BackupsCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(
|
|
||||||
serverInstallationCubit,
|
|
||||||
const BackupsState(preventActions: true),
|
|
||||||
);
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
final BackblazeApi backblaze = BackblazeApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
|
||||||
final BackupConfiguration? backupConfig =
|
|
||||||
await api.getBackupsConfiguration();
|
|
||||||
final List<Backup> backups = await api.getBackups();
|
|
||||||
backups.sort((final a, final b) => b.time.compareTo(a.time));
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
backblazeBucket: bucket,
|
|
||||||
isInitialized: backupConfig?.isInitialized,
|
|
||||||
autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero,
|
|
||||||
autobackupQuotas: backupConfig?.autobackupQuotas,
|
|
||||||
backups: backups,
|
|
||||||
preventActions: false,
|
|
||||||
refreshing: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initializeBackups() async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
final String? encryptionKey =
|
|
||||||
(await api.getBackupsConfiguration())?.encryptionKey;
|
|
||||||
if (encryptionKey == null) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar("Couldn't get encryption key from your server.");
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final BackblazeBucket bucket;
|
|
||||||
|
|
||||||
if (state.backblazeBucket == null) {
|
|
||||||
final String domain = serverInstallationCubit
|
|
||||||
.state.serverDomain!.domainName
|
|
||||||
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
|
||||||
final int serverId = serverInstallationCubit.state.serverDetails!.id;
|
|
||||||
String bucketName =
|
|
||||||
'${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain';
|
|
||||||
if (bucketName.length > 49) {
|
|
||||||
bucketName = bucketName.substring(0, 49);
|
|
||||||
}
|
|
||||||
final String bucketId = await backblaze.createBucket(bucketName);
|
|
||||||
|
|
||||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
|
||||||
bucket = BackblazeBucket(
|
|
||||||
bucketId: bucketId,
|
|
||||||
bucketName: bucketName,
|
|
||||||
applicationKey: key.applicationKey,
|
|
||||||
applicationKeyId: key.applicationKeyId,
|
|
||||||
encryptionKey: encryptionKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
|
|
||||||
emit(state.copyWith(backblazeBucket: bucket));
|
|
||||||
} else {
|
|
||||||
bucket = state.backblazeBucket!;
|
|
||||||
}
|
|
||||||
|
|
||||||
final GenericResult result = await api.initializeRepository(
|
|
||||||
InitializeRepositoryInput(
|
|
||||||
provider: BackupsProviderType.backblaze,
|
|
||||||
locationId: bucket.bucketId,
|
|
||||||
locationName: bucket.bucketName,
|
|
||||||
login: bucket.applicationKeyId,
|
|
||||||
password: bucket.applicationKey,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result.success == false) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'Unknown error');
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await updateBackups();
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'Backups repository is now initializing. It may take a while.',
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reuploadKey() async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
|
||||||
if (bucket == null) {
|
|
||||||
emit(state.copyWith(isInitialized: false));
|
|
||||||
} else {
|
|
||||||
String login = bucket.applicationKeyId;
|
|
||||||
String password = bucket.applicationKey;
|
|
||||||
if (login.isEmpty || password.isEmpty) {
|
|
||||||
final BackblazeApplicationKey key =
|
|
||||||
await backblaze.createKey(bucket.bucketId);
|
|
||||||
login = key.applicationKeyId;
|
|
||||||
password = key.applicationKey;
|
|
||||||
bucket = BackblazeBucket(
|
|
||||||
bucketId: bucket.bucketId,
|
|
||||||
bucketName: bucket.bucketName,
|
|
||||||
encryptionKey: bucket.encryptionKey,
|
|
||||||
applicationKey: password,
|
|
||||||
applicationKeyId: login,
|
|
||||||
);
|
|
||||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
|
|
||||||
emit(state.copyWith(backblazeBucket: bucket));
|
|
||||||
}
|
|
||||||
final GenericResult result = await api.initializeRepository(
|
|
||||||
InitializeRepositoryInput(
|
|
||||||
provider: BackupsProviderType.backblaze,
|
|
||||||
locationId: bucket.bucketId,
|
|
||||||
locationName: bucket.bucketName,
|
|
||||||
login: login,
|
|
||||||
password: password,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result.success == false) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'Unknown error');
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
getIt<NavigationService>().showSnackBar('backup.reuploaded_key'.tr());
|
|
||||||
await updateBackups();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("we don't have states")
|
|
||||||
Duration refreshTimeFromState() => const Duration(seconds: 60);
|
|
||||||
|
|
||||||
Future<void> updateBackups({final bool useTimer = false}) async {
|
|
||||||
emit(state.copyWith(refreshing: true));
|
|
||||||
final backups = await api.getBackups();
|
|
||||||
backups.sort((final a, final b) => b.time.compareTo(a.time));
|
|
||||||
final backupConfig = await api.getBackupsConfiguration();
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
backups: backups,
|
|
||||||
refreshTimer: refreshTimeFromState(),
|
|
||||||
refreshing: false,
|
|
||||||
isInitialized: backupConfig?.isInitialized ?? false,
|
|
||||||
autobackupPeriod: backupConfig?.autobackupPeriod,
|
|
||||||
autobackupQuotas: backupConfig?.autobackupQuotas,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (useTimer) {
|
|
||||||
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> forceUpdateBackups() async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
|
|
||||||
await api.forceBackupListReload();
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createMultipleBackups(final List<Service> services) async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
for (final service in services) {
|
|
||||||
await api.startBackup(service.id);
|
|
||||||
}
|
|
||||||
await updateBackups();
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createBackup(final String serviceId) async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
await api.startBackup(serviceId);
|
|
||||||
await updateBackups();
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> restoreBackup(
|
|
||||||
final String backupId,
|
|
||||||
final BackupRestoreStrategy strategy,
|
|
||||||
) async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
await api.restoreBackup(backupId, strategy);
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setAutobackupPeriod(final Duration? period) async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
final result = await api.setAutobackupPeriod(period: period?.inMinutes);
|
|
||||||
if (result.success == false) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'Unknown error');
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('backup.autobackup_period_set'.tr());
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
preventActions: false,
|
|
||||||
autobackupPeriod: period ?? Duration.zero,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await updateBackups();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setAutobackupQuotas(final AutobackupQuotas quotas) async {
|
|
||||||
emit(state.copyWith(preventActions: true));
|
|
||||||
final result = await api.setAutobackupQuotas(quotas);
|
|
||||||
if (result.success == false) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'Unknown error');
|
|
||||||
emit(state.copyWith(preventActions: false));
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>().showSnackBar('backup.quotas_set'.tr());
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
preventActions: false,
|
|
||||||
autobackupQuotas: quotas,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await updateBackups();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> forgetSnapshot(final String snapshotId) async {
|
|
||||||
final result = await api.forgetSnapshot(snapshotId);
|
|
||||||
if (!result.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.data == false) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('backup.forget_snapshot_error'.tr());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimistic update
|
|
||||||
final backups = state.backups;
|
|
||||||
final index =
|
|
||||||
backups.indexWhere((final snapshot) => snapshot.id == snapshotId);
|
|
||||||
if (index != -1) {
|
|
||||||
backups.removeAt(index);
|
|
||||||
emit(state.copyWith(backups: backups));
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateBackups();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() async {
|
|
||||||
emit(const BackupsState());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
part of 'backups_cubit.dart';
|
|
||||||
|
|
||||||
class BackupsState extends ServerInstallationDependendState {
|
|
||||||
const BackupsState({
|
|
||||||
this.isInitialized = false,
|
|
||||||
this.backups = const [],
|
|
||||||
this.preventActions = true,
|
|
||||||
this.refreshTimer = const Duration(seconds: 60),
|
|
||||||
this.refreshing = true,
|
|
||||||
this.autobackupPeriod,
|
|
||||||
this.backblazeBucket,
|
|
||||||
this.autobackupQuotas,
|
|
||||||
});
|
|
||||||
|
|
||||||
final bool isInitialized;
|
|
||||||
final List<Backup> backups;
|
|
||||||
final bool preventActions;
|
|
||||||
final Duration refreshTimer;
|
|
||||||
final bool refreshing;
|
|
||||||
final Duration? autobackupPeriod;
|
|
||||||
final BackblazeBucket? backblazeBucket;
|
|
||||||
final AutobackupQuotas? autobackupQuotas;
|
|
||||||
|
|
||||||
List<Backup> serviceBackups(final String serviceId) => backups
|
|
||||||
.where((final backup) => backup.serviceId == serviceId)
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [
|
|
||||||
isInitialized,
|
|
||||||
backups,
|
|
||||||
preventActions,
|
|
||||||
refreshTimer,
|
|
||||||
refreshing,
|
|
||||||
];
|
|
||||||
|
|
||||||
BackupsState copyWith({
|
|
||||||
final bool? isInitialized,
|
|
||||||
final List<Backup>? backups,
|
|
||||||
final bool? preventActions,
|
|
||||||
final Duration? refreshTimer,
|
|
||||||
final bool? refreshing,
|
|
||||||
final Duration? autobackupPeriod,
|
|
||||||
final BackblazeBucket? backblazeBucket,
|
|
||||||
final AutobackupQuotas? autobackupQuotas,
|
|
||||||
}) =>
|
|
||||||
BackupsState(
|
|
||||||
isInitialized: isInitialized ?? this.isInitialized,
|
|
||||||
backups: backups ?? this.backups,
|
|
||||||
preventActions: preventActions ?? this.preventActions,
|
|
||||||
refreshTimer: refreshTimer ?? this.refreshTimer,
|
|
||||||
refreshing: refreshing ?? this.refreshing,
|
|
||||||
// The autobackupPeriod might be null, so if the duration is set to 0, we
|
|
||||||
// set it to null.
|
|
||||||
autobackupPeriod: autobackupPeriod?.inSeconds == 0
|
|
||||||
? null
|
|
||||||
: autobackupPeriod ?? this.autobackupPeriod,
|
|
||||||
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
|
|
||||||
autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,36 +1,52 @@
|
||||||
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/cubit/services/services_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.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.usersCubit,
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
required this.servicesCubit,
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
}) : super(JobsStateEmpty());
|
(final ApiData apiData) {
|
||||||
|
if (apiData.serverJobs.data != null &&
|
||||||
|
apiData.serverJobs.data!.isNotEmpty) {
|
||||||
|
_handleServerJobs(apiData.serverJobs.data!);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
StreamSubscription? _apiDataSubscription;
|
||||||
final UsersCubit usersCubit;
|
|
||||||
final ServicesCubit servicesCubit;
|
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -38,61 +54,153 @@ 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 getIt<ApiConnectionRepository>().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 if (result.success) {
|
||||||
|
emit(
|
||||||
|
JobsStateFinished(
|
||||||
|
[UpgradeServerJob(status: JobStatusEnum.finished)],
|
||||||
|
null,
|
||||||
|
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();
|
||||||
|
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());
|
||||||
await servicesCubit.load();
|
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,135 @@ 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();
|
||||||
|
if (job.shouldRemoveInsteadOfAdd(clientJobList)) {
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||||
|
} else {
|
||||||
|
newJobsList.add(job);
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||||
|
}
|
||||||
|
if (newJobsList.isEmpty) {
|
||||||
|
return JobsStateEmpty();
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
if (job is ReplaceableJob) {
|
||||||
|
final List<ClientJob> newPostponedJobs = postponedJobs
|
||||||
|
.where((final element) => element.runtimeType != job.runtimeType)
|
||||||
|
.toList();
|
||||||
|
if (job.shouldRemoveInsteadOfAdd(postponedJobs)) {
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
if (job.shouldRemoveInsteadOfAdd(postponedJobs)) {
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.job_removed'.tr());
|
||||||
|
} else {
|
||||||
|
newPostponedJobs.add(job);
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
|
||||||
|
}
|
||||||
|
if (newPostponedJobs.isEmpty) {
|
||||||
|
return JobsStateEmpty();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
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/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
|
||||||
|
|
||||||
part 'devices_state.dart';
|
|
||||||
|
|
||||||
class ApiDevicesCubit
|
|
||||||
extends ServerInstallationDependendCubit<ApiDevicesState> {
|
|
||||||
ApiDevicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(serverInstallationCubit, const ApiDevicesState.initial());
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void load() async {
|
|
||||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
_refetch();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
emit(ApiDevicesState([state.thisDevice], LoadingStatus.refreshing));
|
|
||||||
_refetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _refetch() async {
|
|
||||||
final List<ApiToken>? devices = await _getApiTokens();
|
|
||||||
if (devices != null) {
|
|
||||||
emit(ApiDevicesState(devices, LoadingStatus.success));
|
|
||||||
} else {
|
|
||||||
emit(const ApiDevicesState([], LoadingStatus.error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<ApiToken>?> _getApiTokens() async {
|
|
||||||
final GenericResult<List<ApiToken>> response = await api.getApiTokens();
|
|
||||||
if (response.success) {
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteDevice(final ApiToken device) async {
|
|
||||||
final GenericResult<void> response = await api.deleteApiToken(device.name);
|
|
||||||
if (response.success) {
|
|
||||||
emit(
|
|
||||||
ApiDevicesState(
|
|
||||||
state.devices.where((final d) => d.name != device.name).toList(),
|
|
||||||
LoadingStatus.success,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(response.message ?? 'Error deleting device');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getNewDeviceKey() async {
|
|
||||||
final GenericResult<String> response = await api.createDeviceToken();
|
|
||||||
if (response.success) {
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
response.message ?? 'Error getting new device key',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() {
|
|
||||||
emit(const ApiDevicesState.initial());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
part of 'devices_cubit.dart';
|
|
||||||
|
|
||||||
class ApiDevicesState extends ServerInstallationDependendState {
|
|
||||||
const ApiDevicesState(this._devices, this.status);
|
|
||||||
|
|
||||||
const ApiDevicesState.initial() : this(const [], LoadingStatus.uninitialized);
|
|
||||||
final List<ApiToken> _devices;
|
|
||||||
final LoadingStatus status;
|
|
||||||
|
|
||||||
List<ApiToken> get devices => _devices;
|
|
||||||
ApiToken get thisDevice => _devices.firstWhere(
|
|
||||||
(final device) => device.isCaller,
|
|
||||||
orElse: () => ApiToken(
|
|
||||||
name: 'Error fetching device',
|
|
||||||
isCaller: true,
|
|
||||||
date: DateTime.now(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<ApiToken> get otherDevices =>
|
|
||||||
_devices.where((final device) => !device.isCaller).toList();
|
|
||||||
|
|
||||||
ApiDevicesState copyWith({
|
|
||||||
final List<ApiToken>? devices,
|
|
||||||
final LoadingStatus? status,
|
|
||||||
}) =>
|
|
||||||
ApiDevicesState(
|
|
||||||
devices ?? _devices,
|
|
||||||
status ?? this.status,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [_devices];
|
|
||||||
}
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.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/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||||
|
@ -11,11 +10,9 @@ import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
|
|
||||||
part 'dns_records_state.dart';
|
part 'dns_records_state.dart';
|
||||||
|
|
||||||
class DnsRecordsCubit
|
class DnsRecordsCubit extends ServerConnectionDependentCubit<DnsRecordsState> {
|
||||||
extends ServerInstallationDependendCubit<DnsRecordsState> {
|
DnsRecordsCubit()
|
||||||
DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(
|
: super(
|
||||||
serverInstallationCubit,
|
|
||||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -30,39 +27,45 @@ class DnsRecordsCubit
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
final String? ipAddress =
|
||||||
final String? ipAddress =
|
getIt<ApiConnectionRepository>().serverDetails?.ip4;
|
||||||
serverInstallationCubit.state.serverDetails?.ip4;
|
|
||||||
|
|
||||||
if (domain == null || ipAddress == null) {
|
if (domain == null || ipAddress == null) {
|
||||||
emit(const DnsRecordsState());
|
emit(const DnsRecordsState());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DnsRecord> allDnsRecords = await api.getDnsRecords();
|
final List<DnsRecord> allDnsRecords = await api.getDnsRecords();
|
||||||
allDnsRecords.removeWhere((final record) => record.type == 'AAAA');
|
final foundRecords = await validateDnsRecords(
|
||||||
final foundRecords = await validateDnsRecords(
|
domain,
|
||||||
domain,
|
extractDkimRecord(allDnsRecords)?.content ?? '',
|
||||||
extractDkimRecord(allDnsRecords)?.content ?? '',
|
allDnsRecords,
|
||||||
allDnsRecords,
|
);
|
||||||
ipAddress,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!foundRecords.success || foundRecords.data.isEmpty) {
|
|
||||||
emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!foundRecords.success && foundRecords.message == 'link-local') {
|
||||||
emit(
|
emit(
|
||||||
DnsRecordsState(
|
DnsRecordsState(
|
||||||
|
dnsState: DnsRecordsStatus.error,
|
||||||
dnsRecords: foundRecords.data,
|
dnsRecords: foundRecords.data,
|
||||||
dnsState: foundRecords.data.any((final r) => r.isSatisfied == false)
|
|
||||||
? DnsRecordsStatus.error
|
|
||||||
: DnsRecordsStatus.good,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!foundRecords.success || foundRecords.data.isEmpty) {
|
||||||
|
emit(const DnsRecordsState());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(
|
||||||
|
DnsRecordsState(
|
||||||
|
dnsRecords: foundRecords.data,
|
||||||
|
dnsState: foundRecords.data.any((final r) => r.isSatisfied == false)
|
||||||
|
? DnsRecordsStatus.error
|
||||||
|
: DnsRecordsStatus.good,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to check whether all known DNS records on the domain by ip4
|
/// Tries to check whether all known DNS records on the domain by ip4
|
||||||
|
@ -74,19 +77,7 @@ class DnsRecordsCubit
|
||||||
final ServerDomain domain,
|
final ServerDomain domain,
|
||||||
final String dkimPublicKey,
|
final String dkimPublicKey,
|
||||||
final List<DnsRecord> pendingDnsRecords,
|
final List<DnsRecord> pendingDnsRecords,
|
||||||
final String ip4,
|
|
||||||
) async {
|
) async {
|
||||||
final matchMap = await validateDnsMatch(domain.domainName, ['api'], ip4);
|
|
||||||
if (matchMap.values.any((final status) => status != DnsRecordStatus.ok)) {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'domain.domain_validation_failure'.tr(),
|
|
||||||
);
|
|
||||||
return GenericResult(
|
|
||||||
success: false,
|
|
||||||
data: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await ProvidersController.currentDnsProvider!
|
final result = await ProvidersController.currentDnsProvider!
|
||||||
.getDnsRecords(domain: domain);
|
.getDnsRecords(domain: domain);
|
||||||
if (result.data.isEmpty || !result.success) {
|
if (result.data.isEmpty || !result.success) {
|
||||||
|
@ -158,6 +149,17 @@ class DnsRecordsCubit
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// If providerDnsRecords contains a link-local ipv6 record, return an error
|
||||||
|
if (providerDnsRecords.any(
|
||||||
|
(final r) =>
|
||||||
|
r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false),
|
||||||
|
)) {
|
||||||
|
return GenericResult(
|
||||||
|
data: foundRecords,
|
||||||
|
success: false,
|
||||||
|
message: 'link-local',
|
||||||
|
);
|
||||||
|
}
|
||||||
return GenericResult(
|
return GenericResult(
|
||||||
data: foundRecords,
|
data: foundRecords,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -184,14 +186,36 @@ class DnsRecordsCubit
|
||||||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||||
final List<DnsRecord> records = await api.getDnsRecords();
|
final List<DnsRecord> records = await api.getDnsRecords();
|
||||||
|
|
||||||
|
// If there are explicit link-local ipv6 records, remove them from the list
|
||||||
|
records.removeWhere(
|
||||||
|
(final r) =>
|
||||||
|
r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there are no AAAA records, make empty copies of A records
|
||||||
|
if (!records.any((final r) => r.type == 'AAAA')) {
|
||||||
|
final recordsToAdd = records
|
||||||
|
.where((final r) => r.type == 'A')
|
||||||
|
.map(
|
||||||
|
(final r) => DnsRecord(
|
||||||
|
name: r.name,
|
||||||
|
type: 'AAAA',
|
||||||
|
content: null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
records.addAll(recordsToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TODO: Error handling?
|
/// TODO: Error handling?
|
||||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
final ServerDomain? domain = getIt<ApiConnectionRepository>().serverDomain;
|
||||||
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
||||||
records: records,
|
records: records,
|
||||||
domain: domain!,
|
domain: domain!,
|
||||||
);
|
);
|
||||||
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
||||||
records: records,
|
records: records.where((final r) => r.content != null).toList(),
|
||||||
domain: domain,
|
domain: domain,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.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:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
|
|
||||||
class FieldCubitFactory {
|
class FieldCubitFactory {
|
||||||
FieldCubitFactory(this.context);
|
FieldCubitFactory(this.context);
|
||||||
|
@ -27,7 +27,7 @@ class FieldCubitFactory {
|
||||||
),
|
),
|
||||||
ValidationModel(
|
ValidationModel(
|
||||||
(final String login) =>
|
(final String login) =>
|
||||||
context.read<UsersCubit>().state.isLoginRegistered(login),
|
context.read<UsersBloc>().state.isLoginRegistered(login),
|
||||||
'validations.already_exist'.tr(),
|
'validations.already_exist'.tr(),
|
||||||
),
|
),
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.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/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/price.dart';
|
|
||||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
|
||||||
|
|
||||||
part 'provider_volume_state.dart';
|
|
||||||
|
|
||||||
class ApiProviderVolumeCubit
|
|
||||||
extends ServerInstallationDependendCubit<ApiProviderVolumeState> {
|
|
||||||
ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(serverInstallationCubit, const ApiProviderVolumeState.initial());
|
|
||||||
final ServerApi serverApi = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
unawaited(_refetch());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Price?> getPricePerGb() async {
|
|
||||||
Price? price;
|
|
||||||
final pricingResult =
|
|
||||||
await ProvidersController.currentServerProvider!.getAdditionalPricing();
|
|
||||||
if (pricingResult.data == null || !pricingResult.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('server.pricing_error'.tr());
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
price = pricingResult.data!.perVolumeGb;
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
|
||||||
unawaited(_refetch());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refetch() async {
|
|
||||||
if (ProvidersController.currentServerProvider == null) {
|
|
||||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
final volumesResult =
|
|
||||||
await ProvidersController.currentServerProvider!.getVolumes();
|
|
||||||
|
|
||||||
if (!volumesResult.success || volumesResult.data.isEmpty) {
|
|
||||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
|
||||||
ApiProviderVolumeState(
|
|
||||||
volumesResult.data,
|
|
||||||
LoadingStatus.success,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> attachVolume(final DiskVolume volume) async {
|
|
||||||
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
|
||||||
await ProvidersController.currentServerProvider!
|
|
||||||
.attachVolume(volume.providerVolume!, server.id);
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> detachVolume(final DiskVolume volume) async {
|
|
||||||
await ProvidersController.currentServerProvider!
|
|
||||||
.detachVolume(volume.providerVolume!);
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> resizeVolume(
|
|
||||||
final DiskVolume volume,
|
|
||||||
final DiskSize newSize,
|
|
||||||
final Function() callback,
|
|
||||||
) async {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'Starting resize',
|
|
||||||
);
|
|
||||||
emit(state.copyWith(isResizing: true));
|
|
||||||
final resizedResult =
|
|
||||||
await ProvidersController.currentServerProvider!.resizeVolume(
|
|
||||||
volume.providerVolume!,
|
|
||||||
newSize,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!resizedResult.success || !resizedResult.data) {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'storage.extending_volume_error'.tr(),
|
|
||||||
);
|
|
||||||
emit(state.copyWith(isResizing: false));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'Provider volume resized, waiting 10 seconds',
|
|
||||||
);
|
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
|
||||||
|
|
||||||
await ServerApi().resizeVolume(volume.name);
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'Server volume resized, waiting 20 seconds',
|
|
||||||
);
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 20));
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
'Restarting server',
|
|
||||||
);
|
|
||||||
|
|
||||||
await refresh();
|
|
||||||
emit(state.copyWith(isResizing: false));
|
|
||||||
await callback();
|
|
||||||
await serverApi.reboot();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createVolume(final DiskSize size) async {
|
|
||||||
final ServerVolume? volume = (await ProvidersController
|
|
||||||
.currentServerProvider!
|
|
||||||
.createVolume(size.gibibyte.toInt()))
|
|
||||||
.data;
|
|
||||||
|
|
||||||
final diskVolume = DiskVolume(providerVolume: volume);
|
|
||||||
await attachVolume(diskVolume);
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
|
||||||
|
|
||||||
await ServerApi().mountVolume(volume!.name);
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteVolume(final DiskVolume volume) async {
|
|
||||||
await ProvidersController.currentServerProvider!
|
|
||||||
.deleteVolume(volume.providerVolume!);
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() {
|
|
||||||
emit(const ApiProviderVolumeState.initial());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
part of 'provider_volume_cubit.dart';
|
|
||||||
|
|
||||||
class ApiProviderVolumeState extends ServerInstallationDependendState {
|
|
||||||
const ApiProviderVolumeState(this._volumes, this.status, this.isResizing);
|
|
||||||
|
|
||||||
const ApiProviderVolumeState.initial()
|
|
||||||
: this(const [], LoadingStatus.uninitialized, false);
|
|
||||||
final List<ServerVolume> _volumes;
|
|
||||||
final LoadingStatus status;
|
|
||||||
final bool isResizing;
|
|
||||||
|
|
||||||
List<ServerVolume> get volumes => _volumes;
|
|
||||||
|
|
||||||
ApiProviderVolumeState copyWith({
|
|
||||||
final List<ServerVolume>? volumes,
|
|
||||||
final LoadingStatus? status,
|
|
||||||
final bool? isResizing,
|
|
||||||
}) =>
|
|
||||||
ApiProviderVolumeState(
|
|
||||||
volumes ?? _volumes,
|
|
||||||
status ?? this.status,
|
|
||||||
isResizing ?? this.isResizing,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [_volumes, status, isResizing];
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/provider.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
|
||||||
export 'package:selfprivacy/logic/models/state_types.dart';
|
|
||||||
|
|
||||||
part 'providers_state.dart';
|
|
||||||
|
|
||||||
class ProvidersCubit extends Cubit<ProvidersState> {
|
|
||||||
ProvidersCubit() : super(InitialProviderState());
|
|
||||||
|
|
||||||
void connect(final ProviderModel provider) {
|
|
||||||
final ProvidersState newState =
|
|
||||||
state.updateElement(provider, StateType.stable);
|
|
||||||
emit(newState);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
part of 'providers_cubit.dart';
|
|
||||||
|
|
||||||
class ProvidersState extends Equatable {
|
|
||||||
const ProvidersState(this.all);
|
|
||||||
|
|
||||||
final List<ProviderModel> all;
|
|
||||||
|
|
||||||
ProvidersState updateElement(
|
|
||||||
final ProviderModel provider,
|
|
||||||
final StateType newState,
|
|
||||||
) {
|
|
||||||
final List<ProviderModel> newList = [...all];
|
|
||||||
final int index = newList.indexOf(provider);
|
|
||||||
newList[index] = provider.updateState(newState);
|
|
||||||
return ProvidersState(newList);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ProviderModel> get connected => all
|
|
||||||
.where((final service) => service.state != StateType.uninitialized)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<ProviderModel> get uninitialized => all
|
|
||||||
.where((final service) => service.state == StateType.uninitialized)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
bool get isFullyInitialized => uninitialized.isEmpty;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => all;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InitialProviderState extends ProvidersState {
|
|
||||||
InitialProviderState()
|
|
||||||
: super(
|
|
||||||
ProviderType.values
|
|
||||||
.map(
|
|
||||||
(final type) => ProviderModel(
|
|
||||||
state: StateType.uninitialized,
|
|
||||||
type: type,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
|
||||||
|
|
||||||
part 'recovery_key_state.dart';
|
|
||||||
|
|
||||||
class RecoveryKeyCubit
|
|
||||||
extends ServerInstallationDependendCubit<RecoveryKeyState> {
|
|
||||||
RecoveryKeyCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(serverInstallationCubit, const RecoveryKeyState.initial());
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void load() async {
|
|
||||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
|
||||||
if (status == null) {
|
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
|
||||||
} else {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
status: status,
|
|
||||||
loadingStatus: LoadingStatus.success,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// } else {
|
|
||||||
// emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<RecoveryKeyStatus?> _getRecoveryKeyStatus() async {
|
|
||||||
final GenericResult<RecoveryKeyStatus?> response =
|
|
||||||
await api.getRecoveryTokenStatus();
|
|
||||||
if (response.success) {
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.refreshing));
|
|
||||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
|
||||||
if (status == null) {
|
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
|
||||||
} else {
|
|
||||||
emit(
|
|
||||||
state.copyWith(status: status, loadingStatus: LoadingStatus.success),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> generateRecoveryKey({
|
|
||||||
final DateTime? expirationDate,
|
|
||||||
final int? numberOfUses,
|
|
||||||
}) async {
|
|
||||||
final GenericResult<String> response =
|
|
||||||
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
|
||||||
if (response.success) {
|
|
||||||
unawaited(refresh());
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
throw GenerationError(response.message ?? 'Unknown error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() {
|
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GenerationError extends Error {
|
|
||||||
GenerationError(this.message);
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
part of 'recovery_key_cubit.dart';
|
|
||||||
|
|
||||||
class RecoveryKeyState extends ServerInstallationDependendState {
|
|
||||||
const RecoveryKeyState(this._status, this.loadingStatus);
|
|
||||||
|
|
||||||
const RecoveryKeyState.initial()
|
|
||||||
: this(
|
|
||||||
const RecoveryKeyStatus(exists: false, valid: false),
|
|
||||||
LoadingStatus.refreshing,
|
|
||||||
);
|
|
||||||
|
|
||||||
final RecoveryKeyStatus _status;
|
|
||||||
final LoadingStatus loadingStatus;
|
|
||||||
|
|
||||||
bool get exists => _status.exists;
|
|
||||||
bool get isValid => _status.valid;
|
|
||||||
DateTime? get generatedAt => _status.date;
|
|
||||||
DateTime? get expiresAt => _status.expiration;
|
|
||||||
int? get usesLeft => _status.usesLeft;
|
|
||||||
|
|
||||||
bool get isInvalidBecauseExpired =>
|
|
||||||
_status.expiration != null &&
|
|
||||||
_status.expiration!.isBefore(DateTime.now());
|
|
||||||
|
|
||||||
bool get isInvalidBecauseUsed =>
|
|
||||||
_status.usesLeft != null && _status.usesLeft == 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [_status, loadingStatus];
|
|
||||||
|
|
||||||
RecoveryKeyState copyWith({
|
|
||||||
final RecoveryKeyStatus? status,
|
|
||||||
final LoadingStatus? loadingStatus,
|
|
||||||
}) =>
|
|
||||||
RecoveryKeyState(
|
|
||||||
status ?? _status,
|
|
||||||
loadingStatus ?? this.loadingStatus,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
|
||||||
|
export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
|
||||||
|
part 'server_connection_dependent_state.dart';
|
||||||
|
|
||||||
|
abstract class ServerConnectionDependentCubit<
|
||||||
|
T extends ServerInstallationDependendState> extends Cubit<T> {
|
||||||
|
ServerConnectionDependentCubit(
|
||||||
|
super.initState,
|
||||||
|
) {
|
||||||
|
final connectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
|
||||||
|
apiStatusSubscription =
|
||||||
|
connectionRepository.connectionStatusStream.listen(checkAuthStatus);
|
||||||
|
checkAuthStatus(connectionRepository.connectionStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAuthStatus(final ConnectionStatus state) {
|
||||||
|
switch (state) {
|
||||||
|
case ConnectionStatus.nonexistent:
|
||||||
|
clear();
|
||||||
|
isLoaded = false;
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (!isLoaded) {
|
||||||
|
load();
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription apiStatusSubscription;
|
||||||
|
bool isLoaded = false;
|
||||||
|
|
||||||
|
void load();
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
apiStatusSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'authentication_dependend_cubit.dart';
|
part of 'server_connection_dependent_cubit.dart';
|
||||||
|
|
||||||
abstract class ServerInstallationDependendState extends Equatable {
|
abstract class ServerInstallationDependendState extends Equatable {
|
||||||
const ServerInstallationDependendState();
|
const ServerInstallationDependendState();
|
|
@ -1,35 +1,75 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/system_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||||
|
|
||||||
part 'server_detailed_info_state.dart';
|
part 'server_detailed_info_state.dart';
|
||||||
|
|
||||||
class ServerDetailsCubit
|
class ServerDetailsCubit
|
||||||
extends ServerInstallationDependendCubit<ServerDetailsState> {
|
extends ServerConnectionDependentCubit<ServerDetailsState> {
|
||||||
ServerDetailsCubit(final ServerInstallationCubit serverInstallationCubit)
|
ServerDetailsCubit() : super(const ServerDetailsInitial()) {
|
||||||
: super(serverInstallationCubit, ServerDetailsInitial());
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
if (apiData.settings.data != null) {
|
||||||
|
_handleServerSettings(apiData.settings.data!);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ServerDetailsRepository repository = ServerDetailsRepository();
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
|
||||||
|
void _handleServerSettings(final SystemSettings settings) {
|
||||||
|
emit(
|
||||||
|
Loaded(
|
||||||
|
metadata: state.metadata,
|
||||||
|
serverTimezone: TimeZoneSettings.fromString(settings.timezone),
|
||||||
|
autoUpgradeSettings: settings.autoUpgradeSettings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ServerMetadataEntity>> get _metadata async {
|
||||||
|
List<ServerMetadataEntity> data = [];
|
||||||
|
|
||||||
|
final serverProviderApi = ProvidersController.currentServerProvider;
|
||||||
|
final dnsProviderApi = ProvidersController.currentDnsProvider;
|
||||||
|
if (serverProviderApi != null && dnsProviderApi != null) {
|
||||||
|
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0;
|
||||||
|
final metadataResult = await serverProviderApi.getMetadata(serverId);
|
||||||
|
metadataResult.data.add(
|
||||||
|
ServerMetadataEntity(
|
||||||
|
trId: 'server.dns_provider',
|
||||||
|
value: dnsProviderApi.type.displayName,
|
||||||
|
type: MetadataType.other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
data = metadataResult.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
void check() async {
|
void check() async {
|
||||||
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null;
|
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null;
|
||||||
try {
|
try {
|
||||||
if (isReadyToCheck) {
|
if (isReadyToCheck) {
|
||||||
emit(ServerDetailsLoading());
|
emit(const ServerDetailsLoading());
|
||||||
final ServerDetailsRepositoryDto data = await repository.load();
|
final List<ServerMetadataEntity> metadata = await _metadata;
|
||||||
emit(
|
emit(
|
||||||
Loaded(
|
state.copyWith(
|
||||||
metadata: data.metadata,
|
metadata: metadata,
|
||||||
autoUpgradeSettings: data.autoUpgradeSettings,
|
|
||||||
serverTimezone: data.serverTimezone,
|
|
||||||
checkTime: DateTime.now(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
emit(ServerDetailsNotReady());
|
emit(const ServerDetailsNotReady());
|
||||||
}
|
}
|
||||||
} on StateError {
|
} on StateError {
|
||||||
print('Tried to emit server info state when cubit is closed');
|
print('Tried to emit server info state when cubit is closed');
|
||||||
|
@ -38,11 +78,17 @@ class ServerDetailsCubit
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clear() {
|
void clear() {
|
||||||
emit(ServerDetailsNotReady());
|
emit(const ServerDetailsNotReady());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void load() async {
|
void load() async {
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
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/models/auto_upgrade_settings.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
|
||||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
|
||||||
|
|
||||||
class ServerDetailsRepository {
|
|
||||||
ServerApi server = ServerApi();
|
|
||||||
|
|
||||||
Future<ServerDetailsRepositoryDto> load() async {
|
|
||||||
final settings = await server.getSystemSettings();
|
|
||||||
return ServerDetailsRepositoryDto(
|
|
||||||
autoUpgradeSettings: settings.autoUpgradeSettings,
|
|
||||||
metadata: await metadata,
|
|
||||||
serverTimezone: TimeZoneSettings.fromString(
|
|
||||||
settings.timezone,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<ServerMetadataEntity>> get metadata async {
|
|
||||||
List<ServerMetadataEntity> data = [];
|
|
||||||
|
|
||||||
final serverProviderApi = ProvidersController.currentServerProvider;
|
|
||||||
final dnsProviderApi = ProvidersController.currentDnsProvider;
|
|
||||||
if (serverProviderApi != null && dnsProviderApi != null) {
|
|
||||||
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0;
|
|
||||||
final metadataResult = await serverProviderApi.getMetadata(serverId);
|
|
||||||
metadataResult.data.add(
|
|
||||||
ServerMetadataEntity(
|
|
||||||
trId: 'server.dns_provider',
|
|
||||||
value: dnsProviderApi.type.displayName,
|
|
||||||
type: MetadataType.other,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
data = metadataResult.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 {
|
|
||||||
ServerDetailsRepositoryDto({
|
|
||||||
required this.metadata,
|
|
||||||
required this.serverTimezone,
|
|
||||||
required this.autoUpgradeSettings,
|
|
||||||
});
|
|
||||||
final List<ServerMetadataEntity> metadata;
|
|
||||||
final TimeZoneSettings serverTimezone;
|
|
||||||
final AutoUpgradeSettings autoUpgradeSettings;
|
|
||||||
}
|
|
|
@ -1,37 +1,78 @@
|
||||||
part of 'server_detailed_info_cubit.dart';
|
part of 'server_detailed_info_cubit.dart';
|
||||||
|
|
||||||
abstract class ServerDetailsState extends ServerInstallationDependendState {
|
abstract class ServerDetailsState extends ServerInstallationDependendState {
|
||||||
const ServerDetailsState();
|
const ServerDetailsState({
|
||||||
|
required this.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ServerMetadataEntity> metadata;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [];
|
List<Object> get props => [metadata];
|
||||||
|
|
||||||
|
ServerDetailsState copyWith({
|
||||||
|
final List<ServerMetadataEntity>? metadata,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerDetailsInitial extends ServerDetailsState {}
|
class ServerDetailsInitial extends ServerDetailsState {
|
||||||
|
const ServerDetailsInitial({super.metadata = const []});
|
||||||
|
|
||||||
class ServerDetailsLoading extends ServerDetailsState {}
|
@override
|
||||||
|
ServerDetailsInitial copyWith({final List<ServerMetadataEntity>? metadata}) =>
|
||||||
|
ServerDetailsInitial(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class ServerDetailsNotReady extends ServerDetailsState {}
|
class ServerDetailsLoading extends ServerDetailsState {
|
||||||
|
const ServerDetailsLoading({super.metadata = const []});
|
||||||
|
|
||||||
class Loading extends ServerDetailsState {}
|
@override
|
||||||
|
ServerDetailsLoading copyWith({final List<ServerMetadataEntity>? metadata}) =>
|
||||||
|
ServerDetailsLoading(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerDetailsNotReady extends ServerDetailsState {
|
||||||
|
const ServerDetailsNotReady({super.metadata = const []});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServerDetailsNotReady copyWith({
|
||||||
|
final List<ServerMetadataEntity>? metadata,
|
||||||
|
}) =>
|
||||||
|
ServerDetailsNotReady(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class Loaded extends ServerDetailsState {
|
class Loaded extends ServerDetailsState {
|
||||||
const Loaded({
|
const Loaded({
|
||||||
required this.metadata,
|
required super.metadata,
|
||||||
required this.serverTimezone,
|
required this.serverTimezone,
|
||||||
required this.autoUpgradeSettings,
|
required this.autoUpgradeSettings,
|
||||||
required this.checkTime,
|
|
||||||
});
|
});
|
||||||
final List<ServerMetadataEntity> metadata;
|
|
||||||
final TimeZoneSettings serverTimezone;
|
final TimeZoneSettings serverTimezone;
|
||||||
final AutoUpgradeSettings autoUpgradeSettings;
|
final AutoUpgradeSettings autoUpgradeSettings;
|
||||||
final DateTime checkTime;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [
|
List<Object> get props => [
|
||||||
metadata,
|
metadata,
|
||||||
serverTimezone,
|
serverTimezone,
|
||||||
autoUpgradeSettings,
|
autoUpgradeSettings,
|
||||||
checkTime,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Loaded copyWith({
|
||||||
|
final List<ServerMetadataEntity>? metadata,
|
||||||
|
final TimeZoneSettings? serverTimezone,
|
||||||
|
final AutoUpgradeSettings? autoUpgradeSettings,
|
||||||
|
final DateTime? checkTime,
|
||||||
|
}) =>
|
||||||
|
Loaded(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
serverTimezone: serverTimezone ?? this.serverTimezone,
|
||||||
|
autoUpgradeSettings: autoUpgradeSettings ?? this.autoUpgradeSettings,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,7 +233,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
try {
|
try {
|
||||||
bucket = await BackblazeApi()
|
bucket = await BackblazeApi()
|
||||||
.fetchBucket(backblazeCredential, configuration);
|
.fetchBucket(backblazeCredential, configuration);
|
||||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket!);
|
await getIt<ApiConfigModel>().setBackblazeBucket(bucket!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
@ -484,6 +484,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
if (dkimCreated) {
|
if (dkimCreated) {
|
||||||
await repository.saveHasFinalChecked(true);
|
await repository.saveHasFinalChecked(true);
|
||||||
emit(dataState.finish());
|
emit(dataState.finish());
|
||||||
|
getIt<ApiConnectionRepository>().init();
|
||||||
} else {
|
} else {
|
||||||
runDelayed(
|
runDelayed(
|
||||||
finishCheckIfServerIsOkay,
|
finishCheckIfServerIsOkay,
|
||||||
|
@ -724,7 +725,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
ip4: server.ip,
|
ip4: server.ip,
|
||||||
id: server.id,
|
id: server.id,
|
||||||
createTime: server.created,
|
createTime: server.created,
|
||||||
volume: ServerVolume(
|
volume: ServerProviderVolume(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: 'recovered_volume',
|
name: 'recovered_volume',
|
||||||
sizeByte: 0,
|
sizeByte: 0,
|
||||||
|
@ -802,6 +803,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
serverTypeIdentificator: serverType.data!.identifier,
|
serverTypeIdentificator: serverType.data!.identifier,
|
||||||
);
|
);
|
||||||
emit(updatedState.finish());
|
emit(updatedState.finish());
|
||||||
|
getIt<ApiConnectionRepository>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -313,7 +313,7 @@ class ServerInstallationRepository {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return ServerHostingDetails(
|
return ServerHostingDetails(
|
||||||
apiToken: result.data,
|
apiToken: result.data,
|
||||||
volume: ServerVolume(
|
volume: ServerProviderVolume(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
sizeByte: 0,
|
sizeByte: 0,
|
||||||
|
@ -350,7 +350,7 @@ class ServerInstallationRepository {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return ServerHostingDetails(
|
return ServerHostingDetails(
|
||||||
apiToken: result.data,
|
apiToken: result.data,
|
||||||
volume: ServerVolume(
|
volume: ServerProviderVolume(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
sizeByte: 0,
|
sizeByte: 0,
|
||||||
|
@ -385,7 +385,7 @@ class ServerInstallationRepository {
|
||||||
if (await serverApi.isHttpServerWorking()) {
|
if (await serverApi.isHttpServerWorking()) {
|
||||||
return ServerHostingDetails(
|
return ServerHostingDetails(
|
||||||
apiToken: apiToken,
|
apiToken: apiToken,
|
||||||
volume: ServerVolume(
|
volume: ServerProviderVolume(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
serverId: 0,
|
serverId: 0,
|
||||||
|
@ -416,7 +416,7 @@ class ServerInstallationRepository {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return ServerHostingDetails(
|
return ServerHostingDetails(
|
||||||
apiToken: result.data,
|
apiToken: result.data,
|
||||||
volume: ServerVolume(
|
volume: ServerProviderVolume(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
sizeByte: 0,
|
sizeByte: 0,
|
||||||
|
@ -470,7 +470,7 @@ class ServerInstallationRepository {
|
||||||
Future<void> saveServerDetails(
|
Future<void> saveServerDetails(
|
||||||
final ServerHostingDetails serverDetails,
|
final ServerHostingDetails serverDetails,
|
||||||
) async {
|
) async {
|
||||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
await getIt<ApiConfigModel>().setServerDetails(serverDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteServerDetails() async {
|
Future<void> deleteServerDetails() async {
|
||||||
|
@ -483,18 +483,18 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveDnsProviderType(final DnsProviderType type) async {
|
Future<void> saveDnsProviderType(final DnsProviderType type) async {
|
||||||
await getIt<ApiConfigModel>().storeDnsProviderType(type);
|
await getIt<ApiConfigModel>().setDnsProviderType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveServerProviderKey(final String key) async {
|
Future<void> saveServerProviderKey(final String key) async {
|
||||||
await getIt<ApiConfigModel>().storeServerProviderKey(key);
|
await getIt<ApiConfigModel>().setServerProviderKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveServerType(final ServerType serverType) async {
|
Future<void> saveServerType(final ServerType serverType) async {
|
||||||
await getIt<ApiConfigModel>().storeServerTypeIdentifier(
|
await getIt<ApiConfigModel>().setServerTypeIdentifier(
|
||||||
serverType.identifier,
|
serverType.identifier,
|
||||||
);
|
);
|
||||||
await getIt<ApiConfigModel>().storeServerLocation(
|
await getIt<ApiConfigModel>().setServerLocation(
|
||||||
serverType.location.identifier,
|
serverType.location.identifier,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -507,7 +507,7 @@ class ServerInstallationRepository {
|
||||||
Future<void> saveBackblazeKey(
|
Future<void> saveBackblazeKey(
|
||||||
final BackupsCredential backblazeCredential,
|
final BackupsCredential backblazeCredential,
|
||||||
) async {
|
) async {
|
||||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
await getIt<ApiConfigModel>().setBackblazeCredential(backblazeCredential);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteBackblazeKey() async {
|
Future<void> deleteBackblazeKey() async {
|
||||||
|
@ -516,7 +516,7 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setDnsApiToken(final String key) async {
|
Future<void> setDnsApiToken(final String key) async {
|
||||||
await getIt<ApiConfigModel>().storeDnsProviderKey(key);
|
await getIt<ApiConfigModel>().setDnsProviderKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteDnsProviderKey() async {
|
Future<void> deleteDnsProviderKey() async {
|
||||||
|
@ -525,7 +525,7 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||||
await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
|
await getIt<ApiConfigModel>().setServerDomain(serverDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteDomain() async {
|
Future<void> deleteDomain() async {
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.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/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
part 'server_jobs_state.dart';
|
|
||||||
|
|
||||||
class ServerJobsCubit
|
|
||||||
extends ServerInstallationDependendCubit<ServerJobsState> {
|
|
||||||
ServerJobsCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(
|
|
||||||
serverInstallationCubit,
|
|
||||||
ServerJobsState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Timer? timer;
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() async {
|
|
||||||
emit(
|
|
||||||
ServerJobsState(),
|
|
||||||
);
|
|
||||||
if (timer != null && timer!.isActive) {
|
|
||||||
timer!.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void load() async {
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
final List<ServerJob> jobs = await api.getServerJobs();
|
|
||||||
emit(
|
|
||||||
ServerJobsState(
|
|
||||||
serverJobList: jobs,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> migrateToBinds(final Map<String, String> serviceToDisk) async {
|
|
||||||
final result = await api.migrateToBinds(serviceToDisk);
|
|
||||||
if (result.data == null) {
|
|
||||||
getIt<NavigationService>().showSnackBar(result.message!);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
|
||||||
ServerJobsState(
|
|
||||||
migrationJobUid: result.data,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerJob? getServerJobByUid(final String uid) {
|
|
||||||
ServerJob? job;
|
|
||||||
|
|
||||||
try {
|
|
||||||
job = state.serverJobList.firstWhere(
|
|
||||||
(final ServerJob job) => job.uid == uid,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> removeServerJob(final String uid) async {
|
|
||||||
final result = await api.removeApiJob(uid);
|
|
||||||
if (!result.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
|
||||||
ServerJobsState(
|
|
||||||
serverJobList: [
|
|
||||||
for (final ServerJob job in state.serverJobList)
|
|
||||||
if (job.uid != uid) job,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> removeAllFinishedJobs() async {
|
|
||||||
final List<ServerJob> finishedJobs = state.serverJobList
|
|
||||||
.where(
|
|
||||||
(final ServerJob job) =>
|
|
||||||
job.status == JobStatusEnum.finished ||
|
|
||||||
job.status == JobStatusEnum.error,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
for (final ServerJob job in finishedJobs) {
|
|
||||||
await removeServerJob(job.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reload({final bool useTimer = false}) async {
|
|
||||||
final List<ServerJob> jobs = await api.getServerJobs();
|
|
||||||
emit(
|
|
||||||
ServerJobsState(
|
|
||||||
serverJobList: jobs,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (useTimer) {
|
|
||||||
timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
part of 'server_jobs_cubit.dart';
|
|
||||||
|
|
||||||
class ServerJobsState extends ServerInstallationDependendState {
|
|
||||||
ServerJobsState({
|
|
||||||
final serverJobList = const <ServerJob>[],
|
|
||||||
this.migrationJobUid,
|
|
||||||
}) {
|
|
||||||
_serverJobList = serverJobList;
|
|
||||||
}
|
|
||||||
|
|
||||||
late final List<ServerJob> _serverJobList;
|
|
||||||
final String? migrationJobUid;
|
|
||||||
|
|
||||||
List<ServerJob> get serverJobList {
|
|
||||||
try {
|
|
||||||
final List<ServerJob> list = _serverJobList;
|
|
||||||
list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt));
|
|
||||||
return list;
|
|
||||||
} on UnsupportedError {
|
|
||||||
return _serverJobList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ServerJob> get backupJobList => serverJobList
|
|
||||||
.where(
|
|
||||||
// The backup jobs has the format of 'service.<service_id>.backup'
|
|
||||||
(final job) =>
|
|
||||||
job.typeId.contains('backup') || job.typeId.contains('restore'),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
bool get hasRemovableJobs => serverJobList.any(
|
|
||||||
(final job) =>
|
|
||||||
job.status == JobStatusEnum.finished ||
|
|
||||||
job.status == JobStatusEnum.error,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [migrationJobUid, _serverJobList];
|
|
||||||
|
|
||||||
ServerJobsState copyWith({
|
|
||||||
final List<ServerJob>? serverJobList,
|
|
||||||
final String? migrationJobUid,
|
|
||||||
}) =>
|
|
||||||
ServerJobsState(
|
|
||||||
serverJobList: serverJobList ?? _serverJobList,
|
|
||||||
migrationJobUid: migrationJobUid ?? this.migrationJobUid,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
|
||||||
|
|
||||||
part 'server_volume_state.dart';
|
|
||||||
|
|
||||||
class ApiServerVolumeCubit
|
|
||||||
extends ServerInstallationDependendCubit<ApiServerVolumeState> {
|
|
||||||
ApiServerVolumeCubit(
|
|
||||||
final ServerInstallationCubit serverInstallationCubit,
|
|
||||||
this.providerVolumeCubit,
|
|
||||||
) : super(serverInstallationCubit, ApiServerVolumeState.initial()) {
|
|
||||||
_providerVolumeSubscription =
|
|
||||||
providerVolumeCubit.stream.listen(checkProviderVolumes);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ServerApi serverApi = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
unawaited(reload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
late StreamSubscription<ApiProviderVolumeState> _providerVolumeSubscription;
|
|
||||||
final ApiProviderVolumeCubit providerVolumeCubit;
|
|
||||||
|
|
||||||
void checkProviderVolumes(final ApiProviderVolumeState state) {
|
|
||||||
emit(
|
|
||||||
ApiServerVolumeState(
|
|
||||||
this.state._volumes,
|
|
||||||
this.state.status,
|
|
||||||
this.state.usesBinds,
|
|
||||||
DiskStatus.fromVolumes(this.state._volumes, state.volumes),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reload() async {
|
|
||||||
final volumes = await serverApi.getServerDiskVolumes();
|
|
||||||
final usesBinds = await serverApi.isUsingBinds();
|
|
||||||
var status = LoadingStatus.error;
|
|
||||||
|
|
||||||
if (volumes.isNotEmpty) {
|
|
||||||
status = LoadingStatus.success;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
|
||||||
ApiServerVolumeState(
|
|
||||||
volumes,
|
|
||||||
status,
|
|
||||||
usesBinds,
|
|
||||||
DiskStatus.fromVolumes(
|
|
||||||
volumes,
|
|
||||||
providerVolumeCubit.state.volumes,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() {
|
|
||||||
emit(ApiServerVolumeState.initial());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_providerVolumeSubscription.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
part of 'server_volume_cubit.dart';
|
|
||||||
|
|
||||||
class ApiServerVolumeState extends ServerInstallationDependendState {
|
|
||||||
const ApiServerVolumeState(
|
|
||||||
this._volumes,
|
|
||||||
this.status,
|
|
||||||
this.usesBinds,
|
|
||||||
this._diskStatus,
|
|
||||||
);
|
|
||||||
|
|
||||||
ApiServerVolumeState.initial()
|
|
||||||
: this(const [], LoadingStatus.uninitialized, null, DiskStatus());
|
|
||||||
|
|
||||||
final List<ServerDiskVolume> _volumes;
|
|
||||||
final DiskStatus _diskStatus;
|
|
||||||
final bool? usesBinds;
|
|
||||||
final LoadingStatus status;
|
|
||||||
|
|
||||||
List<DiskVolume> get volumes => _diskStatus.diskVolumes;
|
|
||||||
DiskStatus get diskStatus => _diskStatus;
|
|
||||||
|
|
||||||
DiskVolume getVolume(final String volumeName) => volumes.firstWhere(
|
|
||||||
(final volume) => volume.name == volumeName,
|
|
||||||
orElse: () => DiskVolume(),
|
|
||||||
);
|
|
||||||
|
|
||||||
ApiServerVolumeState copyWith({
|
|
||||||
final List<ServerDiskVolume>? volumes,
|
|
||||||
final LoadingStatus? status,
|
|
||||||
final bool? usesBinds,
|
|
||||||
final DiskStatus? diskStatus,
|
|
||||||
}) =>
|
|
||||||
ApiServerVolumeState(
|
|
||||||
volumes ?? _volumes,
|
|
||||||
status ?? this.status,
|
|
||||||
usesBinds ?? this.usesBinds,
|
|
||||||
diskStatus ?? _diskStatus,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [_volumes, status, usesBinds];
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.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/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
|
||||||
|
|
||||||
part 'services_state.dart';
|
|
||||||
|
|
||||||
class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
|
||||||
ServicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(serverInstallationCubit, const ServicesState.empty());
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
Timer? timer;
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
final List<Service> services = await api.getAllServices();
|
|
||||||
emit(
|
|
||||||
ServicesState(
|
|
||||||
services: services,
|
|
||||||
lockedServices: const [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reload({final bool useTimer = false}) async {
|
|
||||||
final List<Service> services = await api.getAllServices();
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
services: services,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (useTimer) {
|
|
||||||
timer = Timer(const Duration(seconds: 60), () => reload(useTimer: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> restart(final String serviceId) async {
|
|
||||||
emit(state.copyWith(lockedServices: [...state.lockedServices, serviceId]));
|
|
||||||
final result = await api.restartService(serviceId);
|
|
||||||
if (!result.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!result.data) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
|
||||||
unawaited(reload());
|
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
lockedServices: state.lockedServices
|
|
||||||
.where((final element) => element != serviceId)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
unawaited(reload());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> moveService(
|
|
||||||
final String serviceId,
|
|
||||||
final String destination,
|
|
||||||
) async {
|
|
||||||
await api.moveService(serviceId, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() async {
|
|
||||||
emit(const ServicesState.empty());
|
|
||||||
if (timer != null && timer!.isActive) {
|
|
||||||
timer!.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
part of 'services_cubit.dart';
|
|
||||||
|
|
||||||
class ServicesState extends ServerInstallationDependendState {
|
|
||||||
const ServicesState({
|
|
||||||
required this.services,
|
|
||||||
required this.lockedServices,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ServicesState.empty()
|
|
||||||
: this(services: const [], lockedServices: const []);
|
|
||||||
|
|
||||||
final List<Service> services;
|
|
||||||
final List<String> lockedServices;
|
|
||||||
|
|
||||||
List<Service> get servicesThatCanBeBackedUp => services
|
|
||||||
.where(
|
|
||||||
(final service) => service.canBeBackedUp,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
bool isServiceLocked(final String serviceId) =>
|
|
||||||
lockedServices.contains(serviceId);
|
|
||||||
|
|
||||||
Service? getServiceById(final String id) {
|
|
||||||
final service = services.firstWhere(
|
|
||||||
(final service) => service.id == id,
|
|
||||||
orElse: () => Service.empty,
|
|
||||||
);
|
|
||||||
if (service.id == 'empty') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [
|
|
||||||
services,
|
|
||||||
lockedServices,
|
|
||||||
];
|
|
||||||
|
|
||||||
ServicesState copyWith({
|
|
||||||
final List<Service>? services,
|
|
||||||
final List<String>? lockedServices,
|
|
||||||
}) =>
|
|
||||||
ServicesState(
|
|
||||||
services: services ?? this.services,
|
|
||||||
lockedServices: lockedServices ?? this.lockedServices,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
part 'users_state.dart';
|
|
||||||
|
|
||||||
class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
|
||||||
UsersCubit(final ServerInstallationCubit serverInstallationCubit)
|
|
||||||
: super(
|
|
||||||
serverInstallationCubit,
|
|
||||||
const UsersState(
|
|
||||||
<User>[],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Box<User> box = Hive.box<User>(BNames.usersBox);
|
|
||||||
Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
if (serverInstallationCubit.state is! ServerInstallationFinished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<User> loadedUsers = box.values.toList();
|
|
||||||
if (loadedUsers.isNotEmpty) {
|
|
||||||
emit(
|
|
||||||
UsersState(
|
|
||||||
loadedUsers,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
if (serverInstallationCubit.state is! ServerInstallationFinished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit(state.copyWith(isLoading: true));
|
|
||||||
final List<User> usersFromServer = await api.getAllUsers();
|
|
||||||
if (usersFromServer.isNotEmpty) {
|
|
||||||
emit(
|
|
||||||
UsersState(
|
|
||||||
usersFromServer,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Update the users it the box
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(usersFromServer);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_fetch_users'.tr());
|
|
||||||
emit(state.copyWith(isLoading: false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createUser(final User user) async {
|
|
||||||
// If user exists on server, do nothing
|
|
||||||
if (state.users
|
|
||||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String? password = user.password;
|
|
||||||
if (password == null) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If API returned error, do nothing
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.createUser(user.login, password);
|
|
||||||
if (result.data == null) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<User> loadedUsers = List<User>.from(state.users);
|
|
||||||
loadedUsers.add(result.data!);
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(loadedUsers);
|
|
||||||
emit(state.copyWith(users: loadedUsers));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteUser(final User user) async {
|
|
||||||
// If user is primary or root, don't delete
|
|
||||||
if (user.type != UserType.normal) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_delete_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<User> loadedUsers = List<User>.from(state.users);
|
|
||||||
final GenericResult result = await api.deleteUser(user.login);
|
|
||||||
if (result.success && result.data) {
|
|
||||||
loadedUsers.removeWhere((final User u) => u.login == user.login);
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(loadedUsers);
|
|
||||||
emit(state.copyWith(users: loadedUsers));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> changeUserPassword(
|
|
||||||
final User user,
|
|
||||||
final String newPassword,
|
|
||||||
) async {
|
|
||||||
if (user.type == UserType.root) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_change_password'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.updateUser(user.login, newPassword);
|
|
||||||
if (result.data == null) {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
result.message ?? 'users.could_not_change_password'.tr(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addSshKey(final User user, final String publicKey) async {
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.addSshKey(user.login, publicKey);
|
|
||||||
if (result.data != null) {
|
|
||||||
final User updatedUser = result.data!;
|
|
||||||
final int index =
|
|
||||||
state.users.indexWhere((final User u) => u.login == user.login);
|
|
||||||
await box.putAt(index, updatedUser);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
users: box.values.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.removeSshKey(user.login, publicKey);
|
|
||||||
if (result.data != null) {
|
|
||||||
final User updatedUser = result.data!;
|
|
||||||
final int index =
|
|
||||||
state.users.indexWhere((final User u) => u.login == user.login);
|
|
||||||
await box.putAt(index, updatedUser);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
users: box.values.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() async {
|
|
||||||
emit(
|
|
||||||
const UsersState(
|
|
||||||
<User>[],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,47 +42,47 @@ class ApiConfigModel {
|
||||||
_serverProvider = value;
|
_serverProvider = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeDnsProviderType(final DnsProviderType value) async {
|
Future<void> setDnsProviderType(final DnsProviderType value) async {
|
||||||
await _box.put(BNames.dnsProvider, value);
|
await _box.put(BNames.dnsProvider, value);
|
||||||
_dnsProvider = value;
|
_dnsProvider = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeServerProviderKey(final String value) async {
|
Future<void> setServerProviderKey(final String value) async {
|
||||||
await _box.put(BNames.hetznerKey, value);
|
await _box.put(BNames.hetznerKey, value);
|
||||||
_serverProviderKey = value;
|
_serverProviderKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeDnsProviderKey(final String value) async {
|
Future<void> setDnsProviderKey(final String value) async {
|
||||||
await _box.put(BNames.cloudFlareKey, value);
|
await _box.put(BNames.cloudFlareKey, value);
|
||||||
_dnsProviderKey = value;
|
_dnsProviderKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeServerTypeIdentifier(final String typeIdentifier) async {
|
Future<void> setServerTypeIdentifier(final String typeIdentifier) async {
|
||||||
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
|
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
|
||||||
_serverType = typeIdentifier;
|
_serverType = typeIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeServerLocation(final String serverLocation) async {
|
Future<void> setServerLocation(final String serverLocation) async {
|
||||||
await _box.put(BNames.serverLocation, serverLocation);
|
await _box.put(BNames.serverLocation, serverLocation);
|
||||||
_serverLocation = serverLocation;
|
_serverLocation = serverLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeBackblazeCredential(final BackupsCredential value) async {
|
Future<void> setBackblazeCredential(final BackupsCredential value) async {
|
||||||
await _box.put(BNames.backblazeCredential, value);
|
await _box.put(BNames.backblazeCredential, value);
|
||||||
_backblazeCredential = value;
|
_backblazeCredential = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeServerDomain(final ServerDomain value) async {
|
Future<void> setServerDomain(final ServerDomain value) async {
|
||||||
await _box.put(BNames.serverDomain, value);
|
await _box.put(BNames.serverDomain, value);
|
||||||
_serverDomain = value;
|
_serverDomain = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeServerDetails(final ServerHostingDetails value) async {
|
Future<void> setServerDetails(final ServerHostingDetails value) async {
|
||||||
await _box.put(BNames.serverDetails, value);
|
await _box.put(BNames.serverDetails, value);
|
||||||
_serverDetails = value;
|
_serverDetails = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeBackblazeBucket(final BackblazeBucket value) async {
|
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
|
||||||
await _box.put(BNames.backblazeBucket, value);
|
await _box.put(BNames.backblazeBucket, value);
|
||||||
_backblazeBucket = value;
|
_backblazeBucket = value;
|
||||||
}
|
}
|
||||||
|
|
435
lib/logic/get_it/api_connection_repository.dart
Normal file
435
lib/logic/get_it/api_connection_repository.dart
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/system_settings.dart';
|
||||||
|
|
||||||
|
/// Repository for all API calls
|
||||||
|
/// Stores the current state of all data from API and exposes it to Blocs.
|
||||||
|
class ApiConnectionRepository {
|
||||||
|
Box box = Hive.box(BNames.serverInstallationBox);
|
||||||
|
final ServerApi api = ServerApi();
|
||||||
|
|
||||||
|
final ApiData _apiData = ApiData(ServerApi());
|
||||||
|
|
||||||
|
ApiData get apiData => _apiData;
|
||||||
|
|
||||||
|
ConnectionStatus connectionStatus = ConnectionStatus.nonexistent;
|
||||||
|
|
||||||
|
final _dataStream = StreamController<ApiData>.broadcast();
|
||||||
|
final _connectionStatusStream =
|
||||||
|
StreamController<ConnectionStatus>.broadcast();
|
||||||
|
|
||||||
|
Stream<ApiData> get dataStream => _dataStream.stream;
|
||||||
|
Stream<ConnectionStatus> get connectionStatusStream =>
|
||||||
|
_connectionStatusStream.stream;
|
||||||
|
|
||||||
|
ConnectionStatus get currentConnectionStatus => connectionStatus;
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
Future<void> removeServerJob(final String uid) async {
|
||||||
|
await api.removeApiJob(uid);
|
||||||
|
_apiData.serverJobs.data
|
||||||
|
?.removeWhere((final ServerJob element) => element.uid == uid);
|
||||||
|
_dataStream.add(_apiData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeAllFinishedServerJobs() async {
|
||||||
|
final List<ServerJob> finishedJobs = _apiData.serverJobs.data
|
||||||
|
?.where(
|
||||||
|
(final ServerJob element) =>
|
||||||
|
element.status == JobStatusEnum.finished ||
|
||||||
|
element.status == JobStatusEnum.error,
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
// Optimistically remove the jobs from the list
|
||||||
|
_apiData.serverJobs.data?.removeWhere(
|
||||||
|
(final ServerJob element) =>
|
||||||
|
element.status == JobStatusEnum.finished ||
|
||||||
|
element.status == JobStatusEnum.error,
|
||||||
|
);
|
||||||
|
_dataStream.add(_apiData);
|
||||||
|
|
||||||
|
await Future.forEach<ServerJob>(
|
||||||
|
finishedJobs,
|
||||||
|
(final ServerJob job) async => removeServerJob(job.uid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> createUser(final User user) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return (false, 'basis.network_error'.tr());
|
||||||
|
}
|
||||||
|
// If user exists on server, do nothing
|
||||||
|
if (loadedUsers
|
||||||
|
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||||
|
return (false, 'users.user_already_exists'.tr());
|
||||||
|
}
|
||||||
|
final String? password = user.password;
|
||||||
|
if (password == null) {
|
||||||
|
return (false, 'users.could_not_create_user'.tr());
|
||||||
|
}
|
||||||
|
// If API returned error, do nothing
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.createUser(user.login, password);
|
||||||
|
if (result.data == null) {
|
||||||
|
return (false, result.message ?? 'users.could_not_create_user'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
_apiData.users.data?.add(result.data!);
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> deleteUser(final User user) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return (false, 'basis.network_error'.tr());
|
||||||
|
}
|
||||||
|
// If user is primary or root, don't delete
|
||||||
|
if (user.type != UserType.normal) {
|
||||||
|
return (false, 'users.could_not_delete_user'.tr());
|
||||||
|
}
|
||||||
|
final GenericResult result = await api.deleteUser(user.login);
|
||||||
|
if (result.success && result.data) {
|
||||||
|
_apiData.users.data?.removeWhere((final User u) => u.login == user.login);
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success || !result.data) {
|
||||||
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> changeUserPassword(
|
||||||
|
final User user,
|
||||||
|
final String newPassword,
|
||||||
|
) async {
|
||||||
|
if (user.type == UserType.root) {
|
||||||
|
return (false, 'users.could_not_change_password'.tr());
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result = await api.updateUser(
|
||||||
|
user.login,
|
||||||
|
newPassword,
|
||||||
|
);
|
||||||
|
if (result.data == null) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
result.message ?? 'users.could_not_change_password'.tr(),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
false,
|
||||||
|
result.message ?? 'users.could_not_change_password'.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> addSshKey(
|
||||||
|
final User user,
|
||||||
|
final String publicKey,
|
||||||
|
) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return (false, 'basis.network_error'.tr());
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.addSshKey(user.login, publicKey);
|
||||||
|
if (result.data != null) {
|
||||||
|
final User updatedUser = result.data!;
|
||||||
|
final int index =
|
||||||
|
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||||
|
loadedUsers[index] = updatedUser;
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
} else {
|
||||||
|
return (false, result.message ?? 'users.could_not_add_ssh_key'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> deleteSshKey(
|
||||||
|
final User user,
|
||||||
|
final String publicKey,
|
||||||
|
) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return (false, 'basis.network_error'.tr());
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.removeSshKey(user.login, publicKey);
|
||||||
|
if (result.data != null) {
|
||||||
|
final User updatedUser = result.data!;
|
||||||
|
final int index =
|
||||||
|
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||||
|
loadedUsers[index] = updatedUser;
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
} else {
|
||||||
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> setAutoUpgradeSettings(
|
||||||
|
final bool enable,
|
||||||
|
final bool allowReboot,
|
||||||
|
) async {
|
||||||
|
final GenericResult<AutoUpgradeSettings?> result =
|
||||||
|
await api.setAutoUpgradeSettings(
|
||||||
|
AutoUpgradeSettings(
|
||||||
|
enable: enable,
|
||||||
|
allowReboot: allowReboot,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_apiData.settings.invalidate();
|
||||||
|
if (result.data != null) {
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
} else {
|
||||||
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(bool, String)> setServerTimezone(
|
||||||
|
final String timezone,
|
||||||
|
) async {
|
||||||
|
final GenericResult result = await api.setTimezone(timezone);
|
||||||
|
_apiData.settings.invalidate();
|
||||||
|
if (result.success) {
|
||||||
|
return (true, result.message ?? 'basis.done'.tr());
|
||||||
|
} else {
|
||||||
|
return (false, result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_dataStream.close();
|
||||||
|
_connectionStatusStream.close();
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerHostingDetails? get serverDetails =>
|
||||||
|
getIt<ApiConfigModel>().serverDetails;
|
||||||
|
ServerDomain? get serverDomain => getIt<ApiConfigModel>().serverDomain;
|
||||||
|
|
||||||
|
void init() async {
|
||||||
|
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||||
|
final hasFinalChecked =
|
||||||
|
box.get(BNames.hasFinalChecked, defaultValue: false);
|
||||||
|
if (serverDetails == null || !hasFinalChecked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectionStatus = ConnectionStatus.reconnecting;
|
||||||
|
_connectionStatusStream.add(connectionStatus);
|
||||||
|
|
||||||
|
final String? apiVersion = await api.getApiVersion();
|
||||||
|
if (apiVersion == null) {
|
||||||
|
connectionStatus = ConnectionStatus.offline;
|
||||||
|
_connectionStatusStream.add(connectionStatus);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_apiData.apiVersion.data = apiVersion;
|
||||||
|
_dataStream.add(_apiData);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _refetchEverything(Version.parse(apiVersion));
|
||||||
|
|
||||||
|
connectionStatus = ConnectionStatus.connected;
|
||||||
|
_connectionStatusStream.add(connectionStatus);
|
||||||
|
|
||||||
|
// Use timer to periodically check for new jobs
|
||||||
|
_timer = Timer.periodic(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
reload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refetchEverything(final Version version) async {
|
||||||
|
await _apiData.serverJobs
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.backups
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.backupConfig
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.services
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.volumes
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.recoveryKeyStatus
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.devices
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.settings
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> reload(final Timer? timer) async {
|
||||||
|
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||||
|
if (serverDetails == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? apiVersion = await api.getApiVersion();
|
||||||
|
if (apiVersion == null) {
|
||||||
|
connectionStatus = ConnectionStatus.offline;
|
||||||
|
_connectionStatusStream.add(connectionStatus);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
connectionStatus = ConnectionStatus.connected;
|
||||||
|
_connectionStatusStream.add(connectionStatus);
|
||||||
|
_apiData.apiVersion.data = apiVersion;
|
||||||
|
}
|
||||||
|
final Version version = Version.parse(apiVersion);
|
||||||
|
await _refetchEverything(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitData() {
|
||||||
|
_dataStream.add(_apiData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiData {
|
||||||
|
ApiData(final ServerApi api)
|
||||||
|
: apiVersion = ApiDataElement<String>(
|
||||||
|
fetchData: () async => api.getApiVersion(),
|
||||||
|
),
|
||||||
|
serverJobs = ApiDataElement<List<ServerJob>>(
|
||||||
|
fetchData: () async => api.getServerJobs(),
|
||||||
|
ttl: 10,
|
||||||
|
),
|
||||||
|
backupConfig = ApiDataElement<BackupConfiguration>(
|
||||||
|
fetchData: () async => api.getBackupsConfiguration(),
|
||||||
|
requiredApiVersion: '>=2.4.2',
|
||||||
|
ttl: 120,
|
||||||
|
),
|
||||||
|
backups = ApiDataElement<List<Backup>>(
|
||||||
|
fetchData: () async => api.getBackups(),
|
||||||
|
requiredApiVersion: '>=2.4.2',
|
||||||
|
ttl: 120,
|
||||||
|
),
|
||||||
|
services = ApiDataElement<List<Service>>(
|
||||||
|
fetchData: () async => api.getAllServices(),
|
||||||
|
requiredApiVersion: '>=2.4.3',
|
||||||
|
),
|
||||||
|
volumes = ApiDataElement<List<ServerDiskVolume>>(
|
||||||
|
fetchData: () async => api.getServerDiskVolumes(),
|
||||||
|
),
|
||||||
|
recoveryKeyStatus = ApiDataElement<RecoveryKeyStatus>(
|
||||||
|
fetchData: () async => (await api.getRecoveryTokenStatus()).data,
|
||||||
|
ttl: 300,
|
||||||
|
),
|
||||||
|
devices = ApiDataElement<List<ApiToken>>(
|
||||||
|
fetchData: () async => (await api.getApiTokens()).data,
|
||||||
|
),
|
||||||
|
users = ApiDataElement<List<User>>(
|
||||||
|
fetchData: () async => api.getAllUsers(),
|
||||||
|
),
|
||||||
|
settings = ApiDataElement<SystemSettings>(
|
||||||
|
fetchData: () async => api.getSystemSettings(),
|
||||||
|
ttl: 600,
|
||||||
|
);
|
||||||
|
|
||||||
|
ApiDataElement<List<ServerJob>> serverJobs;
|
||||||
|
ApiDataElement<String> apiVersion;
|
||||||
|
ApiDataElement<BackupConfiguration> backupConfig;
|
||||||
|
ApiDataElement<List<Backup>> backups;
|
||||||
|
ApiDataElement<List<Service>> services;
|
||||||
|
ApiDataElement<List<ServerDiskVolume>> volumes;
|
||||||
|
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
||||||
|
ApiDataElement<List<ApiToken>> devices;
|
||||||
|
ApiDataElement<List<User>> users;
|
||||||
|
ApiDataElement<SystemSettings> settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConnectionStatus {
|
||||||
|
nonexistent,
|
||||||
|
connected,
|
||||||
|
reconnecting,
|
||||||
|
offline,
|
||||||
|
unauthorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiDataElement<T> {
|
||||||
|
ApiDataElement({
|
||||||
|
required this.fetchData,
|
||||||
|
final T? data,
|
||||||
|
this.requiredApiVersion = '>=2.3.0',
|
||||||
|
this.ttl = 60,
|
||||||
|
}) : _data = data,
|
||||||
|
_lastUpdated = DateTime.now();
|
||||||
|
|
||||||
|
T? _data;
|
||||||
|
final String requiredApiVersion;
|
||||||
|
|
||||||
|
final Future<T?> Function() fetchData;
|
||||||
|
|
||||||
|
Future<void> refetchData(
|
||||||
|
final Version version,
|
||||||
|
final Function callback,
|
||||||
|
) async {
|
||||||
|
if (VersionConstraint.parse(requiredApiVersion).allows(version)) {
|
||||||
|
if (isExpired || _data == null) {
|
||||||
|
final newData = await fetchData();
|
||||||
|
if (T is List) {
|
||||||
|
if (Object.hashAll(newData as Iterable<Object?>) !=
|
||||||
|
Object.hashAll(_data as Iterable<Object?>)) {
|
||||||
|
_data = [...newData] as T?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newData.hashCode != _data.hashCode) {
|
||||||
|
_data = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TTL of the data in seconds
|
||||||
|
final int ttl;
|
||||||
|
|
||||||
|
Type get type => T;
|
||||||
|
|
||||||
|
void invalidate() {
|
||||||
|
_lastUpdated = DateTime.fromMillisecondsSinceEpoch(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timestamp of when the data was last updated
|
||||||
|
DateTime _lastUpdated;
|
||||||
|
|
||||||
|
bool get isExpired {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final difference = now.difference(_lastUpdated);
|
||||||
|
return difference.inSeconds > ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
T? get data => _data;
|
||||||
|
|
||||||
|
/// Sets the data and updates the lastUpdated timestamp
|
||||||
|
set data(final T? data) {
|
||||||
|
_data = data;
|
||||||
|
_lastUpdated = DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last time the data was updated
|
||||||
|
DateTime get lastUpdated => _lastUpdated;
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class TimerModel extends ChangeNotifier {
|
|
||||||
DateTime _time = DateTime.now();
|
|
||||||
|
|
||||||
DateTime get time => _time;
|
|
||||||
|
|
||||||
void restart() {
|
|
||||||
_time = DateTime.now();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
||||||
|
@ -40,7 +41,7 @@ extension BackupReasonExtension on Enum$BackupReason {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackupConfiguration {
|
class BackupConfiguration extends Equatable {
|
||||||
BackupConfiguration.fromGraphQL(
|
BackupConfiguration.fromGraphQL(
|
||||||
final Query$BackupConfiguration$backup$configuration configuration,
|
final Query$BackupConfiguration$backup$configuration configuration,
|
||||||
) : this(
|
) : this(
|
||||||
|
@ -58,7 +59,7 @@ class BackupConfiguration {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
BackupConfiguration({
|
const BackupConfiguration({
|
||||||
required this.autobackupPeriod,
|
required this.autobackupPeriod,
|
||||||
required this.encryptionKey,
|
required this.encryptionKey,
|
||||||
required this.isInitialized,
|
required this.isInitialized,
|
||||||
|
@ -75,9 +76,39 @@ class BackupConfiguration {
|
||||||
final String? locationName;
|
final String? locationName;
|
||||||
final BackupsProviderType provider;
|
final BackupsProviderType provider;
|
||||||
final AutobackupQuotas autobackupQuotas;
|
final AutobackupQuotas autobackupQuotas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
autobackupPeriod,
|
||||||
|
encryptionKey,
|
||||||
|
isInitialized,
|
||||||
|
locationId,
|
||||||
|
locationName,
|
||||||
|
provider,
|
||||||
|
autobackupQuotas,
|
||||||
|
];
|
||||||
|
|
||||||
|
BackupConfiguration copyWith({
|
||||||
|
final Duration? autobackupPeriod,
|
||||||
|
final String? encryptionKey,
|
||||||
|
final bool? isInitialized,
|
||||||
|
final String? locationId,
|
||||||
|
final String? locationName,
|
||||||
|
final BackupsProviderType? provider,
|
||||||
|
final AutobackupQuotas? autobackupQuotas,
|
||||||
|
}) =>
|
||||||
|
BackupConfiguration(
|
||||||
|
autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod,
|
||||||
|
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||||
|
isInitialized: isInitialized ?? this.isInitialized,
|
||||||
|
locationId: locationId ?? this.locationId,
|
||||||
|
locationName: locationName ?? this.locationName,
|
||||||
|
provider: provider ?? this.provider,
|
||||||
|
autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutobackupQuotas {
|
class AutobackupQuotas extends Equatable {
|
||||||
AutobackupQuotas.fromGraphQL(
|
AutobackupQuotas.fromGraphQL(
|
||||||
final Query$BackupConfiguration$backup$configuration$autobackupQuotas
|
final Query$BackupConfiguration$backup$configuration$autobackupQuotas
|
||||||
autobackupQuotas,
|
autobackupQuotas,
|
||||||
|
@ -89,7 +120,7 @@ class AutobackupQuotas {
|
||||||
yearly: autobackupQuotas.yearly,
|
yearly: autobackupQuotas.yearly,
|
||||||
);
|
);
|
||||||
|
|
||||||
AutobackupQuotas({
|
const AutobackupQuotas({
|
||||||
required this.last,
|
required this.last,
|
||||||
required this.daily,
|
required this.daily,
|
||||||
required this.weekly,
|
required this.weekly,
|
||||||
|
@ -117,6 +148,15 @@ class AutobackupQuotas {
|
||||||
monthly: monthly ?? this.monthly,
|
monthly: monthly ?? this.monthly,
|
||||||
yearly: yearly ?? this.yearly,
|
yearly: yearly ?? this.yearly,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
last,
|
||||||
|
daily,
|
||||||
|
weekly,
|
||||||
|
monthly,
|
||||||
|
yearly,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BackupRestoreStrategy {
|
enum BackupRestoreStrategy {
|
||||||
|
|
|
@ -9,13 +9,12 @@ class DiskVolume {
|
||||||
this.sizeUsed = const DiskSize(byte: 0),
|
this.sizeUsed = const DiskSize(byte: 0),
|
||||||
this.root = false,
|
this.root = false,
|
||||||
this.isResizable = false,
|
this.isResizable = false,
|
||||||
this.serverDiskVolume,
|
|
||||||
this.providerVolume,
|
this.providerVolume,
|
||||||
});
|
});
|
||||||
|
|
||||||
DiskVolume.fromServerDiscVolume(
|
DiskVolume.fromServerDiscVolume(
|
||||||
final ServerDiskVolume volume,
|
final ServerDiskVolume volume,
|
||||||
final ServerVolume? providerVolume,
|
final ServerProviderVolume? providerVolume,
|
||||||
) : this(
|
) : this(
|
||||||
name: volume.name,
|
name: volume.name,
|
||||||
sizeTotal: DiskSize(
|
sizeTotal: DiskSize(
|
||||||
|
@ -27,7 +26,6 @@ class DiskVolume {
|
||||||
),
|
),
|
||||||
root: volume.root,
|
root: volume.root,
|
||||||
isResizable: providerVolume != null,
|
isResizable: providerVolume != null,
|
||||||
serverDiskVolume: volume,
|
|
||||||
providerVolume: providerVolume,
|
providerVolume: providerVolume,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -51,8 +49,7 @@ class DiskVolume {
|
||||||
String name;
|
String name;
|
||||||
bool root;
|
bool root;
|
||||||
bool isResizable;
|
bool isResizable;
|
||||||
ServerDiskVolume? serverDiskVolume;
|
ServerProviderVolume? providerVolume;
|
||||||
ServerVolume? providerVolume;
|
|
||||||
|
|
||||||
/// from 0.0 to 1.0
|
/// from 0.0 to 1.0
|
||||||
double get percentage =>
|
double get percentage =>
|
||||||
|
@ -67,7 +64,7 @@ class DiskVolume {
|
||||||
final bool? root,
|
final bool? root,
|
||||||
final bool? isResizable,
|
final bool? isResizable,
|
||||||
final ServerDiskVolume? serverDiskVolume,
|
final ServerDiskVolume? serverDiskVolume,
|
||||||
final ServerVolume? providerVolume,
|
final ServerProviderVolume? providerVolume,
|
||||||
}) =>
|
}) =>
|
||||||
DiskVolume(
|
DiskVolume(
|
||||||
sizeUsed: sizeUsed ?? this.sizeUsed,
|
sizeUsed: sizeUsed ?? this.sizeUsed,
|
||||||
|
@ -75,7 +72,6 @@ class DiskVolume {
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
root: root ?? this.root,
|
root: root ?? this.root,
|
||||||
isResizable: isResizable ?? this.isResizable,
|
isResizable: isResizable ?? this.isResizable,
|
||||||
serverDiskVolume: serverDiskVolume ?? this.serverDiskVolume,
|
|
||||||
providerVolume: providerVolume ?? this.providerVolume,
|
providerVolume: providerVolume ?? this.providerVolume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,14 +79,15 @@ class DiskVolume {
|
||||||
class DiskStatus {
|
class DiskStatus {
|
||||||
DiskStatus.fromVolumes(
|
DiskStatus.fromVolumes(
|
||||||
final List<ServerDiskVolume> serverVolumes,
|
final List<ServerDiskVolume> serverVolumes,
|
||||||
final List<ServerVolume> providerVolumes,
|
final List<ServerProviderVolume> providerVolumes,
|
||||||
) {
|
) {
|
||||||
diskVolumes = serverVolumes.map((
|
diskVolumes = serverVolumes.map((
|
||||||
final ServerDiskVolume volume,
|
final ServerDiskVolume volume,
|
||||||
) {
|
) {
|
||||||
ServerVolume? providerVolume;
|
ServerProviderVolume? providerVolume;
|
||||||
|
|
||||||
for (final ServerVolume iterableProviderVolume in providerVolumes) {
|
for (final ServerProviderVolume iterableProviderVolume
|
||||||
|
in providerVolumes) {
|
||||||
if (iterableProviderVolume.linuxDevice == null ||
|
if (iterableProviderVolume.linuxDevice == null ||
|
||||||
volume.model == null ||
|
volume.model == null ||
|
||||||
volume.serial == null) {
|
volume.serial == null) {
|
||||||
|
|
|
@ -29,4 +29,19 @@ class BackblazeBucket {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => bucketName;
|
String toString() => bucketName;
|
||||||
|
|
||||||
|
BackblazeBucket copyWith({
|
||||||
|
final String? bucketId,
|
||||||
|
final String? applicationKeyId,
|
||||||
|
final String? applicationKey,
|
||||||
|
final String? bucketName,
|
||||||
|
final String? encryptionKey,
|
||||||
|
}) =>
|
||||||
|
BackblazeBucket(
|
||||||
|
bucketId: bucketId ?? this.bucketId,
|
||||||
|
applicationKeyId: applicationKeyId ?? this.applicationKeyId,
|
||||||
|
applicationKey: applicationKey ?? this.applicationKey,
|
||||||
|
bucketName: bucketName ?? this.bucketName,
|
||||||
|
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,9 @@ class ServerHostingDetails {
|
||||||
@HiveField(2)
|
@HiveField(2)
|
||||||
final DateTime? startTime;
|
final DateTime? startTime;
|
||||||
|
|
||||||
|
// TODO: Check if it is still needed
|
||||||
@HiveField(4)
|
@HiveField(4)
|
||||||
final ServerVolume volume;
|
final ServerProviderVolume volume;
|
||||||
|
|
||||||
@HiveField(5)
|
@HiveField(5)
|
||||||
final String apiToken;
|
final String apiToken;
|
||||||
|
@ -52,8 +53,8 @@ class ServerHostingDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
@HiveType(typeId: 5)
|
@HiveType(typeId: 5)
|
||||||
class ServerVolume {
|
class ServerProviderVolume {
|
||||||
ServerVolume({
|
ServerProviderVolume({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.sizeByte,
|
required this.sizeByte,
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
|
||||||
ip4: fields[0] as String,
|
ip4: fields[0] as String,
|
||||||
id: fields[1] as int,
|
id: fields[1] as int,
|
||||||
createTime: fields[3] as DateTime?,
|
createTime: fields[3] as DateTime?,
|
||||||
volume: fields[4] as ServerVolume,
|
volume: fields[4] as ServerProviderVolume,
|
||||||
apiToken: fields[5] as String,
|
apiToken: fields[5] as String,
|
||||||
provider: fields[6] == null
|
provider: fields[6] == null
|
||||||
? ServerProviderType.hetzner
|
? ServerProviderType.hetzner
|
||||||
|
@ -60,17 +60,17 @@ class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
|
||||||
typeId == other.typeId;
|
typeId == other.typeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
class ServerProviderVolumeAdapter extends TypeAdapter<ServerProviderVolume> {
|
||||||
@override
|
@override
|
||||||
final int typeId = 5;
|
final int typeId = 5;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ServerVolume read(BinaryReader reader) {
|
ServerProviderVolume read(BinaryReader reader) {
|
||||||
final numOfFields = reader.readByte();
|
final numOfFields = reader.readByte();
|
||||||
final fields = <int, dynamic>{
|
final fields = <int, dynamic>{
|
||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
};
|
};
|
||||||
return ServerVolume(
|
return ServerProviderVolume(
|
||||||
id: fields[1] as int,
|
id: fields[1] as int,
|
||||||
name: fields[2] as String,
|
name: fields[2] as String,
|
||||||
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
|
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
|
||||||
|
@ -81,7 +81,7 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, ServerVolume obj) {
|
void write(BinaryWriter writer, ServerProviderVolume obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(6)
|
..writeByte(6)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
|
@ -104,7 +104,7 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is ServerVolumeAdapter &&
|
other is ServerProviderVolumeAdapter &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
typeId == other.typeId;
|
typeId == other.typeId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
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/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/config/get_it_config.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';
|
||||||
|
|
||||||
|
@ -11,69 +12,148 @@ 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();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [id, title];
|
List<Object> get props => [id, title, status];
|
||||||
|
|
||||||
|
ClientJob copyWithNewStatus({
|
||||||
|
required final JobStatusEnum status,
|
||||||
|
final String? message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class RebuildServerJob extends ClientJob {
|
class UpgradeServerJob extends ClientJob {
|
||||||
RebuildServerJob({
|
UpgradeServerJob({
|
||||||
required super.title,
|
super.status,
|
||||||
|
super.message,
|
||||||
super.id,
|
super.id,
|
||||||
});
|
}) : super(title: 'jobs.start_server_upgrade'.tr());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool canAddTo(final List<ClientJob> jobs) =>
|
bool canAddTo(final List<ClientJob> jobs) =>
|
||||||
!jobs.any((final job) => job is RebuildServerJob);
|
!jobs.any((final job) => job is UpgradeServerJob);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute() async => (false, 'unimplemented');
|
||||||
await cubit.upgradeServer();
|
|
||||||
}
|
@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() 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() async =>
|
||||||
await cubit.usersCubit.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() async =>
|
||||||
await cubit.usersCubit.changeUserPassword(user, user.password!);
|
getIt<ApiConnectionRepository>().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;
|
||||||
|
@ -84,18 +164,32 @@ class DeleteUserJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute() async =>
|
||||||
await cubit.usersCubit.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}',
|
||||||
|
@ -110,36 +204,70 @@ class ServiceToggleJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute() async {
|
||||||
await cubit.api.switchService(service.id, needToTurnOn);
|
final result = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.switchService(service.id, needToTurnOn);
|
||||||
|
return (result.success, result.message ?? 'jobs.generic_error'.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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() async =>
|
||||||
await cubit.usersCubit.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;
|
||||||
|
@ -154,10 +282,120 @@ class DeleteSSHKeyJob extends ClientJob {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
Future<(bool, String)> execute() async =>
|
||||||
await cubit.usersCubit.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() async => getIt<ApiConnectionRepository>()
|
||||||
|
.setAutoUpgradeSettings(enable, allowReboot);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||||
|
final currentSettings = getIt<ApiConnectionRepository>()
|
||||||
|
.apiData
|
||||||
|
.settings
|
||||||
|
.data
|
||||||
|
?.autoUpgradeSettings;
|
||||||
|
if (currentSettings == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return currentSettings.enable == enable &&
|
||||||
|
currentSettings.allowReboot == allowReboot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [id, title, user, publicKey];
|
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() async =>
|
||||||
|
getIt<ApiConnectionRepository>().setServerTimezone(timezone);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRemoveInsteadOfAdd(final List<ClientJob> jobs) {
|
||||||
|
final currentSettings =
|
||||||
|
getIt<ApiConnectionRepository>().apiData.settings.data?.timezone;
|
||||||
|
if (currentSettings == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return currentSettings == timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,13 +1,14 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
||||||
|
|
||||||
part 'api_token.g.dart';
|
part 'api_token.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ApiToken {
|
class ApiToken extends Equatable {
|
||||||
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
||||||
_$ApiTokenFromJson(json);
|
_$ApiTokenFromJson(json);
|
||||||
ApiToken({
|
const ApiToken({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.isCaller,
|
required this.isCaller,
|
||||||
|
@ -25,4 +26,7 @@ class ApiToken {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
@JsonKey(name: 'is_caller')
|
@JsonKey(name: 'is_caller')
|
||||||
final bool isCaller;
|
final bool isCaller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [name, date, isCaller];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'server_disk_volume.g.dart';
|
part 'server_disk_volume.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ServerDiskVolume {
|
class ServerDiskVolume extends Equatable {
|
||||||
factory ServerDiskVolume.fromJson(final Map<String, dynamic> json) =>
|
factory ServerDiskVolume.fromJson(final Map<String, dynamic> json) =>
|
||||||
_$ServerDiskVolumeFromJson(json);
|
_$ServerDiskVolumeFromJson(json);
|
||||||
ServerDiskVolume({
|
const ServerDiskVolume({
|
||||||
required this.freeSpace,
|
required this.freeSpace,
|
||||||
required this.model,
|
required this.model,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -25,4 +26,16 @@ class ServerDiskVolume {
|
||||||
final String totalSpace;
|
final String totalSpace;
|
||||||
final String type;
|
final String type;
|
||||||
final String usedSpace;
|
final String usedSpace;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
freeSpace,
|
||||||
|
model,
|
||||||
|
name,
|
||||||
|
root,
|
||||||
|
serial,
|
||||||
|
totalSpace,
|
||||||
|
type,
|
||||||
|
usedSpace,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
||||||
|
|
||||||
part 'server_job.g.dart';
|
part 'server_job.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ServerJob {
|
class ServerJob extends Equatable {
|
||||||
factory ServerJob.fromJson(final Map<String, dynamic> json) =>
|
factory ServerJob.fromJson(final Map<String, dynamic> json) =>
|
||||||
_$ServerJobFromJson(json);
|
_$ServerJobFromJson(json);
|
||||||
ServerJob({
|
const ServerJob({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.status,
|
required this.status,
|
||||||
|
@ -50,6 +51,22 @@ class ServerJob {
|
||||||
final String? result;
|
final String? result;
|
||||||
final String? statusText;
|
final String? statusText;
|
||||||
final DateTime? finishedAt;
|
final DateTime? finishedAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
status,
|
||||||
|
uid,
|
||||||
|
typeId,
|
||||||
|
updatedAt,
|
||||||
|
createdAt,
|
||||||
|
error,
|
||||||
|
progress,
|
||||||
|
result,
|
||||||
|
statusText,
|
||||||
|
finishedAt,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum JobStatusEnum {
|
enum JobStatusEnum {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:graphql/client.dart';
|
import 'package:graphql/client.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
final DateFormat formatter = DateFormat('hh:mm');
|
/// TODO(misterfourtytwo): add equality override
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
Message({this.text, this.severity = MessageSeverity.normal})
|
Message({this.text, this.severity = MessageSeverity.normal})
|
||||||
: time = DateTime.now();
|
: time = DateTime.now();
|
||||||
|
@ -13,7 +12,9 @@ class Message {
|
||||||
final String? text;
|
final String? text;
|
||||||
final DateTime time;
|
final DateTime time;
|
||||||
final MessageSeverity severity;
|
final MessageSeverity severity;
|
||||||
String get timeString => formatter.format(time);
|
|
||||||
|
static final DateFormat _formatter = DateFormat('hh:mm');
|
||||||
|
String get timeString => _formatter.format(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageSeverity {
|
enum MessageSeverity {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
|
|
||||||
class Service {
|
class Service extends Equatable {
|
||||||
Service.fromGraphQL(final Query$AllServices$services$allServices service)
|
Service.fromGraphQL(final Query$AllServices$services$allServices service)
|
||||||
: this(
|
: this(
|
||||||
id: service.id,
|
id: service.id,
|
||||||
|
@ -36,7 +37,7 @@ class Service {
|
||||||
[],
|
[],
|
||||||
url: service.url,
|
url: service.url,
|
||||||
);
|
);
|
||||||
Service({
|
const Service({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.displayName,
|
required this.displayName,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
@ -71,7 +72,7 @@ class Service {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Service empty = Service(
|
static Service empty = const Service(
|
||||||
id: 'empty',
|
id: 'empty',
|
||||||
displayName: '',
|
displayName: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
@ -82,7 +83,7 @@ class Service {
|
||||||
backupDescription: '',
|
backupDescription: '',
|
||||||
status: ServiceStatus.off,
|
status: ServiceStatus.off,
|
||||||
storageUsage: ServiceStorageUsage(
|
storageUsage: ServiceStorageUsage(
|
||||||
used: const DiskSize(byte: 0),
|
used: DiskSize(byte: 0),
|
||||||
volume: '',
|
volume: '',
|
||||||
),
|
),
|
||||||
svgIcon: '',
|
svgIcon: '',
|
||||||
|
@ -103,16 +104,36 @@ class Service {
|
||||||
final String svgIcon;
|
final String svgIcon;
|
||||||
final String? url;
|
final String? url;
|
||||||
final List<DnsRecord> dnsRecords;
|
final List<DnsRecord> dnsRecords;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
displayName,
|
||||||
|
description,
|
||||||
|
isEnabled,
|
||||||
|
isRequired,
|
||||||
|
isMovable,
|
||||||
|
canBeBackedUp,
|
||||||
|
backupDescription,
|
||||||
|
status,
|
||||||
|
storageUsage,
|
||||||
|
svgIcon,
|
||||||
|
dnsRecords,
|
||||||
|
url,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServiceStorageUsage {
|
class ServiceStorageUsage extends Equatable {
|
||||||
ServiceStorageUsage({
|
const ServiceStorageUsage({
|
||||||
required this.used,
|
required this.used,
|
||||||
required this.volume,
|
required this.volume,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DiskSize used;
|
final DiskSize used;
|
||||||
final String? volume;
|
final String? volume;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [used, volume];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServiceStatus {
|
enum ServiceStatus {
|
||||||
|
|
|
@ -336,7 +336,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
final volumes = await getVolumes();
|
final volumes = await getVolumes();
|
||||||
final ServerVolume volumeToRemove;
|
final ServerProviderVolume volumeToRemove;
|
||||||
volumeToRemove = volumes.data.firstWhere(
|
volumeToRemove = volumes.data.firstWhere(
|
||||||
(final el) => el.serverId == foundServer!.id,
|
(final el) => el.serverId == foundServer!.id,
|
||||||
);
|
);
|
||||||
|
@ -548,10 +548,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<List<ServerVolume>>> getVolumes({
|
Future<GenericResult<List<ServerProviderVolume>>> getVolumes({
|
||||||
final String? status,
|
final String? status,
|
||||||
}) async {
|
}) async {
|
||||||
final List<ServerVolume> volumes = [];
|
final List<ServerProviderVolume> volumes = [];
|
||||||
|
|
||||||
final result = await _adapter.api().getVolumes();
|
final result = await _adapter.api().getVolumes();
|
||||||
|
|
||||||
|
@ -568,7 +568,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
int id = 0;
|
int id = 0;
|
||||||
for (final rawVolume in result.data) {
|
for (final rawVolume in result.data) {
|
||||||
final String volumeName = rawVolume.name;
|
final String volumeName = rawVolume.name;
|
||||||
final volume = ServerVolume(
|
final volume = ServerProviderVolume(
|
||||||
id: id++,
|
id: id++,
|
||||||
name: volumeName,
|
name: volumeName,
|
||||||
sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024,
|
sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024,
|
||||||
|
@ -597,8 +597,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<ServerVolume?>> createVolume(final int gb) async {
|
Future<GenericResult<ServerProviderVolume?>> createVolume(
|
||||||
ServerVolume? volume;
|
final int gb,
|
||||||
|
) async {
|
||||||
|
ServerProviderVolume? volume;
|
||||||
|
|
||||||
final result = await _adapter.api().createVolume(gb);
|
final result = await _adapter.api().createVolume(gb);
|
||||||
|
|
||||||
|
@ -623,7 +625,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
final String volumeName = result.data!.name;
|
final String volumeName = result.data!.name;
|
||||||
volume = ServerVolume(
|
volume = ServerProviderVolume(
|
||||||
id: getVolumesResult.data.length,
|
id: getVolumesResult.data.length,
|
||||||
name: volumeName,
|
name: volumeName,
|
||||||
sizeByte: result.data!.sizeGigabytes,
|
sizeByte: result.data!.sizeGigabytes,
|
||||||
|
@ -638,10 +640,10 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GenericResult<ServerVolume?>> getVolume(
|
Future<GenericResult<ServerProviderVolume?>> getVolume(
|
||||||
final String volumeUuid,
|
final String volumeUuid,
|
||||||
) async {
|
) async {
|
||||||
ServerVolume? requestedVolume;
|
ServerProviderVolume? requestedVolume;
|
||||||
|
|
||||||
final result = await getVolumes();
|
final result = await getVolumes();
|
||||||
|
|
||||||
|
@ -668,7 +670,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> attachVolume(
|
Future<GenericResult<bool>> attachVolume(
|
||||||
final ServerVolume volume,
|
final ServerProviderVolume volume,
|
||||||
final int serverId,
|
final int serverId,
|
||||||
) async =>
|
) async =>
|
||||||
_adapter.api().attachVolume(
|
_adapter.api().attachVolume(
|
||||||
|
@ -678,7 +680,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> detachVolume(
|
Future<GenericResult<bool>> detachVolume(
|
||||||
final ServerVolume volume,
|
final ServerProviderVolume volume,
|
||||||
) async =>
|
) async =>
|
||||||
_adapter.api().detachVolume(
|
_adapter.api().detachVolume(
|
||||||
volume.name,
|
volume.name,
|
||||||
|
@ -687,7 +689,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> deleteVolume(
|
Future<GenericResult<void>> deleteVolume(
|
||||||
final ServerVolume volume,
|
final ServerProviderVolume volume,
|
||||||
) async =>
|
) async =>
|
||||||
_adapter.api().deleteVolume(
|
_adapter.api().deleteVolume(
|
||||||
volume.uuid!,
|
volume.uuid!,
|
||||||
|
@ -695,7 +697,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> resizeVolume(
|
Future<GenericResult<bool>> resizeVolume(
|
||||||
final ServerVolume volume,
|
final ServerProviderVolume volume,
|
||||||
final DiskSize size,
|
final DiskSize size,
|
||||||
) async =>
|
) async =>
|
||||||
_adapter.api().resizeVolume(
|
_adapter.api().resizeVolume(
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue