Merge pull request 'SelfPrivacy 0.6.0' (#90) from naiji-dev into master
Reviewed-on: https://git.selfprivacy.org/kherel/selfprivacy.org.app/pulls/90
29
.metadata
|
@ -1,10 +1,33 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818
|
||||
channel: stable
|
||||
revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
channel: beta
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
base_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
- platform: linux
|
||||
create_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
base_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
- platform: windows
|
||||
create_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
base_revision: 5293f3cd4427b4b48ed155e7a3852c6b3c53d94a
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
|
|
68
analysis_options.yaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/generated_plugin_registrant.dart
|
||||
- lib/**.g.dart
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
always_use_package_imports: true
|
||||
invariant_booleans: true
|
||||
no_adjacent_strings_in_list: true
|
||||
unnecessary_statements: true
|
||||
always_declare_return_types: true
|
||||
always_put_required_named_parameters_first: true
|
||||
always_put_control_body_on_new_line: true
|
||||
avoid_escaping_inner_quotes: true
|
||||
avoid_setters_without_getters: true
|
||||
eol_at_end_of_file: true
|
||||
prefer_constructors_over_static_methods: true
|
||||
prefer_expression_function_bodies: true
|
||||
prefer_final_in_for_each: true
|
||||
prefer_final_locals: true
|
||||
prefer_final_parameters: true
|
||||
prefer_foreach: true
|
||||
prefer_if_elements_to_conditional_expressions: true
|
||||
prefer_mixin: true
|
||||
prefer_null_aware_method_calls: true
|
||||
require_trailing_commas: true
|
||||
sized_box_shrink_expand: true
|
||||
sort_constructors_first: true
|
||||
unnecessary_await_in_return: true
|
||||
unnecessary_null_checks: true
|
||||
unnecessary_parenthesis: true
|
||||
use_enums: true
|
||||
use_if_null_to_convert_nulls_to_bools: true
|
||||
use_is_even_rather_than_modulo: true
|
||||
use_late_for_private_fields_and_variables: true
|
||||
use_named_constants: true
|
||||
use_setters_to_change_properties: true
|
||||
use_string_buffers: true
|
||||
use_super_parameters: true
|
||||
use_to_and_as_if_applicable: true
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
|
@ -26,12 +26,22 @@ apply plugin: 'kotlin-android'
|
|||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
@ -39,7 +49,7 @@ android {
|
|||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "pro.kherel.selfprivacy"
|
||||
minSdkVersion 18
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
18
android/app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,12 +1,12 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.5.10'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
|
|
3
assets/markdown/how_fallback_old-en.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
In the next window, enter the token obtained from the console of the previous version of the application.
|
||||
|
||||
Enter it without the word *Bearer*.
|
3
assets/markdown/how_fallback_old-ru.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Введите в следующем окне токен, полученный из консоли прошлой версии приложения.
|
||||
|
||||
Вводить нужно без слова *Bearer*.
|
19
assets/markdown/how_fallback_ssh-en.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
19
assets/markdown/how_fallback_ssh-ru.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
Войдите как root пользователь на свой сервер и посмотрите содержимое файла `/etc/nixos/userdata/tokens.json`
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
```
|
||||
|
||||
В этом файле будет схожая конструкция:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "токен_который_надо_скопировать",
|
||||
"name": "имя_устройства",
|
||||
"date": "дата"
|
||||
}
|
||||
```
|
||||
|
||||
Скопируйте токен из файла и вставьте в следующем окне.
|
26
assets/markdown/how_fallback_terminal-en.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
In the Hetzner server control panel, go to the **Rescue** tab. Then, click on **Enable rescue & power cycle**.
|
||||
|
||||
In *Choose a Recue OS* select **linux64**, and in *SSH Key* select your key if it has been added to your Hetzner account.
|
||||
|
||||
Click **Enable rescue & power cycle** and wait for the server to reboot. The login and password will be displayed on the screen. Login to the root user using your login and password information.
|
||||
|
||||
Mount your server file system and see the contents of the token file:
|
||||
|
||||
```sh
|
||||
mount /dev/sda1 /mnt
|
||||
cat /mnt/etc/nixos/userdata/tokens.json
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
26
assets/markdown/how_fallback_terminal-ru.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
В панели управления сервером в Hetzner перейдите во вкладку **Rescue**. Затем, нажмите на кнопку **Enable rescue & power cycle**.
|
||||
|
||||
В поле *Choose a Recue OS* выберите **linux64**, а в *SSH Key* свой ключ, если он был добавлен в ваш аккаунт Hetzner.
|
||||
|
||||
Нажмите **Enable rescue & power cycle** и подождите перезагрузки сервера. На экране будет отображён пароль для входа. Войдите в root пользователя используя данные логин и пароль.
|
||||
|
||||
Примонтируйте файловую систему вашего сервера и посмотрите содержимое файла с токенами:
|
||||
|
||||
```sh
|
||||
mount /dev/sda1 /mnt
|
||||
cat /mnt/etc/nixos/userdata/tokens.json
|
||||
```
|
||||
|
||||
В этом файле будет схожая конструкция:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "токен_который_надо_скопировать",
|
||||
"name": "имя_устройства",
|
||||
"date": "дата"
|
||||
}
|
||||
```
|
||||
|
||||
Скопируйте токен из файла и вставьте в следующем окне.
|
|
@ -21,13 +21,15 @@
|
|||
"saving": "Saving..",
|
||||
"nickname": "Nickname",
|
||||
"loading": "Loading...",
|
||||
"later": "I will setup it later",
|
||||
"later": "Skip to setup later",
|
||||
"connect_to_existing": "Connect to an existing server",
|
||||
"reset": "Reset",
|
||||
"details": "Details",
|
||||
"no_data": "No data",
|
||||
"wait": "Wait",
|
||||
"remove": "Remove",
|
||||
"apply": "Apply"
|
||||
"apply": "Apply",
|
||||
"done": "Done"
|
||||
},
|
||||
"more": {
|
||||
"_comment": "'More' tab",
|
||||
|
@ -35,7 +37,7 @@
|
|||
"about_project": "About us",
|
||||
"about_app": "About application",
|
||||
"onboarding": "Onboarding",
|
||||
"create_ssh_key": "Create ssh key",
|
||||
"create_ssh_key": "Create SSH key",
|
||||
"generate_key": "Generate key",
|
||||
"generate_key_text": "You can generate ssh key",
|
||||
"console": "Console",
|
||||
|
@ -255,7 +257,7 @@
|
|||
"initializing": {
|
||||
"_comment": "initializing page",
|
||||
"1": "Connect a server",
|
||||
"2": "Here, your data and SelfPrivacy services wiil reside",
|
||||
"2": "A place where your data and SelfPrivacy services will reside:",
|
||||
"how": "How to obtain API token",
|
||||
"3": "Connect CloudFlare",
|
||||
"4": "To manage your domain's DNS",
|
||||
|
@ -273,7 +275,6 @@
|
|||
"15": "Server created. DNS checks and server boot in progress...",
|
||||
"16": "Until the next check: ",
|
||||
"17": "Check",
|
||||
"18": "How to obtain Hetzner API Token",
|
||||
"19": "1 Go via this link ",
|
||||
"20": "\n",
|
||||
"21": "One more restart to apply your security certificates.",
|
||||
|
@ -282,6 +283,94 @@
|
|||
"finish": "Everything is initialized",
|
||||
"checks": "Checks have been completed \n{} ouf of {}"
|
||||
},
|
||||
"recovering": {
|
||||
"recovery_main_header": "Connect to an existing server",
|
||||
"domain_recovery_description": "Enter a server domain you want to get access for:",
|
||||
"domain_recover_placeholder": "Your domain",
|
||||
"domain_recover_error": "Server with such domain was not found",
|
||||
"method_select_description": "Select a recovery method:",
|
||||
"method_select_other_device": "I have access on another device",
|
||||
"method_select_recovery_key": "I have a recovery key",
|
||||
"method_select_nothing": "I don't have any of that",
|
||||
"method_device_description": "Open the application on another device, then go to the devices page. Press \"Add device\" to receive your token.",
|
||||
"method_device_button": "I have received my token",
|
||||
"method_device_input_description": "Enter your authorization token",
|
||||
"method_device_input_placeholder": "Token",
|
||||
"method_recovery_input_description": "Enter your recovery key",
|
||||
"fallback_select_description": "What exactly do you have? Pick the first available option:",
|
||||
"fallback_select_token_copy": "Copy of auth token from other version of the application.",
|
||||
"fallback_select_root_ssh": "Root SSH access to the server.",
|
||||
"fallback_select_provider_console": "Access to the server console of my prodiver.",
|
||||
"authorization_failed": "Couldn't log in with this key",
|
||||
"fallback_select_provider_console_hint": "For example: Hetzner.",
|
||||
"hetzner_connected": "Connect to Hetzner",
|
||||
"hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:",
|
||||
"hetzner_connected_placeholder": "Hetzner token",
|
||||
"confirm_server": "Confirm server",
|
||||
"confirm_server_description": "Found your server! Confirm it is correct.",
|
||||
"confirm_server_accept": "Yes! That's it",
|
||||
"confirm_server_decline": "Choose a different server",
|
||||
"choose_server": "Choose your server",
|
||||
"choose_server_description": "We couldn't figure out which server your are trying to connect to.",
|
||||
"no_servers": "There is no available servers on your account.",
|
||||
"domain_not_available_on_token": "Selected domain is not available on this token.",
|
||||
"modal_confirmation_title": "Is it really your server?",
|
||||
"modal_confirmation_description": "If you connect to a wrong server you may lose all your data.",
|
||||
"modal_confirmation_dns_valid": "Reverse DNS is valid",
|
||||
"modal_confirmation_dns_invalid": "Reverse DNS points to another domain",
|
||||
"modal_confirmation_ip_valid": "IP is the same as in DNS record",
|
||||
"modal_confirmation_ip_invalid": "IP is not the same as in DNS record",
|
||||
"confirm_cloudflare": "Connect to CloudFlare",
|
||||
"confirm_cloudflare_description": "Enter a Cloudflare token with access to {}:",
|
||||
"confirm_backblaze": "Connect to Backblaze",
|
||||
"confirm_backblaze_description": "Enter a Backblaze token with access to backup storage:"
|
||||
},
|
||||
"devices": {
|
||||
"main_screen": {
|
||||
"header": "Devices",
|
||||
"description": "These devices have full access to the server via SelfPrivacy app.",
|
||||
"this_device": "This device",
|
||||
"other_devices": "Other devices",
|
||||
"authorize_new_device": "Authorize new device",
|
||||
"access_granted_on" : "Access granted on {}",
|
||||
"tip": "Press on the device to revoke access."
|
||||
},
|
||||
"add_new_device_screen": {
|
||||
"header": "Authorizing new device",
|
||||
"description": "Enter the key on the device you want to authorize:",
|
||||
"please_wait": "Please wait",
|
||||
"tip": "The key is valid for 10 minutes.",
|
||||
"expired": "The key has expired.",
|
||||
"get_new_key": "Get new key"
|
||||
},
|
||||
"revoke_device_alert": {
|
||||
"header": "Revoke access?",
|
||||
"description": "The device {} will no longer have access to the server.",
|
||||
"yes": "Revoke",
|
||||
"no": "Cancel"
|
||||
}
|
||||
},
|
||||
"recovery_key": {
|
||||
"key_connection_error": "Couldn't connect to the server.",
|
||||
"key_synchronizing": "Synchronizing...",
|
||||
"key_main_header": "Recovery key",
|
||||
"key_main_description": "Is needed for SelfPrivacy authorization when all your other authorized devices aren't available.",
|
||||
"key_amount_toggle": "Limit by number of uses",
|
||||
"key_amount_field_title": "Max number of uses",
|
||||
"key_duedate_toggle": "Limit by time",
|
||||
"key_duedate_field_title": "Due date of expiration",
|
||||
"key_receive_button": "Receive key",
|
||||
"key_valid": "Your key is valid",
|
||||
"key_invalid": "Your key is no longer valid",
|
||||
"key_valid_until": "Valid until {}",
|
||||
"key_valid_for": "Valid for {} uses",
|
||||
"key_creation_date": "Created on {}",
|
||||
"key_replace_button": "Generate new key",
|
||||
"key_receiving_description": "Write down this key and put to a safe place. It is used to restore full access to your server:",
|
||||
"key_receiving_info": "The key will never ever be shown again, but you will be able to replace it with another one.",
|
||||
"key_receiving_done": "Done!",
|
||||
"generation_error": "Couldn't generate a recovery key. {}"
|
||||
},
|
||||
"modals": {
|
||||
"_comment": "messages in modals",
|
||||
"1": "Server with such name, already exist",
|
||||
|
@ -293,7 +382,9 @@
|
|||
"7": "Yes",
|
||||
"8": "Remove task",
|
||||
"9": "Reboot",
|
||||
"yes": "Yes"
|
||||
"10": "You cannot use this API for domains with such TLD.",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"timer": {
|
||||
"sec": "{} sec"
|
||||
|
|
|
@ -21,13 +21,15 @@
|
|||
"saving": "Сохранение…",
|
||||
"nickname": "Никнейм",
|
||||
"loading": "Загрузка",
|
||||
"later": "Настрою потом",
|
||||
"later": "Пропустить и настроить потом",
|
||||
"connect_to_existing": "Подключиться к существующему серверу",
|
||||
"reset": "Сбросить",
|
||||
"details": "Детальная информация",
|
||||
"no_data": "Нет данных",
|
||||
"wait": "Загрузка",
|
||||
"remove": "Удалить",
|
||||
"apply": "Подать"
|
||||
"apply": "Подать",
|
||||
"done": "Готово"
|
||||
},
|
||||
"more": {
|
||||
"_comment": "вкладка ещё",
|
||||
|
@ -283,6 +285,90 @@
|
|||
"finish": "Всё инициализировано.",
|
||||
"checks": "Проверок выполнено: \n{} / {}"
|
||||
},
|
||||
"recovering": {
|
||||
"recovery_main_header": "Подключиться к существующему серверу",
|
||||
"domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:",
|
||||
"domain_recover_placeholder": "Домен",
|
||||
"domain_recover_error": "Не удалось найти сервер с таким доменом",
|
||||
"method_select_description": "Выберите способ входа:",
|
||||
"method_select_other_device": "У меня есть доступ на другом устройстве",
|
||||
"method_select_recovery_key": "У меня есть ключ восстановления",
|
||||
"method_select_nothing": "У меня ничего из этого нет",
|
||||
"method_device_description": "Откройте приложение на другом устройстве и откройте экран управления устройствами. Нажмите \"Добавить устройство\" чтобы получить токен для авторизации.",
|
||||
"method_device_button": "Я получил токен",
|
||||
"method_device_input_description": "Введите ваш токен авторизации",
|
||||
"method_device_input_placeholder": "Токен",
|
||||
"method_recovery_input_description": "Введите ваш токен восстановления",
|
||||
"fallback_select_description": "Что у вас из этого есть? Выберите первое, что подходит:",
|
||||
"fallback_select_token_copy": "Копия токена авторизации из другой версии приложения.",
|
||||
"fallback_select_root_ssh": "Root доступ к серверу по SSH.",
|
||||
"fallback_select_provider_console": "Доступ к консоли хостинга.",
|
||||
"authorization_failed": "Не удалось войти с этим ключом",
|
||||
"fallback_select_provider_console_hint": "Например, Hetzner.",
|
||||
"hetzner_connected": "Подключение к Hetzner",
|
||||
"hetzner_connected_description": "Связь с сервером установлена. Введите токен Hetzner с доступом к {}:",
|
||||
"hetzner_connected_placeholder": "Hetzner токен",
|
||||
"confirm_server": "Подтвердите сервер",
|
||||
"confirm_server_description": "Нашли сервер! Подтвердите, что это он:",
|
||||
"confirm_server_accept": "Да, это он",
|
||||
"confirm_server_decline": "Выбрать другой сервер",
|
||||
"choose_server": "Выберите сервер",
|
||||
"choose_server_description": "Не удалось определить, с каким сервером вы устанавливаете связь.",
|
||||
"no_servers": "На вашем аккаунте нет доступных серверов.",
|
||||
"domain_not_available_on_token": "Введённый токен не имеет доступа к нужному домену.",
|
||||
"modal_confirmation_title": "Это действительно ваш сервер?",
|
||||
"modal_confirmation_description": "Подключение к неправильному серверу может привести к деструктивным последствиям.",
|
||||
"confirm_cloudflare": "Подключение к Cloudflare",
|
||||
"confirm_cloudflare_description": "Введите токен Cloudflare, который имеет права на {}:",
|
||||
"confirm_backblze": "Подключение к Backblaze",
|
||||
"confirm_backblaze_description": "Введите токен Backblaze, который имеет права на хранилище резервных копий:"
|
||||
},
|
||||
"devices": {
|
||||
"main_screen": {
|
||||
"header": "Устройства",
|
||||
"description": "Эти устройства имеют полный доступ к управлению сервером через приложение SelfPrivacy.",
|
||||
"this_device": "Это устройство",
|
||||
"other_devices": "Другие устройства",
|
||||
"authorize_new_device": "Авторизовать новое устройство",
|
||||
"access_granted_on" : "Доступ выдан {}",
|
||||
"tip": "Нажмите на устройство, чтобы отозвать доступ."
|
||||
},
|
||||
"add_new_device_screen": {
|
||||
"header": "Авторизация нового устройства",
|
||||
"description": "Введите этот ключ на новом устройстве:",
|
||||
"please_wait": "Пожалуйста, подождите",
|
||||
"tip": "Ключ действителен 10 минут.",
|
||||
"expired": "Срок действия ключа истёк.",
|
||||
"get_new_key": "Получить новый ключ"
|
||||
},
|
||||
"revoke_device_alert": {
|
||||
"header": "Отозвать доступ?",
|
||||
"description": "Устройство {} больше не сможет управлять сервером.",
|
||||
"yes": "Отозвать",
|
||||
"no": "Отмена"
|
||||
}
|
||||
},
|
||||
"recovery_key": {
|
||||
"key_connection_error": "Не удалось соединиться с сервером",
|
||||
"key_synchronizing": "Синхронизация...",
|
||||
"key_main_header": "Ключ восстановления",
|
||||
"key_main_description": "Требуется для авторизации SelfPrivacy, когда авторизованные устройства недоступны.",
|
||||
"key_amount_toggle": "Ограничить использования",
|
||||
"key_amount_field_title": "Макс. кол-во использований",
|
||||
"key_duedate_toggle": "Ограничить срок использования",
|
||||
"key_duedate_field_title": "Дата окончания срока",
|
||||
"key_receive_button": "Получить ключ",
|
||||
"key_valid": "Ваш ключ действителен",
|
||||
"key_invalid": "Ваш ключ больше не действителен",
|
||||
"key_valid_until": "Действителен до {}",
|
||||
"key_valid_for": "Можно использовать ещё {} раз",
|
||||
"key_creation_date": "Создан {}",
|
||||
"key_replace_button": "Сгенерировать новый ключ",
|
||||
"key_receiving_description": "Запишите этот ключ в безопасном месте. Он предоставляет полный доступ к вашему серверу:",
|
||||
"key_receiving_info": "Этот ключ больше не будет показан, но вы сможете заменить его новым.",
|
||||
"key_receiving_done": "Готово!",
|
||||
"generation_error": "Не удалось сгенерировать ключ. {}"
|
||||
},
|
||||
"modals": {
|
||||
"_comment": "messages in modals",
|
||||
"1": "Сервер с таким именем уже существует",
|
||||
|
@ -294,7 +380,9 @@
|
|||
"7": "Да, удалить",
|
||||
"8": "Удалить задачу",
|
||||
"9": "Перезагрузить",
|
||||
"yes": "Да"
|
||||
"10": "API не поддерживает домены с таким TLD.",
|
||||
"yes": "Да",
|
||||
"no": "Нет"
|
||||
},
|
||||
"timer": {
|
||||
"sec": "{} сек"
|
||||
|
|
6
fastlane/metadata/android/en-US/changelogs/0.6.0.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
- Added support for multi-device server access from SelfPrivacy app.
|
||||
- You can now create recovery token to regain the access to the server if you lose your device or the app's data.
|
||||
- You can now connect to an existing server, instead of creating a new one.
|
||||
- Initial support for Material Design 3 (Material You).
|
||||
- App now uses your system colors on Android 12 (Material You), Windows 10 (accent color) and Linux (GTK colors).
|
||||
- Minor bug fixes.
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 43 KiB |
|
@ -1,6 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_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/dns_records/dns_records_cubit.dart';
|
||||
|
@ -10,34 +12,56 @@ import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
|||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||
|
||||
class BlocAndProviderConfig extends StatelessWidget {
|
||||
const BlocAndProviderConfig({Key? key, this.child}) : super(key: key);
|
||||
const BlocAndProviderConfig({final super.key, this.child});
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isDark = false;
|
||||
var appConfigCubit = AppConfigCubit()..load();
|
||||
var usersCubit = UsersCubit(appConfigCubit);
|
||||
var servicesCubit = ServicesCubit(appConfigCubit);
|
||||
var backupsCubit = BackupsCubit(appConfigCubit);
|
||||
var dnsRecordsCubit = DnsRecordsCubit(appConfigCubit);
|
||||
Widget build(final BuildContext context) {
|
||||
const isDark = false;
|
||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||
final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit);
|
||||
final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit);
|
||||
final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit);
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => AppSettingsCubit(
|
||||
create: (final _) => AppSettingsCubit(
|
||||
isDarkModeOn: isDark,
|
||||
isOnbordingShowing: true,
|
||||
isOnboardingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(create: (_) => appConfigCubit, lazy: false),
|
||||
BlocProvider(create: (_) => ProvidersCubit()),
|
||||
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => backupsCubit..load(), lazy: false),
|
||||
BlocProvider(create: (_) => dnsRecordsCubit..load()),
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(create: (final _) => ProvidersCubit()),
|
||||
BlocProvider(
|
||||
create: (final _) => usersCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsCubit..load(),
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => apiDevicesCubit..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) =>
|
||||
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/ui/components/error/error.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
import './get_it_config.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
|
||||
class SimpleBlocObserver extends BlocObserver {
|
||||
SimpleBlocObserver();
|
||||
|
||||
@override
|
||||
void onError(BlocBase cubit, Object error, StackTrace stackTrace) {
|
||||
final navigator = getIt.get<NavigationService>().navigator!;
|
||||
void onError(
|
||||
final BlocBase<dynamic> bloc,
|
||||
final Object error,
|
||||
final StackTrace stackTrace,
|
||||
) {
|
||||
final NavigatorState navigator = getIt.get<NavigationService>().navigator!;
|
||||
|
||||
navigator.push(
|
||||
materialRoute(
|
||||
|
@ -19,6 +24,6 @@ class SimpleBlocObserver extends BlocObserver {
|
|||
),
|
||||
),
|
||||
);
|
||||
super.onError(cubit, error, stackTrace);
|
||||
super.onError(bloc, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class BrandColors {
|
|||
static const Color gray3 = Color(0xFFFAFAFA);
|
||||
static const Color gray4 = Color(0xFFDDDDDD);
|
||||
static const Color gray5 = Color(0xFFEDEEF1);
|
||||
static Color gray6 = Color(0xFF181818).withOpacity(0.7);
|
||||
static Color gray6 = const Color(0xFF181818).withOpacity(0.7);
|
||||
static const Color grey7 = Color(0xFFABABAB);
|
||||
|
||||
static const Color red1 = Color(0xFFFA0E0E);
|
||||
|
@ -20,8 +20,8 @@ class BrandColors {
|
|||
|
||||
static const Color green2 = Color(0xFF0F8849);
|
||||
|
||||
static get navBackgroundLight => white.withOpacity(0.8);
|
||||
static get navBackgroundDark => black.withOpacity(0.8);
|
||||
static Color get navBackgroundLight => white.withOpacity(0.8);
|
||||
static Color get navBackgroundDark => black.withOpacity(0.8);
|
||||
|
||||
static const List<Color> uninitializedGradientColors = [
|
||||
Color(0xFF555555),
|
||||
|
@ -41,14 +41,14 @@ class BrandColors {
|
|||
Color(0xFFEFD135),
|
||||
];
|
||||
|
||||
static const primary = blue;
|
||||
static const headlineColor = black;
|
||||
static const inactive = gray2;
|
||||
static const scaffoldBackground = gray3;
|
||||
static const inputInactive = gray4;
|
||||
static const Color primary = blue;
|
||||
static const Color headlineColor = black;
|
||||
static const Color inactive = gray2;
|
||||
static const Color scaffoldBackground = gray3;
|
||||
static const Color inputInactive = gray4;
|
||||
|
||||
static const textColor1 = black;
|
||||
static const textColor2 = gray1;
|
||||
static const dividerColor = gray5;
|
||||
static const warning = red1;
|
||||
static const Color textColor1 = black;
|
||||
static const Color textColor2 = gray1;
|
||||
static const Color dividerColor = gray5;
|
||||
static const Color warning = red1;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/text_themes.dart';
|
||||
|
||||
import 'brand_colors.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
|
||||
final lightTheme = ThemeData(
|
||||
final ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
primaryColor: BrandColors.primary,
|
||||
fontFamily: 'Inter',
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: BrandColors.scaffoldBackground,
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
|
@ -38,7 +39,7 @@ final lightTheme = ThemeData(
|
|||
color: BrandColors.red1,
|
||||
),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
listTileTheme: const ListTileThemeData(
|
||||
minLeadingWidth: 24.0,
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
|
@ -47,25 +48,25 @@ final lightTheme = ThemeData(
|
|||
headline3: headline3Style,
|
||||
headline4: headline4Style,
|
||||
bodyText1: body1Style,
|
||||
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||
subtitle1: const TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||
),
|
||||
);
|
||||
|
||||
var darkTheme = lightTheme.copyWith(
|
||||
ThemeData darkTheme = lightTheme.copyWith(
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: Color(0xFF202120),
|
||||
iconTheme: IconThemeData(color: BrandColors.gray3),
|
||||
scaffoldBackgroundColor: const Color(0xFF202120),
|
||||
iconTheme: const IconThemeData(color: BrandColors.gray3),
|
||||
cardColor: BrandColors.gray1,
|
||||
dialogBackgroundColor: Color(0xFF202120),
|
||||
dialogBackgroundColor: const Color(0xFF202120),
|
||||
textTheme: TextTheme(
|
||||
headline1: headline1Style.copyWith(color: BrandColors.white),
|
||||
headline2: headline2Style.copyWith(color: BrandColors.white),
|
||||
headline3: headline3Style.copyWith(color: BrandColors.white),
|
||||
headline4: headline4Style.copyWith(color: BrandColors.white),
|
||||
bodyText1: body1Style.copyWith(color: BrandColors.white),
|
||||
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||
subtitle1: const TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
labelStyle: TextStyle(color: BrandColors.white),
|
||||
hintStyle: TextStyle(color: BrandColors.white),
|
||||
border: OutlineInputBorder(
|
||||
|
@ -81,6 +82,7 @@ var darkTheme = lightTheme.copyWith(
|
|||
),
|
||||
);
|
||||
|
||||
final paddingH15V30 = EdgeInsets.symmetric(horizontal: 15, vertical: 30);
|
||||
const EdgeInsets paddingH15V30 =
|
||||
EdgeInsets.symmetric(horizontal: 15, vertical: 30);
|
||||
|
||||
final paddingH15V0 = EdgeInsets.symmetric(horizontal: 15);
|
||||
const EdgeInsets paddingH15V0 = EdgeInsets.symmetric(horizontal: 15);
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:get_it/get_it.dart';
|
|||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||
import 'package:selfprivacy/logic/get_it/timer.dart';
|
||||
|
||||
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||
|
@ -10,14 +9,13 @@ export 'package:selfprivacy/logic/get_it/console.dart';
|
|||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||
export 'package:selfprivacy/logic/get_it/timer.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
Future<void> getItSetup() async {
|
||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||
|
||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
||||
getIt.registerSingleton<SSHModel>(SSHModel()..init());
|
||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||
|
||||
await getIt.allReady();
|
||||
|
|
|
@ -3,72 +3,119 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.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';
|
||||
|
||||
class HiveConfig {
|
||||
static Future<void> init() async {
|
||||
await Hive.initFlutter();
|
||||
Hive.registerAdapter(UserAdapter());
|
||||
Hive.registerAdapter(HetznerServerDetailsAdapter());
|
||||
Hive.registerAdapter(CloudFlareDomainAdapter());
|
||||
Hive.registerAdapter(ServerHostingDetailsAdapter());
|
||||
Hive.registerAdapter(ServerDomainAdapter());
|
||||
Hive.registerAdapter(BackblazeCredentialAdapter());
|
||||
Hive.registerAdapter(BackblazeBucketAdapter());
|
||||
Hive.registerAdapter(HetznerDataBaseAdapter());
|
||||
Hive.registerAdapter(ServerVolumeAdapter());
|
||||
|
||||
await Hive.openBox(BNames.appSettings);
|
||||
await Hive.openBox<User>(BNames.users);
|
||||
await Hive.openBox(BNames.servicesState);
|
||||
Hive.registerAdapter(DnsProviderAdapter());
|
||||
Hive.registerAdapter(ServerProviderAdapter());
|
||||
|
||||
var cipher = HiveAesCipher(await getEncryptedKey(BNames.key));
|
||||
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.appSettingsBox);
|
||||
|
||||
var sshCipher = HiveAesCipher(await getEncryptedKey(BNames.sshEnckey));
|
||||
await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher);
|
||||
final HiveAesCipher cipher = HiveAesCipher(
|
||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
||||
);
|
||||
|
||||
await Hive.openBox<User>(BNames.usersDeprecated);
|
||||
await Hive.openBox<User>(BNames.usersBox, encryptionCipher: cipher);
|
||||
|
||||
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated);
|
||||
if (deprecatedUsers.isNotEmpty) {
|
||||
final Box<User> users = Hive.box<User>(BNames.usersBox);
|
||||
users.addAll(deprecatedUsers.values.toList());
|
||||
deprecatedUsers.clear();
|
||||
}
|
||||
|
||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||
}
|
||||
|
||||
static Future<Uint8List> getEncryptedKey(String encKey) async {
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
var hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
||||
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||
if (!hasEncryptionKey) {
|
||||
var key = Hive.generateSecureKey();
|
||||
final List<int> key = Hive.generateSecureKey();
|
||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||
}
|
||||
|
||||
String? string = await secureStorage.read(key: encKey);
|
||||
final String? string = await secureStorage.read(key: encKey);
|
||||
return base64Url.decode(string!);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mappings for the different boxes and their keys
|
||||
class BNames {
|
||||
static String appConfig = 'appConfig';
|
||||
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
|
||||
static String appSettingsBox = 'appSettings';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isDarkModeOn = 'isDarkModeOn';
|
||||
static String isOnbordingShowing = 'isOnbordingShowing';
|
||||
static String users = 'users';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isOnboardingShowing = 'isOnboardingShowing';
|
||||
|
||||
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
|
||||
static String serverInstallationEncryptionKey = 'key';
|
||||
|
||||
/// Server installation box. Contains server details and provider tokens.
|
||||
static String serverInstallationBox = 'appConfig';
|
||||
|
||||
/// A List<String> field of [serverInstallationBox] box.
|
||||
static String rootKeys = 'rootKeys';
|
||||
|
||||
static String appSettings = 'appSettings';
|
||||
static String servicesState = 'servicesState';
|
||||
|
||||
static String key = 'key';
|
||||
static String sshEnckey = 'sshEngkey';
|
||||
|
||||
static String cloudFlareDomain = 'cloudFlareDomain';
|
||||
static String hetznerKey = 'hetznerKey';
|
||||
static String cloudFlareKey = 'cloudFlareKey';
|
||||
static String rootUser = 'rootUser';
|
||||
static String hetznerServer = 'hetznerServer';
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String hasFinalChecked = 'hasFinalChecked';
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isServerStarted = 'isServerStarted';
|
||||
static String backblazeKey = 'backblazeKey';
|
||||
|
||||
/// A [ServerDomain] field of [serverInstallationBox] box.
|
||||
static String serverDomain = 'cloudFlareDomain';
|
||||
|
||||
/// A String field of [serverInstallationBox] box.
|
||||
static String hetznerKey = 'hetznerKey';
|
||||
|
||||
/// A String field of [serverInstallationBox] box.
|
||||
static String cloudFlareKey = 'cloudFlareKey';
|
||||
|
||||
/// A [User] field of [serverInstallationBox] box.
|
||||
static String rootUser = 'rootUser';
|
||||
|
||||
/// A [ServerHostingDetails] field of [serverInstallationBox] box.
|
||||
static String serverDetails = 'hetznerServer';
|
||||
|
||||
/// A [BackblazeCredential] field of [serverInstallationBox] box.
|
||||
static String backblazeCredential = 'backblazeKey';
|
||||
|
||||
/// A [BackblazeBucket] field of [serverInstallationBox] box.
|
||||
static String backblazeBucket = 'backblazeBucket';
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isLoading = 'isLoading';
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isServerResetedFirstTime = 'isServerResetedFirstTime';
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isServerResetedSecondTime = 'isServerResetedSecondTime';
|
||||
static String sshConfig = 'sshConfig';
|
||||
static String sshPrivateKey = "sshPrivateKey";
|
||||
static String sshPublicKey = "sshPublicKey";
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isRecoveringServer = 'isRecoveringServer';
|
||||
|
||||
/// Deprecated users box as it is unencrypted
|
||||
static String usersDeprecated = 'users';
|
||||
|
||||
/// Box with users
|
||||
static String usersBox = 'usersEncrypted';
|
||||
}
|
||||
|
|
|
@ -3,20 +3,18 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class Localization extends StatelessWidget {
|
||||
const Localization({
|
||||
Key? key,
|
||||
final super.key,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final Widget? child;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EasyLocalization(
|
||||
supportedLocales: [Locale('ru'), Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: Locale('en'),
|
||||
saveLocale: false,
|
||||
useOnlyLangCode: true,
|
||||
child: child!,
|
||||
);
|
||||
}
|
||||
Widget build(final BuildContext context) => EasyLocalization(
|
||||
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
saveLocale: false,
|
||||
useOnlyLangCode: true,
|
||||
child: child!,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,80 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||
|
||||
import 'brand_colors.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
|
||||
final defaultTextStyle = TextStyle(
|
||||
const TextStyle defaultTextStyle = TextStyle(
|
||||
fontSize: 15,
|
||||
color: BrandColors.textColor1,
|
||||
);
|
||||
|
||||
final headline1Style = defaultTextStyle.copyWith(
|
||||
final TextStyle headline1Style = defaultTextStyle.copyWith(
|
||||
fontSize: 40,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final headline2Style = defaultTextStyle.copyWith(
|
||||
final TextStyle headline2Style = defaultTextStyle.copyWith(
|
||||
fontSize: 24,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final onboardingTitle = defaultTextStyle.copyWith(
|
||||
final TextStyle onboardingTitle = defaultTextStyle.copyWith(
|
||||
fontSize: 30,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final headline3Style = defaultTextStyle.copyWith(
|
||||
final TextStyle headline3Style = defaultTextStyle.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final headline4Style = defaultTextStyle.copyWith(
|
||||
final TextStyle headline4Style = defaultTextStyle.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: NamedFontWeight.medium,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final headline4UnderlinedStyle = defaultTextStyle.copyWith(
|
||||
final TextStyle headline4UnderlinedStyle = defaultTextStyle.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: NamedFontWeight.medium,
|
||||
color: BrandColors.headlineColor,
|
||||
decoration: TextDecoration.underline,
|
||||
);
|
||||
|
||||
final headline5Style = defaultTextStyle.copyWith(
|
||||
final TextStyle headline5Style = defaultTextStyle.copyWith(
|
||||
fontSize: 15,
|
||||
fontWeight: NamedFontWeight.medium,
|
||||
color: BrandColors.headlineColor.withOpacity(0.8),
|
||||
);
|
||||
|
||||
final body1Style = defaultTextStyle;
|
||||
final body2Style = defaultTextStyle.copyWith(
|
||||
const TextStyle body1Style = defaultTextStyle;
|
||||
final TextStyle body2Style = defaultTextStyle.copyWith(
|
||||
color: BrandColors.textColor2,
|
||||
);
|
||||
|
||||
final buttonTitleText = defaultTextStyle.copyWith(
|
||||
final TextStyle buttonTitleText = defaultTextStyle.copyWith(
|
||||
color: BrandColors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1,
|
||||
);
|
||||
|
||||
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
|
||||
final TextStyle mediumStyle =
|
||||
defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
|
||||
|
||||
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
||||
final TextStyle smallStyle =
|
||||
defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
||||
|
||||
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);
|
||||
|
||||
final progressTextStyleLight = TextStyle(
|
||||
const TextStyle progressTextStyleLight = TextStyle(
|
||||
fontSize: 11,
|
||||
color: BrandColors.textColor1,
|
||||
height: 1.7,
|
||||
);
|
||||
|
||||
final progressTextStyleDark = progressTextStyleLight.copyWith(
|
||||
final TextStyle progressTextStyleDark = progressTextStyleLight.copyWith(
|
||||
color: BrandColors.white,
|
||||
);
|
||||
|
|
|
@ -10,27 +10,32 @@ import 'package:selfprivacy/logic/models/message.dart';
|
|||
|
||||
abstract class ApiMap {
|
||||
Future<Dio> getClient() async {
|
||||
var dio = Dio(await options);
|
||||
final Dio dio = Dio(await options);
|
||||
if (hasLogger) {
|
||||
dio.interceptors.add(PrettyDioLogger());
|
||||
}
|
||||
dio.interceptors.add(ConsoleInterceptor());
|
||||
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||
(HttpClient client) {
|
||||
(final HttpClient client) {
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
(final X509Certificate cert, final String host, final int port) =>
|
||||
true;
|
||||
return client;
|
||||
};
|
||||
|
||||
dio.interceptors.add(InterceptorsWrapper(onError: (DioError e, handler) {
|
||||
print(e.requestOptions.path);
|
||||
print(e.requestOptions.data);
|
||||
dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onError: (final DioError e, final ErrorInterceptorHandler handler) {
|
||||
print(e.requestOptions.path);
|
||||
print(e.requestOptions.data);
|
||||
|
||||
print(e.message);
|
||||
print(e.response);
|
||||
print(e.message);
|
||||
print(e.response);
|
||||
|
||||
return handler.next(e);
|
||||
}));
|
||||
return handler.next(e);
|
||||
},
|
||||
),
|
||||
);
|
||||
return dio;
|
||||
}
|
||||
|
||||
|
@ -42,21 +47,21 @@ abstract class ApiMap {
|
|||
|
||||
ValidateStatus? validateStatus;
|
||||
|
||||
void close(Dio client) {
|
||||
void close(final Dio client) {
|
||||
client.close();
|
||||
validateStatus = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsoleInterceptor extends InterceptorsWrapper {
|
||||
void addMessage(Message message) {
|
||||
void addMessage(final Message message) {
|
||||
getIt.get<ConsoleModel>().addMessage(message);
|
||||
}
|
||||
|
||||
@override
|
||||
Future onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler requestInterceptorHandler,
|
||||
Future<void> onRequest(
|
||||
final RequestOptions options,
|
||||
final RequestInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
Message(
|
||||
|
@ -64,13 +69,13 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}',
|
||||
),
|
||||
);
|
||||
return super.onRequest(options, requestInterceptorHandler);
|
||||
return super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
Future onResponse(
|
||||
Response response,
|
||||
ResponseInterceptorHandler requestInterceptorHandler,
|
||||
Future<void> onResponse(
|
||||
final Response response,
|
||||
final ResponseInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
Message(
|
||||
|
@ -80,13 +85,16 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
);
|
||||
return super.onResponse(
|
||||
response,
|
||||
requestInterceptorHandler,
|
||||
handler,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future onError(DioError err, ErrorInterceptorHandler handler) async {
|
||||
var response = err.response;
|
||||
Future<void> onError(
|
||||
final DioError err,
|
||||
final ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
final Response? response = err.response;
|
||||
log(err.toString());
|
||||
addMessage(
|
||||
Message.warn(
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:io';
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
||||
|
||||
class BackblazeApiAuth {
|
||||
BackblazeApiAuth({required this.authorizationToken, required this.apiUrl});
|
||||
|
@ -13,8 +13,10 @@ class BackblazeApiAuth {
|
|||
}
|
||||
|
||||
class BackblazeApplicationKey {
|
||||
BackblazeApplicationKey(
|
||||
{required this.applicationKeyId, required this.applicationKey});
|
||||
BackblazeApplicationKey({
|
||||
required this.applicationKeyId,
|
||||
required this.applicationKey,
|
||||
});
|
||||
|
||||
final String applicationKeyId;
|
||||
final String applicationKey;
|
||||
|
@ -23,11 +25,13 @@ class BackblazeApplicationKey {
|
|||
class BackblazeApi extends ApiMap {
|
||||
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
|
||||
|
||||
@override
|
||||
BaseOptions get options {
|
||||
var options = BaseOptions(baseUrl: rootAddress);
|
||||
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||
if (isWithToken) {
|
||||
var backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
var token = backblazeCredential!.applicationKey;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final String token = backblazeCredential!.applicationKey;
|
||||
options.headers = {'Authorization': 'Basic $token'};
|
||||
}
|
||||
|
||||
|
@ -44,14 +48,17 @@ class BackblazeApi extends ApiMap {
|
|||
String apiPrefix = '/b2api/v2';
|
||||
|
||||
Future<BackblazeApiAuth> getAuthorizationToken() async {
|
||||
var client = await getClient();
|
||||
var backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
final Dio client = await getClient();
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
if (backblazeCredential == null) {
|
||||
throw Exception('Backblaze credential is null');
|
||||
}
|
||||
final String encodedApiKey = encodedBackblazeKey(
|
||||
backblazeCredential.keyId, backblazeCredential.applicationKey);
|
||||
var response = await client.get(
|
||||
backblazeCredential.keyId,
|
||||
backblazeCredential.applicationKey,
|
||||
);
|
||||
final Response response = await client.get(
|
||||
'b2_authorize_account',
|
||||
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
|
||||
);
|
||||
|
@ -64,32 +71,38 @@ class BackblazeApi extends ApiMap {
|
|||
);
|
||||
}
|
||||
|
||||
Future<bool> isValid(String encodedApiKey) async {
|
||||
var client = await getClient();
|
||||
Response response = await client.get(
|
||||
'b2_authorize_account',
|
||||
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
|
||||
);
|
||||
close(client);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
if (response.data['allowed']['capabilities'].contains('listBuckets')) {
|
||||
return true;
|
||||
Future<bool> isValid(final String encodedApiKey) async {
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
final Response response = await client.get(
|
||||
'b2_authorize_account',
|
||||
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
if (response.data['allowed']['capabilities'].contains('listBuckets')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||
return false;
|
||||
} else {
|
||||
throw Exception('code: ${response.statusCode}');
|
||||
}
|
||||
} on DioError {
|
||||
return false;
|
||||
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||
return false;
|
||||
} else {
|
||||
throw Exception('code: ${response.statusCode}');
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
}
|
||||
|
||||
// Create bucket
|
||||
Future<String> createBucket(String bucketName) async {
|
||||
final auth = await getAuthorizationToken();
|
||||
var backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
|
||||
var client = await getClient();
|
||||
Future<String> createBucket(final String bucketName) async {
|
||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final Dio client = await getClient();
|
||||
client.options.baseUrl = auth.apiUrl;
|
||||
var response = await client.post(
|
||||
final Response response = await client.post(
|
||||
'$apiPrefix/b2_create_bucket',
|
||||
data: {
|
||||
'accountId': backblazeCredential!.keyId,
|
||||
|
@ -97,9 +110,9 @@ class BackblazeApi extends ApiMap {
|
|||
'bucketType': 'allPrivate',
|
||||
'lifecycleRules': [
|
||||
{
|
||||
"daysFromHidingToDeleting": 30,
|
||||
"daysFromUploadingToHiding": null,
|
||||
"fileNamePrefix": ""
|
||||
'daysFromHidingToDeleting': 30,
|
||||
'daysFromUploadingToHiding': null,
|
||||
'fileNamePrefix': ''
|
||||
}
|
||||
],
|
||||
},
|
||||
|
@ -116,11 +129,11 @@ class BackblazeApi extends ApiMap {
|
|||
}
|
||||
|
||||
// Create a limited capability key with access to the given bucket
|
||||
Future<BackblazeApplicationKey> createKey(String bucketId) async {
|
||||
final auth = await getAuthorizationToken();
|
||||
var client = await getClient();
|
||||
Future<BackblazeApplicationKey> createKey(final String bucketId) async {
|
||||
final BackblazeApiAuth auth = await getAuthorizationToken();
|
||||
final Dio client = await getClient();
|
||||
client.options.baseUrl = auth.apiUrl;
|
||||
var response = await client.post(
|
||||
final Response response = await client.post(
|
||||
'$apiPrefix/b2_create_key',
|
||||
data: {
|
||||
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId,
|
||||
|
@ -135,8 +148,9 @@ class BackblazeApi extends ApiMap {
|
|||
close(client);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
return BackblazeApplicationKey(
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey']);
|
||||
applicationKeyId: response.data['applicationKeyId'],
|
||||
applicationKey: response.data['applicationKey'],
|
||||
);
|
||||
} else {
|
||||
throw Exception('code: ${response.statusCode}');
|
||||
}
|
||||
|
|
|
@ -3,20 +3,40 @@ import 'dart:io';
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/dns_records.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||
|
||||
class DomainNotFoundException implements Exception {
|
||||
DomainNotFoundException(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
||||
class CloudflareApi extends ApiMap {
|
||||
CloudflareApi({this.hasLogger = false, this.isWithToken = true});
|
||||
CloudflareApi({
|
||||
this.hasLogger = false,
|
||||
this.isWithToken = true,
|
||||
this.customToken,
|
||||
});
|
||||
@override
|
||||
final bool hasLogger;
|
||||
@override
|
||||
final bool isWithToken;
|
||||
|
||||
final String? customToken;
|
||||
|
||||
@override
|
||||
BaseOptions get options {
|
||||
var options = BaseOptions(baseUrl: rootAddress);
|
||||
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||
if (isWithToken) {
|
||||
var token = getIt<ApiConfigModel>().cloudFlareKey;
|
||||
final String? token = getIt<ApiConfigModel>().cloudFlareKey;
|
||||
assert(token != null);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
||||
if (customToken != null) {
|
||||
options.headers = {'Authorization': 'Bearer $customToken'};
|
||||
}
|
||||
|
||||
if (validateStatus != null) {
|
||||
options.validateStatus = validateStatus!;
|
||||
}
|
||||
|
@ -26,14 +46,15 @@ class CloudflareApi extends ApiMap {
|
|||
@override
|
||||
String rootAddress = 'https://api.cloudflare.com/client/v4';
|
||||
|
||||
Future<bool> isValid(String token) async {
|
||||
validateStatus = (status) {
|
||||
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
};
|
||||
Future<bool> isValid(final String token) async {
|
||||
validateStatus = (final status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
|
||||
var client = await getClient();
|
||||
Response response = await client.get('/user/tokens/verify',
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}));
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(
|
||||
'/user/tokens/verify',
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
|
||||
close(client);
|
||||
|
||||
|
@ -46,37 +67,40 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String> getZoneId(String domain) async {
|
||||
validateStatus = (status) {
|
||||
return status == HttpStatus.ok || status == HttpStatus.forbidden;
|
||||
};
|
||||
var client = await getClient();
|
||||
Response response = await client.get(
|
||||
Future<String> getZoneId(final String domain) async {
|
||||
validateStatus = (final status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.forbidden;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(
|
||||
'/zones',
|
||||
queryParameters: {'name': domain},
|
||||
);
|
||||
|
||||
close(client);
|
||||
|
||||
return response.data['result'][0]['id'];
|
||||
if (response.data['result'].isEmpty) {
|
||||
throw DomainNotFoundException('No domains found');
|
||||
} else {
|
||||
return response.data['result'][0]['id'];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeSimilarRecords({
|
||||
String? ip4,
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
required final ServerDomain cloudFlareDomain,
|
||||
final String? ip4,
|
||||
}) async {
|
||||
var domainName = cloudFlareDomain.domainName;
|
||||
var domainZoneId = cloudFlareDomain.zoneId;
|
||||
final String domainName = cloudFlareDomain.domainName;
|
||||
final String domainZoneId = cloudFlareDomain.zoneId;
|
||||
|
||||
var url = '/zones/$domainZoneId/dns_records';
|
||||
final String url = '/zones/$domainZoneId/dns_records';
|
||||
|
||||
var client = await getClient();
|
||||
Response response = await client.get(url);
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(url);
|
||||
|
||||
List records = response.data['result'] ?? [];
|
||||
var allDeleteFutures = <Future>[];
|
||||
final List records = response.data['result'] ?? [];
|
||||
final List<Future> allDeleteFutures = <Future>[];
|
||||
|
||||
for (var record in records) {
|
||||
for (final record in records) {
|
||||
if (record['zone_name'] == domainName) {
|
||||
allDeleteFutures.add(
|
||||
client.delete('$url/${record["id"]}'),
|
||||
|
@ -89,28 +113,30 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<List<DnsRecord>> getDnsRecords({
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
required final ServerDomain cloudFlareDomain,
|
||||
}) async {
|
||||
var domainName = cloudFlareDomain.domainName;
|
||||
var domainZoneId = cloudFlareDomain.zoneId;
|
||||
final String domainName = cloudFlareDomain.domainName;
|
||||
final String domainZoneId = cloudFlareDomain.zoneId;
|
||||
|
||||
var url = '/zones/$domainZoneId/dns_records';
|
||||
final String url = '/zones/$domainZoneId/dns_records';
|
||||
|
||||
var client = await getClient();
|
||||
Response response = await client.get(url);
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(url);
|
||||
|
||||
List records = response.data['result'] ?? [];
|
||||
var allRecords = <DnsRecord>[];
|
||||
final List records = response.data['result'] ?? [];
|
||||
final List<DnsRecord> allRecords = <DnsRecord>[];
|
||||
|
||||
for (var record in records) {
|
||||
for (final record in records) {
|
||||
if (record['zone_name'] == domainName) {
|
||||
allRecords.add(DnsRecord(
|
||||
name: record['name'],
|
||||
type: record['type'],
|
||||
content: record['content'],
|
||||
ttl: record['ttl'],
|
||||
proxied: record['proxied'],
|
||||
));
|
||||
allRecords.add(
|
||||
DnsRecord(
|
||||
name: record['name'],
|
||||
type: record['type'],
|
||||
content: record['content'],
|
||||
ttl: record['ttl'],
|
||||
proxied: record['proxied'],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,51 +145,59 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<void> createMultipleDnsRecords({
|
||||
String? ip4,
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
required final ServerDomain cloudFlareDomain,
|
||||
final String? ip4,
|
||||
}) async {
|
||||
var domainName = cloudFlareDomain.domainName;
|
||||
var domainZoneId = cloudFlareDomain.zoneId;
|
||||
var listDnsRecords = projectDnsRecords(domainName, ip4);
|
||||
final String domainName = cloudFlareDomain.domainName;
|
||||
final String domainZoneId = cloudFlareDomain.zoneId;
|
||||
final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4);
|
||||
final List<Future> allCreateFutures = <Future>[];
|
||||
|
||||
var url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||
|
||||
var allCreateFutures = <Future>[];
|
||||
var client = await getClient();
|
||||
|
||||
for (var record in listDnsRecords) {
|
||||
allCreateFutures.add(
|
||||
client.post(
|
||||
url,
|
||||
data: record.toJson(),
|
||||
),
|
||||
);
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
for (final DnsRecord record in listDnsRecords) {
|
||||
allCreateFutures.add(
|
||||
client.post(
|
||||
'/zones/$domainZoneId/dns_records',
|
||||
data: record.toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
await Future.wait(allCreateFutures);
|
||||
} on DioError catch (e) {
|
||||
print(e.message);
|
||||
rethrow;
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
|
||||
await Future.wait(allCreateFutures);
|
||||
close(client);
|
||||
}
|
||||
|
||||
List<DnsRecord> projectDnsRecords(String? domainName, String? ip4) {
|
||||
var domainA = DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||
List<DnsRecord> projectDnsRecords(
|
||||
final String? domainName,
|
||||
final String? ip4,
|
||||
) {
|
||||
final DnsRecord domainA =
|
||||
DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||
|
||||
var mx = DnsRecord(type: 'MX', name: '@', content: domainName);
|
||||
var apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
|
||||
var cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
|
||||
var gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
|
||||
var meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
|
||||
var passwordA = DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||
var socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||
var vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
|
||||
final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: domainName);
|
||||
final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
|
||||
final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
|
||||
final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
|
||||
final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
|
||||
final DnsRecord passwordA =
|
||||
DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||
final DnsRecord socialA =
|
||||
DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||
final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
|
||||
|
||||
var txt1 = DnsRecord(
|
||||
final DnsRecord txt1 = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: '_dmarc',
|
||||
content: 'v=DMARC1; p=none',
|
||||
ttl: 18000,
|
||||
);
|
||||
|
||||
var txt2 = DnsRecord(
|
||||
final DnsRecord txt2 = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: domainName,
|
||||
content: 'v=spf1 a mx ip4:$ip4 -all',
|
||||
|
@ -186,18 +220,20 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<void> setDkim(
|
||||
String dkimRecordString, CloudFlareDomain cloudFlareDomain) async {
|
||||
final domainZoneId = cloudFlareDomain.zoneId;
|
||||
final url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||
final String dkimRecordString,
|
||||
final ServerDomain cloudFlareDomain,
|
||||
) async {
|
||||
final String domainZoneId = cloudFlareDomain.zoneId;
|
||||
final String url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||
|
||||
final dkimRecord = DnsRecord(
|
||||
final DnsRecord dkimRecord = DnsRecord(
|
||||
type: 'TXT',
|
||||
name: 'selector._domainkey',
|
||||
content: dkimRecordString,
|
||||
ttl: 18000,
|
||||
);
|
||||
|
||||
var client = await getClient();
|
||||
final Dio client = await getClient();
|
||||
await client.post(
|
||||
url,
|
||||
data: dkimRecord.toJson(),
|
||||
|
@ -207,23 +243,17 @@ class CloudflareApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<List<String>> domainList() async {
|
||||
var url = '$rootAddress/zones?per_page=50';
|
||||
var client = await getClient();
|
||||
final String url = '$rootAddress/zones';
|
||||
final Dio client = await getClient();
|
||||
|
||||
var response = await client.get(
|
||||
final Response response = await client.get(
|
||||
url,
|
||||
queryParameters: {'per_page': 50},
|
||||
);
|
||||
|
||||
close(client);
|
||||
return response.data['result']
|
||||
.map<String>((el) => el['name'] as String)
|
||||
.map<String>((final el) => el['name'] as String)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
final bool hasLogger;
|
||||
|
||||
@override
|
||||
final bool isWithToken;
|
||||
}
|
||||
|
|
|
@ -4,21 +4,23 @@ import 'dart:io';
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class HetznerApi extends ApiMap {
|
||||
HetznerApi({this.hasLogger = false, this.isWithToken = true});
|
||||
@override
|
||||
bool hasLogger;
|
||||
@override
|
||||
bool isWithToken;
|
||||
|
||||
HetznerApi({this.hasLogger = false, this.isWithToken = true});
|
||||
|
||||
@override
|
||||
BaseOptions get options {
|
||||
var options = BaseOptions(baseUrl: rootAddress);
|
||||
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||
if (isWithToken) {
|
||||
var token = getIt<ApiConfigModel>().hetznerKey;
|
||||
final String? token = getIt<ApiConfigModel>().hetznerKey;
|
||||
assert(token != null);
|
||||
options.headers = {'Authorization': 'Bearer $token'};
|
||||
}
|
||||
|
@ -33,12 +35,11 @@ class HetznerApi extends ApiMap {
|
|||
@override
|
||||
String rootAddress = 'https://api.hetzner.cloud/v1';
|
||||
|
||||
Future<bool> isValid(String token) async {
|
||||
validateStatus = (status) {
|
||||
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
};
|
||||
var client = await getClient();
|
||||
Response response = await client.get(
|
||||
Future<bool> isValid(final String token) async {
|
||||
validateStatus = (final int? status) =>
|
||||
status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get(
|
||||
'/servers',
|
||||
options: Options(
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
|
@ -55,99 +56,98 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> isFreeToCreate() async {
|
||||
var client = await getClient();
|
||||
|
||||
Response serversReponse = await client.get('/servers');
|
||||
List servers = serversReponse.data['servers'];
|
||||
var server = servers.firstWhere(
|
||||
(el) => el['name'] == 'selfprivacy-server',
|
||||
orElse: null,
|
||||
);
|
||||
client.close();
|
||||
return server == null;
|
||||
}
|
||||
|
||||
Future<HetznerDataBase> createVolume() async {
|
||||
var client = await getClient();
|
||||
Response dbCreateResponse = await client.post(
|
||||
Future<ServerVolume> createVolume() async {
|
||||
final Dio client = await getClient();
|
||||
final Response dbCreateResponse = await client.post(
|
||||
'/volumes',
|
||||
data: {
|
||||
"size": 10,
|
||||
"name": StringGenerators.dbStorageName(),
|
||||
"labels": {"labelkey": "value"},
|
||||
"location": "fsn1",
|
||||
"automount": false,
|
||||
"format": "ext4"
|
||||
'size': 10,
|
||||
'name': StringGenerators.dbStorageName(),
|
||||
'labels': {'labelkey': 'value'},
|
||||
'location': 'fsn1',
|
||||
'automount': false,
|
||||
'format': 'ext4'
|
||||
},
|
||||
);
|
||||
var dbId = dbCreateResponse.data['volume']['id'];
|
||||
return HetznerDataBase(
|
||||
final dbId = dbCreateResponse.data['volume']['id'];
|
||||
return ServerVolume(
|
||||
id: dbId,
|
||||
name: dbCreateResponse.data['volume']['name'],
|
||||
);
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> createServer({
|
||||
required String cloudFlareKey,
|
||||
required User rootUser,
|
||||
required String domainName,
|
||||
required HetznerDataBase dataBase,
|
||||
Future<ServerHostingDetails?> createServer({
|
||||
required final String cloudFlareKey,
|
||||
required final User rootUser,
|
||||
required final String domainName,
|
||||
required final ServerVolume dataBase,
|
||||
}) async {
|
||||
var client = await getClient();
|
||||
final Dio client = await getClient();
|
||||
|
||||
var dbPassword = StringGenerators.dbPassword();
|
||||
var dbId = dataBase.id;
|
||||
final String dbPassword = StringGenerators.dbPassword();
|
||||
final int dbId = dataBase.id;
|
||||
|
||||
final apiToken = StringGenerators.apiToken();
|
||||
final String apiToken = StringGenerators.apiToken();
|
||||
|
||||
final hostname = getHostnameFromDomain(domainName);
|
||||
final String hostname = getHostnameFromDomain(domainName);
|
||||
|
||||
final base64Password =
|
||||
final String base64Password =
|
||||
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
||||
|
||||
print("hostname: $hostname");
|
||||
print('hostname: $hostname');
|
||||
|
||||
/// add ssh key when you need it: e.g. "ssh_keys":["kherel"]
|
||||
/// check the branch name, it could be "development" or "master".
|
||||
///
|
||||
final userdataString =
|
||||
final String userdataString =
|
||||
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
|
||||
print(userdataString);
|
||||
|
||||
final data = {
|
||||
"name": hostname,
|
||||
"server_type": "cx11",
|
||||
"start_after_create": false,
|
||||
"image": "ubuntu-20.04",
|
||||
"volumes": [dbId],
|
||||
"networks": [],
|
||||
"user_data": userdataString,
|
||||
"labels": {},
|
||||
"automount": true,
|
||||
"location": "fsn1"
|
||||
final Map<String, Object> data = {
|
||||
'name': hostname,
|
||||
'server_type': 'cx11',
|
||||
'start_after_create': false,
|
||||
'image': 'ubuntu-20.04',
|
||||
'volumes': [dbId],
|
||||
'networks': [],
|
||||
'user_data': userdataString,
|
||||
'labels': {},
|
||||
'automount': true,
|
||||
'location': 'fsn1'
|
||||
};
|
||||
print("Decoded data: $data");
|
||||
print('Decoded data: $data');
|
||||
|
||||
Response serverCreateResponse = await client.post(
|
||||
'/servers',
|
||||
data: data,
|
||||
);
|
||||
ServerHostingDetails? serverDetails;
|
||||
|
||||
print(serverCreateResponse.data);
|
||||
client.close();
|
||||
return HetznerServerDetails(
|
||||
id: serverCreateResponse.data['server']['id'],
|
||||
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
||||
createTime: DateTime.now(),
|
||||
dataBase: dataBase,
|
||||
apiToken: apiToken,
|
||||
);
|
||||
try {
|
||||
final Response serverCreateResponse = await client.post(
|
||||
'/servers',
|
||||
data: data,
|
||||
);
|
||||
print(serverCreateResponse.data);
|
||||
serverDetails = ServerHostingDetails(
|
||||
id: serverCreateResponse.data['server']['id'],
|
||||
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
||||
createTime: DateTime.now(),
|
||||
volume: dataBase,
|
||||
apiToken: apiToken,
|
||||
provider: ServerProvider.hetzner,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
print(e);
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
||||
return serverDetails;
|
||||
}
|
||||
|
||||
static String getHostnameFromDomain(String domain) {
|
||||
static String getHostnameFromDomain(final String domain) {
|
||||
// Replace all non-alphanumeric characters with an underscore
|
||||
var hostname =
|
||||
String hostname =
|
||||
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
if (hostname.endsWith('-')) {
|
||||
hostname = hostname.substring(0, hostname.length - 1);
|
||||
|
@ -163,24 +163,24 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<void> deleteSelfprivacyServerAndAllVolumes({
|
||||
required String domainName,
|
||||
required final String domainName,
|
||||
}) async {
|
||||
var client = await getClient();
|
||||
final Dio client = await getClient();
|
||||
|
||||
final hostname = getHostnameFromDomain(domainName);
|
||||
final String hostname = getHostnameFromDomain(domainName);
|
||||
|
||||
Response serversReponse = await client.get('/servers');
|
||||
List servers = serversReponse.data['servers'];
|
||||
Map server = servers.firstWhere((el) => el['name'] == hostname);
|
||||
List volumes = server['volumes'];
|
||||
var laterFutures = <Future>[];
|
||||
final Response serversReponse = await client.get('/servers');
|
||||
final List servers = serversReponse.data['servers'];
|
||||
final Map server = servers.firstWhere((final el) => el['name'] == hostname);
|
||||
final List volumes = server['volumes'];
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
|
||||
for (var volumeId in volumes) {
|
||||
for (final volumeId in volumes) {
|
||||
await client.post('/volumes/$volumeId/actions/detach');
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 10));
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
for (var volumeId in volumes) {
|
||||
for (final volumeId in volumes) {
|
||||
laterFutures.add(client.delete('/volumes/$volumeId'));
|
||||
}
|
||||
laterFutures.add(client.delete('/servers/${server['id']}'));
|
||||
|
@ -189,20 +189,20 @@ class HetznerApi extends ApiMap {
|
|||
close(client);
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> reset() async {
|
||||
var server = getIt<ApiConfigModel>().hetznerServer!;
|
||||
Future<ServerHostingDetails> reset() async {
|
||||
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||
|
||||
var client = await getClient();
|
||||
final Dio client = await getClient();
|
||||
await client.post('/servers/${server.id}/actions/reset');
|
||||
close(client);
|
||||
|
||||
return server.copyWith(startTime: DateTime.now());
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> powerOn() async {
|
||||
var server = getIt<ApiConfigModel>().hetznerServer!;
|
||||
Future<ServerHostingDetails> powerOn() async {
|
||||
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||
|
||||
var client = await getClient();
|
||||
final Dio client = await getClient();
|
||||
await client.post('/servers/${server.id}/actions/poweron');
|
||||
close(client);
|
||||
|
||||
|
@ -210,16 +210,20 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> getMetrics(
|
||||
DateTime start, DateTime end, String type) async {
|
||||
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||
var client = await getClient();
|
||||
final DateTime start,
|
||||
final DateTime end,
|
||||
final String type,
|
||||
) async {
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
final Dio client = await getClient();
|
||||
|
||||
Map<String, dynamic> queryParameters = {
|
||||
"start": start.toUtc().toIso8601String(),
|
||||
"end": end.toUtc().toIso8601String(),
|
||||
"type": type
|
||||
final Map<String, dynamic> queryParameters = {
|
||||
'start': start.toUtc().toIso8601String(),
|
||||
'end': end.toUtc().toIso8601String(),
|
||||
'type': type
|
||||
};
|
||||
var res = await client.get(
|
||||
final Response res = await client.get(
|
||||
'/servers/${hetznerServer!.id}/metrics',
|
||||
queryParameters: queryParameters,
|
||||
);
|
||||
|
@ -228,27 +232,46 @@ class HetznerApi extends ApiMap {
|
|||
}
|
||||
|
||||
Future<HetznerServerInfo> getInfo() async {
|
||||
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||
var client = await getClient();
|
||||
Response response = await client.get('/servers/${hetznerServer!.id}');
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get('/servers/${hetznerServer!.id}');
|
||||
close(client);
|
||||
|
||||
return HetznerServerInfo.fromJson(response.data!['server']);
|
||||
}
|
||||
|
||||
Future<void> createReverseDns({
|
||||
required String ip4,
|
||||
required String domainName,
|
||||
}) async {
|
||||
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||
var client = await getClient();
|
||||
await client.post(
|
||||
'/servers/${hetznerServer!.id}/actions/change_dns_ptr',
|
||||
data: {
|
||||
"ip": ip4,
|
||||
"dns_ptr": domainName,
|
||||
},
|
||||
);
|
||||
Future<List<HetznerServerInfo>> getServers() async {
|
||||
final Dio client = await getClient();
|
||||
final Response response = await client.get('/servers');
|
||||
close(client);
|
||||
|
||||
return (response.data!['servers'] as List)
|
||||
// ignore: unnecessary_lambdas
|
||||
.map((final e) => HetznerServerInfo.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> createReverseDns({
|
||||
required final String ip4,
|
||||
required final String domainName,
|
||||
}) async {
|
||||
final ServerHostingDetails? hetznerServer =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
await client.post(
|
||||
'/servers/${hetznerServer!.id}/actions/change_dns_ptr',
|
||||
data: {
|
||||
'ip': ip4,
|
||||
'dns_ptr': domainName,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:ionicons/ionicons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
|
||||
enum LoadingStatus {
|
||||
uninitialized,
|
||||
refreshing,
|
||||
success,
|
||||
error,
|
||||
}
|
||||
|
||||
enum InitializingSteps {
|
||||
setHetznerKey,
|
||||
setCloudFlareKey,
|
||||
|
@ -13,6 +19,7 @@ enum InitializingSteps {
|
|||
startServer,
|
||||
checkSystemDnsAndDkimSet,
|
||||
}
|
||||
|
||||
enum Period { hour, day, month }
|
||||
|
||||
enum ServiceTypes {
|
||||
|
@ -126,9 +133,9 @@ extension ServiceTypesExt on ServiceTypes {
|
|||
case ServiceTypes.git:
|
||||
return BrandIcons.git;
|
||||
case ServiceTypes.vpn:
|
||||
return Ionicons.shield_checkmark_outline;
|
||||
return Icons.vpn_lock_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
String get txt => this.toString().split('.')[1];
|
||||
String get txt => toString().split('.')[1];
|
||||
}
|
||||
|
|
|
@ -1,378 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
|
||||
import 'app_config_repository.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'app_config_state.dart';
|
||||
|
||||
/// Initializing steps:
|
||||
///
|
||||
/// The set phase.
|
||||
/// 1.1. Hetzner key |setHetznerKey
|
||||
/// 1.2. Cloudflare key |setCloudflareKey
|
||||
/// 1.3. Backblaze Id + Key |setBackblazeKey
|
||||
/// 1.4. Set Domain address |setDomain
|
||||
/// 1.5. Set Root user name password |setRootUser
|
||||
/// 1.6. Set Create server ans set DNS-Records |createServerAndSetDnsRecords
|
||||
/// (without start)
|
||||
///
|
||||
/// The check phase.
|
||||
///
|
||||
/// 2.1. a. wait 60sec checkDnsAndStartServer |startServerIfDnsIsOkay
|
||||
/// b. checkDns
|
||||
/// c. if dns is okay start server
|
||||
///
|
||||
/// 2.2. a. wait 60sec |resetServerIfServerIsOkay
|
||||
/// b. checkServer
|
||||
/// c. if server is ok wait 30 sec
|
||||
/// d. reset server
|
||||
///
|
||||
/// 2.3. a. wait 60sec |oneMoreReset()
|
||||
/// d. reset server
|
||||
///
|
||||
/// 2.4. a. wait 30sec |finishCheckIfServerIsOkay
|
||||
/// b. checkServer
|
||||
/// c. if server is okay set that fully checked
|
||||
|
||||
class AppConfigCubit extends Cubit<AppConfigState> {
|
||||
AppConfigCubit() : super(AppConfigEmpty());
|
||||
|
||||
final repository = AppConfigRepository();
|
||||
|
||||
Future<void> load() async {
|
||||
var state = await repository.load();
|
||||
|
||||
if (state is AppConfigFinished) {
|
||||
emit(state);
|
||||
} else if (state is AppConfigNotFinished) {
|
||||
if (state.progress == 6) {
|
||||
startServerIfDnsIsOkay(state: state, isImmediate: true);
|
||||
} else if (state.progress == 7) {
|
||||
resetServerIfServerIsOkay(state: state, isImmediate: true);
|
||||
} else if (state.progress == 8) {
|
||||
oneMoreReset(state: state, isImmediate: true);
|
||||
} else if (state.progress == 9) {
|
||||
finishCheckIfServerIsOkay(state: state, isImmediate: true);
|
||||
} else {
|
||||
emit(state);
|
||||
}
|
||||
} else {
|
||||
throw 'wrong state';
|
||||
}
|
||||
}
|
||||
|
||||
void startServerIfDnsIsOkay({
|
||||
AppConfigNotFinished? state,
|
||||
bool isImmediate = false,
|
||||
}) async {
|
||||
state = state ?? this.state as AppConfigNotFinished;
|
||||
|
||||
final work = () async {
|
||||
emit(TimerState(dataState: state!, isLoading: true));
|
||||
|
||||
var ip4 = state.hetznerServer!.ip4;
|
||||
var domainName = state.cloudFlareDomain!.domainName;
|
||||
|
||||
var matches = await repository.isDnsAddressesMatch(
|
||||
domainName, ip4, state.dnsMatches);
|
||||
|
||||
if (matches.values.every((value) => value)) {
|
||||
var server = await repository.startServer(
|
||||
state.hetznerServer!,
|
||||
);
|
||||
await repository.saveServerDetails(server);
|
||||
await repository.saveIsServerStarted(true);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
isServerStarted: true,
|
||||
isLoading: false,
|
||||
hetznerServer: server,
|
||||
),
|
||||
);
|
||||
resetServerIfServerIsOkay();
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
dnsMatches: matches,
|
||||
),
|
||||
);
|
||||
startServerIfDnsIsOkay();
|
||||
}
|
||||
};
|
||||
|
||||
if (isImmediate) {
|
||||
work();
|
||||
} else {
|
||||
var pauseDuration = Duration(seconds: 30);
|
||||
emit(TimerState(
|
||||
dataState: state,
|
||||
timerStart: DateTime.now(),
|
||||
duration: pauseDuration,
|
||||
isLoading: false,
|
||||
));
|
||||
timer = Timer(pauseDuration, work);
|
||||
}
|
||||
}
|
||||
|
||||
void oneMoreReset({
|
||||
AppConfigNotFinished? state,
|
||||
bool isImmediate = false,
|
||||
}) async {
|
||||
var dataState = state ?? this.state as AppConfigNotFinished;
|
||||
|
||||
var work = () async {
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
var isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
var pauseDuration = Duration(seconds: 30);
|
||||
emit(TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
isLoading: false,
|
||||
duration: pauseDuration,
|
||||
));
|
||||
timer = Timer(pauseDuration, () async {
|
||||
var hetznerServerDetails = await repository.restart();
|
||||
await repository.saveIsServerResetedSecondTime(true);
|
||||
await repository.saveServerDetails(hetznerServerDetails);
|
||||
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
isServerResetedSecondTime: true,
|
||||
hetznerServer: hetznerServerDetails,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
finishCheckIfServerIsOkay();
|
||||
});
|
||||
} else {
|
||||
oneMoreReset();
|
||||
}
|
||||
};
|
||||
if (isImmediate) {
|
||||
work();
|
||||
} else {
|
||||
var pauseDuration = Duration(seconds: 60);
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
duration: pauseDuration,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
timer = Timer(pauseDuration, work);
|
||||
}
|
||||
}
|
||||
|
||||
void resetServerIfServerIsOkay({
|
||||
AppConfigNotFinished? state,
|
||||
bool isImmediate = false,
|
||||
}) async {
|
||||
var dataState = state ?? this.state as AppConfigNotFinished;
|
||||
|
||||
var work = () async {
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
var isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
var pauseDuration = Duration(seconds: 30);
|
||||
emit(TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
isLoading: false,
|
||||
duration: pauseDuration,
|
||||
));
|
||||
timer = Timer(pauseDuration, () async {
|
||||
var hetznerServerDetails = await repository.restart();
|
||||
await repository.saveIsServerResetedFirstTime(true);
|
||||
await repository.saveServerDetails(hetznerServerDetails);
|
||||
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
isServerResetedFirstTime: true,
|
||||
hetznerServer: hetznerServerDetails,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
oneMoreReset();
|
||||
});
|
||||
} else {
|
||||
resetServerIfServerIsOkay();
|
||||
}
|
||||
};
|
||||
if (isImmediate) {
|
||||
work();
|
||||
} else {
|
||||
var pauseDuration = Duration(seconds: 60);
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
duration: pauseDuration,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
timer = Timer(pauseDuration, work);
|
||||
}
|
||||
}
|
||||
|
||||
Timer? timer;
|
||||
|
||||
void finishCheckIfServerIsOkay({
|
||||
AppConfigNotFinished? state,
|
||||
bool isImmediate = false,
|
||||
}) async {
|
||||
state = state ?? this.state as AppConfigNotFinished;
|
||||
|
||||
var work = () async {
|
||||
emit(TimerState(dataState: state!, isLoading: true));
|
||||
|
||||
var isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
await repository.createDkimRecord(state.cloudFlareDomain!);
|
||||
await repository.saveHasFinalChecked(true);
|
||||
|
||||
emit(state.finish());
|
||||
} else {
|
||||
finishCheckIfServerIsOkay();
|
||||
}
|
||||
};
|
||||
if (isImmediate) {
|
||||
work();
|
||||
} else {
|
||||
var pauseDuration = Duration(seconds: 60);
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: state,
|
||||
timerStart: DateTime.now(),
|
||||
duration: pauseDuration,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
timer = Timer(pauseDuration, work);
|
||||
}
|
||||
}
|
||||
|
||||
void clearAppConfig() {
|
||||
closeTimer();
|
||||
|
||||
repository.clearAppConfig();
|
||||
emit(AppConfigEmpty());
|
||||
}
|
||||
|
||||
Future<void> serverDelete() async {
|
||||
closeTimer();
|
||||
|
||||
if (state.hetznerServer != null) {
|
||||
await repository.deleteServer(state.cloudFlareDomain!);
|
||||
await getIt<SSHModel>().clear();
|
||||
}
|
||||
await repository.deleteRecords();
|
||||
emit(AppConfigNotFinished(
|
||||
hetznerKey: state.hetznerKey,
|
||||
cloudFlareDomain: state.cloudFlareDomain,
|
||||
cloudFlareKey: state.cloudFlareKey,
|
||||
backblazeCredential: state.backblazeCredential,
|
||||
rootUser: state.rootUser,
|
||||
hetznerServer: null,
|
||||
isServerStarted: false,
|
||||
isServerResetedFirstTime: false,
|
||||
isServerResetedSecondTime: false,
|
||||
isLoading: false,
|
||||
dnsMatches: null,
|
||||
));
|
||||
}
|
||||
|
||||
void setHetznerKey(String hetznerKey) async {
|
||||
await repository.saveHetznerKey(hetznerKey);
|
||||
emit((state as AppConfigNotFinished).copyWith(hetznerKey: hetznerKey));
|
||||
}
|
||||
|
||||
void setCloudflareKey(String cloudFlareKey) async {
|
||||
await repository.saveCloudFlareKey(cloudFlareKey);
|
||||
emit(
|
||||
(state as AppConfigNotFinished).copyWith(cloudFlareKey: cloudFlareKey));
|
||||
}
|
||||
|
||||
void setBackblazeKey(String keyId, String applicationKey) async {
|
||||
var backblazeCredential = BackblazeCredential(
|
||||
keyId: keyId,
|
||||
applicationKey: applicationKey,
|
||||
);
|
||||
await repository.saveBackblazeKey(backblazeCredential);
|
||||
emit((state as AppConfigNotFinished)
|
||||
.copyWith(backblazeCredential: backblazeCredential));
|
||||
}
|
||||
|
||||
void setDomain(CloudFlareDomain cloudFlareDomain) async {
|
||||
await repository.saveDomain(cloudFlareDomain);
|
||||
emit((state as AppConfigNotFinished)
|
||||
.copyWith(cloudFlareDomain: cloudFlareDomain));
|
||||
}
|
||||
|
||||
void setRootUser(User rootUser) async {
|
||||
await repository.saveRootUser(rootUser);
|
||||
emit((state as AppConfigNotFinished).copyWith(rootUser: rootUser));
|
||||
}
|
||||
|
||||
void createServerAndSetDnsRecords() async {
|
||||
AppConfigNotFinished _stateCopy = state as AppConfigNotFinished;
|
||||
var onSuccess = (HetznerServerDetails serverDetails) async {
|
||||
await repository.createDnsRecords(
|
||||
serverDetails.ip4,
|
||||
state.cloudFlareDomain!,
|
||||
);
|
||||
|
||||
emit((state as AppConfigNotFinished).copyWith(
|
||||
isLoading: false,
|
||||
hetznerServer: serverDetails,
|
||||
));
|
||||
startServerIfDnsIsOkay();
|
||||
};
|
||||
|
||||
var onCancel =
|
||||
() => emit((state as AppConfigNotFinished).copyWith(isLoading: false));
|
||||
|
||||
try {
|
||||
emit((state as AppConfigNotFinished).copyWith(isLoading: true));
|
||||
await repository.createServer(
|
||||
state.rootUser!,
|
||||
state.cloudFlareDomain!.domainName,
|
||||
state.cloudFlareKey!,
|
||||
state.backblazeCredential!,
|
||||
onCancel: onCancel,
|
||||
onSuccess: onSuccess,
|
||||
);
|
||||
} catch (e) {
|
||||
emit(_stateCopy);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
closeTimer();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void closeTimer() {
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,303 +0,0 @@
|
|||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
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/cloudflare.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/message.dart';
|
||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
||||
|
||||
import 'app_config_cubit.dart';
|
||||
|
||||
class AppConfigRepository {
|
||||
Box box = Hive.box(BNames.appConfig);
|
||||
|
||||
Future<AppConfigState> load() async {
|
||||
late AppConfigState res;
|
||||
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
||||
res = AppConfigFinished(
|
||||
hetznerKey: getIt<ApiConfigModel>().hetznerKey!,
|
||||
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey!,
|
||||
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain!,
|
||||
backblazeCredential: getIt<ApiConfigModel>().backblazeCredential!,
|
||||
hetznerServer: getIt<ApiConfigModel>().hetznerServer!,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
||||
isServerResetedFirstTime:
|
||||
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||
isServerResetedSecondTime:
|
||||
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||
);
|
||||
} else {
|
||||
res = AppConfigNotFinished(
|
||||
hetznerKey: getIt<ApiConfigModel>().hetznerKey,
|
||||
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey,
|
||||
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain,
|
||||
backblazeCredential: getIt<ApiConfigModel>().backblazeCredential,
|
||||
hetznerServer: getIt<ApiConfigModel>().hetznerServer,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
||||
isServerResetedFirstTime:
|
||||
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||
isServerResetedSecondTime:
|
||||
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||
isLoading: box.get(BNames.isLoading, defaultValue: false),
|
||||
dnsMatches: null,
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void clearAppConfig() {
|
||||
box.clear();
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> startServer(
|
||||
HetznerServerDetails hetznerServer,
|
||||
) async {
|
||||
var hetznerApi = HetznerApi();
|
||||
var serverDetails = await hetznerApi.powerOn();
|
||||
|
||||
return serverDetails;
|
||||
}
|
||||
|
||||
Future<Map<String, bool>> isDnsAddressesMatch(String? domainName, String? ip4,
|
||||
Map<String, bool>? skippedMatches) async {
|
||||
var addresses = <String>[
|
||||
'$domainName',
|
||||
'api.$domainName',
|
||||
'cloud.$domainName',
|
||||
'meet.$domainName',
|
||||
'password.$domainName'
|
||||
];
|
||||
|
||||
var matches = <String, bool>{};
|
||||
|
||||
for (var address in addresses) {
|
||||
if (skippedMatches != null && skippedMatches[address] == true) {
|
||||
matches[address] = true;
|
||||
continue;
|
||||
}
|
||||
var lookupRecordRes = await DnsUtils.lookupRecord(
|
||||
address,
|
||||
RRecordType.A,
|
||||
provider: DnsApiProvider.CLOUDFLARE,
|
||||
);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
Message(
|
||||
text:
|
||||
'DnsLookup: address: $address, $RRecordType, provider: CLOUDFLARE, ip4: $ip4',
|
||||
),
|
||||
);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
Message(
|
||||
text:
|
||||
'DnsLookup: ${lookupRecordRes == null ? 'empty' : (lookupRecordRes[0].data != ip4 ? 'wrong ip4' : 'right ip4')}',
|
||||
),
|
||||
);
|
||||
if (lookupRecordRes == null ||
|
||||
lookupRecordRes.isEmpty ||
|
||||
lookupRecordRes[0].data != ip4) {
|
||||
matches[address] = false;
|
||||
} else {
|
||||
matches[address] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
Future<void> createServer(
|
||||
User rootUser,
|
||||
String domainName,
|
||||
String cloudFlareKey,
|
||||
BackblazeCredential backblazeCredential, {
|
||||
required void Function() onCancel,
|
||||
required Future<void> Function(HetznerServerDetails serverDetails)
|
||||
onSuccess,
|
||||
}) async {
|
||||
var hetznerApi = HetznerApi();
|
||||
late HetznerDataBase dataBase;
|
||||
|
||||
try {
|
||||
dataBase = await hetznerApi.createVolume();
|
||||
|
||||
var serverDetails = await hetznerApi.createServer(
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
rootUser: rootUser,
|
||||
domainName: domainName,
|
||||
dataBase: dataBase,
|
||||
);
|
||||
saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
} on DioError catch (e) {
|
||||
if (e.response!.data['error']['code'] == 'uniqueness_error') {
|
||||
var nav = getIt.get<NavigationService>();
|
||||
nav.showPopUpDialog(
|
||||
BrandAlert(
|
||||
title: 'modals.1'.tr(),
|
||||
contentText: 'modals.2'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.delete'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () async {
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: domainName);
|
||||
|
||||
var serverDetails = await hetznerApi.createServer(
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
rootUser: rootUser,
|
||||
domainName: domainName,
|
||||
dataBase: dataBase,
|
||||
);
|
||||
|
||||
await saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
onPressed: () {
|
||||
onCancel();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createDnsRecords(
|
||||
String ip4,
|
||||
CloudFlareDomain cloudFlareDomain,
|
||||
) async {
|
||||
var cloudflareApi = CloudflareApi();
|
||||
|
||||
await cloudflareApi.removeSimilarRecords(
|
||||
ip4: ip4,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
);
|
||||
|
||||
await cloudflareApi.createMultipleDnsRecords(
|
||||
ip4: ip4,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
);
|
||||
|
||||
await HetznerApi().createReverseDns(
|
||||
ip4: ip4,
|
||||
domainName: cloudFlareDomain.domainName,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> createDkimRecord(CloudFlareDomain cloudFlareDomain) async {
|
||||
var cloudflareApi = CloudflareApi();
|
||||
var api = ServerApi();
|
||||
|
||||
var dkimRecordString = await api.getDkim();
|
||||
|
||||
await cloudflareApi.setDkim(dkimRecordString, cloudFlareDomain);
|
||||
}
|
||||
|
||||
Future<bool> isHttpServerWorking() async {
|
||||
var api = ServerApi();
|
||||
var isHttpServerWorking = await api.isHttpServerWorking();
|
||||
try {
|
||||
await api.getDkim();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return isHttpServerWorking;
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> restart() async {
|
||||
var hetznerApi = HetznerApi();
|
||||
return await hetznerApi.reset();
|
||||
}
|
||||
|
||||
Future<HetznerServerDetails> powerOn() async {
|
||||
var hetznerApi = HetznerApi();
|
||||
return await hetznerApi.powerOn();
|
||||
}
|
||||
|
||||
Future<void> saveServerDetails(HetznerServerDetails serverDetails) async {
|
||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
||||
}
|
||||
|
||||
Future<void> saveHetznerKey(String key) async {
|
||||
print('saved');
|
||||
await getIt<ApiConfigModel>().storeHetznerKey(key);
|
||||
}
|
||||
|
||||
Future<void> saveBackblazeKey(BackblazeCredential backblazeCredential) async {
|
||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
||||
}
|
||||
|
||||
Future<void> saveCloudFlareKey(String key) async {
|
||||
await getIt<ApiConfigModel>().storeCloudFlareKey(key);
|
||||
}
|
||||
|
||||
Future<void> saveDomain(CloudFlareDomain cloudFlareDomain) async {
|
||||
await getIt<ApiConfigModel>().storeCloudFlareDomain(cloudFlareDomain);
|
||||
}
|
||||
|
||||
Future<void> saveIsServerStarted(bool value) async {
|
||||
await box.put(BNames.isServerStarted, value);
|
||||
}
|
||||
|
||||
Future<void> saveIsServerResetedFirstTime(bool value) async {
|
||||
await box.put(BNames.isServerResetedFirstTime, value);
|
||||
}
|
||||
|
||||
Future<void> saveIsServerResetedSecondTime(bool value) async {
|
||||
await box.put(BNames.isServerResetedSecondTime, value);
|
||||
}
|
||||
|
||||
Future<void> saveRootUser(User rootUser) async {
|
||||
await box.put(BNames.rootUser, rootUser);
|
||||
}
|
||||
|
||||
Future<void> saveHasFinalChecked(bool value) async {
|
||||
await box.put(BNames.hasFinalChecked, value);
|
||||
}
|
||||
|
||||
Future<void> deleteServer(CloudFlareDomain cloudFlareDomain) async {
|
||||
var hetznerApi = HetznerApi();
|
||||
var cloudFlare = CloudflareApi();
|
||||
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: cloudFlareDomain.domainName,
|
||||
);
|
||||
|
||||
await box.put(BNames.hasFinalChecked, false);
|
||||
await box.put(BNames.isServerStarted, false);
|
||||
await box.put(BNames.isServerResetedFirstTime, false);
|
||||
await box.put(BNames.isServerResetedSecondTime, false);
|
||||
await box.put(BNames.isLoading, false);
|
||||
await box.put(BNames.hetznerServer, null);
|
||||
|
||||
await cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
|
||||
}
|
||||
|
||||
Future<void> deleteRecords() async {
|
||||
await box.deleteAll([
|
||||
BNames.hetznerServer,
|
||||
BNames.isServerStarted,
|
||||
BNames.isServerResetedFirstTime,
|
||||
BNames.isServerResetedSecondTime,
|
||||
BNames.hasFinalChecked,
|
||||
BNames.isLoading,
|
||||
]);
|
||||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
part of 'app_config_cubit.dart';
|
||||
|
||||
abstract class AppConfigState extends Equatable {
|
||||
const AppConfigState({
|
||||
required this.hetznerKey,
|
||||
required this.cloudFlareKey,
|
||||
required this.backblazeCredential,
|
||||
required this.cloudFlareDomain,
|
||||
required this.rootUser,
|
||||
required this.hetznerServer,
|
||||
required this.isServerStarted,
|
||||
required this.isServerResetedFirstTime,
|
||||
required this.isServerResetedSecondTime,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
cloudFlareDomain,
|
||||
rootUser,
|
||||
hetznerServer,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
];
|
||||
|
||||
final String? hetznerKey;
|
||||
final String? cloudFlareKey;
|
||||
final BackblazeCredential? backblazeCredential;
|
||||
final CloudFlareDomain? cloudFlareDomain;
|
||||
final User? rootUser;
|
||||
final HetznerServerDetails? hetznerServer;
|
||||
final bool isServerStarted;
|
||||
final bool isServerResetedFirstTime;
|
||||
final bool isServerResetedSecondTime;
|
||||
|
||||
bool get isHetznerFilled => hetznerKey != null;
|
||||
bool get isCloudFlareFilled => cloudFlareKey != null;
|
||||
bool get isBackblazeFilled => backblazeCredential != null;
|
||||
bool get isDomainFilled => cloudFlareDomain != null;
|
||||
bool get isUserFilled => rootUser != null;
|
||||
bool get isServerCreated => hetznerServer != null;
|
||||
|
||||
bool get isFullyInitilized => _fulfilementList.every((el) => el!);
|
||||
int get progress => _fulfilementList.where((el) => el!).length;
|
||||
|
||||
int get porgressBar {
|
||||
if (progress < 6) {
|
||||
return progress;
|
||||
} else if (progress < 10) {
|
||||
return 6;
|
||||
} else {
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
List<bool?> get _fulfilementList {
|
||||
var res = [
|
||||
isHetznerFilled,
|
||||
isCloudFlareFilled,
|
||||
isBackblazeFilled,
|
||||
isDomainFilled,
|
||||
isUserFilled,
|
||||
isServerCreated,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
isServerResetedSecondTime,
|
||||
];
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class TimerState extends AppConfigNotFinished {
|
||||
TimerState({
|
||||
required this.dataState,
|
||||
this.timerStart,
|
||||
this.duration,
|
||||
required bool isLoading,
|
||||
}) : super(
|
||||
hetznerKey: dataState.hetznerKey,
|
||||
cloudFlareKey: dataState.cloudFlareKey,
|
||||
backblazeCredential: dataState.backblazeCredential,
|
||||
cloudFlareDomain: dataState.cloudFlareDomain,
|
||||
rootUser: dataState.rootUser,
|
||||
hetznerServer: dataState.hetznerServer,
|
||||
isServerStarted: dataState.isServerStarted,
|
||||
isServerResetedFirstTime: dataState.isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: dataState.isServerResetedSecondTime,
|
||||
isLoading: isLoading,
|
||||
dnsMatches: dataState.dnsMatches,
|
||||
);
|
||||
|
||||
final AppConfigNotFinished dataState;
|
||||
final DateTime? timerStart;
|
||||
final Duration? duration;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
dataState,
|
||||
timerStart,
|
||||
duration,
|
||||
];
|
||||
}
|
||||
|
||||
class AppConfigNotFinished extends AppConfigState {
|
||||
final bool isLoading;
|
||||
final Map<String, bool>? dnsMatches;
|
||||
|
||||
AppConfigNotFinished({
|
||||
String? hetznerKey,
|
||||
String? cloudFlareKey,
|
||||
BackblazeCredential? backblazeCredential,
|
||||
CloudFlareDomain? cloudFlareDomain,
|
||||
User? rootUser,
|
||||
HetznerServerDetails? hetznerServer,
|
||||
required bool isServerStarted,
|
||||
required bool isServerResetedFirstTime,
|
||||
required bool isServerResetedSecondTime,
|
||||
required this.isLoading,
|
||||
required this.dnsMatches,
|
||||
}) : super(
|
||||
hetznerKey: hetznerKey,
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
backblazeCredential: backblazeCredential,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
rootUser: rootUser,
|
||||
hetznerServer: hetznerServer,
|
||||
isServerStarted: isServerStarted,
|
||||
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
cloudFlareDomain,
|
||||
rootUser,
|
||||
hetznerServer,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
isLoading,
|
||||
dnsMatches,
|
||||
];
|
||||
|
||||
AppConfigNotFinished copyWith({
|
||||
String? hetznerKey,
|
||||
String? cloudFlareKey,
|
||||
BackblazeCredential? backblazeCredential,
|
||||
CloudFlareDomain? cloudFlareDomain,
|
||||
User? rootUser,
|
||||
HetznerServerDetails? hetznerServer,
|
||||
bool? isServerStarted,
|
||||
bool? isServerResetedFirstTime,
|
||||
bool? isServerResetedSecondTime,
|
||||
bool? isLoading,
|
||||
Map<String, bool>? dnsMatches,
|
||||
}) =>
|
||||
AppConfigNotFinished(
|
||||
hetznerKey: hetznerKey ?? this.hetznerKey,
|
||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||
cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
|
||||
rootUser: rootUser ?? this.rootUser,
|
||||
hetznerServer: hetznerServer ?? this.hetznerServer,
|
||||
isServerStarted: isServerStarted ?? this.isServerStarted,
|
||||
isServerResetedFirstTime:
|
||||
isServerResetedFirstTime ?? this.isServerResetedFirstTime,
|
||||
isServerResetedSecondTime:
|
||||
isServerResetedSecondTime ?? this.isServerResetedSecondTime,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
dnsMatches: dnsMatches ?? this.dnsMatches,
|
||||
);
|
||||
|
||||
AppConfigFinished finish() => AppConfigFinished(
|
||||
hetznerKey: hetznerKey!,
|
||||
cloudFlareKey: cloudFlareKey!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
cloudFlareDomain: cloudFlareDomain!,
|
||||
rootUser: rootUser!,
|
||||
hetznerServer: hetznerServer!,
|
||||
isServerStarted: isServerStarted,
|
||||
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||
);
|
||||
}
|
||||
|
||||
class AppConfigEmpty extends AppConfigNotFinished {
|
||||
AppConfigEmpty()
|
||||
: super(
|
||||
hetznerKey: null,
|
||||
cloudFlareKey: null,
|
||||
backblazeCredential: null,
|
||||
cloudFlareDomain: null,
|
||||
rootUser: null,
|
||||
hetznerServer: null,
|
||||
isServerStarted: false,
|
||||
isServerResetedFirstTime: false,
|
||||
isServerResetedSecondTime: false,
|
||||
isLoading: false,
|
||||
dnsMatches: null,
|
||||
);
|
||||
}
|
||||
|
||||
class AppConfigFinished extends AppConfigState {
|
||||
const AppConfigFinished({
|
||||
required String hetznerKey,
|
||||
required String cloudFlareKey,
|
||||
required BackblazeCredential backblazeCredential,
|
||||
required CloudFlareDomain cloudFlareDomain,
|
||||
required User rootUser,
|
||||
required HetznerServerDetails hetznerServer,
|
||||
required bool isServerStarted,
|
||||
required bool isServerResetedFirstTime,
|
||||
required bool isServerResetedSecondTime,
|
||||
}) : super(
|
||||
hetznerKey: hetznerKey,
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
backblazeCredential: backblazeCredential,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
rootUser: rootUser,
|
||||
hetznerServer: hetznerServer,
|
||||
isServerStarted: isServerStarted,
|
||||
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
cloudFlareDomain,
|
||||
rootUser,
|
||||
hetznerServer,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
];
|
||||
}
|
|
@ -1,32 +1,33 @@
|
|||
import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
export 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
part 'authentication_dependend_state.dart';
|
||||
|
||||
abstract class AppConfigDependendCubit<T extends AppConfigDependendState>
|
||||
extends Cubit<T> {
|
||||
AppConfigDependendCubit(
|
||||
this.appConfigCubit,
|
||||
T initState,
|
||||
abstract class ServerInstallationDependendCubit<
|
||||
T extends ServerInstallationDependendState> extends Cubit<T> {
|
||||
ServerInstallationDependendCubit(
|
||||
this.serverInstallationCubit,
|
||||
final T initState,
|
||||
) : super(initState) {
|
||||
authCubitSubscription = appConfigCubit.stream.listen(checkAuthStatus);
|
||||
checkAuthStatus(appConfigCubit.state);
|
||||
authCubitSubscription =
|
||||
serverInstallationCubit.stream.listen(checkAuthStatus);
|
||||
checkAuthStatus(serverInstallationCubit.state);
|
||||
}
|
||||
|
||||
void checkAuthStatus(AppConfigState state) {
|
||||
if (state is AppConfigFinished) {
|
||||
void checkAuthStatus(final ServerInstallationState state) {
|
||||
if (state is ServerInstallationFinished) {
|
||||
load();
|
||||
} else if (state is AppConfigEmpty) {
|
||||
} else if (state is ServerInstallationEmpty) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
late StreamSubscription authCubitSubscription;
|
||||
final AppConfigCubit appConfigCubit;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
void load();
|
||||
void clear();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
part of 'authentication_dependend_cubit.dart';
|
||||
|
||||
abstract class AppConfigDependendState extends Equatable {
|
||||
const AppConfigDependendState();
|
||||
abstract class ServerInstallationDependendState extends Equatable {
|
||||
const ServerInstallationDependendState();
|
||||
}
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'app_settings_state.dart';
|
||||
|
||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||
AppSettingsCubit({
|
||||
required bool isDarkModeOn,
|
||||
required bool isOnbordingShowing,
|
||||
required final bool isDarkModeOn,
|
||||
required final bool isOnboardingShowing,
|
||||
}) : super(
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnbordingShowing: isOnbordingShowing,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
|
||||
Box box = Hive.box(BNames.appSettings);
|
||||
Box box = Hive.box(BNames.appSettingsBox);
|
||||
|
||||
void load() {
|
||||
bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||
bool? isOnbordingShowing = box.get(BNames.isOnbordingShowing);
|
||||
emit(state.copyWith(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnbordingShowing: isOnbordingShowing,
|
||||
));
|
||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateDarkMode({required bool isDarkModeOn}) {
|
||||
void updateDarkMode({required final bool isDarkModeOn}) {
|
||||
box.put(BNames.isDarkModeOn, isDarkModeOn);
|
||||
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
||||
}
|
||||
|
||||
void turnOffOnboarding() {
|
||||
box.put(BNames.isOnbordingShowing, false);
|
||||
box.put(BNames.isOnboardingShowing, false);
|
||||
|
||||
emit(state.copyWith(isOnbordingShowing: false));
|
||||
emit(state.copyWith(isOnboardingShowing: false));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,21 @@ part of 'app_settings_cubit.dart';
|
|||
class AppSettingsState extends Equatable {
|
||||
const AppSettingsState({
|
||||
required this.isDarkModeOn,
|
||||
required this.isOnbordingShowing,
|
||||
required this.isOnboardingShowing,
|
||||
});
|
||||
|
||||
final bool isDarkModeOn;
|
||||
final bool isOnbordingShowing;
|
||||
final bool isOnboardingShowing;
|
||||
|
||||
AppSettingsState copyWith({isDarkModeOn, isOnbordingShowing}) =>
|
||||
AppSettingsState copyWith({
|
||||
final bool? isDarkModeOn,
|
||||
final bool? isOnboardingShowing,
|
||||
}) =>
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||
isOnbordingShowing: isOnbordingShowing ?? this.isOnbordingShowing,
|
||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object> get props => [isDarkModeOn, isOnbordingShowing];
|
||||
List<Object> get props => [isDarkModeOn, isOnboardingShowing];
|
||||
}
|
||||
|
|
|
@ -5,78 +5,95 @@ import 'package:selfprivacy/config/get_it_config.dart';
|
|||
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/json/backup.dart';
|
||||
|
||||
part 'backups_state.dart';
|
||||
|
||||
class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
||||
BackupsCubit(AppConfigCubit appConfigCubit)
|
||||
: super(appConfigCubit, BackupsState(preventActions: true));
|
||||
class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
|
||||
BackupsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const BackupsState(preventActions: true),
|
||||
);
|
||||
|
||||
final api = ServerApi();
|
||||
final backblaze = BackblazeApi();
|
||||
final ServerApi api = ServerApi();
|
||||
final BackblazeApi backblaze = BackblazeApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (appConfigCubit.state is AppConfigFinished) {
|
||||
final bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
if (bucket == null) {
|
||||
emit(BackupsState(
|
||||
isInitialized: false, preventActions: false, refreshing: false));
|
||||
emit(
|
||||
const BackupsState(
|
||||
isInitialized: false,
|
||||
preventActions: false,
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final status = await api.getBackupStatus();
|
||||
final BackupStatus status = await api.getBackupStatus();
|
||||
switch (status.status) {
|
||||
case BackupStatusEnum.noKey:
|
||||
case BackupStatusEnum.notInitialized:
|
||||
emit(BackupsState(
|
||||
backups: [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshing: false,
|
||||
));
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.initializing:
|
||||
emit(BackupsState(
|
||||
backups: [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshTimer: Duration(seconds: 10),
|
||||
refreshing: false,
|
||||
));
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: const [],
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: 0,
|
||||
status: status.status,
|
||||
refreshTimer: const Duration(seconds: 10),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.initialized:
|
||||
case BackupStatusEnum.error:
|
||||
final backups = await api.getBackups();
|
||||
emit(BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshing: false,
|
||||
));
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: false,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BackupStatusEnum.backingUp:
|
||||
case BackupStatusEnum.restoring:
|
||||
final backups = await api.getBackups();
|
||||
emit(BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: true,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshTimer: Duration(seconds: 5),
|
||||
refreshing: false,
|
||||
));
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
emit(
|
||||
BackupsState(
|
||||
backups: backups,
|
||||
isInitialized: true,
|
||||
preventActions: true,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage ?? '',
|
||||
refreshTimer: const Duration(seconds: 5),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
emit(BackupsState());
|
||||
emit(const BackupsState());
|
||||
}
|
||||
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
|
||||
}
|
||||
|
@ -85,22 +102,23 @@ class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
|||
|
||||
Future<void> createBucket() async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
final domain = appConfigCubit.state.cloudFlareDomain!.domainName
|
||||
final String domain = serverInstallationCubit.state.serverDomain!.domainName
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
final serverId = appConfigCubit.state.hetznerServer!.id;
|
||||
var bucketName = 'selfprivacy-$domain-$serverId';
|
||||
final int serverId = serverInstallationCubit.state.serverDetails!.id;
|
||||
String bucketName = 'selfprivacy-$domain-$serverId';
|
||||
// If bucket name is too long, shorten it
|
||||
if (bucketName.length > 49) {
|
||||
bucketName = bucketName.substring(0, 49);
|
||||
}
|
||||
final bucketId = await backblaze.createBucket(bucketName);
|
||||
final String bucketId = await backblaze.createBucket(bucketName);
|
||||
|
||||
final key = await backblaze.createKey(bucketId);
|
||||
final bucket = BackblazeBucket(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId);
|
||||
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
|
||||
final BackblazeBucket bucket = BackblazeBucket(
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
applicationKey: key.applicationKey,
|
||||
applicationKeyId: key.applicationKeyId,
|
||||
);
|
||||
|
||||
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
|
||||
await api.uploadBackblazeConfig(bucket);
|
||||
|
@ -111,7 +129,7 @@ class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
|||
|
||||
Future<void> reuploadKey() async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
final bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
|
||||
if (bucket == null) {
|
||||
emit(state.copyWith(isInitialized: false));
|
||||
} else {
|
||||
|
@ -121,32 +139,35 @@ class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
|||
}
|
||||
}
|
||||
|
||||
Duration refreshTimeFromState(BackupStatusEnum status) {
|
||||
Duration refreshTimeFromState(final BackupStatusEnum status) {
|
||||
switch (status) {
|
||||
case BackupStatusEnum.backingUp:
|
||||
case BackupStatusEnum.restoring:
|
||||
return Duration(seconds: 5);
|
||||
return const Duration(seconds: 5);
|
||||
case BackupStatusEnum.initializing:
|
||||
return Duration(seconds: 10);
|
||||
return const Duration(seconds: 10);
|
||||
default:
|
||||
return Duration(seconds: 60);
|
||||
return const Duration(seconds: 60);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateBackups({bool useTimer = false}) async {
|
||||
Future<void> updateBackups({final bool useTimer = false}) async {
|
||||
emit(state.copyWith(refreshing: true));
|
||||
final backups = await api.getBackups();
|
||||
final status = await api.getBackupStatus();
|
||||
emit(state.copyWith(
|
||||
backups: backups,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage,
|
||||
refreshTimer: refreshTimeFromState(status.status),
|
||||
refreshing: false,
|
||||
));
|
||||
if (useTimer)
|
||||
final List<Backup> backups = await api.getBackups();
|
||||
final BackupStatus status = await api.getBackupStatus();
|
||||
emit(
|
||||
state.copyWith(
|
||||
backups: backups,
|
||||
progress: status.progress,
|
||||
status: status.status,
|
||||
error: status.errorMessage,
|
||||
refreshTimer: refreshTimeFromState(status.status),
|
||||
refreshing: false,
|
||||
),
|
||||
);
|
||||
if (useTimer) {
|
||||
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> forceUpdateBackups() async {
|
||||
|
@ -164,7 +185,7 @@ class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
|||
emit(state.copyWith(preventActions: false));
|
||||
}
|
||||
|
||||
Future<void> restoreBackup(String backupId) async {
|
||||
Future<void> restoreBackup(final String backupId) async {
|
||||
emit(state.copyWith(preventActions: true));
|
||||
await api.restoreBackup(backupId);
|
||||
emit(state.copyWith(preventActions: false));
|
||||
|
@ -172,6 +193,6 @@ class BackupsCubit extends AppConfigDependendCubit<BackupsState> {
|
|||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(BackupsState());
|
||||
emit(const BackupsState());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
part of 'backups_cubit.dart';
|
||||
|
||||
class BackupsState extends AppConfigDependendState {
|
||||
class BackupsState extends ServerInstallationDependendState {
|
||||
const BackupsState({
|
||||
this.isInitialized = false,
|
||||
this.backups = const [],
|
||||
this.progress = 0.0,
|
||||
this.status = BackupStatusEnum.noKey,
|
||||
this.preventActions = true,
|
||||
this.error = "",
|
||||
this.error = '',
|
||||
this.refreshTimer = const Duration(seconds: 60),
|
||||
this.refreshing = true,
|
||||
});
|
||||
|
@ -34,14 +34,14 @@ class BackupsState extends AppConfigDependendState {
|
|||
];
|
||||
|
||||
BackupsState copyWith({
|
||||
bool? isInitialized,
|
||||
List<Backup>? backups,
|
||||
double? progress,
|
||||
BackupStatusEnum? status,
|
||||
bool? preventActions,
|
||||
String? error,
|
||||
Duration? refreshTimer,
|
||||
bool? refreshing,
|
||||
final bool? isInitialized,
|
||||
final List<Backup>? backups,
|
||||
final double? progress,
|
||||
final BackupStatusEnum? status,
|
||||
final bool? preventActions,
|
||||
final String? error,
|
||||
final Duration? refreshTimer,
|
||||
final bool? refreshing,
|
||||
}) =>
|
||||
BackupsState(
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
|
|
78
lib/logic/cubit/devices/devices_cubit.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.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) {
|
||||
final List<ApiToken>? devices = await _getApiTokens();
|
||||
if (devices != null) {
|
||||
emit(ApiDevicesState(devices, LoadingStatus.success));
|
||||
} else {
|
||||
emit(const ApiDevicesState([], LoadingStatus.error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
emit(const ApiDevicesState([], LoadingStatus.refreshing));
|
||||
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 ApiResponse<List<ApiToken>> response = await api.getApiTokens();
|
||||
if (response.isSuccess) {
|
||||
return response.data;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteDevice(final ApiToken device) async {
|
||||
final ApiResponse<void> response = await api.deleteApiToken(device.name);
|
||||
if (response.isSuccess) {
|
||||
emit(
|
||||
ApiDevicesState(
|
||||
state.devices.where((final d) => d.name != device.name).toList(),
|
||||
LoadingStatus.success,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar(response.errorMessage ?? 'Error deleting device');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getNewDeviceKey() async {
|
||||
final ApiResponse<String> response = await api.createDeviceToken();
|
||||
if (response.isSuccess) {
|
||||
return response.data;
|
||||
} else {
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
response.errorMessage ?? 'Error getting new device key',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(const ApiDevicesState.initial());
|
||||
}
|
||||
}
|
34
lib/logic/cubit/devices/devices_state.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
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,91 +1,109 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/dns_records.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||
|
||||
import '../../api_maps/cloudflare.dart';
|
||||
import '../../api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
|
||||
part 'dns_records_state.dart';
|
||||
|
||||
class DnsRecordsCubit extends AppConfigDependendCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit(AppConfigCubit appConfigCubit)
|
||||
: super(appConfigCubit,
|
||||
DnsRecordsState(dnsState: DnsRecordsStatus.refreshing));
|
||||
class DnsRecordsCubit
|
||||
extends ServerInstallationDependendCubit<DnsRecordsState> {
|
||||
DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
serverInstallationCubit,
|
||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
||||
);
|
||||
|
||||
final api = ServerApi();
|
||||
final cloudflare = CloudflareApi();
|
||||
final ServerApi api = ServerApi();
|
||||
final CloudflareApi cloudflare = CloudflareApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
emit(DnsRecordsState(
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsState: DnsRecordsStatus.refreshing,
|
||||
dnsRecords: _getDesiredDnsRecords(
|
||||
appConfigCubit.state.cloudFlareDomain?.domainName, "", "")));
|
||||
serverInstallationCubit.state.serverDomain?.domainName,
|
||||
'',
|
||||
'',
|
||||
),
|
||||
),
|
||||
);
|
||||
print('Loading DNS status');
|
||||
if (appConfigCubit.state is AppConfigFinished) {
|
||||
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
|
||||
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final String? ipAddress =
|
||||
serverInstallationCubit.state.serverDetails?.ip4;
|
||||
if (domain != null && ipAddress != null) {
|
||||
final List<DnsRecord> records =
|
||||
await cloudflare.getDnsRecords(cloudFlareDomain: domain);
|
||||
final dkimPublicKey = await api.getDkim();
|
||||
final desiredRecords =
|
||||
final String? dkimPublicKey = await api.getDkim();
|
||||
final List<DesiredDnsRecord> desiredRecords =
|
||||
_getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey);
|
||||
List<DesiredDnsRecord> foundRecords = [];
|
||||
for (final record in desiredRecords) {
|
||||
final List<DesiredDnsRecord> foundRecords = [];
|
||||
for (final DesiredDnsRecord record in desiredRecords) {
|
||||
if (record.description ==
|
||||
'providers.domain.record_description.dkim') {
|
||||
final foundRecord = records.firstWhere(
|
||||
(r) => r.name == record.name && r.type == record.type,
|
||||
orElse: () => DnsRecord(
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
content: '',
|
||||
ttl: 800,
|
||||
proxied: false));
|
||||
final DnsRecord foundRecord = records.firstWhere(
|
||||
(final r) => r.name == record.name && r.type == record.type,
|
||||
orElse: () => DnsRecord(
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
content: '',
|
||||
ttl: 800,
|
||||
proxied: false,
|
||||
),
|
||||
);
|
||||
// remove all spaces and tabulators from
|
||||
// the foundRecord.content and the record.content
|
||||
// to compare them
|
||||
final foundContent =
|
||||
final String? foundContent =
|
||||
foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
|
||||
final content = record.content.replaceAll(RegExp(r'\s+'), '');
|
||||
final String content =
|
||||
record.content.replaceAll(RegExp(r'\s+'), '');
|
||||
if (foundContent == content) {
|
||||
foundRecords.add(record.copyWith(isSatisfied: true));
|
||||
} else {
|
||||
foundRecords.add(record.copyWith(isSatisfied: false));
|
||||
}
|
||||
} else {
|
||||
if (records.any((r) =>
|
||||
r.name == record.name &&
|
||||
r.type == record.type &&
|
||||
r.content == record.content)) {
|
||||
if (records.any(
|
||||
(final r) =>
|
||||
r.name == record.name &&
|
||||
r.type == record.type &&
|
||||
r.content == record.content,
|
||||
)) {
|
||||
foundRecords.add(record.copyWith(isSatisfied: true));
|
||||
} else {
|
||||
foundRecords.add(record.copyWith(isSatisfied: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(DnsRecordsState(
|
||||
dnsRecords: foundRecords,
|
||||
dnsState: foundRecords.any((r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
));
|
||||
emit(
|
||||
DnsRecordsState(
|
||||
dnsRecords: foundRecords,
|
||||
dnsState: foundRecords.any((final r) => r.isSatisfied == false)
|
||||
? DnsRecordsStatus.error
|
||||
: DnsRecordsStatus.good,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(DnsRecordsState());
|
||||
emit(const DnsRecordsState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(Change<DnsRecordsState> change) {
|
||||
void onChange(final Change<DnsRecordsState> change) {
|
||||
// print(change);
|
||||
super.onChange(change);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
emit(DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
||||
emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error));
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
|
@ -95,18 +113,23 @@ class DnsRecordsCubit extends AppConfigDependendCubit<DnsRecordsState> {
|
|||
|
||||
Future<void> fix() async {
|
||||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
|
||||
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
|
||||
final dkimPublicKey = await api.getDkim();
|
||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||
final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4;
|
||||
final String? dkimPublicKey = await api.getDkim();
|
||||
await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!);
|
||||
await cloudflare.createMultipleDnsRecords(
|
||||
cloudFlareDomain: domain, ip4: ipAddress);
|
||||
await cloudflare.setDkim(dkimPublicKey, domain);
|
||||
cloudFlareDomain: domain,
|
||||
ip4: ipAddress,
|
||||
);
|
||||
await cloudflare.setDkim(dkimPublicKey ?? '', domain);
|
||||
await load();
|
||||
}
|
||||
|
||||
List<DesiredDnsRecord> _getDesiredDnsRecords(
|
||||
String? domainName, String? ipAddress, String? dkimPublicKey) {
|
||||
final String? domainName,
|
||||
final String? ipAddress,
|
||||
final String? dkimPublicKey,
|
||||
) {
|
||||
if (domainName == null || ipAddress == null || dkimPublicKey == null) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ enum DnsRecordsCategory {
|
|||
other,
|
||||
}
|
||||
|
||||
class DnsRecordsState extends AppConfigDependendState {
|
||||
class DnsRecordsState extends ServerInstallationDependendState {
|
||||
const DnsRecordsState({
|
||||
this.dnsState = DnsRecordsStatus.uninitialized,
|
||||
this.dnsRecords = const [],
|
||||
|
@ -29,21 +29,20 @@ class DnsRecordsState extends AppConfigDependendState {
|
|||
];
|
||||
|
||||
DnsRecordsState copyWith({
|
||||
DnsRecordsStatus? dnsState,
|
||||
List<DesiredDnsRecord>? dnsRecords,
|
||||
}) {
|
||||
return DnsRecordsState(
|
||||
dnsState: dnsState ?? this.dnsState,
|
||||
dnsRecords: dnsRecords ?? this.dnsRecords,
|
||||
);
|
||||
}
|
||||
final DnsRecordsStatus? dnsState,
|
||||
final List<DesiredDnsRecord>? dnsRecords,
|
||||
}) =>
|
||||
DnsRecordsState(
|
||||
dnsState: dnsState ?? this.dnsState,
|
||||
dnsRecords: dnsRecords ?? this.dnsRecords,
|
||||
);
|
||||
}
|
||||
|
||||
class DesiredDnsRecord {
|
||||
const DesiredDnsRecord({
|
||||
required this.name,
|
||||
this.type = "A",
|
||||
required this.content,
|
||||
this.type = 'A',
|
||||
this.description = '',
|
||||
this.category = DnsRecordsCategory.services,
|
||||
this.isSatisfied = false,
|
||||
|
@ -57,20 +56,19 @@ class DesiredDnsRecord {
|
|||
final bool isSatisfied;
|
||||
|
||||
DesiredDnsRecord copyWith({
|
||||
String? name,
|
||||
String? type,
|
||||
String? content,
|
||||
String? description,
|
||||
DnsRecordsCategory? category,
|
||||
bool? isSatisfied,
|
||||
}) {
|
||||
return DesiredDnsRecord(
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
content: content ?? this.content,
|
||||
description: description ?? this.description,
|
||||
category: category ?? this.category,
|
||||
isSatisfied: isSatisfied ?? this.isSatisfied,
|
||||
);
|
||||
}
|
||||
final String? name,
|
||||
final String? type,
|
||||
final String? content,
|
||||
final String? description,
|
||||
final DnsRecordsCategory? category,
|
||||
final bool? isSatisfied,
|
||||
}) =>
|
||||
DesiredDnsRecord(
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
content: content ?? this.content,
|
||||
description: description ?? this.description,
|
||||
category: category ?? this.category,
|
||||
isSatisfied: isSatisfied ?? this.isSatisfied,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,21 +16,26 @@ class FieldCubitFactory {
|
|||
/// - Must not be a reserved root login
|
||||
/// - Must be unique
|
||||
FieldCubit<String> createUserLoginField() {
|
||||
final userAllowedRegExp = RegExp(r"^[a-z_][a-z0-9_]+$");
|
||||
const userMaxLength = 31;
|
||||
final RegExp userAllowedRegExp = RegExp(r'^[a-z_][a-z0-9_]+$');
|
||||
const int userMaxLength = 31;
|
||||
return FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
ValidationModel<String>(
|
||||
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
|
||||
(final String s) => s.toLowerCase() == 'root',
|
||||
'validations.root_name'.tr(),
|
||||
),
|
||||
ValidationModel(
|
||||
(login) => context.read<UsersCubit>().state.isLoginRegistered(login),
|
||||
(final String login) =>
|
||||
context.read<UsersCubit>().state.isLoginRegistered(login),
|
||||
'validations.user_already_exist'.tr(),
|
||||
),
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
LengthStringLongerValidation(userMaxLength),
|
||||
ValidationModel<String>((s) => !userAllowedRegExp.hasMatch(s),
|
||||
'validations.invalid_format'.tr()),
|
||||
ValidationModel<String>(
|
||||
(final String s) => !userAllowedRegExp.hasMatch(s),
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -40,17 +45,25 @@ class FieldCubitFactory {
|
|||
/// - Must fail on the regural expression of invalid matches: [\n\r\s]+
|
||||
/// - Must not be empty
|
||||
FieldCubit<String> createUserPasswordField() {
|
||||
var passwordForbiddenRegExp = RegExp(r"[\n\r\s]+");
|
||||
final RegExp passwordForbiddenRegExp = RegExp(r'[\n\r\s]+');
|
||||
return FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
(password) => passwordForbiddenRegExp.hasMatch(password),
|
||||
'validations.invalid_format'.tr()),
|
||||
passwordForbiddenRegExp.hasMatch,
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
FieldCubit<String> createRequiredStringField() => FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
],
|
||||
);
|
||||
|
||||
final BuildContext context;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class BackblazeFormCubit extends FormCubit {
|
||||
BackblazeFormCubit(this.initializingCubit) {
|
||||
BackblazeFormCubit(this.serverInstallationCubit) {
|
||||
//var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
|
||||
keyId = FieldCubit(
|
||||
initalValue: '',
|
||||
|
@ -27,13 +27,13 @@ class BackblazeFormCubit extends FormCubit {
|
|||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
initializingCubit.setBackblazeKey(
|
||||
serverInstallationCubit.setBackblazeKey(
|
||||
keyId.state.value,
|
||||
applicationKey.state.value,
|
||||
);
|
||||
}
|
||||
|
||||
final AppConfigCubit initializingCubit;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
late final FieldCubit<String> keyId;
|
||||
late final FieldCubit<String> applicationKey;
|
||||
|
@ -41,16 +41,17 @@ class BackblazeFormCubit extends FormCubit {
|
|||
@override
|
||||
FutureOr<bool> asyncValidation() async {
|
||||
late bool isKeyValid;
|
||||
BackblazeApi apiClient = BackblazeApi(isWithToken: false);
|
||||
final BackblazeApi apiClient = BackblazeApi(isWithToken: false);
|
||||
|
||||
try {
|
||||
String encodedApiKey = encodedBackblazeKey(
|
||||
final String encodedApiKey = encodedBackblazeKey(
|
||||
keyId.state.value,
|
||||
applicationKey.state.value,
|
||||
);
|
||||
isKeyValid = await apiClient.isValid(encodedApiKey);
|
||||
} catch (e) {
|
||||
addError(e);
|
||||
isKeyValid = false;
|
||||
}
|
||||
|
||||
if (!isKeyValid) {
|
|
@ -3,18 +3,20 @@ import 'dart:async';
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||
|
||||
class CloudFlareFormCubit extends FormCubit {
|
||||
CloudFlareFormCubit(this.initializingCubit) {
|
||||
var regExp = RegExp(r"\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
|
||||
final RegExp regExp = RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
|
||||
apiKey = FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||
regExp.hasMatch,
|
||||
'validations.key_format'.tr(),
|
||||
),
|
||||
LengthStringNotEqualValidation(40)
|
||||
],
|
||||
);
|
||||
|
@ -27,14 +29,14 @@ class CloudFlareFormCubit extends FormCubit {
|
|||
initializingCubit.setCloudflareKey(apiKey.state.value);
|
||||
}
|
||||
|
||||
final AppConfigCubit initializingCubit;
|
||||
final ServerInstallationCubit initializingCubit;
|
||||
|
||||
late final FieldCubit<String> apiKey;
|
||||
|
||||
@override
|
||||
FutureOr<bool> asyncValidation() async {
|
||||
late bool isKeyValid;
|
||||
CloudflareApi apiClient = CloudflareApi(isWithToken: false);
|
||||
final CloudflareApi apiClient = CloudflareApi(isWithToken: false);
|
||||
|
||||
try {
|
||||
isKeyValid = await apiClient.isValid(apiKey.state.value);
|
||||
|
@ -48,9 +50,4 @@ class CloudFlareFormCubit extends FormCubit {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
|
||||
class DomainSetupCubit extends Cubit<DomainSetupState> {
|
||||
DomainSetupCubit(this.initializingCubit) : super(Initial());
|
||||
DomainSetupCubit(this.serverInstallationCubit) : super(Initial());
|
||||
|
||||
final AppConfigCubit initializingCubit;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
Future<void> load() async {
|
||||
emit(Loading(LoadingTypes.loadingDomain));
|
||||
var api = CloudflareApi();
|
||||
final CloudflareApi api = CloudflareApi();
|
||||
|
||||
var list = await api.domainList();
|
||||
final List<String> list = await api.domainList();
|
||||
if (list.isEmpty) {
|
||||
emit(Empty());
|
||||
} else if (list.length == 1) {
|
||||
|
@ -23,25 +23,24 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
return super.close();
|
||||
}
|
||||
Future<void> close() => super.close();
|
||||
|
||||
Future<void> saveDomain() async {
|
||||
assert(state is Loaded, 'wrong state');
|
||||
var domainName = (state as Loaded).domain;
|
||||
var api = CloudflareApi();
|
||||
final String domainName = (state as Loaded).domain;
|
||||
final CloudflareApi api = CloudflareApi();
|
||||
|
||||
emit(Loading(LoadingTypes.saving));
|
||||
|
||||
var zoneId = await api.getZoneId(domainName);
|
||||
final String zoneId = await api.getZoneId(domainName);
|
||||
|
||||
var domain = CloudFlareDomain(
|
||||
final ServerDomain domain = ServerDomain(
|
||||
domainName: domainName,
|
||||
zoneId: zoneId,
|
||||
provider: DnsProvider.cloudflare,
|
||||
);
|
||||
|
||||
initializingCubit.setDomain(domain);
|
||||
serverInstallationCubit.setDomain(domain);
|
||||
emit(DomainSet());
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +61,8 @@ class Loading extends DomainSetupState {
|
|||
enum LoadingTypes { loadingDomain, saving }
|
||||
|
||||
class Loaded extends DomainSetupState {
|
||||
final String domain;
|
||||
|
||||
Loaded(this.domain);
|
||||
final String domain;
|
||||
}
|
||||
|
||||
class DomainSet extends DomainSetupState {}
|
|
@ -3,18 +3,20 @@ import 'dart:async';
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||
|
||||
class HetznerFormCubit extends FormCubit {
|
||||
HetznerFormCubit(this.initializingCubit) {
|
||||
var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
|
||||
HetznerFormCubit(this.serverInstallationCubit) {
|
||||
final RegExp regExp = RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
|
||||
apiKey = FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>(
|
||||
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||
regExp.hasMatch,
|
||||
'validations.key_format'.tr(),
|
||||
),
|
||||
LengthStringNotEqualValidation(64)
|
||||
],
|
||||
);
|
||||
|
@ -24,17 +26,17 @@ class HetznerFormCubit extends FormCubit {
|
|||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
initializingCubit.setHetznerKey(apiKey.state.value);
|
||||
serverInstallationCubit.setHetznerKey(apiKey.state.value);
|
||||
}
|
||||
|
||||
final AppConfigCubit initializingCubit;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
late final FieldCubit<String> apiKey;
|
||||
|
||||
@override
|
||||
FutureOr<bool> asyncValidation() async {
|
||||
late bool isKeyValid;
|
||||
HetznerApi apiClient = HetznerApi(isWithToken: false);
|
||||
final HetznerApi apiClient = HetznerApi(isWithToken: false);
|
||||
|
||||
try {
|
||||
isKeyValid = await apiClient.isValid(apiKey.state.value);
|
|
@ -1,13 +1,15 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
class RootUserFormCubit extends FormCubit {
|
||||
RootUserFormCubit(
|
||||
this.initializingCubit, final FieldCubitFactory fieldFactory) {
|
||||
this.serverInstallationCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
) {
|
||||
userName = fieldFactory.createUserLoginField();
|
||||
password = fieldFactory.createUserPasswordField();
|
||||
|
||||
|
@ -18,14 +20,14 @@ class RootUserFormCubit extends FormCubit {
|
|||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
var user = User(
|
||||
final User user = User(
|
||||
login: userName.state.value,
|
||||
password: password.state.value,
|
||||
);
|
||||
initializingCubit.setRootUser(user);
|
||||
serverInstallationCubit.setRootUser(user);
|
||||
}
|
||||
|
||||
final AppConfigCubit initializingCubit;
|
||||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
late final FieldCubit<String> userName;
|
||||
late final FieldCubit<String> password;
|
|
@ -0,0 +1,35 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||
|
||||
class RecoveryDeviceFormCubit extends FormCubit {
|
||||
RecoveryDeviceFormCubit(
|
||||
this.installationCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
this.recoveryMethod,
|
||||
) {
|
||||
tokenField = fieldFactory.createRequiredStringField();
|
||||
|
||||
super.addFields([tokenField]);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
late final String token;
|
||||
// Trim spaces and make lowercase
|
||||
if (recoveryMethod == ServerRecoveryMethods.recoveryKey ||
|
||||
recoveryMethod == ServerRecoveryMethods.newDeviceKey) {
|
||||
token = tokenField.state.value.trim().toLowerCase();
|
||||
} else {
|
||||
token = tokenField.state.value.trim();
|
||||
}
|
||||
|
||||
installationCubit.tryToRecover(token, recoveryMethod);
|
||||
}
|
||||
|
||||
final ServerInstallationCubit installationCubit;
|
||||
late final FieldCubit<String> tokenField;
|
||||
final ServerRecoveryMethods recoveryMethod;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||
|
||||
class RecoveryDomainFormCubit extends FormCubit {
|
||||
RecoveryDomainFormCubit(
|
||||
this.initializingCubit,
|
||||
final FieldCubitFactory fieldFactory,
|
||||
) {
|
||||
serverDomainField = fieldFactory.createRequiredStringField();
|
||||
|
||||
super.addFields([serverDomainField]);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
initializingCubit
|
||||
.submitDomainForAccessRecovery(serverDomainField.state.value);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> asyncValidation() async {
|
||||
final ServerApi api = ServerApi(
|
||||
hasLogger: false,
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomainField.state.value,
|
||||
);
|
||||
|
||||
// API version doesn't require access token,
|
||||
// so if the entered domain is indeed valid
|
||||
// and the server by it is reachable, we will
|
||||
// be able to confirm the input
|
||||
|
||||
final bool domainValid = await api.getApiVersion() != null;
|
||||
if (!domainValid) {
|
||||
serverDomainField.setError('recovering.domain_recover_error'.tr());
|
||||
}
|
||||
|
||||
return domainValid;
|
||||
}
|
||||
|
||||
FutureOr<void> setCustomError(final String error) {
|
||||
serverDomainField.setError(error);
|
||||
}
|
||||
|
||||
final ServerInstallationCubit initializingCubit;
|
||||
late final FieldCubit<String> serverDomainField;
|
||||
}
|
|
@ -4,29 +4,34 @@ import 'package:cubit_form/cubit_form.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
class SshFormCubit extends FormCubit {
|
||||
SshFormCubit({
|
||||
required this.jobsCubit,
|
||||
required this.user,
|
||||
}) {
|
||||
var keyRegExp = RegExp(
|
||||
r"^(ssh-rsa AAAAB3NzaC1yc2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5)[0-9A-Za-z+/]+[=]{0,3}( .*)?$");
|
||||
final RegExp keyRegExp = RegExp(
|
||||
r'^(ssh-rsa AAAAB3NzaC1yc2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5)[0-9A-Za-z+/]+[=]{0,3}( .*)?$',
|
||||
);
|
||||
|
||||
key = FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
ValidationModel(
|
||||
(newKey) => user.sshKeys.any((key) => key == newKey),
|
||||
(final String newKey) =>
|
||||
user.sshKeys.any((final String key) => key == newKey),
|
||||
'validations.key_already_exists'.tr(),
|
||||
),
|
||||
RequiredStringValidation('validations.required'.tr()),
|
||||
ValidationModel<String>((s) {
|
||||
print(s);
|
||||
print(keyRegExp.hasMatch(s));
|
||||
return !keyRegExp.hasMatch(s);
|
||||
}, 'validations.invalid_format'.tr()),
|
||||
ValidationModel<String>(
|
||||
(final String s) {
|
||||
print(s);
|
||||
print(keyRegExp.hasMatch(s));
|
||||
return !keyRegExp.hasMatch(s);
|
||||
},
|
||||
'validations.invalid_format'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -4,29 +4,30 @@ import 'package:cubit_form/cubit_form.dart';
|
|||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class UserFormCubit extends FormCubit {
|
||||
UserFormCubit({
|
||||
required this.jobsCubit,
|
||||
required FieldCubitFactory fieldFactory,
|
||||
User? user,
|
||||
required final FieldCubitFactory fieldFactory,
|
||||
final User? user,
|
||||
}) {
|
||||
var isEdit = user != null;
|
||||
final bool isEdit = user != null;
|
||||
|
||||
login = fieldFactory.createUserLoginField();
|
||||
login.setValue(isEdit ? user!.login : '');
|
||||
login.setValue(isEdit ? user.login : '');
|
||||
password = fieldFactory.createUserPasswordField();
|
||||
password.setValue(
|
||||
isEdit ? (user?.password ?? '') : StringGenerators.userPassword());
|
||||
isEdit ? (user.password ?? '') : StringGenerators.userPassword(),
|
||||
);
|
||||
|
||||
super.addFields([login, password]);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> onSubmit() {
|
||||
var user = User(
|
||||
final User user = User(
|
||||
login: login.state.value,
|
||||
password: password.state.value,
|
||||
);
|
||||
|
|
|
@ -2,27 +2,31 @@ import 'package:cubit_form/cubit_form.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
abstract class LengthStringValidation extends ValidationModel<String> {
|
||||
LengthStringValidation(bool Function(String) predicate, String errorMessage)
|
||||
: super(predicate, errorMessage);
|
||||
LengthStringValidation(super.predicate, super.errorMessage);
|
||||
|
||||
@override
|
||||
String? check(String value) {
|
||||
var length = value.length;
|
||||
var errorMessage = this.errorMassage.replaceAll("[]", length.toString());
|
||||
return test(value) ? errorMessage : null;
|
||||
String? check(final String val) {
|
||||
final int length = val.length;
|
||||
final String errorMessage =
|
||||
errorMassage.replaceAll('[]', length.toString());
|
||||
return test(val) ? errorMessage : null;
|
||||
}
|
||||
}
|
||||
|
||||
class LengthStringNotEqualValidation extends LengthStringValidation {
|
||||
/// String must be equal to [length]
|
||||
LengthStringNotEqualValidation(int length)
|
||||
: super((n) => n.length != length,
|
||||
'validations.length_not_equal'.tr(args: [length.toString()]));
|
||||
LengthStringNotEqualValidation(final int length)
|
||||
: super(
|
||||
(final n) => n.length != length,
|
||||
'validations.length_not_equal'.tr(args: [length.toString()]),
|
||||
);
|
||||
}
|
||||
|
||||
class LengthStringLongerValidation extends LengthStringValidation {
|
||||
/// String must be shorter than or equal to [length]
|
||||
LengthStringLongerValidation(int length)
|
||||
: super((n) => n.length > length,
|
||||
'validations.length_longer'.tr(args: [length.toString()]));
|
||||
LengthStringLongerValidation(final int length)
|
||||
: super(
|
||||
(final n) => n.length > length,
|
||||
'validations.length_longer'.tr(args: [length.toString()]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
||||
|
||||
import 'hetzner_metrics_repository.dart';
|
||||
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart';
|
||||
|
||||
part 'hetzner_metrics_state.dart';
|
||||
|
||||
class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
||||
HetznerMetricsCubit() : super(HetznerMetricsLoading(Period.day));
|
||||
HetznerMetricsCubit() : super(const HetznerMetricsLoading(Period.day));
|
||||
|
||||
final repository = HetznerMetricsRepository();
|
||||
final HetznerMetricsRepository repository = HetznerMetricsRepository();
|
||||
|
||||
Timer? timer;
|
||||
|
||||
close() {
|
||||
@override
|
||||
Future<void> close() {
|
||||
closeTimer();
|
||||
return super.close();
|
||||
}
|
||||
|
@ -27,7 +28,7 @@ class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
|||
}
|
||||
}
|
||||
|
||||
void changePeriod(Period period) async {
|
||||
void changePeriod(final Period period) async {
|
||||
closeTimer();
|
||||
emit(HetznerMetricsLoading(period));
|
||||
load(period);
|
||||
|
@ -37,8 +38,8 @@ class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
|||
load(state.period);
|
||||
}
|
||||
|
||||
void load(Period period) async {
|
||||
var newState = await repository.getMetrics(period);
|
||||
void load(final Period period) async {
|
||||
final HetznerMetricsLoaded newState = await repository.getMetrics(period);
|
||||
timer = Timer(
|
||||
Duration(seconds: newState.stepInSeconds.toInt()),
|
||||
() => load(newState.period),
|
||||
|
|
|
@ -2,40 +2,40 @@ import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
|||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
||||
|
||||
import 'hetzner_metrics_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart';
|
||||
|
||||
class HetznerMetricsRepository {
|
||||
Future<HetznerMetricsLoaded> getMetrics(Period period) async {
|
||||
var end = DateTime.now();
|
||||
Future<HetznerMetricsLoaded> getMetrics(final Period period) async {
|
||||
final DateTime end = DateTime.now();
|
||||
DateTime start;
|
||||
|
||||
switch (period) {
|
||||
case Period.hour:
|
||||
start = end.subtract(Duration(hours: 1));
|
||||
start = end.subtract(const Duration(hours: 1));
|
||||
break;
|
||||
case Period.day:
|
||||
start = end.subtract(Duration(days: 1));
|
||||
start = end.subtract(const Duration(days: 1));
|
||||
break;
|
||||
case Period.month:
|
||||
start = end.subtract(Duration(days: 15));
|
||||
start = end.subtract(const Duration(days: 15));
|
||||
break;
|
||||
}
|
||||
|
||||
var api = HetznerApi(hasLogger: true);
|
||||
final HetznerApi api = HetznerApi(hasLogger: true);
|
||||
|
||||
var results = await Future.wait([
|
||||
final List<Map<String, dynamic>> results = await Future.wait([
|
||||
api.getMetrics(start, end, 'cpu'),
|
||||
api.getMetrics(start, end, 'network'),
|
||||
]);
|
||||
|
||||
var cpuMetricsData = results[0]["metrics"];
|
||||
var networkMetricsData = results[1]["metrics"];
|
||||
final cpuMetricsData = results[0]['metrics'];
|
||||
final networkMetricsData = results[1]['metrics'];
|
||||
|
||||
return HetznerMetricsLoaded(
|
||||
period: period,
|
||||
start: start,
|
||||
end: end,
|
||||
stepInSeconds: cpuMetricsData["step"],
|
||||
stepInSeconds: cpuMetricsData['step'],
|
||||
cpu: timeSeriesSerializer(cpuMetricsData, 'cpu'),
|
||||
ppsIn: timeSeriesSerializer(networkMetricsData, 'network.0.pps.in'),
|
||||
ppsOut: timeSeriesSerializer(networkMetricsData, 'network.0.pps.out'),
|
||||
|
@ -50,7 +50,11 @@ class HetznerMetricsRepository {
|
|||
}
|
||||
|
||||
List<TimeSeriesData> timeSeriesSerializer(
|
||||
Map<String, dynamic> json, String type) {
|
||||
List list = json["time_series"][type]["values"];
|
||||
return list.map((el) => TimeSeriesData(el[0], double.parse(el[1]))).toList();
|
||||
final Map<String, dynamic> json,
|
||||
final String type,
|
||||
) {
|
||||
final List list = json['time_series'][type]['values'];
|
||||
return list
|
||||
.map((final el) => TimeSeriesData(el[0], double.parse(el[1])))
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ abstract class HetznerMetricsState extends Equatable {
|
|||
}
|
||||
|
||||
class HetznerMetricsLoading extends HetznerMetricsState {
|
||||
HetznerMetricsLoading(this.period);
|
||||
const HetznerMetricsLoading(this.period);
|
||||
@override
|
||||
final Period period;
|
||||
|
||||
@override
|
||||
|
@ -15,7 +16,7 @@ class HetznerMetricsLoading extends HetznerMetricsState {
|
|||
}
|
||||
|
||||
class HetznerMetricsLoaded extends HetznerMetricsState {
|
||||
HetznerMetricsLoaded({
|
||||
const HetznerMetricsLoaded({
|
||||
required this.period,
|
||||
required this.start,
|
||||
required this.end,
|
||||
|
@ -27,6 +28,7 @@ class HetznerMetricsLoaded extends HetznerMetricsState {
|
|||
required this.bandwidthOut,
|
||||
});
|
||||
|
||||
@override
|
||||
final Period period;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
|
|
@ -17,12 +17,12 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
required this.servicesCubit,
|
||||
}) : super(JobsStateEmpty());
|
||||
|
||||
final api = ServerApi();
|
||||
final ServerApi api = ServerApi();
|
||||
final UsersCubit usersCubit;
|
||||
final ServicesCubit servicesCubit;
|
||||
|
||||
void addJob(Job job) {
|
||||
var newJobsList = <Job>[];
|
||||
void addJob(final Job job) {
|
||||
final List<Job> newJobsList = <Job>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
|
@ -31,21 +31,22 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
emit(JobsStateWithJobs(newJobsList));
|
||||
}
|
||||
|
||||
void removeJob(String id) {
|
||||
final newState = (state as JobsStateWithJobs).removeById(id);
|
||||
void removeJob(final String id) {
|
||||
final JobsState newState = (state as JobsStateWithJobs).removeById(id);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
void createOrRemoveServiceToggleJob(ToggleJob job) {
|
||||
var newJobsList = <Job>[];
|
||||
void createOrRemoveServiceToggleJob(final ToggleJob job) {
|
||||
final List<Job> newJobsList = <Job>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
var needToRemoveJob =
|
||||
newJobsList.any((el) => el is ServiceToggleJob && el.type == job.type);
|
||||
final bool needToRemoveJob = newJobsList
|
||||
.any((final el) => el is ServiceToggleJob && el.type == job.type);
|
||||
if (needToRemoveJob) {
|
||||
var removingJob = newJobsList
|
||||
.firstWhere(((el) => el is ServiceToggleJob && el.type == job.type));
|
||||
final Job removingJob = newJobsList.firstWhere(
|
||||
(final el) => el is ServiceToggleJob && el.type == job.type,
|
||||
);
|
||||
removeJob(removingJob.id);
|
||||
} else {
|
||||
newJobsList.add(job);
|
||||
|
@ -54,12 +55,13 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
}
|
||||
}
|
||||
|
||||
void createShhJobIfNotExist(CreateSSHKeyJob job) {
|
||||
var newJobsList = <Job>[];
|
||||
void createShhJobIfNotExist(final CreateSSHKeyJob job) {
|
||||
final List<Job> newJobsList = <Job>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
var isExistInJobList = newJobsList.any((el) => el is CreateSSHKeyJob);
|
||||
final bool isExistInJobList =
|
||||
newJobsList.any((final el) => el is CreateSSHKeyJob);
|
||||
if (!isExistInJobList) {
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar('jobs.jobAdded'.tr());
|
||||
|
@ -69,7 +71,7 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
|
||||
Future<void> rebootServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final isSuccessful = await api.reboot();
|
||||
final bool isSuccessful = await api.reboot();
|
||||
if (isSuccessful) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.rebootSuccess'.tr());
|
||||
} else {
|
||||
|
@ -80,8 +82,8 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
|
||||
Future<void> upgradeServer() async {
|
||||
emit(JobsStateLoading());
|
||||
final isPullSuccessful = await api.pullConfigurationUpdate();
|
||||
final isSuccessful = await api.upgrade();
|
||||
final bool isPullSuccessful = await api.pullConfigurationUpdate();
|
||||
final bool isSuccessful = await api.upgrade();
|
||||
if (isSuccessful) {
|
||||
if (!isPullSuccessful) {
|
||||
getIt<NavigationService>().showSnackBar('jobs.configPullFailed'.tr());
|
||||
|
@ -96,10 +98,10 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
|
||||
Future<void> applyAll() async {
|
||||
if (state is JobsStateWithJobs) {
|
||||
var jobs = (state as JobsStateWithJobs).jobList;
|
||||
final List<Job> jobs = (state as JobsStateWithJobs).jobList;
|
||||
emit(JobsStateLoading());
|
||||
var hasServiceJobs = false;
|
||||
for (var job in jobs) {
|
||||
bool hasServiceJobs = false;
|
||||
for (final Job job in jobs) {
|
||||
if (job is CreateUserJob) {
|
||||
await usersCubit.createUser(job.user);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ class JobsStateWithJobs extends JobsState {
|
|||
JobsStateWithJobs(this.jobList);
|
||||
final List<Job> jobList;
|
||||
|
||||
JobsState removeById(String id) {
|
||||
var newJobsList = jobList.where((element) => element.id != id).toList();
|
||||
JobsState removeById(final String id) {
|
||||
final List<Job> newJobsList =
|
||||
jobList.where((final element) => element.id != id).toList();
|
||||
|
||||
if (newJobsList.isEmpty) {
|
||||
return JobsStateEmpty();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/models/provider.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
@ -11,8 +11,9 @@ part 'providers_state.dart';
|
|||
class ProvidersCubit extends Cubit<ProvidersState> {
|
||||
ProvidersCubit() : super(InitialProviderState());
|
||||
|
||||
void connect(ProviderModel provider) {
|
||||
var newState = state.updateElement(provider, StateType.stable);
|
||||
void connect(final ProviderModel provider) {
|
||||
final ProvidersState newState =
|
||||
state.updateElement(provider, StateType.stable);
|
||||
emit(newState);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,23 @@ class ProvidersState extends Equatable {
|
|||
|
||||
final List<ProviderModel> all;
|
||||
|
||||
ProvidersState updateElement(ProviderModel provider, StateType newState) {
|
||||
var newList = [...all];
|
||||
var index = newList.indexOf(provider);
|
||||
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((service) => service.state != StateType.uninitialized).toList();
|
||||
List<ProviderModel> get connected => all
|
||||
.where((final service) => service.state != StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
List<ProviderModel> get uninitialized =>
|
||||
all.where((service) => service.state == StateType.uninitialized).toList();
|
||||
List<ProviderModel> get uninitialized => all
|
||||
.where((final service) => service.state == StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
bool get isFullyInitialized => uninitialized.isEmpty;
|
||||
|
||||
|
@ -29,7 +34,7 @@ class InitialProviderState extends ProvidersState {
|
|||
: super(
|
||||
ProviderType.values
|
||||
.map(
|
||||
(type) => ProviderModel(
|
||||
(final type) => ProviderModel(
|
||||
state: StateType.uninitialized,
|
||||
type: type,
|
||||
),
|
||||
|
|
79
lib/logic/cubit/recovery_key/recovery_key_cubit.dart
Normal file
|
@ -0,0 +1,79 @@
|
|||
import 'package:selfprivacy/logic/api_maps/server.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 ApiResponse<RecoveryKeyStatus?> response =
|
||||
await api.getRecoveryTokenStatus();
|
||||
if (response.isSuccess) {
|
||||
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 ApiResponse<String> response =
|
||||
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
||||
if (response.isSuccess) {
|
||||
refresh();
|
||||
return response.data;
|
||||
} else {
|
||||
throw GenerationError(response.errorMessage ?? 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
||||
}
|
||||
}
|
||||
|
||||
class GenerationError extends Error {
|
||||
GenerationError(this.message);
|
||||
final String message;
|
||||
}
|
39
lib/logic/cubit/recovery_key/recovery_key_state.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
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,
|
||||
);
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.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/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/json/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||
|
||||
part 'server_detailed_info_state.dart';
|
||||
|
@ -14,16 +14,18 @@ class ServerDetailsCubit extends Cubit<ServerDetailsState> {
|
|||
ServerDetailsRepository repository = ServerDetailsRepository();
|
||||
|
||||
void check() async {
|
||||
var isReadyToCheck = getIt<ApiConfigModel>().hetznerServer != null;
|
||||
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null;
|
||||
if (isReadyToCheck) {
|
||||
emit(ServerDetailsLoading());
|
||||
var data = await repository.load();
|
||||
emit(Loaded(
|
||||
serverInfo: data.hetznerServerInfo,
|
||||
autoUpgradeSettings: data.autoUpgradeSettings,
|
||||
serverTimezone: data.serverTimezone,
|
||||
checkTime: DateTime.now(),
|
||||
));
|
||||
final ServerDetailsRepositoryDto data = await repository.load();
|
||||
emit(
|
||||
Loaded(
|
||||
serverInfo: data.hetznerServerInfo,
|
||||
autoUpgradeSettings: data.autoUpgradeSettings,
|
||||
serverTimezone: data.serverTimezone,
|
||||
checkTime: DateTime.now(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(ServerDetailsNotReady());
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/json/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||
|
||||
class ServerDetailsRepository {
|
||||
var hetznerAPi = HetznerApi();
|
||||
var selfprivacyServer = ServerApi();
|
||||
HetznerApi hetznerAPi = HetznerApi();
|
||||
ServerApi selfprivacyServer = ServerApi();
|
||||
|
||||
Future<_ServerDetailsRepositoryDto> load() async {
|
||||
Future<ServerDetailsRepositoryDto> load() async {
|
||||
print('load');
|
||||
return _ServerDetailsRepositoryDto(
|
||||
return ServerDetailsRepositoryDto(
|
||||
autoUpgradeSettings: await selfprivacyServer.getAutoUpgradeSettings(),
|
||||
hetznerServerInfo: await hetznerAPi.getInfo(),
|
||||
serverTimezone: await selfprivacyServer.getServerTimezone(),
|
||||
|
@ -18,16 +18,15 @@ class ServerDetailsRepository {
|
|||
}
|
||||
}
|
||||
|
||||
class _ServerDetailsRepositoryDto {
|
||||
class ServerDetailsRepositoryDto {
|
||||
ServerDetailsRepositoryDto({
|
||||
required this.hetznerServerInfo,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
});
|
||||
final HetznerServerInfo hetznerServerInfo;
|
||||
|
||||
final TimeZoneSettings serverTimezone;
|
||||
|
||||
final AutoUpgradeSettings autoUpgradeSettings;
|
||||
|
||||
_ServerDetailsRepositoryDto({
|
||||
required this.hetznerServerInfo,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@ class ServerDetailsNotReady extends ServerDetailsState {}
|
|||
class Loading extends ServerDetailsState {}
|
||||
|
||||
class Loaded extends ServerDetailsState {
|
||||
const Loaded({
|
||||
required this.serverInfo,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
required this.checkTime,
|
||||
});
|
||||
final HetznerServerInfo serverInfo;
|
||||
|
||||
final TimeZoneSettings serverTimezone;
|
||||
|
@ -23,13 +29,6 @@ class Loaded extends ServerDetailsState {
|
|||
final AutoUpgradeSettings autoUpgradeSettings;
|
||||
final DateTime checkTime;
|
||||
|
||||
Loaded({
|
||||
required this.serverInfo,
|
||||
required this.serverTimezone,
|
||||
required this.autoUpgradeSettings,
|
||||
required this.checkTime,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
serverInfo,
|
||||
|
|
|
@ -0,0 +1,626 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.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/server_basic_info.dart';
|
||||
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part '../server_installation/server_installation_state.dart';
|
||||
|
||||
class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||
ServerInstallationCubit() : super(const ServerInstallationEmpty());
|
||||
|
||||
final ServerInstallationRepository repository =
|
||||
ServerInstallationRepository();
|
||||
|
||||
Timer? timer;
|
||||
|
||||
Future<void> load() async {
|
||||
final ServerInstallationState state = await repository.load();
|
||||
|
||||
if (state is ServerInstallationFinished) {
|
||||
emit(state);
|
||||
} else if (state is ServerInstallationNotFinished) {
|
||||
if (state.progress == ServerSetupProgress.serverCreated) {
|
||||
startServerIfDnsIsOkay(state: state);
|
||||
} else if (state.progress == ServerSetupProgress.serverStarted) {
|
||||
resetServerIfServerIsOkay(state: state);
|
||||
} else if (state.progress == ServerSetupProgress.serverResetedFirstTime) {
|
||||
oneMoreReset(state: state);
|
||||
} else if (state.progress ==
|
||||
ServerSetupProgress.serverResetedSecondTime) {
|
||||
finishCheckIfServerIsOkay(state: state);
|
||||
} else {
|
||||
emit(state);
|
||||
}
|
||||
} else if (state is ServerInstallationRecovery) {
|
||||
emit(state);
|
||||
} else {
|
||||
throw 'wrong state';
|
||||
}
|
||||
}
|
||||
|
||||
void setHetznerKey(final String hetznerKey) async {
|
||||
await repository.saveHetznerKey(hetznerKey);
|
||||
|
||||
if (state is ServerInstallationRecovery) {
|
||||
emit(
|
||||
(state as ServerInstallationRecovery).copyWith(
|
||||
hetznerKey: hetznerKey,
|
||||
currentStep: RecoveryStep.serverSelection,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
(state as ServerInstallationNotFinished).copyWith(hetznerKey: hetznerKey),
|
||||
);
|
||||
}
|
||||
|
||||
void setCloudflareKey(final String cloudFlareKey) async {
|
||||
if (state is ServerInstallationRecovery) {
|
||||
setAndValidateCloudflareToken(cloudFlareKey);
|
||||
return;
|
||||
}
|
||||
await repository.saveCloudFlareKey(cloudFlareKey);
|
||||
emit(
|
||||
(state as ServerInstallationNotFinished)
|
||||
.copyWith(cloudFlareKey: cloudFlareKey),
|
||||
);
|
||||
}
|
||||
|
||||
void setBackblazeKey(final String keyId, final String applicationKey) async {
|
||||
final BackblazeCredential backblazeCredential = BackblazeCredential(
|
||||
keyId: keyId,
|
||||
applicationKey: applicationKey,
|
||||
);
|
||||
await repository.saveBackblazeKey(backblazeCredential);
|
||||
if (state is ServerInstallationRecovery) {
|
||||
finishRecoveryProcess(backblazeCredential);
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
(state as ServerInstallationNotFinished)
|
||||
.copyWith(backblazeCredential: backblazeCredential),
|
||||
);
|
||||
}
|
||||
|
||||
void setDomain(final ServerDomain serverDomain) async {
|
||||
await repository.saveDomain(serverDomain);
|
||||
emit(
|
||||
(state as ServerInstallationNotFinished)
|
||||
.copyWith(serverDomain: serverDomain),
|
||||
);
|
||||
}
|
||||
|
||||
void setRootUser(final User rootUser) async {
|
||||
await repository.saveRootUser(rootUser);
|
||||
emit((state as ServerInstallationNotFinished).copyWith(rootUser: rootUser));
|
||||
}
|
||||
|
||||
void createServerAndSetDnsRecords() async {
|
||||
final ServerInstallationNotFinished stateCopy =
|
||||
state as ServerInstallationNotFinished;
|
||||
void onCancel() => emit(
|
||||
(state as ServerInstallationNotFinished).copyWith(isLoading: false),
|
||||
);
|
||||
|
||||
Future<void> onSuccess(final ServerHostingDetails serverDetails) async {
|
||||
await repository.createDnsRecords(
|
||||
serverDetails.ip4,
|
||||
state.serverDomain!,
|
||||
onCancel: onCancel,
|
||||
);
|
||||
|
||||
emit(
|
||||
(state as ServerInstallationNotFinished).copyWith(
|
||||
isLoading: false,
|
||||
serverDetails: serverDetails,
|
||||
),
|
||||
);
|
||||
runDelayed(startServerIfDnsIsOkay, const Duration(seconds: 30), null);
|
||||
}
|
||||
|
||||
try {
|
||||
emit((state as ServerInstallationNotFinished).copyWith(isLoading: true));
|
||||
await repository.createServer(
|
||||
state.rootUser!,
|
||||
state.serverDomain!.domainName,
|
||||
state.cloudFlareKey!,
|
||||
state.backblazeCredential!,
|
||||
onCancel: onCancel,
|
||||
onSuccess: onSuccess,
|
||||
);
|
||||
} catch (e) {
|
||||
emit(stateCopy);
|
||||
}
|
||||
}
|
||||
|
||||
void startServerIfDnsIsOkay({
|
||||
final ServerInstallationNotFinished? state,
|
||||
}) async {
|
||||
final ServerInstallationNotFinished dataState =
|
||||
state ?? this.state as ServerInstallationNotFinished;
|
||||
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
final String ip4 = dataState.serverDetails!.ip4;
|
||||
final String domainName = dataState.serverDomain!.domainName;
|
||||
|
||||
final Map<String, bool> matches = await repository.isDnsAddressesMatch(
|
||||
domainName,
|
||||
ip4,
|
||||
dataState.dnsMatches ?? {},
|
||||
);
|
||||
|
||||
if (matches.values.every((final bool value) => value)) {
|
||||
final ServerHostingDetails server = await repository.startServer(
|
||||
dataState.serverDetails!,
|
||||
);
|
||||
await repository.saveServerDetails(server);
|
||||
await repository.saveIsServerStarted(true);
|
||||
|
||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||
isServerStarted: true,
|
||||
isLoading: false,
|
||||
serverDetails: server,
|
||||
);
|
||||
emit(newState);
|
||||
runDelayed(
|
||||
resetServerIfServerIsOkay,
|
||||
const Duration(seconds: 60),
|
||||
newState,
|
||||
);
|
||||
} else {
|
||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||
isLoading: false,
|
||||
dnsMatches: matches,
|
||||
);
|
||||
emit(newState);
|
||||
runDelayed(
|
||||
startServerIfDnsIsOkay,
|
||||
const Duration(seconds: 30),
|
||||
newState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void resetServerIfServerIsOkay({
|
||||
final ServerInstallationNotFinished? state,
|
||||
}) async {
|
||||
final ServerInstallationNotFinished dataState =
|
||||
state ?? this.state as ServerInstallationNotFinished;
|
||||
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
final bool isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
const Duration pauseDuration = Duration(seconds: 30);
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
isLoading: false,
|
||||
duration: pauseDuration,
|
||||
),
|
||||
);
|
||||
timer = Timer(pauseDuration, () async {
|
||||
final ServerHostingDetails hetznerServerDetails =
|
||||
await repository.restart();
|
||||
await repository.saveIsServerResetedFirstTime(true);
|
||||
await repository.saveServerDetails(hetznerServerDetails);
|
||||
|
||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||
isServerResetedFirstTime: true,
|
||||
serverDetails: hetznerServerDetails,
|
||||
isLoading: false,
|
||||
);
|
||||
|
||||
emit(newState);
|
||||
runDelayed(oneMoreReset, const Duration(seconds: 60), newState);
|
||||
});
|
||||
} else {
|
||||
runDelayed(
|
||||
resetServerIfServerIsOkay,
|
||||
const Duration(seconds: 60),
|
||||
dataState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void oneMoreReset({final ServerInstallationNotFinished? state}) async {
|
||||
final ServerInstallationNotFinished dataState =
|
||||
state ?? this.state as ServerInstallationNotFinished;
|
||||
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
final bool isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
const Duration pauseDuration = Duration(seconds: 30);
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
isLoading: false,
|
||||
duration: pauseDuration,
|
||||
),
|
||||
);
|
||||
timer = Timer(pauseDuration, () async {
|
||||
final ServerHostingDetails hetznerServerDetails =
|
||||
await repository.restart();
|
||||
await repository.saveIsServerResetedSecondTime(true);
|
||||
await repository.saveServerDetails(hetznerServerDetails);
|
||||
|
||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||
isServerResetedSecondTime: true,
|
||||
serverDetails: hetznerServerDetails,
|
||||
isLoading: false,
|
||||
);
|
||||
|
||||
emit(newState);
|
||||
runDelayed(
|
||||
finishCheckIfServerIsOkay,
|
||||
const Duration(seconds: 60),
|
||||
newState,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
runDelayed(oneMoreReset, const Duration(seconds: 60), dataState);
|
||||
}
|
||||
}
|
||||
|
||||
void finishCheckIfServerIsOkay({
|
||||
final ServerInstallationNotFinished? state,
|
||||
}) async {
|
||||
final ServerInstallationNotFinished dataState =
|
||||
state ?? this.state as ServerInstallationNotFinished;
|
||||
|
||||
emit(TimerState(dataState: dataState, isLoading: true));
|
||||
|
||||
final bool isServerWorking = await repository.isHttpServerWorking();
|
||||
|
||||
if (isServerWorking) {
|
||||
await repository.createDkimRecord(dataState.serverDomain!);
|
||||
await repository.saveHasFinalChecked(true);
|
||||
|
||||
emit(dataState.finish());
|
||||
} else {
|
||||
runDelayed(
|
||||
finishCheckIfServerIsOkay,
|
||||
const Duration(seconds: 60),
|
||||
dataState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void runDelayed(
|
||||
final void Function() work,
|
||||
final Duration delay,
|
||||
final ServerInstallationNotFinished? state,
|
||||
) async {
|
||||
final ServerInstallationNotFinished dataState =
|
||||
state ?? this.state as ServerInstallationNotFinished;
|
||||
|
||||
emit(
|
||||
TimerState(
|
||||
dataState: dataState,
|
||||
timerStart: DateTime.now(),
|
||||
duration: delay,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
timer = Timer(delay, work);
|
||||
}
|
||||
|
||||
void submitDomainForAccessRecovery(final String domain) async {
|
||||
final ServerDomain serverDomain = ServerDomain(
|
||||
domainName: domain,
|
||||
provider: DnsProvider.unknown,
|
||||
zoneId: '',
|
||||
);
|
||||
final ServerRecoveryCapabilities recoveryCapabilities =
|
||||
await repository.getRecoveryCapabilities(serverDomain);
|
||||
|
||||
await repository.saveDomain(serverDomain);
|
||||
await repository.saveIsRecoveringServer(true);
|
||||
|
||||
emit(
|
||||
ServerInstallationRecovery(
|
||||
serverDomain: serverDomain,
|
||||
recoveryCapabilities: recoveryCapabilities,
|
||||
currentStep: RecoveryStep.selecting,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void tryToRecover(
|
||||
final String token,
|
||||
final ServerRecoveryMethods method,
|
||||
) async {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
final ServerDomain? serverDomain = dataState.serverDomain;
|
||||
if (serverDomain == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Future<ServerHostingDetails> Function(
|
||||
ServerDomain,
|
||||
String,
|
||||
ServerRecoveryCapabilities,
|
||||
) recoveryFunction;
|
||||
switch (method) {
|
||||
case ServerRecoveryMethods.newDeviceKey:
|
||||
recoveryFunction = repository.authorizeByNewDeviceKey;
|
||||
break;
|
||||
case ServerRecoveryMethods.recoveryKey:
|
||||
recoveryFunction = repository.authorizeByRecoveryKey;
|
||||
break;
|
||||
case ServerRecoveryMethods.oldToken:
|
||||
recoveryFunction = repository.authorizeByApiToken;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Unknown recovery method');
|
||||
}
|
||||
final ServerHostingDetails serverDetails = await recoveryFunction(
|
||||
serverDomain,
|
||||
token,
|
||||
dataState.recoveryCapabilities,
|
||||
);
|
||||
await repository.saveServerDetails(serverDetails);
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
serverDetails: serverDetails,
|
||||
currentStep: RecoveryStep.hetznerToken,
|
||||
),
|
||||
);
|
||||
} on ServerAuthorizationException {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('recovering.authorization_failed'.tr());
|
||||
return;
|
||||
} on IpNotFoundException {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('recovering.domain_recover_error'.tr());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void revertRecoveryStep() {
|
||||
if (state is ServerInstallationEmpty) {
|
||||
return;
|
||||
}
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
switch (dataState.currentStep) {
|
||||
case RecoveryStep.selecting:
|
||||
repository.deleteDomain();
|
||||
emit(const ServerInstallationEmpty());
|
||||
break;
|
||||
case RecoveryStep.recoveryKey:
|
||||
case RecoveryStep.newDeviceKey:
|
||||
case RecoveryStep.oldToken:
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
currentStep: RecoveryStep.selecting,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case RecoveryStep.serverSelection:
|
||||
repository.deleteHetznerKey();
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
currentStep: RecoveryStep.hetznerToken,
|
||||
),
|
||||
);
|
||||
break;
|
||||
// We won't revert steps after client is authorized
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void selectRecoveryMethod(final ServerRecoveryMethods method) {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
switch (method) {
|
||||
case ServerRecoveryMethods.newDeviceKey:
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
currentStep: RecoveryStep.newDeviceKey,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServerRecoveryMethods.recoveryKey:
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
currentStep: RecoveryStep.recoveryKey,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case ServerRecoveryMethods.oldToken:
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
currentStep: RecoveryStep.oldToken,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ServerBasicInfoWithValidators>>
|
||||
getServersOnHetznerAccount() async {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
final List<ServerBasicInfo> servers =
|
||||
await repository.getServersOnHetznerAccount();
|
||||
final Iterable<ServerBasicInfoWithValidators> validated = servers.map(
|
||||
(final ServerBasicInfo server) =>
|
||||
ServerBasicInfoWithValidators.fromServerBasicInfo(
|
||||
serverBasicInfo: server,
|
||||
isIpValid: server.ip == dataState.serverDetails?.ip4,
|
||||
isReverseDnsValid:
|
||||
server.reverseDns == dataState.serverDomain?.domainName,
|
||||
),
|
||||
);
|
||||
return validated.toList();
|
||||
}
|
||||
|
||||
Future<void> setServerId(final ServerBasicInfo server) async {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
final ServerDomain? serverDomain = dataState.serverDomain;
|
||||
if (serverDomain == null) {
|
||||
return;
|
||||
}
|
||||
final ServerHostingDetails serverDetails = ServerHostingDetails(
|
||||
ip4: server.ip,
|
||||
id: server.id,
|
||||
createTime: server.created,
|
||||
volume: ServerVolume(
|
||||
id: server.volumeId,
|
||||
name: 'recovered_volume',
|
||||
),
|
||||
apiToken: dataState.serverDetails!.apiToken,
|
||||
provider: ServerProvider.hetzner,
|
||||
);
|
||||
await repository.saveDomain(serverDomain);
|
||||
await repository.saveServerDetails(serverDetails);
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
serverDetails: serverDetails,
|
||||
currentStep: RecoveryStep.cloudflareToken,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setAndValidateCloudflareToken(final String token) async {
|
||||
final ServerInstallationRecovery dataState =
|
||||
state as ServerInstallationRecovery;
|
||||
final ServerDomain? serverDomain = dataState.serverDomain;
|
||||
if (serverDomain == null) {
|
||||
return;
|
||||
}
|
||||
final String? zoneId =
|
||||
await repository.getDomainId(token, serverDomain.domainName);
|
||||
if (zoneId == null) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('recovering.domain_not_available_on_token'.tr());
|
||||
return;
|
||||
}
|
||||
await repository.saveDomain(
|
||||
ServerDomain(
|
||||
domainName: serverDomain.domainName,
|
||||
zoneId: zoneId,
|
||||
provider: DnsProvider.cloudflare,
|
||||
),
|
||||
);
|
||||
await repository.saveCloudFlareKey(token);
|
||||
emit(
|
||||
dataState.copyWith(
|
||||
serverDomain: ServerDomain(
|
||||
domainName: serverDomain.domainName,
|
||||
zoneId: zoneId,
|
||||
provider: DnsProvider.cloudflare,
|
||||
),
|
||||
cloudFlareKey: token,
|
||||
currentStep: RecoveryStep.backblazeToken,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void finishRecoveryProcess(
|
||||
final BackblazeCredential backblazeCredential,
|
||||
) async {
|
||||
await repository.saveIsServerStarted(true);
|
||||
await repository.saveIsServerResetedFirstTime(true);
|
||||
await repository.saveIsServerResetedSecondTime(true);
|
||||
await repository.saveHasFinalChecked(true);
|
||||
await repository.saveIsRecoveringServer(false);
|
||||
final User mainUser = await repository.getMainUser();
|
||||
await repository.saveRootUser(mainUser);
|
||||
final ServerInstallationRecovery updatedState =
|
||||
(state as ServerInstallationRecovery).copyWith(
|
||||
backblazeCredential: backblazeCredential,
|
||||
rootUser: mainUser,
|
||||
);
|
||||
emit(updatedState.finish());
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(final Change<ServerInstallationState> change) {
|
||||
super.onChange(change);
|
||||
print('================================');
|
||||
print('ServerInstallationState changed!');
|
||||
print('Current type: ${change.nextState.runtimeType}');
|
||||
print('Hetzner key: ${change.nextState.hetznerKey}');
|
||||
print('Cloudflare key: ${change.nextState.cloudFlareKey}');
|
||||
print('Domain: ${change.nextState.serverDomain}');
|
||||
print('BackblazeCredential: ${change.nextState.backblazeCredential}');
|
||||
if (change.nextState is ServerInstallationRecovery) {
|
||||
print(
|
||||
'Recovery Step: ${(change.nextState as ServerInstallationRecovery).currentStep}',
|
||||
);
|
||||
print(
|
||||
'Recovery Capabilities: ${(change.nextState as ServerInstallationRecovery).recoveryCapabilities}',
|
||||
);
|
||||
}
|
||||
if (change.nextState is TimerState) {
|
||||
print('Timer: ${(change.nextState as TimerState).duration}');
|
||||
}
|
||||
}
|
||||
|
||||
void clearAppConfig() {
|
||||
closeTimer();
|
||||
|
||||
repository.clearAppConfig();
|
||||
emit(const ServerInstallationEmpty());
|
||||
}
|
||||
|
||||
Future<void> serverDelete() async {
|
||||
closeTimer();
|
||||
|
||||
if (state.serverDetails != null) {
|
||||
await repository.deleteServer(state.serverDomain!);
|
||||
}
|
||||
await repository.deleteServerRelatedRecords();
|
||||
emit(
|
||||
ServerInstallationNotFinished(
|
||||
hetznerKey: state.hetznerKey,
|
||||
serverDomain: state.serverDomain,
|
||||
cloudFlareKey: state.cloudFlareKey,
|
||||
backblazeCredential: state.backblazeCredential,
|
||||
rootUser: state.rootUser,
|
||||
serverDetails: null,
|
||||
isServerStarted: false,
|
||||
isServerResetedFirstTime: false,
|
||||
isServerResetedSecondTime: false,
|
||||
isLoading: false,
|
||||
dnsMatches: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
closeTimer();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void closeTimer() {
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,683 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.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/cloudflare.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.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/device_token.dart';
|
||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||
import 'package:selfprivacy/logic/models/message.dart';
|
||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
||||
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
||||
class IpNotFoundException implements Exception {
|
||||
IpNotFoundException(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
||||
class ServerAuthorizationException implements Exception {
|
||||
ServerAuthorizationException(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
||||
class ServerInstallationRepository {
|
||||
Box box = Hive.box(BNames.serverInstallationBox);
|
||||
Box<User> usersBox = Hive.box(BNames.usersBox);
|
||||
|
||||
Future<ServerInstallationState> load() async {
|
||||
final String? hetznerToken = getIt<ApiConfigModel>().hetznerKey;
|
||||
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
|
||||
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
||||
final BackblazeCredential? backblazeCredential =
|
||||
getIt<ApiConfigModel>().backblazeCredential;
|
||||
final ServerHostingDetails? serverDetails =
|
||||
getIt<ApiConfigModel>().serverDetails;
|
||||
|
||||
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
||||
return ServerInstallationFinished(
|
||||
hetznerKey: hetznerToken!,
|
||||
cloudFlareKey: cloudflareToken!,
|
||||
serverDomain: serverDomain!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
serverDetails: serverDetails!,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
||||
isServerResetedFirstTime:
|
||||
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||
isServerResetedSecondTime:
|
||||
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||
);
|
||||
}
|
||||
|
||||
if (box.get(BNames.isRecoveringServer, defaultValue: false) &&
|
||||
serverDomain != null) {
|
||||
return ServerInstallationRecovery(
|
||||
hetznerKey: hetznerToken,
|
||||
cloudFlareKey: cloudflareToken,
|
||||
serverDomain: serverDomain,
|
||||
backblazeCredential: backblazeCredential,
|
||||
serverDetails: serverDetails,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
currentStep: _getCurrentRecoveryStep(
|
||||
hetznerToken,
|
||||
cloudflareToken,
|
||||
serverDomain,
|
||||
serverDetails,
|
||||
),
|
||||
recoveryCapabilities: await getRecoveryCapabilities(serverDomain),
|
||||
);
|
||||
}
|
||||
|
||||
return ServerInstallationNotFinished(
|
||||
hetznerKey: hetznerToken,
|
||||
cloudFlareKey: cloudflareToken,
|
||||
serverDomain: serverDomain,
|
||||
backblazeCredential: backblazeCredential,
|
||||
serverDetails: serverDetails,
|
||||
rootUser: box.get(BNames.rootUser),
|
||||
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
||||
isServerResetedFirstTime:
|
||||
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||
isServerResetedSecondTime:
|
||||
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||
isLoading: box.get(BNames.isLoading, defaultValue: false),
|
||||
dnsMatches: null,
|
||||
);
|
||||
}
|
||||
|
||||
RecoveryStep _getCurrentRecoveryStep(
|
||||
final String? hetznerToken,
|
||||
final String? cloudflareToken,
|
||||
final ServerDomain serverDomain,
|
||||
final ServerHostingDetails? serverDetails,
|
||||
) {
|
||||
if (serverDetails != null) {
|
||||
if (hetznerToken != null) {
|
||||
if (serverDetails.provider != ServerProvider.unknown) {
|
||||
if (serverDomain.provider != DnsProvider.unknown) {
|
||||
return RecoveryStep.backblazeToken;
|
||||
}
|
||||
return RecoveryStep.cloudflareToken;
|
||||
}
|
||||
return RecoveryStep.serverSelection;
|
||||
}
|
||||
return RecoveryStep.hetznerToken;
|
||||
}
|
||||
return RecoveryStep.selecting;
|
||||
}
|
||||
|
||||
void clearAppConfig() {
|
||||
box.clear();
|
||||
usersBox.clear();
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> startServer(
|
||||
final ServerHostingDetails hetznerServer,
|
||||
) async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
final ServerHostingDetails serverDetails = await hetznerApi.powerOn();
|
||||
|
||||
return serverDetails;
|
||||
}
|
||||
|
||||
Future<String?> getDomainId(final String token, final String domain) async {
|
||||
final CloudflareApi cloudflareApi = CloudflareApi(
|
||||
isWithToken: false,
|
||||
customToken: token,
|
||||
);
|
||||
|
||||
try {
|
||||
final String domainId = await cloudflareApi.getZoneId(domain);
|
||||
return domainId;
|
||||
} on DomainNotFoundException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, bool>> isDnsAddressesMatch(
|
||||
final String? domainName,
|
||||
final String? ip4,
|
||||
final Map<String, bool> skippedMatches,
|
||||
) async {
|
||||
final List<String> addresses = <String>[
|
||||
'$domainName',
|
||||
'api.$domainName',
|
||||
'cloud.$domainName',
|
||||
'meet.$domainName',
|
||||
'password.$domainName'
|
||||
];
|
||||
|
||||
final Map<String, bool> matches = <String, bool>{};
|
||||
|
||||
for (final String address in addresses) {
|
||||
if (skippedMatches[address] ?? false) {
|
||||
matches[address] = true;
|
||||
continue;
|
||||
}
|
||||
final List<RRecord>? lookupRecordRes = await DnsUtils.lookupRecord(
|
||||
address,
|
||||
RRecordType.A,
|
||||
provider: DnsApiProvider.CLOUDFLARE,
|
||||
);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
Message(
|
||||
text:
|
||||
'DnsLookup: address: $address, $RRecordType, provider: CLOUDFLARE, ip4: $ip4',
|
||||
),
|
||||
);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
Message(
|
||||
text:
|
||||
'DnsLookup: ${lookupRecordRes == null ? 'empty' : (lookupRecordRes[0].data != ip4 ? 'wrong ip4' : 'right ip4')}',
|
||||
),
|
||||
);
|
||||
if (lookupRecordRes == null ||
|
||||
lookupRecordRes.isEmpty ||
|
||||
lookupRecordRes[0].data != ip4) {
|
||||
matches[address] = false;
|
||||
} else {
|
||||
matches[address] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
Future<void> createServer(
|
||||
final User rootUser,
|
||||
final String domainName,
|
||||
final String cloudFlareKey,
|
||||
final BackblazeCredential backblazeCredential, {
|
||||
required final void Function() onCancel,
|
||||
required final Future<void> Function(ServerHostingDetails serverDetails)
|
||||
onSuccess,
|
||||
}) async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
late ServerVolume dataBase;
|
||||
|
||||
try {
|
||||
dataBase = await hetznerApi.createVolume();
|
||||
|
||||
final ServerHostingDetails? serverDetails = await hetznerApi.createServer(
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
rootUser: rootUser,
|
||||
domainName: domainName,
|
||||
dataBase: dataBase,
|
||||
);
|
||||
if (serverDetails == null) {
|
||||
print('Server is not initialized!');
|
||||
return;
|
||||
}
|
||||
saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
} on DioError catch (e) {
|
||||
if (e.response!.data['error']['code'] == 'uniqueness_error') {
|
||||
final NavigationService nav = getIt.get<NavigationService>();
|
||||
nav.showPopUpDialog(
|
||||
BrandAlert(
|
||||
title: 'modals.1'.tr(),
|
||||
contentText: 'modals.2'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.delete'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () async {
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: domainName,
|
||||
);
|
||||
|
||||
final ServerHostingDetails? serverDetails =
|
||||
await hetznerApi.createServer(
|
||||
cloudFlareKey: cloudFlareKey,
|
||||
rootUser: rootUser,
|
||||
domainName: domainName,
|
||||
dataBase: dataBase,
|
||||
);
|
||||
if (serverDetails == null) {
|
||||
print('Server is not initialized!');
|
||||
return;
|
||||
}
|
||||
await saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
onPressed: onCancel,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createDnsRecords(
|
||||
final String ip4,
|
||||
final ServerDomain cloudFlareDomain, {
|
||||
required final void Function() onCancel,
|
||||
}) async {
|
||||
final CloudflareApi cloudflareApi = CloudflareApi();
|
||||
|
||||
await cloudflareApi.removeSimilarRecords(
|
||||
ip4: ip4,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
);
|
||||
|
||||
try {
|
||||
await cloudflareApi.createMultipleDnsRecords(
|
||||
ip4: ip4,
|
||||
cloudFlareDomain: cloudFlareDomain,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
final NavigationService nav = getIt.get<NavigationService>();
|
||||
nav.showPopUpDialog(
|
||||
BrandAlert(
|
||||
title: e.response!.data['errors'][0]['code'] == 1038
|
||||
? 'modals.10'.tr()
|
||||
: 'providers.domain.states.error'.tr(),
|
||||
contentText: 'modals.6'.tr(),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.delete'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () async {
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: cloudFlareDomain.domainName,
|
||||
);
|
||||
|
||||
onCancel();
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
onPressed: onCancel,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await HetznerApi().createReverseDns(
|
||||
ip4: ip4,
|
||||
domainName: cloudFlareDomain.domainName,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> createDkimRecord(final ServerDomain cloudFlareDomain) async {
|
||||
final CloudflareApi cloudflareApi = CloudflareApi();
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
final String? dkimRecordString = await api.getDkim();
|
||||
|
||||
await cloudflareApi.setDkim(dkimRecordString ?? '', cloudFlareDomain);
|
||||
}
|
||||
|
||||
Future<bool> isHttpServerWorking() async {
|
||||
final ServerApi api = ServerApi();
|
||||
final bool isHttpServerWorking = await api.isHttpServerWorking();
|
||||
try {
|
||||
await api.getDkim();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return isHttpServerWorking;
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> restart() async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
return hetznerApi.reset();
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> powerOn() async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
return hetznerApi.powerOn();
|
||||
}
|
||||
|
||||
Future<ServerRecoveryCapabilities> getRecoveryCapabilities(
|
||||
final ServerDomain serverDomain,
|
||||
) async {
|
||||
final ServerApi serverApi = ServerApi(
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomain.domainName,
|
||||
);
|
||||
final String? serverApiVersion = await serverApi.getApiVersion();
|
||||
if (serverApiVersion == null) {
|
||||
return ServerRecoveryCapabilities.none;
|
||||
}
|
||||
try {
|
||||
final Version parsedVersion = Version.parse(serverApiVersion);
|
||||
if (!VersionConstraint.parse('>=1.2.0').allows(parsedVersion)) {
|
||||
return ServerRecoveryCapabilities.legacy;
|
||||
}
|
||||
return ServerRecoveryCapabilities.loginTokens;
|
||||
} on FormatException {
|
||||
return ServerRecoveryCapabilities.none;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getServerIpFromDomain(final ServerDomain serverDomain) async {
|
||||
final List<RRecord>? lookup = await DnsUtils.lookupRecord(
|
||||
serverDomain.domainName,
|
||||
RRecordType.A,
|
||||
provider: DnsApiProvider.CLOUDFLARE,
|
||||
);
|
||||
if (lookup == null || lookup.isEmpty) {
|
||||
throw IpNotFoundException('No IP found for domain $serverDomain');
|
||||
}
|
||||
return lookup[0].data;
|
||||
}
|
||||
|
||||
Future<String> getDeviceName() async {
|
||||
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
if (kIsWeb) {
|
||||
return deviceInfo.webBrowserInfo.then(
|
||||
(final WebBrowserInfo value) =>
|
||||
'${value.browserName} ${value.platform}',
|
||||
);
|
||||
} else {
|
||||
if (Platform.isAndroid) {
|
||||
return deviceInfo.androidInfo.then(
|
||||
(final AndroidDeviceInfo value) =>
|
||||
'${value.model} ${value.version.release}',
|
||||
);
|
||||
} else if (Platform.isIOS) {
|
||||
return deviceInfo.iosInfo.then(
|
||||
(final IosDeviceInfo value) =>
|
||||
'${value.utsname.machine} ${value.systemName} ${value.systemVersion}',
|
||||
);
|
||||
} else if (Platform.isLinux) {
|
||||
return deviceInfo.linuxInfo
|
||||
.then((final LinuxDeviceInfo value) => value.prettyName);
|
||||
} else if (Platform.isMacOS) {
|
||||
return deviceInfo.macOsInfo.then(
|
||||
(final MacOsDeviceInfo value) =>
|
||||
'${value.hostName} ${value.computerName}',
|
||||
);
|
||||
} else if (Platform.isWindows) {
|
||||
return deviceInfo.windowsInfo
|
||||
.then((final WindowsDeviceInfo value) => value.computerName);
|
||||
}
|
||||
}
|
||||
return 'Unidentified';
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> authorizeByNewDeviceKey(
|
||||
final ServerDomain serverDomain,
|
||||
final String newDeviceKey,
|
||||
final ServerRecoveryCapabilities recoveryCapabilities,
|
||||
) async {
|
||||
final ServerApi serverApi = ServerApi(
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomain.domainName,
|
||||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
final ApiResponse<String> apiResponse = await serverApi.authorizeDevice(
|
||||
DeviceToken(device: await getDeviceName(), token: newDeviceKey),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiResponse.data,
|
||||
volume: ServerVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
),
|
||||
provider: ServerProvider.unknown,
|
||||
id: 0,
|
||||
ip4: serverIp,
|
||||
startTime: null,
|
||||
createTime: null,
|
||||
);
|
||||
}
|
||||
|
||||
throw ServerAuthorizationException(
|
||||
apiResponse.errorMessage ?? apiResponse.data,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> authorizeByRecoveryKey(
|
||||
final ServerDomain serverDomain,
|
||||
final String recoveryKey,
|
||||
final ServerRecoveryCapabilities recoveryCapabilities,
|
||||
) async {
|
||||
final ServerApi serverApi = ServerApi(
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomain.domainName,
|
||||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
final ApiResponse<String> apiResponse = await serverApi.useRecoveryToken(
|
||||
DeviceToken(device: await getDeviceName(), token: recoveryKey),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiResponse.data,
|
||||
volume: ServerVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
),
|
||||
provider: ServerProvider.unknown,
|
||||
id: 0,
|
||||
ip4: serverIp,
|
||||
startTime: null,
|
||||
createTime: null,
|
||||
);
|
||||
}
|
||||
|
||||
throw ServerAuthorizationException(
|
||||
apiResponse.errorMessage ?? apiResponse.data,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ServerHostingDetails> authorizeByApiToken(
|
||||
final ServerDomain serverDomain,
|
||||
final String apiToken,
|
||||
final ServerRecoveryCapabilities recoveryCapabilities,
|
||||
) async {
|
||||
final ServerApi serverApi = ServerApi(
|
||||
isWithToken: false,
|
||||
overrideDomain: serverDomain.domainName,
|
||||
customToken: apiToken,
|
||||
);
|
||||
final String serverIp = await getServerIpFromDomain(serverDomain);
|
||||
if (recoveryCapabilities == ServerRecoveryCapabilities.legacy) {
|
||||
final Map<ServiceTypes, bool> apiResponse =
|
||||
await serverApi.servicesPowerCheck();
|
||||
if (apiResponse.isNotEmpty) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiToken,
|
||||
volume: ServerVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
),
|
||||
provider: ServerProvider.unknown,
|
||||
id: 0,
|
||||
ip4: serverIp,
|
||||
startTime: null,
|
||||
createTime: null,
|
||||
);
|
||||
} else {
|
||||
throw ServerAuthorizationException(
|
||||
"Couldn't connect to server with this token",
|
||||
);
|
||||
}
|
||||
}
|
||||
final ApiResponse<String> deviceAuthKey =
|
||||
await serverApi.createDeviceToken();
|
||||
final ApiResponse<String> apiResponse = await serverApi.authorizeDevice(
|
||||
DeviceToken(device: await getDeviceName(), token: deviceAuthKey.data),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess) {
|
||||
return ServerHostingDetails(
|
||||
apiToken: apiResponse.data,
|
||||
volume: ServerVolume(
|
||||
id: 0,
|
||||
name: '',
|
||||
),
|
||||
provider: ServerProvider.unknown,
|
||||
id: 0,
|
||||
ip4: serverIp,
|
||||
startTime: null,
|
||||
createTime: null,
|
||||
);
|
||||
}
|
||||
|
||||
throw ServerAuthorizationException(
|
||||
apiResponse.errorMessage ?? apiResponse.data,
|
||||
);
|
||||
}
|
||||
|
||||
Future<User> getMainUser() async {
|
||||
final ServerApi serverApi = ServerApi();
|
||||
const User fallbackUser = User(
|
||||
isFoundOnServer: false,
|
||||
note: "Couldn't find main user on server, API is outdated",
|
||||
login: 'UNKNOWN',
|
||||
sshKeys: [],
|
||||
);
|
||||
|
||||
final String? serverApiVersion = await serverApi.getApiVersion();
|
||||
final ApiResponse<List<String>> users =
|
||||
await serverApi.getUsersList(withMainUser: true);
|
||||
if (serverApiVersion == null || !users.isSuccess) {
|
||||
return fallbackUser;
|
||||
}
|
||||
try {
|
||||
final Version parsedVersion = Version.parse(serverApiVersion);
|
||||
if (!VersionConstraint.parse('>=1.2.5').allows(parsedVersion)) {
|
||||
return fallbackUser;
|
||||
}
|
||||
return User(
|
||||
isFoundOnServer: true,
|
||||
login: users.data[0],
|
||||
);
|
||||
} on FormatException {
|
||||
return fallbackUser;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ServerBasicInfo>> getServersOnHetznerAccount() async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
final List<HetznerServerInfo> servers = await hetznerApi.getServers();
|
||||
return servers
|
||||
.map(
|
||||
(final HetznerServerInfo server) => ServerBasicInfo(
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
ip: server.publicNet.ipv4.ip,
|
||||
reverseDns: server.publicNet.ipv4.reverseDns,
|
||||
created: server.created,
|
||||
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> saveServerDetails(
|
||||
final ServerHostingDetails serverDetails,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
||||
}
|
||||
|
||||
Future<void> saveHetznerKey(final String key) async {
|
||||
print('saved');
|
||||
await getIt<ApiConfigModel>().storeHetznerKey(key);
|
||||
}
|
||||
|
||||
Future<void> deleteHetznerKey() async {
|
||||
await box.delete(BNames.hetznerKey);
|
||||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
|
||||
Future<void> saveBackblazeKey(
|
||||
final BackblazeCredential backblazeCredential,
|
||||
) async {
|
||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
||||
}
|
||||
|
||||
Future<void> saveCloudFlareKey(final String key) async {
|
||||
await getIt<ApiConfigModel>().storeCloudFlareKey(key);
|
||||
}
|
||||
|
||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||
await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
|
||||
}
|
||||
|
||||
Future<void> deleteDomain() async {
|
||||
await box.delete(BNames.serverDomain);
|
||||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
|
||||
Future<void> saveIsServerStarted(final bool value) async {
|
||||
await box.put(BNames.isServerStarted, value);
|
||||
}
|
||||
|
||||
Future<void> saveIsServerResetedFirstTime(final bool value) async {
|
||||
await box.put(BNames.isServerResetedFirstTime, value);
|
||||
}
|
||||
|
||||
Future<void> saveIsServerResetedSecondTime(final bool value) async {
|
||||
await box.put(BNames.isServerResetedSecondTime, value);
|
||||
}
|
||||
|
||||
Future<void> saveRootUser(final User rootUser) async {
|
||||
await box.put(BNames.rootUser, rootUser);
|
||||
}
|
||||
|
||||
Future<void> saveIsRecoveringServer(final bool value) async {
|
||||
await box.put(BNames.isRecoveringServer, value);
|
||||
}
|
||||
|
||||
Future<void> saveHasFinalChecked(final bool value) async {
|
||||
await box.put(BNames.hasFinalChecked, value);
|
||||
}
|
||||
|
||||
Future<void> deleteServer(final ServerDomain serverDomain) async {
|
||||
final HetznerApi hetznerApi = HetznerApi();
|
||||
final CloudflareApi cloudFlare = CloudflareApi();
|
||||
|
||||
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||
domainName: serverDomain.domainName,
|
||||
);
|
||||
|
||||
await box.put(BNames.hasFinalChecked, false);
|
||||
await box.put(BNames.isServerStarted, false);
|
||||
await box.put(BNames.isServerResetedFirstTime, false);
|
||||
await box.put(BNames.isServerResetedSecondTime, false);
|
||||
await box.put(BNames.isLoading, false);
|
||||
await box.put(BNames.serverDetails, null);
|
||||
|
||||
await cloudFlare.removeSimilarRecords(cloudFlareDomain: serverDomain);
|
||||
}
|
||||
|
||||
Future<void> deleteServerRelatedRecords() async {
|
||||
await box.deleteAll([
|
||||
BNames.serverDetails,
|
||||
BNames.isServerStarted,
|
||||
BNames.isServerResetedFirstTime,
|
||||
BNames.isServerResetedSecondTime,
|
||||
BNames.hasFinalChecked,
|
||||
BNames.isLoading,
|
||||
]);
|
||||
getIt<ApiConfigModel>().init();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
part of '../server_installation/server_installation_cubit.dart';
|
||||
|
||||
abstract class ServerInstallationState extends Equatable {
|
||||
const ServerInstallationState({
|
||||
required this.hetznerKey,
|
||||
required this.cloudFlareKey,
|
||||
required this.backblazeCredential,
|
||||
required this.serverDomain,
|
||||
required this.rootUser,
|
||||
required this.serverDetails,
|
||||
required this.isServerStarted,
|
||||
required this.isServerResetedFirstTime,
|
||||
required this.isServerResetedSecondTime,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
serverDomain,
|
||||
rootUser,
|
||||
serverDetails,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
];
|
||||
|
||||
final String? hetznerKey;
|
||||
final String? cloudFlareKey;
|
||||
final BackblazeCredential? backblazeCredential;
|
||||
final ServerDomain? serverDomain;
|
||||
final User? rootUser;
|
||||
final ServerHostingDetails? serverDetails;
|
||||
final bool isServerStarted;
|
||||
final bool isServerResetedFirstTime;
|
||||
final bool isServerResetedSecondTime;
|
||||
|
||||
bool get isHetznerFilled => hetznerKey != null;
|
||||
bool get isCloudFlareFilled => cloudFlareKey != null;
|
||||
bool get isBackblazeFilled => backblazeCredential != null;
|
||||
bool get isDomainFilled => serverDomain != null;
|
||||
bool get isUserFilled => rootUser != null;
|
||||
bool get isServerCreated => serverDetails != null;
|
||||
|
||||
bool get isFullyInitilized => _fulfilementList.every((final el) => el!);
|
||||
ServerSetupProgress get progress => ServerSetupProgress
|
||||
.values[_fulfilementList.where((final el) => el!).length];
|
||||
|
||||
int get porgressBar {
|
||||
if (progress.index < 6) {
|
||||
return progress.index;
|
||||
} else if (progress.index < 10) {
|
||||
return 6;
|
||||
} else {
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
List<bool?> get _fulfilementList {
|
||||
final List<bool> res = [
|
||||
isHetznerFilled,
|
||||
isCloudFlareFilled,
|
||||
isBackblazeFilled,
|
||||
isDomainFilled,
|
||||
isUserFilled,
|
||||
isServerCreated,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
isServerResetedSecondTime,
|
||||
];
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class TimerState extends ServerInstallationNotFinished {
|
||||
TimerState({
|
||||
required this.dataState,
|
||||
required final super.isLoading,
|
||||
this.timerStart,
|
||||
this.duration,
|
||||
}) : super(
|
||||
hetznerKey: dataState.hetznerKey,
|
||||
cloudFlareKey: dataState.cloudFlareKey,
|
||||
backblazeCredential: dataState.backblazeCredential,
|
||||
serverDomain: dataState.serverDomain,
|
||||
rootUser: dataState.rootUser,
|
||||
serverDetails: dataState.serverDetails,
|
||||
isServerStarted: dataState.isServerStarted,
|
||||
isServerResetedFirstTime: dataState.isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: dataState.isServerResetedSecondTime,
|
||||
dnsMatches: dataState.dnsMatches,
|
||||
);
|
||||
|
||||
final ServerInstallationNotFinished dataState;
|
||||
final DateTime? timerStart;
|
||||
final Duration? duration;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
dataState,
|
||||
timerStart,
|
||||
duration,
|
||||
];
|
||||
}
|
||||
|
||||
enum ServerSetupProgress {
|
||||
nothingYet,
|
||||
hetznerFilled,
|
||||
cloudFlareFilled,
|
||||
backblazeFilled,
|
||||
domainFilled,
|
||||
userFilled,
|
||||
serverCreated,
|
||||
serverStarted,
|
||||
serverResetedFirstTime,
|
||||
serverResetedSecondTime,
|
||||
}
|
||||
|
||||
class ServerInstallationNotFinished extends ServerInstallationState {
|
||||
const ServerInstallationNotFinished({
|
||||
required final super.isServerStarted,
|
||||
required final super.isServerResetedFirstTime,
|
||||
required final super.isServerResetedSecondTime,
|
||||
required final this.isLoading,
|
||||
required this.dnsMatches,
|
||||
final super.hetznerKey,
|
||||
final super.cloudFlareKey,
|
||||
final super.backblazeCredential,
|
||||
final super.serverDomain,
|
||||
final super.rootUser,
|
||||
final super.serverDetails,
|
||||
});
|
||||
final bool isLoading;
|
||||
final Map<String, bool>? dnsMatches;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
serverDomain,
|
||||
rootUser,
|
||||
serverDetails,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
isLoading,
|
||||
dnsMatches,
|
||||
];
|
||||
|
||||
ServerInstallationNotFinished copyWith({
|
||||
final String? hetznerKey,
|
||||
final String? cloudFlareKey,
|
||||
final BackblazeCredential? backblazeCredential,
|
||||
final ServerDomain? serverDomain,
|
||||
final User? rootUser,
|
||||
final ServerHostingDetails? serverDetails,
|
||||
final bool? isServerStarted,
|
||||
final bool? isServerResetedFirstTime,
|
||||
final bool? isServerResetedSecondTime,
|
||||
final bool? isLoading,
|
||||
final Map<String, bool>? dnsMatches,
|
||||
}) =>
|
||||
ServerInstallationNotFinished(
|
||||
hetznerKey: hetznerKey ?? this.hetznerKey,
|
||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||
serverDomain: serverDomain ?? this.serverDomain,
|
||||
rootUser: rootUser ?? this.rootUser,
|
||||
serverDetails: serverDetails ?? this.serverDetails,
|
||||
isServerStarted: isServerStarted ?? this.isServerStarted,
|
||||
isServerResetedFirstTime:
|
||||
isServerResetedFirstTime ?? this.isServerResetedFirstTime,
|
||||
isServerResetedSecondTime:
|
||||
isServerResetedSecondTime ?? this.isServerResetedSecondTime,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
dnsMatches: dnsMatches ?? this.dnsMatches,
|
||||
);
|
||||
|
||||
ServerInstallationFinished finish() => ServerInstallationFinished(
|
||||
hetznerKey: hetznerKey!,
|
||||
cloudFlareKey: cloudFlareKey!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
serverDomain: serverDomain!,
|
||||
rootUser: rootUser!,
|
||||
serverDetails: serverDetails!,
|
||||
isServerStarted: isServerStarted,
|
||||
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||
);
|
||||
}
|
||||
|
||||
class ServerInstallationEmpty extends ServerInstallationNotFinished {
|
||||
const ServerInstallationEmpty()
|
||||
: super(
|
||||
hetznerKey: null,
|
||||
cloudFlareKey: null,
|
||||
backblazeCredential: null,
|
||||
serverDomain: null,
|
||||
rootUser: null,
|
||||
serverDetails: null,
|
||||
isServerStarted: false,
|
||||
isServerResetedFirstTime: false,
|
||||
isServerResetedSecondTime: false,
|
||||
isLoading: false,
|
||||
dnsMatches: null,
|
||||
);
|
||||
}
|
||||
|
||||
class ServerInstallationFinished extends ServerInstallationState {
|
||||
const ServerInstallationFinished({
|
||||
required final String super.hetznerKey,
|
||||
required final String super.cloudFlareKey,
|
||||
required final BackblazeCredential super.backblazeCredential,
|
||||
required final ServerDomain super.serverDomain,
|
||||
required final User super.rootUser,
|
||||
required final ServerHostingDetails super.serverDetails,
|
||||
required final super.isServerStarted,
|
||||
required final super.isServerResetedFirstTime,
|
||||
required final super.isServerResetedSecondTime,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
serverDomain,
|
||||
rootUser,
|
||||
serverDetails,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
];
|
||||
}
|
||||
|
||||
enum RecoveryStep {
|
||||
selecting,
|
||||
recoveryKey,
|
||||
newDeviceKey,
|
||||
oldToken,
|
||||
hetznerToken,
|
||||
serverSelection,
|
||||
cloudflareToken,
|
||||
backblazeToken,
|
||||
}
|
||||
|
||||
enum ServerRecoveryCapabilities {
|
||||
none,
|
||||
legacy,
|
||||
loginTokens,
|
||||
}
|
||||
|
||||
enum ServerRecoveryMethods {
|
||||
newDeviceKey,
|
||||
recoveryKey,
|
||||
oldToken,
|
||||
}
|
||||
|
||||
class ServerInstallationRecovery extends ServerInstallationState {
|
||||
const ServerInstallationRecovery({
|
||||
required this.currentStep,
|
||||
required this.recoveryCapabilities,
|
||||
final super.hetznerKey,
|
||||
final super.cloudFlareKey,
|
||||
final super.backblazeCredential,
|
||||
final super.serverDomain,
|
||||
final super.rootUser,
|
||||
final super.serverDetails,
|
||||
}) : super(
|
||||
isServerStarted: true,
|
||||
isServerResetedFirstTime: true,
|
||||
isServerResetedSecondTime: true,
|
||||
);
|
||||
final RecoveryStep currentStep;
|
||||
final ServerRecoveryCapabilities recoveryCapabilities;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
hetznerKey,
|
||||
cloudFlareKey,
|
||||
backblazeCredential,
|
||||
serverDomain,
|
||||
rootUser,
|
||||
serverDetails,
|
||||
isServerStarted,
|
||||
isServerResetedFirstTime,
|
||||
currentStep
|
||||
];
|
||||
|
||||
ServerInstallationRecovery copyWith({
|
||||
final String? hetznerKey,
|
||||
final String? cloudFlareKey,
|
||||
final BackblazeCredential? backblazeCredential,
|
||||
final ServerDomain? serverDomain,
|
||||
final User? rootUser,
|
||||
final ServerHostingDetails? serverDetails,
|
||||
final RecoveryStep? currentStep,
|
||||
final ServerRecoveryCapabilities? recoveryCapabilities,
|
||||
}) =>
|
||||
ServerInstallationRecovery(
|
||||
hetznerKey: hetznerKey ?? this.hetznerKey,
|
||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||
serverDomain: serverDomain ?? this.serverDomain,
|
||||
rootUser: rootUser ?? this.rootUser,
|
||||
serverDetails: serverDetails ?? this.serverDetails,
|
||||
currentStep: currentStep ?? this.currentStep,
|
||||
recoveryCapabilities: recoveryCapabilities ?? this.recoveryCapabilities,
|
||||
);
|
||||
|
||||
ServerInstallationFinished finish() => ServerInstallationFinished(
|
||||
hetznerKey: hetznerKey!,
|
||||
cloudFlareKey: cloudFlareKey!,
|
||||
backblazeCredential: backblazeCredential!,
|
||||
serverDomain: serverDomain!,
|
||||
rootUser: rootUser!,
|
||||
serverDetails: serverDetails!,
|
||||
isServerStarted: true,
|
||||
isServerResetedFirstTime: true,
|
||||
isServerResetedSecondTime: true,
|
||||
);
|
||||
}
|
|
@ -1,20 +1,17 @@
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
|
||||
part 'services_state.dart';
|
||||
|
||||
class ServicesCubit extends AppConfigDependendCubit<ServicesState> {
|
||||
ServicesCubit(AppConfigCubit appConfigCubit)
|
||||
: super(appConfigCubit, ServicesState.allOff());
|
||||
|
||||
Box box = Hive.box(BNames.servicesState);
|
||||
final api = ServerApi();
|
||||
class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
||||
ServicesCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(serverInstallationCubit, ServicesState.allOff());
|
||||
final ServerApi api = ServerApi();
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (appConfigCubit.state is AppConfigFinished) {
|
||||
var statuses = await api.servicesPowerCheck();
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final Map<ServiceTypes, bool> statuses = await api.servicesPowerCheck();
|
||||
emit(
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: statuses[ServiceTypes.passwordManager]!,
|
||||
|
@ -29,7 +26,6 @@ class ServicesCubit extends AppConfigDependendCubit<ServicesState> {
|
|||
|
||||
@override
|
||||
void clear() async {
|
||||
box.clear();
|
||||
emit(ServicesState.allOff());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
part of 'services_cubit.dart';
|
||||
|
||||
class ServicesState extends AppConfigDependendState {
|
||||
class ServicesState extends ServerInstallationDependendState {
|
||||
factory ServicesState.allOn() => const ServicesState(
|
||||
isPasswordManagerEnable: true,
|
||||
isCloudEnable: true,
|
||||
isGitEnable: true,
|
||||
isSocialNetworkEnable: true,
|
||||
isVpnEnable: true,
|
||||
);
|
||||
|
||||
factory ServicesState.allOff() => const ServicesState(
|
||||
isPasswordManagerEnable: false,
|
||||
isCloudEnable: false,
|
||||
isGitEnable: false,
|
||||
isSocialNetworkEnable: false,
|
||||
isVpnEnable: false,
|
||||
);
|
||||
const ServicesState({
|
||||
required this.isPasswordManagerEnable,
|
||||
required this.isCloudEnable,
|
||||
|
@ -15,23 +30,8 @@ class ServicesState extends AppConfigDependendState {
|
|||
final bool isSocialNetworkEnable;
|
||||
final bool isVpnEnable;
|
||||
|
||||
factory ServicesState.allOff() => ServicesState(
|
||||
isPasswordManagerEnable: false,
|
||||
isCloudEnable: false,
|
||||
isGitEnable: false,
|
||||
isSocialNetworkEnable: false,
|
||||
isVpnEnable: false,
|
||||
);
|
||||
factory ServicesState.allOn() => ServicesState(
|
||||
isPasswordManagerEnable: true,
|
||||
isCloudEnable: true,
|
||||
isGitEnable: true,
|
||||
isSocialNetworkEnable: true,
|
||||
isVpnEnable: true,
|
||||
);
|
||||
|
||||
ServicesState enableList(
|
||||
List<ServiceTypes> list,
|
||||
final List<ServiceTypes> list,
|
||||
) =>
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||
|
@ -48,7 +48,7 @@ class ServicesState extends AppConfigDependendState {
|
|||
);
|
||||
|
||||
ServicesState disableList(
|
||||
List<ServiceTypes> list,
|
||||
final List<ServiceTypes> list,
|
||||
) =>
|
||||
ServicesState(
|
||||
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||
|
@ -74,7 +74,7 @@ class ServicesState extends AppConfigDependendState {
|
|||
isVpnEnable
|
||||
];
|
||||
|
||||
bool isEnableByType(ServiceTypes type) {
|
||||
bool isEnableByType(final ServiceTypes type) {
|
||||
switch (type) {
|
||||
case ServiceTypes.passwordManager:
|
||||
return isPasswordManagerEnable;
|
||||
|
|
|
@ -1,131 +1,169 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
import '../../api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'users_state.dart';
|
||||
|
||||
class UsersCubit extends AppConfigDependendCubit<UsersState> {
|
||||
UsersCubit(AppConfigCubit appConfigCubit)
|
||||
class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
||||
UsersCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||
: super(
|
||||
appConfigCubit,
|
||||
UsersState(
|
||||
<User>[], User(login: 'root'), User(login: 'loading...')));
|
||||
Box<User> box = Hive.box<User>(BNames.users);
|
||||
Box configBox = Hive.box(BNames.appConfig);
|
||||
serverInstallationCubit,
|
||||
const UsersState(
|
||||
<User>[],
|
||||
User(login: 'root'),
|
||||
User(login: 'loading...'),
|
||||
),
|
||||
);
|
||||
Box<User> box = Hive.box<User>(BNames.usersBox);
|
||||
Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
final api = ServerApi();
|
||||
final ServerApi api = ServerApi();
|
||||
|
||||
@override
|
||||
Future<void> load() async {
|
||||
if (appConfigCubit.state is AppConfigFinished) {
|
||||
var loadedUsers = box.values.toList();
|
||||
final primaryUser = configBox.get(BNames.rootUser,
|
||||
defaultValue: User(login: 'loading...'));
|
||||
List<String> rootKeys = [
|
||||
...configBox.get(BNames.rootKeys, defaultValue: [])
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final List<User> loadedUsers = box.values.toList();
|
||||
final primaryUser = serverInstallationBox.get(
|
||||
BNames.rootUser,
|
||||
defaultValue: const User(login: 'loading...'),
|
||||
);
|
||||
final List<String> rootKeys = [
|
||||
...serverInstallationBox.get(BNames.rootKeys, defaultValue: [])
|
||||
];
|
||||
if (loadedUsers.isNotEmpty) {
|
||||
emit(UsersState(
|
||||
loadedUsers, User(login: 'root', sshKeys: rootKeys), primaryUser));
|
||||
emit(
|
||||
UsersState(
|
||||
loadedUsers,
|
||||
User(login: 'root', sshKeys: rootKeys),
|
||||
primaryUser,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final usersFromServer = await api.getUsersList();
|
||||
final ApiResponse<List<String>> usersFromServer =
|
||||
await api.getUsersList();
|
||||
if (usersFromServer.isSuccess) {
|
||||
final updatedList =
|
||||
final List<User> updatedList =
|
||||
mergeLocalAndServerUsers(loadedUsers, usersFromServer.data);
|
||||
emit(UsersState(
|
||||
updatedList, User(login: 'root', sshKeys: rootKeys), primaryUser));
|
||||
emit(
|
||||
UsersState(
|
||||
updatedList,
|
||||
User(login: 'root', sshKeys: rootKeys),
|
||||
primaryUser,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final usersWithSshKeys = await loadSshKeys(state.users);
|
||||
final List<User> usersWithSshKeys = await loadSshKeys(state.users);
|
||||
// Update the users it the box
|
||||
box.clear();
|
||||
box.addAll(usersWithSshKeys);
|
||||
|
||||
final rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||
configBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final primaryUserWithSshKeys =
|
||||
final User rootUserWithSshKeys =
|
||||
(await loadSshKeys([state.rootUser])).first;
|
||||
serverInstallationBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final User primaryUserWithSshKeys =
|
||||
(await loadSshKeys([state.primaryUser])).first;
|
||||
configBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(UsersState(
|
||||
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys));
|
||||
serverInstallationBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(
|
||||
UsersState(
|
||||
usersWithSshKeys,
|
||||
rootUserWithSshKeys,
|
||||
primaryUserWithSshKeys,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<User> mergeLocalAndServerUsers(
|
||||
List<User> localUsers, List<String> serverUsers) {
|
||||
final List<User> localUsers,
|
||||
final List<String> serverUsers,
|
||||
) {
|
||||
// If local user not exists on server, add it with isFoundOnServer = false
|
||||
// If server user not exists on local, add it
|
||||
|
||||
List<User> mergedUsers = [];
|
||||
List<String> serverUsersCopy = List.from(serverUsers);
|
||||
final List<User> mergedUsers = [];
|
||||
final List<String> serverUsersCopy = List.from(serverUsers);
|
||||
|
||||
for (var localUser in localUsers) {
|
||||
for (final User localUser in localUsers) {
|
||||
if (serverUsersCopy.contains(localUser.login)) {
|
||||
mergedUsers.add(User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: localUser.password,
|
||||
sshKeys: localUser.sshKeys,
|
||||
));
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: localUser.password,
|
||||
sshKeys: localUser.sshKeys,
|
||||
),
|
||||
);
|
||||
serverUsersCopy.remove(localUser.login);
|
||||
} else {
|
||||
mergedUsers.add(User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: false,
|
||||
password: localUser.password,
|
||||
note: localUser.note,
|
||||
));
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: localUser.login,
|
||||
isFoundOnServer: false,
|
||||
password: localUser.password,
|
||||
note: localUser.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (var serverUser in serverUsersCopy) {
|
||||
mergedUsers.add(User(
|
||||
login: serverUser,
|
||||
isFoundOnServer: true,
|
||||
));
|
||||
for (final String serverUser in serverUsersCopy) {
|
||||
mergedUsers.add(
|
||||
User(
|
||||
login: serverUser,
|
||||
isFoundOnServer: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return mergedUsers;
|
||||
}
|
||||
|
||||
Future<List<User>> loadSshKeys(List<User> users) async {
|
||||
List<User> updatedUsers = [];
|
||||
Future<List<User>> loadSshKeys(final List<User> users) async {
|
||||
final List<User> updatedUsers = [];
|
||||
|
||||
for (var user in users) {
|
||||
for (final User user in users) {
|
||||
if (user.isFoundOnServer ||
|
||||
user.login == 'root' ||
|
||||
user.login == state.primaryUser.login) {
|
||||
final sshKeys = await api.getUserSshKeys(user);
|
||||
final ApiResponse<List<String>> sshKeys =
|
||||
await api.getUserSshKeys(user);
|
||||
print('sshKeys for $user: ${sshKeys.data}');
|
||||
if (sshKeys.isSuccess) {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
sshKeys: sshKeys.data,
|
||||
note: user.note,
|
||||
));
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
sshKeys: sshKeys.data,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
));
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
updatedUsers.add(User(
|
||||
login: user.login,
|
||||
isFoundOnServer: false,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
));
|
||||
updatedUsers.add(
|
||||
User(
|
||||
login: user.login,
|
||||
isFoundOnServer: false,
|
||||
password: user.password,
|
||||
note: user.note,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return updatedUsers;
|
||||
|
@ -133,27 +171,34 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
|
|||
|
||||
Future<void> refresh() async {
|
||||
List<User> updatedUsers = List<User>.from(state.users);
|
||||
final usersFromServer = await api.getUsersList();
|
||||
final ApiResponse<List<String>> usersFromServer = await api.getUsersList();
|
||||
if (usersFromServer.isSuccess) {
|
||||
updatedUsers =
|
||||
mergeLocalAndServerUsers(updatedUsers, usersFromServer.data);
|
||||
}
|
||||
final usersWithSshKeys = await loadSshKeys(updatedUsers);
|
||||
final List<User> usersWithSshKeys = await loadSshKeys(updatedUsers);
|
||||
box.clear();
|
||||
box.addAll(usersWithSshKeys);
|
||||
final rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||
configBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final primaryUserWithSshKeys =
|
||||
final User rootUserWithSshKeys =
|
||||
(await loadSshKeys([state.rootUser])).first;
|
||||
serverInstallationBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||
final User primaryUserWithSshKeys =
|
||||
(await loadSshKeys([state.primaryUser])).first;
|
||||
configBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(UsersState(
|
||||
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys));
|
||||
serverInstallationBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||
emit(
|
||||
UsersState(
|
||||
usersWithSshKeys,
|
||||
rootUserWithSshKeys,
|
||||
primaryUserWithSshKeys,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> createUser(User user) async {
|
||||
Future<void> createUser(final User user) async {
|
||||
// If user exists on server, do nothing
|
||||
if (state.users.any((u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
if (state.users
|
||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||
return;
|
||||
}
|
||||
// If user is root or primary user, do nothing
|
||||
|
@ -161,78 +206,82 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
|
|||
return;
|
||||
}
|
||||
// If API returned error, do nothing
|
||||
final result = await api.createUser(user);
|
||||
final ApiResponse<User> result = await api.createUser(user);
|
||||
if (!result.isSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
var loadedUsers = List<User>.from(state.users);
|
||||
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(User user) async {
|
||||
Future<void> deleteUser(final User user) async {
|
||||
// If user is primary or root, don't delete
|
||||
if (user.login == state.primaryUser.login || user.login == 'root') {
|
||||
return;
|
||||
}
|
||||
var loadedUsers = List<User>.from(state.users);
|
||||
final result = await api.deleteUser(user);
|
||||
final List<User> loadedUsers = List<User>.from(state.users);
|
||||
final bool result = await api.deleteUser(user);
|
||||
if (result) {
|
||||
loadedUsers.removeWhere((u) => u.login == user.login);
|
||||
loadedUsers.removeWhere((final User u) => u.login == user.login);
|
||||
await box.clear();
|
||||
await box.addAll(loadedUsers);
|
||||
emit(state.copyWith(users: loadedUsers));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addSshKey(User user, String publicKey) async {
|
||||
Future<void> addSshKey(final User user, final String publicKey) async {
|
||||
// If adding root key, use api.addRootSshKey
|
||||
// Otherwise, use api.addUserSshKey
|
||||
if (user.login == 'root') {
|
||||
final result = await api.addRootSshKey(publicKey);
|
||||
final ApiResponse<void> result = await api.addRootSshKey(publicKey);
|
||||
if (result.isSuccess) {
|
||||
// Add ssh key to the array of root keys
|
||||
final rootKeys =
|
||||
configBox.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
final List<String> rootKeys = serverInstallationBox
|
||||
.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
rootKeys.add(publicKey);
|
||||
configBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
serverInstallationBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(
|
||||
state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final result = await api.addUserSshKey(user, publicKey);
|
||||
final ApiResponse<void> result = await api.addUserSshKey(user, publicKey);
|
||||
if (result.isSuccess) {
|
||||
// If it is primary user, update primary user
|
||||
if (user.login == state.primaryUser.login) {
|
||||
List<String> primaryUserKeys =
|
||||
final List<String> primaryUserKeys =
|
||||
List<String>.from(state.primaryUser.sshKeys);
|
||||
primaryUserKeys.add(publicKey);
|
||||
final updatedUser = User(
|
||||
final User updatedUser = User(
|
||||
login: state.primaryUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.primaryUser.password,
|
||||
sshKeys: primaryUserKeys,
|
||||
note: state.primaryUser.note,
|
||||
);
|
||||
configBox.put(BNames.rootUser, updatedUser);
|
||||
emit(state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
));
|
||||
serverInstallationBox.put(BNames.rootUser, updatedUser);
|
||||
emit(
|
||||
state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// If it is not primary user, update user
|
||||
List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
final List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
userKeys.add(publicKey);
|
||||
final updatedUser = User(
|
||||
final User updatedUser = User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
|
@ -240,59 +289,66 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
|
|||
note: user.note,
|
||||
);
|
||||
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||
emit(state.copyWith(
|
||||
users: box.values.toList(),
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteSshKey(User user, String publicKey) async {
|
||||
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
||||
// All keys are deleted via api.deleteUserSshKey
|
||||
|
||||
final result = await api.deleteUserSshKey(user, publicKey);
|
||||
final ApiResponse<void> result =
|
||||
await api.deleteUserSshKey(user, publicKey);
|
||||
if (result.isSuccess) {
|
||||
// If it is root user, delete key from root keys
|
||||
// If it is primary user, update primary user
|
||||
// If it is not primary user, update user
|
||||
|
||||
if (user.login == 'root') {
|
||||
final rootKeys =
|
||||
configBox.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
final List<String> rootKeys = serverInstallationBox
|
||||
.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||
rootKeys.remove(publicKey);
|
||||
configBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
serverInstallationBox.put(BNames.rootKeys, rootKeys);
|
||||
emit(
|
||||
state.copyWith(
|
||||
rootUser: User(
|
||||
login: state.rootUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.rootUser.password,
|
||||
sshKeys: rootKeys,
|
||||
note: state.rootUser.note,
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (user.login == state.primaryUser.login) {
|
||||
List<String> primaryUserKeys =
|
||||
final List<String> primaryUserKeys =
|
||||
List<String>.from(state.primaryUser.sshKeys);
|
||||
primaryUserKeys.remove(publicKey);
|
||||
final updatedUser = User(
|
||||
final User updatedUser = User(
|
||||
login: state.primaryUser.login,
|
||||
isFoundOnServer: true,
|
||||
password: state.primaryUser.password,
|
||||
sshKeys: primaryUserKeys,
|
||||
note: state.primaryUser.note,
|
||||
);
|
||||
configBox.put(BNames.rootUser, updatedUser);
|
||||
emit(state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
));
|
||||
serverInstallationBox.put(BNames.rootUser, updatedUser);
|
||||
emit(
|
||||
state.copyWith(
|
||||
primaryUser: updatedUser,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
final List<String> userKeys = List<String>.from(user.sshKeys);
|
||||
userKeys.remove(publicKey);
|
||||
final updatedUser = User(
|
||||
final User updatedUser = User(
|
||||
login: user.login,
|
||||
isFoundOnServer: true,
|
||||
password: user.password,
|
||||
|
@ -300,14 +356,22 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
|
|||
note: user.note,
|
||||
);
|
||||
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||
emit(state.copyWith(
|
||||
users: box.values.toList(),
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
users: box.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() async {
|
||||
emit(UsersState(<User>[], User(login: 'root'), User(login: 'loading...')));
|
||||
emit(
|
||||
const UsersState(
|
||||
<User>[],
|
||||
User(login: 'root'),
|
||||
User(login: 'loading...'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
part of 'users_cubit.dart';
|
||||
|
||||
class UsersState extends AppConfigDependendState {
|
||||
class UsersState extends ServerInstallationDependendState {
|
||||
const UsersState(this.users, this.rootUser, this.primaryUser);
|
||||
|
||||
final List<User> users;
|
||||
|
@ -11,22 +11,20 @@ class UsersState extends AppConfigDependendState {
|
|||
List<Object> get props => [users, rootUser, primaryUser];
|
||||
|
||||
UsersState copyWith({
|
||||
List<User>? users,
|
||||
User? rootUser,
|
||||
User? primaryUser,
|
||||
}) {
|
||||
return UsersState(
|
||||
users ?? this.users,
|
||||
rootUser ?? this.rootUser,
|
||||
primaryUser ?? this.primaryUser,
|
||||
);
|
||||
}
|
||||
final List<User>? users,
|
||||
final User? rootUser,
|
||||
final User? primaryUser,
|
||||
}) =>
|
||||
UsersState(
|
||||
users ?? this.users,
|
||||
rootUser ?? this.rootUser,
|
||||
primaryUser ?? this.primaryUser,
|
||||
);
|
||||
|
||||
bool isLoginRegistered(String login) {
|
||||
return users.any((user) => user.login == login) ||
|
||||
login == rootUser.login ||
|
||||
login == primaryUser.login;
|
||||
}
|
||||
bool isLoginRegistered(final String login) =>
|
||||
users.any((final User user) => user.login == login) ||
|
||||
login == rootUser.login ||
|
||||
login == primaryUser.login;
|
||||
|
||||
bool get isEmpty => users.isEmpty;
|
||||
}
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
|
||||
class ApiConfigModel {
|
||||
Box _box = Hive.box(BNames.appConfig);
|
||||
final Box _box = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
HetznerServerDetails? get hetznerServer => _hetznerServer;
|
||||
ServerHostingDetails? get serverDetails => _serverDetails;
|
||||
String? get hetznerKey => _hetznerKey;
|
||||
String? get cloudFlareKey => _cloudFlareKey;
|
||||
BackblazeCredential? get backblazeCredential => _backblazeCredential;
|
||||
CloudFlareDomain? get cloudFlareDomain => _cloudFlareDomain;
|
||||
ServerDomain? get serverDomain => _serverDomain;
|
||||
BackblazeBucket? get backblazeBucket => _backblazeBucket;
|
||||
|
||||
String? _hetznerKey;
|
||||
String? _cloudFlareKey;
|
||||
HetznerServerDetails? _hetznerServer;
|
||||
ServerHostingDetails? _serverDetails;
|
||||
BackblazeCredential? _backblazeCredential;
|
||||
CloudFlareDomain? _cloudFlareDomain;
|
||||
ServerDomain? _serverDomain;
|
||||
BackblazeBucket? _backblazeBucket;
|
||||
|
||||
Future<void> storeHetznerKey(String value) async {
|
||||
Future<void> storeHetznerKey(final String value) async {
|
||||
await _box.put(BNames.hetznerKey, value);
|
||||
_hetznerKey = value;
|
||||
}
|
||||
|
||||
Future<void> storeCloudFlareKey(String value) async {
|
||||
Future<void> storeCloudFlareKey(final String value) async {
|
||||
await _box.put(BNames.cloudFlareKey, value);
|
||||
_cloudFlareKey = value;
|
||||
}
|
||||
|
||||
Future<void> storeBackblazeCredential(BackblazeCredential value) async {
|
||||
await _box.put(BNames.backblazeKey, value);
|
||||
Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
|
||||
await _box.put(BNames.backblazeCredential, value);
|
||||
|
||||
_backblazeCredential = value;
|
||||
}
|
||||
|
||||
Future<void> storeCloudFlareDomain(CloudFlareDomain value) async {
|
||||
await _box.put(BNames.cloudFlareDomain, value);
|
||||
_cloudFlareDomain = value;
|
||||
Future<void> storeServerDomain(final ServerDomain value) async {
|
||||
await _box.put(BNames.serverDomain, value);
|
||||
_serverDomain = value;
|
||||
}
|
||||
|
||||
Future<void> storeServerDetails(HetznerServerDetails value) async {
|
||||
await _box.put(BNames.hetznerServer, value);
|
||||
_hetznerServer = value;
|
||||
Future<void> storeServerDetails(final ServerHostingDetails value) async {
|
||||
await _box.put(BNames.serverDetails, value);
|
||||
_serverDetails = value;
|
||||
}
|
||||
|
||||
Future<void> storeBackblazeBucket(BackblazeBucket value) async {
|
||||
Future<void> storeBackblazeBucket(final BackblazeBucket value) async {
|
||||
await _box.put(BNames.backblazeBucket, value);
|
||||
_backblazeBucket = value;
|
||||
}
|
||||
|
||||
clear() {
|
||||
void clear() {
|
||||
_hetznerKey = null;
|
||||
_cloudFlareKey = null;
|
||||
_backblazeCredential = null;
|
||||
_cloudFlareDomain = null;
|
||||
_hetznerServer = null;
|
||||
_serverDomain = null;
|
||||
_serverDetails = null;
|
||||
_backblazeBucket = null;
|
||||
}
|
||||
|
||||
|
@ -66,9 +66,9 @@ class ApiConfigModel {
|
|||
_hetznerKey = _box.get(BNames.hetznerKey);
|
||||
|
||||
_cloudFlareKey = _box.get(BNames.cloudFlareKey);
|
||||
_backblazeCredential = _box.get(BNames.backblazeKey);
|
||||
_cloudFlareDomain = _box.get(BNames.cloudFlareDomain);
|
||||
_hetznerServer = _box.get(BNames.hetznerServer);
|
||||
_backblazeCredential = _box.get(BNames.backblazeCredential);
|
||||
_serverDomain = _box.get(BNames.serverDomain);
|
||||
_serverDetails = _box.get(BNames.serverDetails);
|
||||
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/logic/models/message.dart';
|
||||
|
||||
class ConsoleModel extends ChangeNotifier {
|
||||
List<Message> _messages = [];
|
||||
final List<Message> _messages = [];
|
||||
|
||||
List<Message> get messages => _messages;
|
||||
|
||||
void addMessage(Message message) {
|
||||
void addMessage(final Message message) {
|
||||
messages.add(message);
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
@ -9,18 +9,18 @@ class NavigationService {
|
|||
|
||||
NavigatorState? get navigator => navigatorKey.currentState;
|
||||
|
||||
void showPopUpDialog(AlertDialog dialog) {
|
||||
final context = navigatorKey.currentState!.overlay!.context;
|
||||
void showPopUpDialog(final AlertDialog dialog) {
|
||||
final BuildContext context = navigatorKey.currentState!.overlay!.context;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => dialog,
|
||||
builder: (final _) => dialog,
|
||||
);
|
||||
}
|
||||
|
||||
void showSnackBar(String text) {
|
||||
final state = scaffoldMessengerKey.currentState!;
|
||||
final snack = SnackBar(
|
||||
void showSnackBar(final String text) {
|
||||
final ScaffoldMessengerState state = scaffoldMessengerKey.currentState!;
|
||||
final SnackBar snack = SnackBar(
|
||||
backgroundColor: BrandColors.black.withOpacity(0.8),
|
||||
content: Text(text, style: buttonTitleText),
|
||||
duration: const Duration(seconds: 2),
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:pointycastle/pointycastle.dart';
|
||||
import 'package:rsa_encrypt/rsa_encrypt.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:pointycastle/api.dart' as crypto;
|
||||
import 'package:ssh_key/ssh_key.dart' as ssh_key;
|
||||
|
||||
class SSHModel {
|
||||
Box _box = Hive.box(BNames.sshConfig);
|
||||
String? savedPrivateKey;
|
||||
String? savedPubKey;
|
||||
|
||||
Future<void> generateKeys() async {
|
||||
var helper = RsaKeyHelper();
|
||||
crypto.AsymmetricKeyPair pair =
|
||||
await helper.computeRSAKeyPair(helper.getSecureRandom());
|
||||
var privateKey = pair.privateKey as RSAPrivateKey;
|
||||
var publicKey = pair.publicKey as RSAPublicKey;
|
||||
|
||||
savedPrivateKey = helper.encodePrivateKeyToPemPKCS1(privateKey);
|
||||
savedPubKey = publicKey.encode(ssh_key.PubKeyEncoding.openSsh);
|
||||
|
||||
await _box.put(BNames.sshPrivateKey, savedPrivateKey);
|
||||
await _box.put(BNames.sshPublicKey, savedPubKey);
|
||||
}
|
||||
|
||||
void init() async {
|
||||
savedPrivateKey = _box.get(BNames.sshPrivateKey);
|
||||
savedPubKey = _box.get(BNames.sshPublicKey);
|
||||
}
|
||||
|
||||
bool get isSSHKeyGenerated => savedPrivateKey != null && savedPubKey != null;
|
||||
|
||||
Future<void> clear() async {
|
||||
savedPrivateKey = null;
|
||||
savedPubKey = null;
|
||||
await _box.clear();
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'cloudflare_domain.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class CloudFlareDomain {
|
||||
CloudFlareDomain({
|
||||
required this.domainName,
|
||||
required this.zoneId,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String domainName;
|
||||
|
||||
@HiveField(1)
|
||||
final String zoneId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$domainName: $zoneId';
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cloudflare_domain.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
CloudFlareDomain read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return CloudFlareDomain(
|
||||
domainName: fields[0] as String,
|
||||
zoneId: fields[1] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CloudFlareDomain obj) {
|
||||
writer
|
||||
..writeByte(2)
|
||||
..writeByte(0)
|
||||
..write(obj.domainName)
|
||||
..writeByte(1)
|
||||
..write(obj.zoneId);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CloudFlareDomainAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
13
lib/logic/models/hive/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Registered Hive Types
|
||||
|
||||
1. User
|
||||
2. ServerHostingDetails
|
||||
3. ServerDomain
|
||||
4. BackblazeCredential
|
||||
5. ServerVolume
|
||||
6. BackblazeBucket
|
||||
|
||||
|
||||
## Enums
|
||||
100. DnsProvider
|
||||
101. ServerProvider
|
|
@ -4,11 +4,12 @@ part 'backblaze_bucket.g.dart';
|
|||
|
||||
@HiveType(typeId: 6)
|
||||
class BackblazeBucket {
|
||||
BackblazeBucket(
|
||||
{required this.bucketId,
|
||||
required this.bucketName,
|
||||
required this.applicationKeyId,
|
||||
required this.applicationKey});
|
||||
BackblazeBucket({
|
||||
required this.bucketId,
|
||||
required this.bucketName,
|
||||
required this.applicationKeyId,
|
||||
required this.applicationKey,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String bucketId;
|
||||
|
@ -23,7 +24,5 @@ class BackblazeBucket {
|
|||
final String bucketName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$bucketName';
|
||||
}
|
||||
String toString() => bucketName;
|
||||
}
|
|
@ -14,16 +14,14 @@ class BackblazeCredential {
|
|||
@HiveField(1)
|
||||
final String applicationKey;
|
||||
|
||||
get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
|
||||
String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$keyId: $encodedApiKey';
|
||||
}
|
||||
String toString() => '$keyId: $encodedApiKey';
|
||||
}
|
||||
|
||||
String encodedBackblazeKey(String? keyId, String? applicationKey) {
|
||||
String _apiKey = '$keyId:$applicationKey';
|
||||
String encodedApiKey = base64.encode(utf8.encode(_apiKey));
|
||||
String encodedBackblazeKey(final String? keyId, final String? applicationKey) {
|
||||
final String apiKey = '$keyId:$applicationKey';
|
||||
final String encodedApiKey = base64.encode(utf8.encode(apiKey));
|
||||
return encodedApiKey;
|
||||
}
|
72
lib/logic/models/hive/server_details.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'server_details.g.dart';
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
class ServerHostingDetails {
|
||||
ServerHostingDetails({
|
||||
required this.ip4,
|
||||
required this.id,
|
||||
required this.createTime,
|
||||
required this.volume,
|
||||
required this.apiToken,
|
||||
required this.provider,
|
||||
this.startTime,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String ip4;
|
||||
|
||||
@HiveField(1)
|
||||
final int id;
|
||||
|
||||
@HiveField(3)
|
||||
final DateTime? createTime;
|
||||
|
||||
@HiveField(2)
|
||||
final DateTime? startTime;
|
||||
|
||||
@HiveField(4)
|
||||
final ServerVolume volume;
|
||||
|
||||
@HiveField(5)
|
||||
final String apiToken;
|
||||
|
||||
@HiveField(6, defaultValue: ServerProvider.hetzner)
|
||||
final ServerProvider provider;
|
||||
|
||||
ServerHostingDetails copyWith({final DateTime? startTime}) =>
|
||||
ServerHostingDetails(
|
||||
startTime: startTime ?? this.startTime,
|
||||
createTime: createTime,
|
||||
id: id,
|
||||
ip4: ip4,
|
||||
volume: volume,
|
||||
apiToken: apiToken,
|
||||
provider: provider,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => id.toString();
|
||||
}
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
class ServerVolume {
|
||||
ServerVolume({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
@HiveField(1)
|
||||
int id;
|
||||
@HiveField(2)
|
||||
String name;
|
||||
}
|
||||
|
||||
@HiveType(typeId: 101)
|
||||
enum ServerProvider {
|
||||
@HiveField(0)
|
||||
unknown,
|
||||
@HiveField(1)
|
||||
hetzner,
|
||||
}
|
|
@ -6,30 +6,33 @@ part of 'server_details.dart';
|
|||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
|
||||
class ServerHostingDetailsAdapter extends TypeAdapter<ServerHostingDetails> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
HetznerServerDetails read(BinaryReader reader) {
|
||||
ServerHostingDetails read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return HetznerServerDetails(
|
||||
return ServerHostingDetails(
|
||||
ip4: fields[0] as String,
|
||||
id: fields[1] as int,
|
||||
createTime: fields[3] as DateTime?,
|
||||
dataBase: fields[4] as HetznerDataBase,
|
||||
volume: fields[4] as ServerVolume,
|
||||
apiToken: fields[5] as String,
|
||||
provider: fields[6] == null
|
||||
? ServerProvider.hetzner
|
||||
: fields[6] as ServerProvider,
|
||||
startTime: fields[2] as DateTime?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, HetznerServerDetails obj) {
|
||||
void write(BinaryWriter writer, ServerHostingDetails obj) {
|
||||
writer
|
||||
..writeByte(6)
|
||||
..writeByte(7)
|
||||
..writeByte(0)
|
||||
..write(obj.ip4)
|
||||
..writeByte(1)
|
||||
|
@ -39,9 +42,11 @@ class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
|
|||
..writeByte(2)
|
||||
..write(obj.startTime)
|
||||
..writeByte(4)
|
||||
..write(obj.dataBase)
|
||||
..write(obj.volume)
|
||||
..writeByte(5)
|
||||
..write(obj.apiToken);
|
||||
..write(obj.apiToken)
|
||||
..writeByte(6)
|
||||
..write(obj.provider);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -50,29 +55,29 @@ class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
|
|||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is HetznerServerDetailsAdapter &&
|
||||
other is ServerHostingDetailsAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class HetznerDataBaseAdapter extends TypeAdapter<HetznerDataBase> {
|
||||
class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
HetznerDataBase read(BinaryReader reader) {
|
||||
ServerVolume read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return HetznerDataBase(
|
||||
return ServerVolume(
|
||||
id: fields[1] as int,
|
||||
name: fields[2] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, HetznerDataBase obj) {
|
||||
void write(BinaryWriter writer, ServerVolume obj) {
|
||||
writer
|
||||
..writeByte(2)
|
||||
..writeByte(1)
|
||||
|
@ -87,7 +92,46 @@ class HetznerDataBaseAdapter extends TypeAdapter<HetznerDataBase> {
|
|||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is HetznerDataBaseAdapter &&
|
||||
other is ServerVolumeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ServerProviderAdapter extends TypeAdapter<ServerProvider> {
|
||||
@override
|
||||
final int typeId = 101;
|
||||
|
||||
@override
|
||||
ServerProvider read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ServerProvider.unknown;
|
||||
case 1:
|
||||
return ServerProvider.hetzner;
|
||||
default:
|
||||
return ServerProvider.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ServerProvider obj) {
|
||||
switch (obj) {
|
||||
case ServerProvider.unknown:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case ServerProvider.hetzner:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ServerProviderAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
32
lib/logic/models/hive/server_domain.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'server_domain.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class ServerDomain {
|
||||
ServerDomain({
|
||||
required this.domainName,
|
||||
required this.zoneId,
|
||||
required this.provider,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String domainName;
|
||||
|
||||
@HiveField(1)
|
||||
final String zoneId;
|
||||
|
||||
@HiveField(2, defaultValue: DnsProvider.cloudflare)
|
||||
final DnsProvider provider;
|
||||
|
||||
@override
|
||||
String toString() => '$domainName: $zoneId';
|
||||
}
|
||||
|
||||
@HiveType(typeId: 100)
|
||||
enum DnsProvider {
|
||||
@HiveField(0)
|
||||
unknown,
|
||||
@HiveField(1)
|
||||
cloudflare,
|
||||
}
|
87
lib/logic/models/hive/server_domain.g.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'server_domain.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ServerDomainAdapter extends TypeAdapter<ServerDomain> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
ServerDomain read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ServerDomain(
|
||||
domainName: fields[0] as String,
|
||||
zoneId: fields[1] as String,
|
||||
provider:
|
||||
fields[2] == null ? DnsProvider.cloudflare : fields[2] as DnsProvider,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ServerDomain obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.domainName)
|
||||
..writeByte(1)
|
||||
..write(obj.zoneId)
|
||||
..writeByte(2)
|
||||
..write(obj.provider);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ServerDomainAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class DnsProviderAdapter extends TypeAdapter<DnsProvider> {
|
||||
@override
|
||||
final int typeId = 100;
|
||||
|
||||
@override
|
||||
DnsProvider read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return DnsProvider.unknown;
|
||||
case 1:
|
||||
return DnsProvider.cloudflare;
|
||||
default:
|
||||
return DnsProvider.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DnsProvider obj) {
|
||||
switch (obj) {
|
||||
case DnsProvider.unknown:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case DnsProvider.cloudflare:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DnsProviderAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
|
@ -8,7 +8,7 @@ part 'user.g.dart';
|
|||
|
||||
@HiveType(typeId: 1)
|
||||
class User extends Equatable {
|
||||
User({
|
||||
const User({
|
||||
required this.login,
|
||||
this.password,
|
||||
this.sshKeys = const [],
|
||||
|
@ -22,7 +22,7 @@ class User extends Equatable {
|
|||
@HiveField(1)
|
||||
final String? password;
|
||||
|
||||
@HiveField(2, defaultValue: const [])
|
||||
@HiveField(2, defaultValue: [])
|
||||
final List<String> sshKeys;
|
||||
|
||||
@HiveField(3, defaultValue: true)
|
||||
|
@ -36,7 +36,7 @@ class User extends Equatable {
|
|||
|
||||
Color get color => stringToColor(login);
|
||||
|
||||
String toString() {
|
||||
return '$login, ${isFoundOnServer ? 'found' : 'not found'}, ${sshKeys.length} ssh keys, note: $note';
|
||||
}
|
||||
@override
|
||||
String toString() =>
|
||||
'$login, ${isFoundOnServer ? 'found' : 'not found'}, ${sshKeys.length} ssh keys, note: $note';
|
||||
}
|
|
@ -11,9 +11,9 @@ class UserAdapter extends TypeAdapter<User> {
|
|||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
User read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
User read(final BinaryReader reader) {
|
||||
final int numOfFields = reader.readByte();
|
||||
final Map<int, dynamic> fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return User(
|
||||
|
@ -26,7 +26,7 @@ class UserAdapter extends TypeAdapter<User> {
|
|||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, User obj) {
|
||||
void write(final BinaryWriter writer, final User obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
|
@ -45,7 +45,7 @@ class UserAdapter extends TypeAdapter<User> {
|
|||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
bool operator ==(final Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
|
@ -4,13 +4,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
import 'user.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||
|
||||
@immutable
|
||||
class Job extends Equatable {
|
||||
Job({
|
||||
String? id,
|
||||
required this.title,
|
||||
final String? id,
|
||||
}) : id = id ?? StringGenerators.simpleId();
|
||||
|
||||
final String title;
|
||||
|
@ -45,8 +45,8 @@ class DeleteUserJob extends Job {
|
|||
class ToggleJob extends Job {
|
||||
ToggleJob({
|
||||
required this.type,
|
||||
required String title,
|
||||
}) : super(title: title);
|
||||
required final super.title,
|
||||
});
|
||||
|
||||
final dynamic type;
|
||||
|
||||
|
@ -56,12 +56,11 @@ class ToggleJob extends Job {
|
|||
|
||||
class ServiceToggleJob extends ToggleJob {
|
||||
ServiceToggleJob({
|
||||
required ServiceTypes type,
|
||||
required final ServiceTypes super.type,
|
||||
required this.needToTurnOn,
|
||||
}) : super(
|
||||
title:
|
||||
'${needToTurnOn ? "jobs.serviceTurnOn".tr() : "jobs.serviceTurnOff".tr()} ${type.title}',
|
||||
type: type,
|
||||
);
|
||||
|
||||
final bool needToTurnOn;
|
||||
|
@ -71,7 +70,7 @@ class CreateSSHKeyJob extends Job {
|
|||
CreateSSHKeyJob({
|
||||
required this.user,
|
||||
required this.publicKey,
|
||||
}) : super(title: '${"jobs.create_ssh_key".tr(args: [user.login])}');
|
||||
}) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login]));
|
||||
|
||||
final User user;
|
||||
final String publicKey;
|
||||
|
@ -84,7 +83,7 @@ class DeleteSSHKeyJob extends Job {
|
|||
DeleteSSHKeyJob({
|
||||
required this.user,
|
||||
required this.publicKey,
|
||||
}) : super(title: '${"jobs.delete_ssh_key".tr(args: [user.login])}');
|
||||
}) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login]));
|
||||
|
||||
final User user;
|
||||
final String publicKey;
|
||||
|
|
19
lib/logic/models/json/api_token.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'api_token.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ApiToken {
|
||||
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
||||
_$ApiTokenFromJson(json);
|
||||
ApiToken({
|
||||
required this.name,
|
||||
required this.date,
|
||||
required this.isCaller,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final DateTime date;
|
||||
@JsonKey(name: 'is_caller')
|
||||
final bool isCaller;
|
||||
}
|
13
lib/logic/models/json/api_token.g.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'api_token.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ApiToken _$ApiTokenFromJson(Map<String, dynamic> json) => ApiToken(
|
||||
name: json['name'] as String,
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
isCaller: json['is_caller'] as bool,
|
||||
);
|
|
@ -5,18 +5,18 @@ part 'auto_upgrade_settings.g.dart';
|
|||
|
||||
@JsonSerializable(createToJson: true)
|
||||
class AutoUpgradeSettings extends Equatable {
|
||||
final bool enable;
|
||||
final bool allowReboot;
|
||||
factory AutoUpgradeSettings.fromJson(final Map<String, dynamic> json) =>
|
||||
_$AutoUpgradeSettingsFromJson(json);
|
||||
|
||||
AutoUpgradeSettings({
|
||||
const AutoUpgradeSettings({
|
||||
required this.enable,
|
||||
required this.allowReboot,
|
||||
});
|
||||
final bool enable;
|
||||
final bool allowReboot;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [enable, allowReboot];
|
||||
factory AutoUpgradeSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$AutoUpgradeSettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$AutoUpgradeSettingsToJson(this);
|
||||
}
|
|
@ -4,14 +4,14 @@ part 'backup.g.dart';
|
|||
|
||||
@JsonSerializable()
|
||||
class Backup {
|
||||
factory Backup.fromJson(final Map<String, dynamic> json) =>
|
||||
_$BackupFromJson(json);
|
||||
Backup({required this.time, required this.id});
|
||||
|
||||
// Time of the backup
|
||||
final DateTime time;
|
||||
@JsonKey(name: 'short_id')
|
||||
final String id;
|
||||
|
||||
factory Backup.fromJson(Map<String, dynamic> json) => _$BackupFromJson(json);
|
||||
}
|
||||
|
||||
enum BackupStatusEnum {
|
||||
|
@ -33,16 +33,16 @@ enum BackupStatusEnum {
|
|||
|
||||
@JsonSerializable()
|
||||
class BackupStatus {
|
||||
BackupStatus(
|
||||
{required this.status,
|
||||
required this.progress,
|
||||
required this.errorMessage});
|
||||
factory BackupStatus.fromJson(final Map<String, dynamic> json) =>
|
||||
_$BackupStatusFromJson(json);
|
||||
BackupStatus({
|
||||
required this.status,
|
||||
required this.progress,
|
||||
required this.errorMessage,
|
||||
});
|
||||
|
||||
final BackupStatusEnum status;
|
||||
final double progress;
|
||||
@JsonKey(name: 'error_message')
|
||||
final String? errorMessage;
|
||||
|
||||
factory BackupStatus.fromJson(Map<String, dynamic> json) =>
|
||||
_$BackupStatusFromJson(json);
|
||||
}
|