feat(hosting): Implement Digital Ocean hosting support #140
After Width: | Height: | Size: 36 KiB |
|
@ -31,7 +31,8 @@
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"continue": "Continue"
|
"continue": "Continue",
|
||||||
|
"alert": "Alert"
|
||||||
},
|
},
|
||||||
"more_page": {
|
"more_page": {
|
||||||
"configuration_wizard": "Setup wizard",
|
"configuration_wizard": "Setup wizard",
|
||||||
|
@ -109,6 +110,7 @@
|
||||||
"disk": "Disk local",
|
"disk": "Disk local",
|
||||||
"monthly_cost": "Monthly cost",
|
"monthly_cost": "Monthly cost",
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
|
"provider": "Provider",
|
||||||
"core_count": {
|
"core_count": {
|
||||||
"one": "{} core",
|
"one": "{} core",
|
||||||
"two": "{} cores",
|
"two": "{} cores",
|
||||||
|
@ -267,9 +269,14 @@
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"connect_to_server": "Connect a server",
|
"connect_to_server": "Connect a server",
|
||||||
|
"select_provider": "Select your provider",
|
||||||
"place_where_data": "A place where your data and SelfPrivacy services will reside:",
|
"place_where_data": "A place where your data and SelfPrivacy services will reside:",
|
||||||
"how": "How to obtain API token",
|
"how": "How to obtain API token",
|
||||||
"hetzner_bad_key_error": "Hetzner API key is invalid",
|
"provider_bad_key_error": "Provider API key is invalid",
|
||||||
|
"choose_location_type": "Choose your server location and type:",
|
||||||
|
"back_to_locations": "Go back to available locations!",
|
||||||
|
"no_locations_found": "No available locations found. Make sure your account is accessible.",
|
||||||
|
"no_server_types_found": "No available server types found. Make sure your account is accessible and try to change your server location.",
|
||||||
"cloudflare_bad_key_error": "Cloudflare API key is invalid",
|
"cloudflare_bad_key_error": "Cloudflare API key is invalid",
|
||||||
"backblaze_bad_key_error": "Backblaze storage information is invalid",
|
"backblaze_bad_key_error": "Backblaze storage information is invalid",
|
||||||
"connect_cloudflare": "Connect CloudFlare",
|
"connect_cloudflare": "Connect CloudFlare",
|
||||||
|
@ -295,6 +302,7 @@
|
||||||
"checks": "Checks have been completed \n{} out of {}"
|
"checks": "Checks have been completed \n{} out of {}"
|
||||||
},
|
},
|
||||||
"recovering": {
|
"recovering": {
|
||||||
|
"generic_error": "Operation failed, please try again.",
|
||||||
"recovery_main_header": "Connect to an existing server",
|
"recovery_main_header": "Connect to an existing server",
|
||||||
"domain_recovery_description": "Enter a server domain you want to get access for:",
|
"domain_recovery_description": "Enter a server domain you want to get access for:",
|
||||||
"domain_recover_placeholder": "Your domain",
|
"domain_recover_placeholder": "Your domain",
|
||||||
|
@ -314,9 +322,9 @@
|
||||||
"fallback_select_provider_console": "Access to the server console of my prodiver.",
|
"fallback_select_provider_console": "Access to the server console of my prodiver.",
|
||||||
"authorization_failed": "Couldn't log in with this key",
|
"authorization_failed": "Couldn't log in with this key",
|
||||||
"fallback_select_provider_console_hint": "For example: Hetzner.",
|
"fallback_select_provider_console_hint": "For example: Hetzner.",
|
||||||
"hetzner_connected": "Connect to Hetzner",
|
"server_provider_connected": "Connect to your Server Provider",
|
||||||
"hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:",
|
"server_provider_connected_description": "Communication established. Enter you token with access to {}:",
|
||||||
"hetzner_connected_placeholder": "Hetzner token",
|
"server_provider_connected_placeholder": "Server Provider token",
|
||||||
"confirm_server": "Confirm server",
|
"confirm_server": "Confirm server",
|
||||||
"confirm_server_description": "Found your server! Confirm it is the right one:",
|
"confirm_server_description": "Found your server! Confirm it is the right one:",
|
||||||
"confirm_server_accept": "Yes! That's it",
|
"confirm_server_accept": "Yes! That's it",
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
"remove": "Удалить",
|
"remove": "Удалить",
|
||||||
"apply": "Применить",
|
"apply": "Применить",
|
||||||
"done": "Готово",
|
"done": "Готово",
|
||||||
"continue": "Продолжить"
|
"continue": "Продолжить",
|
||||||
|
"alert": "Уведомление"
|
||||||
},
|
},
|
||||||
"more_page": {
|
"more_page": {
|
||||||
"configuration_wizard": "Мастер настройки",
|
"configuration_wizard": "Мастер настройки",
|
||||||
|
@ -109,6 +110,7 @@
|
||||||
"disk": "Диск",
|
"disk": "Диск",
|
||||||
"monthly_cost": "Ежемесячная стоимость",
|
"monthly_cost": "Ежемесячная стоимость",
|
||||||
"location": "Размещение",
|
"location": "Размещение",
|
||||||
|
"provider": "Провайдер",
|
||||||
"core_count": {
|
"core_count": {
|
||||||
"one": "{} ядро",
|
"one": "{} ядро",
|
||||||
"two": "{} ядра",
|
"two": "{} ядра",
|
||||||
|
@ -269,7 +271,11 @@
|
||||||
"connect_to_server": "Подключите сервер",
|
"connect_to_server": "Подключите сервер",
|
||||||
"place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:",
|
"place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:",
|
||||||
"how": "Как получить API Token",
|
"how": "Как получить API Token",
|
||||||
"hetzner_bad_key_error": "Hetzner API ключ неверен",
|
"provider_bad_key_error": "API ключ провайдера неверен",
|
||||||
|
"choose_location_type": "Выберите локацию и тип вашего сервера:",
|
||||||
|
"back_to_locations": "Назад к доступным локациям!",
|
||||||
|
"no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.",
|
||||||
|
"no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.",
|
||||||
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
|
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
|
||||||
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
|
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
|
||||||
"connect_cloudflare": "Подключите CloudFlare",
|
"connect_cloudflare": "Подключите CloudFlare",
|
||||||
|
@ -295,6 +301,7 @@
|
||||||
"checks": "Проверок выполнено: \n{} / {}"
|
"checks": "Проверок выполнено: \n{} / {}"
|
||||||
},
|
},
|
||||||
"recovering": {
|
"recovering": {
|
||||||
|
"generic_error": "Ошибка проведения операции, попробуйте ещё раз.",
|
||||||
"recovery_main_header": "Подключиться к существующему серверу",
|
"recovery_main_header": "Подключиться к существующему серверу",
|
||||||
"domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:",
|
"domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:",
|
||||||
"domain_recover_placeholder": "Домен",
|
"domain_recover_placeholder": "Домен",
|
||||||
|
|
|
@ -87,9 +87,18 @@ class BNames {
|
||||||
/// A String field of [serverInstallationBox] box.
|
/// A String field of [serverInstallationBox] box.
|
||||||
static String hetznerKey = 'hetznerKey';
|
static String hetznerKey = 'hetznerKey';
|
||||||
|
|
||||||
|
/// A String field of [serverInstallationBox] box.
|
||||||
|
static String serverProvider = 'serverProvider';
|
||||||
|
|
||||||
|
/// A String field of [serverLocation] box.
|
||||||
|
static String serverLocation = 'serverLocation';
|
||||||
|
|
||||||
/// A String field of [serverInstallationBox] box.
|
/// A String field of [serverInstallationBox] box.
|
||||||
static String cloudFlareKey = 'cloudFlareKey';
|
static String cloudFlareKey = 'cloudFlareKey';
|
||||||
|
|
||||||
|
/// A String field of [serverTypeIdentifier] box.
|
||||||
|
static String serverTypeIdentifier = 'serverTypeIdentifier';
|
||||||
|
|
||||||
/// A [User] field of [serverInstallationBox] box.
|
/// A [User] field of [serverInstallationBox] box.
|
||||||
static String rootUser = 'rootUser';
|
static String rootUser = 'rootUser';
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,27 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||||
|
import 'package:http/io_client.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/staging_options.dart';
|
||||||
|
|
||||||
abstract class ApiMap {
|
abstract class ApiMap {
|
||||||
Future<GraphQLClient> getClient() async {
|
Future<GraphQLClient> getClient() async {
|
||||||
|
IOClient? ioClient;
|
||||||
|
if (StagingOptions.stagingAcme) {
|
||||||
|
final HttpClient httpClient = HttpClient();
|
||||||
|
httpClient.badCertificateCallback = (
|
||||||
|
final cert,
|
||||||
|
final host,
|
||||||
|
final port,
|
||||||
|
) =>
|
||||||
|
true;
|
||||||
|
ioClient = IOClient(httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
final httpLink = HttpLink(
|
final httpLink = HttpLink(
|
||||||
'https://api.$rootAddress/graphql',
|
'https://api.$rootAddress/graphql',
|
||||||
|
httpClient: ioClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
final String token = _getApiToken();
|
final String token = _getApiToken();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:graphql/client.dart' as graphql;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/utils/scalars.dart';
|
import 'package:selfprivacy/utils/scalars.dart';
|
||||||
import 'schema.graphql.dart';
|
import 'schema.graphql.dart';
|
||||||
import 'services.graphql.dart';
|
import 'server_api.graphql.dart';
|
||||||
part 'disk_volumes.graphql.g.dart';
|
part 'disk_volumes.graphql.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
|
|
@ -173,6 +173,7 @@ input RecoveryKeyLimitsInput {
|
||||||
|
|
||||||
enum ServerProvider {
|
enum ServerProvider {
|
||||||
HETZNER
|
HETZNER
|
||||||
|
DIGITALOCEAN
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service {
|
type Service {
|
||||||
|
|
|
@ -693,6 +693,8 @@ enum Enum$DnsProvider {
|
||||||
enum Enum$ServerProvider {
|
enum Enum$ServerProvider {
|
||||||
@JsonValue('HETZNER')
|
@JsonValue('HETZNER')
|
||||||
HETZNER,
|
HETZNER,
|
||||||
|
@JsonValue('DIGITALOCEAN')
|
||||||
|
DIGITALOCEAN,
|
||||||
$unknown
|
$unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,14 @@ mutation RebootSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query SystemServerProvider {
|
||||||
|
system {
|
||||||
|
provider {
|
||||||
|
provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query GetApiTokens {
|
query GetApiTokens {
|
||||||
api {
|
api {
|
||||||
devices {
|
devices {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:graphql/client.dart' as graphql;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/utils/scalars.dart';
|
import 'package:selfprivacy/utils/scalars.dart';
|
||||||
import 'schema.graphql.dart';
|
import 'schema.graphql.dart';
|
||||||
import 'services.graphql.dart';
|
|
||||||
part 'server_api.graphql.g.dart';
|
part 'server_api.graphql.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
@ -3178,6 +3177,425 @@ class _CopyWithStubImpl$Mutation$RebootSystem$rebootSystem<TRes>
|
||||||
_res;
|
_res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class Query$SystemServerProvider {
|
||||||
|
Query$SystemServerProvider({required this.system, required this.$__typename});
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory Query$SystemServerProvider.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$Query$SystemServerProviderFromJson(json);
|
||||||
|
|
||||||
|
final Query$SystemServerProvider$system system;
|
||||||
|
|
||||||
|
@JsonKey(name: '__typename')
|
||||||
|
final String $__typename;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$Query$SystemServerProviderToJson(this);
|
||||||
|
int get hashCode {
|
||||||
|
final l$system = system;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
return Object.hashAll([l$system, l$$__typename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (!(other is Query$SystemServerProvider) ||
|
||||||
|
runtimeType != other.runtimeType) return false;
|
||||||
|
final l$system = system;
|
||||||
|
final lOther$system = other.system;
|
||||||
|
if (l$system != lOther$system) return false;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
final lOther$$__typename = other.$__typename;
|
||||||
|
if (l$$__typename != lOther$$__typename) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UtilityExtension$Query$SystemServerProvider
|
||||||
|
on Query$SystemServerProvider {
|
||||||
|
CopyWith$Query$SystemServerProvider<Query$SystemServerProvider>
|
||||||
|
get copyWith => CopyWith$Query$SystemServerProvider(this, (i) => i);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CopyWith$Query$SystemServerProvider<TRes> {
|
||||||
|
factory CopyWith$Query$SystemServerProvider(
|
||||||
|
Query$SystemServerProvider instance,
|
||||||
|
TRes Function(Query$SystemServerProvider) then) =
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider;
|
||||||
|
|
||||||
|
factory CopyWith$Query$SystemServerProvider.stub(TRes res) =
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider;
|
||||||
|
|
||||||
|
TRes call({Query$SystemServerProvider$system? system, String? $__typename});
|
||||||
|
CopyWith$Query$SystemServerProvider$system<TRes> get system;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithImpl$Query$SystemServerProvider<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider<TRes> {
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider(this._instance, this._then);
|
||||||
|
|
||||||
|
final Query$SystemServerProvider _instance;
|
||||||
|
|
||||||
|
final TRes Function(Query$SystemServerProvider) _then;
|
||||||
|
|
||||||
|
static const _undefined = {};
|
||||||
|
|
||||||
|
TRes call({Object? system = _undefined, Object? $__typename = _undefined}) =>
|
||||||
|
_then(Query$SystemServerProvider(
|
||||||
|
system: system == _undefined || system == null
|
||||||
|
? _instance.system
|
||||||
|
: (system as Query$SystemServerProvider$system),
|
||||||
|
$__typename: $__typename == _undefined || $__typename == null
|
||||||
|
? _instance.$__typename
|
||||||
|
: ($__typename as String)));
|
||||||
|
CopyWith$Query$SystemServerProvider$system<TRes> get system {
|
||||||
|
final local$system = _instance.system;
|
||||||
|
return CopyWith$Query$SystemServerProvider$system(
|
||||||
|
local$system, (e) => call(system: e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithStubImpl$Query$SystemServerProvider<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider<TRes> {
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider(this._res);
|
||||||
|
|
||||||
|
TRes _res;
|
||||||
|
|
||||||
|
call({Query$SystemServerProvider$system? system, String? $__typename}) =>
|
||||||
|
_res;
|
||||||
|
CopyWith$Query$SystemServerProvider$system<TRes> get system =>
|
||||||
|
CopyWith$Query$SystemServerProvider$system.stub(_res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentNodeQuerySystemServerProvider = DocumentNode(definitions: [
|
||||||
|
OperationDefinitionNode(
|
||||||
|
type: OperationType.query,
|
||||||
|
name: NameNode(value: 'SystemServerProvider'),
|
||||||
|
variableDefinitions: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: SelectionSetNode(selections: [
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: 'system'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: SelectionSetNode(selections: [
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: 'provider'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: SelectionSetNode(selections: [
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: 'provider'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: null),
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: '__typename'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: null)
|
||||||
|
])),
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: '__typename'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: null)
|
||||||
|
])),
|
||||||
|
FieldNode(
|
||||||
|
name: NameNode(value: '__typename'),
|
||||||
|
alias: null,
|
||||||
|
arguments: [],
|
||||||
|
directives: [],
|
||||||
|
selectionSet: null)
|
||||||
|
])),
|
||||||
|
]);
|
||||||
|
Query$SystemServerProvider _parserFn$Query$SystemServerProvider(
|
||||||
|
Map<String, dynamic> data) =>
|
||||||
|
Query$SystemServerProvider.fromJson(data);
|
||||||
|
|
||||||
|
class Options$Query$SystemServerProvider
|
||||||
|
extends graphql.QueryOptions<Query$SystemServerProvider> {
|
||||||
|
Options$Query$SystemServerProvider(
|
||||||
|
{String? operationName,
|
||||||
|
graphql.FetchPolicy? fetchPolicy,
|
||||||
|
graphql.ErrorPolicy? errorPolicy,
|
||||||
|
graphql.CacheRereadPolicy? cacheRereadPolicy,
|
||||||
|
Object? optimisticResult,
|
||||||
|
Duration? pollInterval,
|
||||||
|
graphql.Context? context})
|
||||||
|
: super(
|
||||||
|
operationName: operationName,
|
||||||
|
fetchPolicy: fetchPolicy,
|
||||||
|
errorPolicy: errorPolicy,
|
||||||
|
cacheRereadPolicy: cacheRereadPolicy,
|
||||||
|
optimisticResult: optimisticResult,
|
||||||
|
pollInterval: pollInterval,
|
||||||
|
context: context,
|
||||||
|
document: documentNodeQuerySystemServerProvider,
|
||||||
|
parserFn: _parserFn$Query$SystemServerProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchOptions$Query$SystemServerProvider
|
||||||
|
extends graphql.WatchQueryOptions<Query$SystemServerProvider> {
|
||||||
|
WatchOptions$Query$SystemServerProvider(
|
||||||
|
{String? operationName,
|
||||||
|
graphql.FetchPolicy? fetchPolicy,
|
||||||
|
graphql.ErrorPolicy? errorPolicy,
|
||||||
|
graphql.CacheRereadPolicy? cacheRereadPolicy,
|
||||||
|
Object? optimisticResult,
|
||||||
|
graphql.Context? context,
|
||||||
|
Duration? pollInterval,
|
||||||
|
bool? eagerlyFetchResults,
|
||||||
|
bool carryForwardDataOnException = true,
|
||||||
|
bool fetchResults = false})
|
||||||
|
: super(
|
||||||
|
operationName: operationName,
|
||||||
|
fetchPolicy: fetchPolicy,
|
||||||
|
errorPolicy: errorPolicy,
|
||||||
|
cacheRereadPolicy: cacheRereadPolicy,
|
||||||
|
optimisticResult: optimisticResult,
|
||||||
|
context: context,
|
||||||
|
document: documentNodeQuerySystemServerProvider,
|
||||||
|
pollInterval: pollInterval,
|
||||||
|
eagerlyFetchResults: eagerlyFetchResults,
|
||||||
|
carryForwardDataOnException: carryForwardDataOnException,
|
||||||
|
fetchResults: fetchResults,
|
||||||
|
parserFn: _parserFn$Query$SystemServerProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FetchMoreOptions$Query$SystemServerProvider
|
||||||
|
extends graphql.FetchMoreOptions {
|
||||||
|
FetchMoreOptions$Query$SystemServerProvider(
|
||||||
|
{required graphql.UpdateQuery updateQuery})
|
||||||
|
: super(
|
||||||
|
updateQuery: updateQuery,
|
||||||
|
document: documentNodeQuerySystemServerProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ClientExtension$Query$SystemServerProvider on graphql.GraphQLClient {
|
||||||
|
Future<graphql.QueryResult<Query$SystemServerProvider>>
|
||||||
|
query$SystemServerProvider(
|
||||||
|
[Options$Query$SystemServerProvider? options]) async =>
|
||||||
|
await this.query(options ?? Options$Query$SystemServerProvider());
|
||||||
|
graphql.ObservableQuery<Query$SystemServerProvider>
|
||||||
|
watchQuery$SystemServerProvider(
|
||||||
|
[WatchOptions$Query$SystemServerProvider? options]) =>
|
||||||
|
this.watchQuery(options ?? WatchOptions$Query$SystemServerProvider());
|
||||||
|
void writeQuery$SystemServerProvider(
|
||||||
|
{required Query$SystemServerProvider data, bool broadcast = true}) =>
|
||||||
|
this.writeQuery(
|
||||||
|
graphql.Request(
|
||||||
|
operation: graphql.Operation(
|
||||||
|
document: documentNodeQuerySystemServerProvider)),
|
||||||
|
data: data.toJson(),
|
||||||
|
broadcast: broadcast);
|
||||||
|
Query$SystemServerProvider? readQuery$SystemServerProvider(
|
||||||
|
{bool optimistic = true}) {
|
||||||
|
final result = this.readQuery(
|
||||||
|
graphql.Request(
|
||||||
|
operation: graphql.Operation(
|
||||||
|
document: documentNodeQuerySystemServerProvider)),
|
||||||
|
optimistic: optimistic);
|
||||||
|
return result == null ? null : Query$SystemServerProvider.fromJson(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class Query$SystemServerProvider$system {
|
||||||
|
Query$SystemServerProvider$system(
|
||||||
|
{required this.provider, required this.$__typename});
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory Query$SystemServerProvider$system.fromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$Query$SystemServerProvider$systemFromJson(json);
|
||||||
|
|
||||||
|
final Query$SystemServerProvider$system$provider provider;
|
||||||
|
|
||||||
|
@JsonKey(name: '__typename')
|
||||||
|
final String $__typename;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() =>
|
||||||
|
_$Query$SystemServerProvider$systemToJson(this);
|
||||||
|
int get hashCode {
|
||||||
|
final l$provider = provider;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
return Object.hashAll([l$provider, l$$__typename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (!(other is Query$SystemServerProvider$system) ||
|
||||||
|
runtimeType != other.runtimeType) return false;
|
||||||
|
final l$provider = provider;
|
||||||
|
final lOther$provider = other.provider;
|
||||||
|
if (l$provider != lOther$provider) return false;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
final lOther$$__typename = other.$__typename;
|
||||||
|
if (l$$__typename != lOther$$__typename) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UtilityExtension$Query$SystemServerProvider$system
|
||||||
|
on Query$SystemServerProvider$system {
|
||||||
|
CopyWith$Query$SystemServerProvider$system<Query$SystemServerProvider$system>
|
||||||
|
get copyWith =>
|
||||||
|
CopyWith$Query$SystemServerProvider$system(this, (i) => i);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CopyWith$Query$SystemServerProvider$system<TRes> {
|
||||||
|
factory CopyWith$Query$SystemServerProvider$system(
|
||||||
|
Query$SystemServerProvider$system instance,
|
||||||
|
TRes Function(Query$SystemServerProvider$system) then) =
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider$system;
|
||||||
|
|
||||||
|
factory CopyWith$Query$SystemServerProvider$system.stub(TRes res) =
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider$system;
|
||||||
|
|
||||||
|
TRes call(
|
||||||
|
{Query$SystemServerProvider$system$provider? provider,
|
||||||
|
String? $__typename});
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider<TRes> get provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithImpl$Query$SystemServerProvider$system<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider$system<TRes> {
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider$system(this._instance, this._then);
|
||||||
|
|
||||||
|
final Query$SystemServerProvider$system _instance;
|
||||||
|
|
||||||
|
final TRes Function(Query$SystemServerProvider$system) _then;
|
||||||
|
|
||||||
|
static const _undefined = {};
|
||||||
|
|
||||||
|
TRes call(
|
||||||
|
{Object? provider = _undefined, Object? $__typename = _undefined}) =>
|
||||||
|
_then(Query$SystemServerProvider$system(
|
||||||
|
provider: provider == _undefined || provider == null
|
||||||
|
? _instance.provider
|
||||||
|
: (provider as Query$SystemServerProvider$system$provider),
|
||||||
|
$__typename: $__typename == _undefined || $__typename == null
|
||||||
|
? _instance.$__typename
|
||||||
|
: ($__typename as String)));
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider<TRes> get provider {
|
||||||
|
final local$provider = _instance.provider;
|
||||||
|
return CopyWith$Query$SystemServerProvider$system$provider(
|
||||||
|
local$provider, (e) => call(provider: e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithStubImpl$Query$SystemServerProvider$system<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider$system<TRes> {
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider$system(this._res);
|
||||||
|
|
||||||
|
TRes _res;
|
||||||
|
|
||||||
|
call(
|
||||||
|
{Query$SystemServerProvider$system$provider? provider,
|
||||||
|
String? $__typename}) =>
|
||||||
|
_res;
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider<TRes> get provider =>
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider.stub(_res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class Query$SystemServerProvider$system$provider {
|
||||||
|
Query$SystemServerProvider$system$provider(
|
||||||
|
{required this.provider, required this.$__typename});
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory Query$SystemServerProvider$system$provider.fromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$Query$SystemServerProvider$system$providerFromJson(json);
|
||||||
|
|
||||||
|
@JsonKey(unknownEnumValue: Enum$ServerProvider.$unknown)
|
||||||
|
final Enum$ServerProvider provider;
|
||||||
|
|
||||||
|
@JsonKey(name: '__typename')
|
||||||
|
final String $__typename;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() =>
|
||||||
|
_$Query$SystemServerProvider$system$providerToJson(this);
|
||||||
|
int get hashCode {
|
||||||
|
final l$provider = provider;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
return Object.hashAll([l$provider, l$$__typename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (!(other is Query$SystemServerProvider$system$provider) ||
|
||||||
|
runtimeType != other.runtimeType) return false;
|
||||||
|
final l$provider = provider;
|
||||||
|
final lOther$provider = other.provider;
|
||||||
|
if (l$provider != lOther$provider) return false;
|
||||||
|
final l$$__typename = $__typename;
|
||||||
|
final lOther$$__typename = other.$__typename;
|
||||||
|
if (l$$__typename != lOther$$__typename) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UtilityExtension$Query$SystemServerProvider$system$provider
|
||||||
|
on Query$SystemServerProvider$system$provider {
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider<
|
||||||
|
Query$SystemServerProvider$system$provider>
|
||||||
|
get copyWith =>
|
||||||
|
CopyWith$Query$SystemServerProvider$system$provider(this, (i) => i);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CopyWith$Query$SystemServerProvider$system$provider<TRes> {
|
||||||
|
factory CopyWith$Query$SystemServerProvider$system$provider(
|
||||||
|
Query$SystemServerProvider$system$provider instance,
|
||||||
|
TRes Function(Query$SystemServerProvider$system$provider) then) =
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider$system$provider;
|
||||||
|
|
||||||
|
factory CopyWith$Query$SystemServerProvider$system$provider.stub(TRes res) =
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider$system$provider;
|
||||||
|
|
||||||
|
TRes call({Enum$ServerProvider? provider, String? $__typename});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithImpl$Query$SystemServerProvider$system$provider<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider$system$provider<TRes> {
|
||||||
|
_CopyWithImpl$Query$SystemServerProvider$system$provider(
|
||||||
|
this._instance, this._then);
|
||||||
|
|
||||||
|
final Query$SystemServerProvider$system$provider _instance;
|
||||||
|
|
||||||
|
final TRes Function(Query$SystemServerProvider$system$provider) _then;
|
||||||
|
|
||||||
|
static const _undefined = {};
|
||||||
|
|
||||||
|
TRes call(
|
||||||
|
{Object? provider = _undefined, Object? $__typename = _undefined}) =>
|
||||||
|
_then(Query$SystemServerProvider$system$provider(
|
||||||
|
provider: provider == _undefined || provider == null
|
||||||
|
? _instance.provider
|
||||||
|
: (provider as Enum$ServerProvider),
|
||||||
|
$__typename: $__typename == _undefined || $__typename == null
|
||||||
|
? _instance.$__typename
|
||||||
|
: ($__typename as String)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyWithStubImpl$Query$SystemServerProvider$system$provider<TRes>
|
||||||
|
implements CopyWith$Query$SystemServerProvider$system$provider<TRes> {
|
||||||
|
_CopyWithStubImpl$Query$SystemServerProvider$system$provider(this._res);
|
||||||
|
|
||||||
|
TRes _res;
|
||||||
|
|
||||||
|
call({Enum$ServerProvider? provider, String? $__typename}) => _res;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class Query$GetApiTokens {
|
class Query$GetApiTokens {
|
||||||
Query$GetApiTokens({required this.api, required this.$__typename});
|
Query$GetApiTokens({required this.api, required this.$__typename});
|
||||||
|
|
|
@ -330,6 +330,58 @@ Map<String, dynamic> _$Mutation$RebootSystem$rebootSystemToJson(
|
||||||
'__typename': instance.$__typename,
|
'__typename': instance.$__typename,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Query$SystemServerProvider _$Query$SystemServerProviderFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
Query$SystemServerProvider(
|
||||||
|
system: Query$SystemServerProvider$system.fromJson(
|
||||||
|
json['system'] as Map<String, dynamic>),
|
||||||
|
$__typename: json['__typename'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$Query$SystemServerProviderToJson(
|
||||||
|
Query$SystemServerProvider instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'system': instance.system.toJson(),
|
||||||
|
'__typename': instance.$__typename,
|
||||||
|
};
|
||||||
|
|
||||||
|
Query$SystemServerProvider$system _$Query$SystemServerProvider$systemFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
Query$SystemServerProvider$system(
|
||||||
|
provider: Query$SystemServerProvider$system$provider.fromJson(
|
||||||
|
json['provider'] as Map<String, dynamic>),
|
||||||
|
$__typename: json['__typename'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$Query$SystemServerProvider$systemToJson(
|
||||||
|
Query$SystemServerProvider$system instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'provider': instance.provider.toJson(),
|
||||||
|
'__typename': instance.$__typename,
|
||||||
|
};
|
||||||
|
|
||||||
|
Query$SystemServerProvider$system$provider
|
||||||
|
_$Query$SystemServerProvider$system$providerFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
Query$SystemServerProvider$system$provider(
|
||||||
|
provider: $enumDecode(_$Enum$ServerProviderEnumMap, json['provider'],
|
||||||
|
unknownValue: Enum$ServerProvider.$unknown),
|
||||||
|
$__typename: json['__typename'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$Query$SystemServerProvider$system$providerToJson(
|
||||||
|
Query$SystemServerProvider$system$provider instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'provider': _$Enum$ServerProviderEnumMap[instance.provider]!,
|
||||||
|
'__typename': instance.$__typename,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$Enum$ServerProviderEnumMap = {
|
||||||
|
Enum$ServerProvider.HETZNER: 'HETZNER',
|
||||||
|
Enum$ServerProvider.DIGITALOCEAN: 'DIGITALOCEAN',
|
||||||
|
Enum$ServerProvider.$unknown: r'$unknown',
|
||||||
|
};
|
||||||
|
|
||||||
Query$GetApiTokens _$Query$GetApiTokensFromJson(Map<String, dynamic> json) =>
|
Query$GetApiTokens _$Query$GetApiTokensFromJson(Map<String, dynamic> json) =>
|
||||||
Query$GetApiTokens(
|
Query$GetApiTokens(
|
||||||
api: Query$GetApiTokens$api.fromJson(json['api'] as Map<String, dynamic>),
|
api: Query$GetApiTokens$api.fromJson(json['api'] as Map<String, dynamic>),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:gql/ast.dart';
|
||||||
import 'package:graphql/client.dart' as graphql;
|
import 'package:graphql/client.dart' as graphql;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'schema.graphql.dart';
|
import 'schema.graphql.dart';
|
||||||
import 'services.graphql.dart';
|
import 'server_api.graphql.dart';
|
||||||
part 'server_settings.graphql.g.dart';
|
part 'server_settings.graphql.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:graphql/client.dart' as graphql;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/utils/scalars.dart';
|
import 'package:selfprivacy/utils/scalars.dart';
|
||||||
import 'schema.graphql.dart';
|
import 'schema.graphql.dart';
|
||||||
|
import 'server_api.graphql.dart';
|
||||||
part 'services.graphql.g.dart';
|
part 'services.graphql.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:gql/ast.dart';
|
||||||
import 'package:graphql/client.dart' as graphql;
|
import 'package:graphql/client.dart' as graphql;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'schema.graphql.dart';
|
import 'schema.graphql.dart';
|
||||||
import 'services.graphql.dart';
|
import 'server_api.graphql.dart';
|
||||||
part 'users.graphql.g.dart';
|
part 'users.graphql.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'server.dart';
|
part of 'server_api.dart';
|
||||||
|
|
||||||
mixin JobsApi on ApiMap {
|
mixin JobsApi on ApiMap {
|
||||||
Future<List<ServerJob>> getServerJobs() async {
|
Future<List<ServerJob>> getServerJobs() async {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'server.dart';
|
part of 'server_api.dart';
|
||||||
|
|
||||||
mixin ServerActionsApi on ApiMap {
|
mixin ServerActionsApi on ApiMap {
|
||||||
Future<bool> _commonBoolRequest(final Function graphQLMethod) async {
|
Future<bool> _commonBoolRequest(final Function graphQLMethod) async {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/backup.dart';
|
import 'package:selfprivacy/logic/models/json/backup.dart';
|
||||||
|
@ -88,6 +89,25 @@ class ServerApi extends ApiMap
|
||||||
return apiVersion;
|
return apiVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ServerProvider> getServerProviderType() async {
|
||||||
|
QueryResult<Query$SystemServerProvider> response;
|
||||||
|
ServerProvider providerType = ServerProvider.unknown;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final GraphQLClient client = await getClient();
|
||||||
|
response = await client.query$SystemServerProvider();
|
||||||
NaiJi marked this conversation as resolved
|
|||||||
|
if (response.hasException) {
|
||||||
inex
commented
Review
let's do let's do `if (response.hasException || response.parsedData == null) {` just in case
NaiJi
commented
Review
Condition If Condition `response.hasException` checks for a graphql exception content to `print(response.exception.toString())` it.
If `hasException` returns false, then `response.exception.toString()` won't do anything, it has nothing to do with `parsedData`.
|
|||||||
|
print(response.exception.toString());
|
||||||
|
}
|
||||||
|
providerType = ServerProvider.fromGraphQL(
|
||||||
|
response.parsedData!.system.provider.provider,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
return providerType;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> isUsingBinds() async {
|
Future<bool> isUsingBinds() async {
|
||||||
QueryResult response;
|
QueryResult response;
|
||||||
bool usesBinds = false;
|
bool usesBinds = false;
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'server.dart';
|
part of 'server_api.dart';
|
||||||
|
|
||||||
mixin ServicesApi on ApiMap {
|
mixin ServicesApi on ApiMap {
|
||||||
Future<List<Service>> getAllServices() async {
|
Future<List<Service>> getAllServices() async {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'server.dart';
|
part of 'server_api.dart';
|
||||||
|
|
||||||
mixin UsersApi on ApiMap {
|
mixin UsersApi on ApiMap {
|
||||||
Future<List<User>> getAllUsers() async {
|
Future<List<User>> getAllUsers() async {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
part of 'server.dart';
|
part of 'server_api.dart';
|
||||||
|
|
||||||
mixin VolumeApi on ApiMap {
|
mixin VolumeApi on ApiMap {
|
||||||
Future<List<ServerDiskVolume>> getServerDiskVolumes() async {
|
Future<List<ServerDiskVolume>> getServerDiskVolumes() async {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
||||||
|
|
||||||
|
class ApiController {
|
||||||
|
static VolumeProviderApiFactory? get currentVolumeProviderApiFactory =>
|
||||||
|
_volumeProviderApiFactory;
|
||||||
|
static DnsProviderApiFactory? get currentDnsProviderApiFactory =>
|
||||||
|
_dnsProviderApiFactory;
|
||||||
|
static ServerProviderApiFactory? get currentServerProviderApiFactory =>
|
||||||
|
_serverProviderApiFactory;
|
||||||
|
|
||||||
|
static void initVolumeProviderApiFactory(
|
||||||
|
final ServerProviderApiFactorySettings settings,
|
||||||
|
) {
|
||||||
|
_volumeProviderApiFactory =
|
||||||
|
VolumeApiFactoryCreator.createVolumeProviderApiFactory(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initDnsProviderApiFactory(
|
||||||
|
final DnsProviderApiFactorySettings settings,
|
||||||
|
) {
|
||||||
|
_dnsProviderApiFactory =
|
||||||
|
ApiFactoryCreator.createDnsProviderApiFactory(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initServerProviderApiFactory(
|
||||||
|
final ServerProviderApiFactorySettings settings,
|
||||||
|
) {
|
||||||
|
_serverProviderApiFactory =
|
||||||
|
ApiFactoryCreator.createServerProviderApiFactory(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearProviderApiFactories() {
|
||||||
|
_volumeProviderApiFactory = null;
|
||||||
|
_dnsProviderApiFactory = null;
|
||||||
|
_serverProviderApiFactory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VolumeProviderApiFactory? _volumeProviderApiFactory;
|
||||||
|
static DnsProviderApiFactory? _dnsProviderApiFactory;
|
||||||
|
static ServerProviderApiFactory? _serverProviderApiFactory;
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
@ -12,20 +14,22 @@ class UnknownApiProviderException implements Exception {
|
||||||
|
|
||||||
class ApiFactoryCreator {
|
class ApiFactoryCreator {
|
||||||
static ServerProviderApiFactory createServerProviderApiFactory(
|
static ServerProviderApiFactory createServerProviderApiFactory(
|
||||||
final ServerProvider provider,
|
final ServerProviderApiFactorySettings settings,
|
||||||
) {
|
) {
|
||||||
switch (provider) {
|
switch (settings.provider) {
|
||||||
case ServerProvider.hetzner:
|
case ServerProvider.hetzner:
|
||||||
return HetznerApiFactory();
|
return HetznerApiFactory(region: settings.location);
|
||||||
|
case ServerProvider.digitalOcean:
|
||||||
|
return DigitalOceanApiFactory(region: settings.location);
|
||||||
case ServerProvider.unknown:
|
case ServerProvider.unknown:
|
||||||
throw UnknownApiProviderException('Unknown server provider');
|
throw UnknownApiProviderException('Unknown server provider');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DnsProviderApiFactory createDnsProviderApiFactory(
|
static DnsProviderApiFactory createDnsProviderApiFactory(
|
||||||
final DnsProvider provider,
|
final DnsProviderApiFactorySettings settings,
|
||||||
) {
|
) {
|
||||||
switch (provider) {
|
switch (settings.provider) {
|
||||||
case DnsProvider.cloudflare:
|
case DnsProvider.cloudflare:
|
||||||
return CloudflareApiFactory();
|
return CloudflareApiFactory();
|
||||||
case DnsProvider.unknown:
|
case DnsProvider.unknown:
|
||||||
|
@ -36,11 +40,13 @@ class ApiFactoryCreator {
|
||||||
|
|
||||||
class VolumeApiFactoryCreator {
|
class VolumeApiFactoryCreator {
|
||||||
static VolumeProviderApiFactory createVolumeProviderApiFactory(
|
static VolumeProviderApiFactory createVolumeProviderApiFactory(
|
||||||
final ServerProvider provider,
|
final ServerProviderApiFactorySettings settings,
|
||||||
) {
|
) {
|
||||||
switch (provider) {
|
switch (settings.provider) {
|
||||||
case ServerProvider.hetzner:
|
case ServerProvider.hetzner:
|
||||||
return HetznerApiFactory();
|
return HetznerApiFactory();
|
||||||
|
case ServerProvider.digitalOcean:
|
||||||
|
return DigitalOceanApiFactory();
|
||||||
case ServerProvider.unknown:
|
case ServerProvider.unknown:
|
||||||
throw UnknownApiProviderException('Unknown volume provider');
|
throw UnknownApiProviderException('Unknown volume provider');
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
|
||||||
|
class ServerProviderApiFactorySettings {
|
||||||
|
ServerProviderApiFactorySettings({
|
||||||
|
required this.provider,
|
||||||
|
this.location,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ServerProvider provider;
|
||||||
|
final String? location;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DnsProviderApiFactorySettings {
|
||||||
|
DnsProviderApiFactorySettings({
|
||||||
|
required this.provider,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DnsProvider provider;
|
||||||
|
}
|
|
@ -9,8 +9,8 @@ import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/message.dart';
|
||||||
|
|
||||||
abstract class ApiMap {
|
abstract class ApiMap {
|
||||||
Future<Dio> getClient() async {
|
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
||||||
final Dio dio = Dio(await options);
|
final Dio dio = Dio(customOptions ?? (await options));
|
||||||
if (hasLogger) {
|
if (hasLogger) {
|
||||||
dio.interceptors.add(PrettyDioLogger());
|
dio.interceptors.add(PrettyDioLogger());
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ abstract class ApiMap {
|
||||||
|
|
||||||
FutureOr<BaseOptions> get options;
|
FutureOr<BaseOptions> get options;
|
||||||
|
|
||||||
abstract final String rootAddress;
|
String get rootAddress;
|
||||||
|
|
||||||
abstract final bool hasLogger;
|
abstract final bool hasLogger;
|
||||||
abstract final bool isWithToken;
|
abstract final bool isWithToken;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
||||||
|
|
||||||
class CloudflareApiFactory extends DnsProviderApiFactory {
|
class CloudflareApiFactory extends DnsProviderApiFactory {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
||||||
|
|
||||||
|
class DnsProviderApiSettings extends ProviderApiSettings {
|
||||||
|
const DnsProviderApiSettings({
|
||||||
|
super.hasLogger = false,
|
||||||
|
super.isWithToken = true,
|
||||||
|
this.customToken,
|
||||||
|
});
|
||||||
|
final String? customToken;
|
||||||
|
}
|
|
@ -1,17 +1,8 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
||||||
|
|
||||||
class DnsProviderApiSettings extends ProviderApiSettings {
|
|
||||||
const DnsProviderApiSettings({
|
|
||||||
super.hasLogger = false,
|
|
||||||
super.isWithToken = true,
|
|
||||||
this.customToken,
|
|
||||||
});
|
|
||||||
final String? customToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DnsProviderApiFactory {
|
abstract class DnsProviderApiFactory {
|
||||||
DnsProviderApi getDnsProvider({
|
DnsProviderApi getDnsProvider({
|
||||||
final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
|
final DnsProviderApiSettings settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
class ProviderApiSettings {
|
class ProviderApiSettings {
|
||||||
const ProviderApiSettings({this.hasLogger = false, this.isWithToken = true});
|
const ProviderApiSettings({
|
||||||
|
this.hasLogger = false,
|
||||||
|
this.isWithToken = true,
|
||||||
|
});
|
||||||
final bool hasLogger;
|
final bool hasLogger;
|
||||||
final bool isWithToken;
|
final bool isWithToken;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,780 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/staging_options.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
||||||
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
|
class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
|
DigitalOceanApi({
|
||||||
|
required this.region,
|
||||||
|
this.hasLogger = false,
|
||||||
|
this.isWithToken = true,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
bool hasLogger;
|
||||||
|
@override
|
||||||
|
bool isWithToken;
|
||||||
|
|
||||||
|
final String? region;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BaseOptions get options {
|
||||||
|
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||||
|
if (isWithToken) {
|
||||||
|
final String? token = getIt<ApiConfigModel>().serverProviderKey;
|
||||||
|
assert(token != null);
|
||||||
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateStatus != null) {
|
||||||
|
options.validateStatus = validateStatus!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get rootAddress => 'https://api.digitalocean.com/v2';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get infectProviderName => 'digitalocean';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get displayProviderName => 'Digital Ocean';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isApiTokenValid(final String token) async {
|
||||||
|
bool isValid = false;
|
||||||
|
Response? response;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
response = await client.get(
|
||||||
|
'/account',
|
||||||
|
options: Options(
|
||||||
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
isValid = false;
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null) {
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
isValid = true;
|
||||||
|
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
throw Exception('code: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hardcoded on their documentation and there is no pricing API at all
|
||||||
|
/// Probably we should scrap the doc page manually
|
||||||
|
@override
|
||||||
|
Future<Price?> getPricePerGb() async => Price(
|
||||||
|
value: 0.10,
|
||||||
|
currency: 'USD',
|
||||||
inex
commented
Review
As we have a Price class, why don't make a Currency class, to ensure type safety and get cool getters like
As we have a Price class, why don't make a Currency class, to ensure type safety and get cool getters like
- getShortcode
- getFormat
- getIcon
etc
NaiJi
commented
Review
It's beyond the scope of Digital Ocean implementation, though it indeed is better to be done asap, it will be planned. It's beyond the scope of Digital Ocean implementation, though it indeed is better to be done asap, it will be planned.
|
|||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ServerVolume?> createVolume() async {
|
||||||
|
ServerVolume? volume;
|
||||||
|
|
||||||
|
final Response createVolumeResponse;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final List<ServerVolume> volumes = await getVolumes();
|
||||||
|
await Future.delayed(const Duration(seconds: 6));
|
||||||
|
|
||||||
|
createVolumeResponse = await client.post(
|
||||||
|
'/volumes',
|
||||||
|
data: {
|
||||||
|
'size_gigabytes': 10,
|
||||||
|
'name': 'volume${StringGenerators.storageName()}',
|
||||||
|
'labels': {'labelkey': 'value'},
|
||||||
|
'region': region,
|
||||||
|
'filesystem_type': 'ext4',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final volumeId = createVolumeResponse.data['volume']['id'];
|
||||||
|
final volumeSize = createVolumeResponse.data['volume']['size_gigabytes'];
|
||||||
|
final volumeName = createVolumeResponse.data['volume']['name'];
|
||||||
|
volume = ServerVolume(
|
||||||
|
id: volumes.length,
|
||||||
|
name: volumeName,
|
||||||
|
sizeByte: volumeSize,
|
||||||
|
serverId: null,
|
||||||
|
linuxDevice: '/dev/disk/by-id/scsi-0DO_Volume_$volumeName',
|
||||||
|
uuid: volumeId,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerVolume>> getVolumes({final String? status}) async {
|
||||||
|
final List<ServerVolume> volumes = [];
|
||||||
|
|
||||||
|
final Response getVolumesResponse;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
getVolumesResponse = await client.get(
|
||||||
|
'/volumes',
|
||||||
|
queryParameters: {
|
||||||
|
'status': status,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final List<dynamic> rawVolumes = getVolumesResponse.data['volumes'];
|
||||||
|
int id = 0;
|
||||||
|
for (final rawVolume in rawVolumes) {
|
||||||
|
final volumeId = rawVolume['id'];
|
||||||
|
final int volumeSize = rawVolume['size_gigabytes'] * 1024 * 1024 * 1024;
|
||||||
|
final volumeDropletIds = rawVolume['droplet_ids'];
|
||||||
|
final String volumeName = rawVolume['name'];
|
||||||
|
final volume = ServerVolume(
|
||||||
|
id: id++,
|
||||||
|
name: volumeName,
|
||||||
|
sizeByte: volumeSize,
|
||||||
|
serverId: volumeDropletIds.isNotEmpty ? volumeDropletIds[0] : null,
|
||||||
|
linuxDevice: 'scsi-0DO_Volume_$volumeName',
|
||||||
|
uuid: volumeId,
|
||||||
|
);
|
||||||
|
volumes.add(volume);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ServerVolume?> getVolume(final String volumeUuid) async {
|
||||||
|
ServerVolume? requestedVolume;
|
||||||
|
|
||||||
|
final List<ServerVolume> volumes = await getVolumes();
|
||||||
|
|
||||||
|
for (final volume in volumes) {
|
||||||
|
if (volume.uuid == volumeUuid) {
|
||||||
|
requestedVolume = volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestedVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteVolume(final ServerVolume volume) async {
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
await client.delete('/volumes/${volume.uuid}');
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> attachVolume(
|
||||||
|
final ServerVolume volume,
|
||||||
|
final int serverId,
|
||||||
|
) async {
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
final Response attachVolumeResponse;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
attachVolumeResponse = await client.post(
|
||||||
|
'/volumes/actions',
|
||||||
|
data: {
|
||||||
|
'type': 'attach',
|
||||||
|
'volume_name': volume.name,
|
||||||
|
'region': region,
|
||||||
|
'droplet_id': serverId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
success =
|
||||||
|
attachVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> detachVolume(final ServerVolume volume) async {
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
final Response detachVolumeResponse;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
detachVolumeResponse = await client.post(
|
||||||
|
'/volumes/actions',
|
||||||
|
data: {
|
||||||
|
'type': 'detach',
|
||||||
|
'volume_name': volume.name,
|
||||||
|
'droplet_id': volume.serverId,
|
||||||
|
'region': region,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
success =
|
||||||
|
detachVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> resizeVolume(
|
||||||
|
final ServerVolume volume,
|
||||||
|
final DiskSize size,
|
||||||
|
) async {
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
final Response resizeVolumeResponse;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
resizeVolumeResponse = await client.post(
|
||||||
|
'/volumes/actions',
|
||||||
|
data: {
|
||||||
|
'type': 'resize',
|
||||||
|
'volume_name': volume.name,
|
||||||
|
'size_gigabytes': size.gibibyte,
|
||||||
|
'region': region,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
success =
|
||||||
|
resizeVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getHostnameFromDomain(final String domain) {
|
||||||
|
// Replace all non-alphanumeric characters with an underscore
|
||||||
|
String hostname =
|
||||||
|
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||||
|
if (hostname.endsWith('-')) {
|
||||||
|
hostname = hostname.substring(0, hostname.length - 1);
|
||||||
|
}
|
||||||
|
if (hostname.startsWith('-')) {
|
||||||
|
hostname = hostname.substring(1);
|
||||||
|
}
|
||||||
|
if (hostname.isEmpty) {
|
||||||
|
hostname = 'selfprivacy-server';
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ServerHostingDetails?> createServer({
|
||||||
|
required final String dnsApiToken,
|
||||||
|
required final User rootUser,
|
||||||
|
required final String domainName,
|
||||||
|
required final String serverType,
|
||||||
|
}) async {
|
||||||
|
ServerHostingDetails? serverDetails;
|
||||||
|
|
||||||
|
final String dbPassword = StringGenerators.dbPassword();
|
||||||
|
final String apiToken = StringGenerators.apiToken();
|
||||||
|
|
||||||
|
final String base64Password =
|
||||||
|
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
||||||
|
|
||||||
|
final String formattedHostname = getHostnameFromDomain(domainName);
|
||||||
|
const String infectBranch = 'providers/digital-ocean';
|
||||||
|
final String stagingAcme = StagingOptions.stagingAcme ? 'true' : 'false';
|
||||||
|
|
||||||
|
final String userdataString =
|
||||||
|
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | PROVIDER=$infectProviderName STAGING_ACME='$stagingAcme' DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$formattedHostname bash 2>&1 | tee /tmp/infect.log";
|
||||||
|
print(userdataString);
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Map<String, Object> data = {
|
||||||
|
'name': formattedHostname,
|
||||||
|
'size': serverType,
|
||||||
|
'image': 'ubuntu-20-04-x64',
|
||||||
|
'user_data': userdataString,
|
||||||
|
'region': region!,
|
||||||
|
};
|
||||||
|
print('Decoded data: $data');
|
||||||
|
|
||||||
|
final Response serverCreateResponse = await client.post(
|
||||||
|
'/droplets',
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int serverId = serverCreateResponse.data['droplet']['id'];
|
||||||
|
final ServerVolume? newVolume = await createVolume();
|
||||||
|
final bool attachedVolume = await attachVolume(newVolume!, serverId);
|
||||||
|
|
||||||
|
String? ipv4;
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < 5 && ipv4 == null) {
|
||||||
|
await Future.delayed(const Duration(seconds: 20));
|
||||||
|
final List<ServerBasicInfo> servers = await getServers();
|
||||||
|
for (final server in servers) {
|
||||||
|
if (server.name == formattedHostname && server.ip != '0.0.0.0') {
|
||||||
|
ipv4 = server.ip;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++attempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachedVolume && ipv4 != null) {
|
||||||
|
serverDetails = ServerHostingDetails(
|
||||||
|
id: serverId,
|
||||||
|
ip4: ipv4,
|
||||||
|
createTime: DateTime.now(),
|
||||||
|
volume: newVolume,
|
||||||
|
apiToken: apiToken,
|
||||||
|
provider: ServerProvider.digitalOcean,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteServer({
|
||||||
|
required final String domainName,
|
||||||
|
}) async {
|
||||||
|
final Dio client = await getClient();
|
||||||
|
|
||||||
|
final ServerBasicInfo serverToRemove = (await getServers()).firstWhere(
|
||||||
|
(final el) => el.name == domainName,
|
||||||
|
);
|
||||||
|
final ServerVolume volumeToRemove = (await getVolumes()).firstWhere(
|
||||||
|
(final el) => el.serverId == serverToRemove.id,
|
||||||
|
);
|
||||||
|
final List<Future> laterFutures = <Future>[];
|
||||||
|
|
||||||
|
await detachVolume(volumeToRemove);
|
||||||
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
|
||||||
|
try {
|
||||||
|
laterFutures.add(deleteVolume(volumeToRemove));
|
||||||
|
laterFutures.add(client.delete('/droplets/$serverToRemove.id'));
|
||||||
|
await Future.wait(laterFutures);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ServerHostingDetails> restart() async {
|
||||||
|
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
await client.post(
|
||||||
|
'/droplets/${server.id}/actions',
|
||||||
|
data: {
|
||||||
|
'type': 'reboot',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.copyWith(startTime: DateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ServerHostingDetails> powerOn() async {
|
||||||
|
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
await client.post(
|
||||||
|
'/droplets/${server.id}/actions',
|
||||||
|
data: {
|
||||||
|
'type': 'power_on',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.copyWith(startTime: DateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Digital Ocean returns a map of lists of /proc/stat values,
|
||||||
|
/// so here we are trying to implement average CPU
|
||||||
|
/// load calculation for each point in time on a given interval.
|
||||||
|
///
|
||||||
|
/// For each point of time:
|
||||||
|
///
|
||||||
|
/// `Average Load = 100 * (1 - (Idle Load / Total Load))`
|
||||||
|
///
|
||||||
|
/// For more info please proceed to read:
|
||||||
|
/// https://rosettacode.org/wiki/Linux_CPU_utilization
|
||||||
|
List<TimeSeriesData> calculateCpuLoadMetrics(final List rawProcStatMetrics) {
|
||||||
|
final List<TimeSeriesData> cpuLoads = [];
|
||||||
|
|
||||||
|
final int pointsInTime = (rawProcStatMetrics[0]['values'] as List).length;
|
||||||
|
for (int i = 0; i < pointsInTime; ++i) {
|
||||||
|
double currentMetricLoad = 0.0;
|
||||||
|
double? currentMetricIdle;
|
||||||
|
for (final rawProcStat in rawProcStatMetrics) {
|
||||||
|
final String rawProcValue = rawProcStat['values'][i][1];
|
||||||
|
// Converting MBit into bit
|
||||||
|
final double procValue = double.parse(rawProcValue) * 1000000;
|
||||||
|
currentMetricLoad += procValue;
|
||||||
|
if (currentMetricIdle == null &&
|
||||||
|
rawProcStat['metric']['mode'] == 'idle') {
|
||||||
|
currentMetricIdle = procValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentMetricIdle ??= 0.0;
|
||||||
|
currentMetricLoad = 100.0 * (1 - (currentMetricIdle / currentMetricLoad));
|
||||||
|
cpuLoads.add(
|
||||||
|
TimeSeriesData(
|
||||||
|
rawProcStatMetrics[0]['values'][i][0],
|
||||||
|
currentMetricLoad,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cpuLoads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ServerMetrics?> getMetrics(
|
||||||
|
final int serverId,
|
||||||
|
final DateTime start,
|
||||||
|
final DateTime end,
|
||||||
|
) async {
|
||||||
|
ServerMetrics? metrics;
|
||||||
|
|
||||||
NaiJi marked this conversation as resolved
inex
commented
Review
commented try block? commented try block?
|
|||||||
|
const int step = 15;
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
Response response = await client.get(
|
||||||
|
'/monitoring/metrics/droplet/bandwidth',
|
||||||
|
queryParameters: {
|
||||||
|
'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'host_id': '$serverId',
|
||||||
|
'interface': 'public',
|
||||||
|
'direction': 'inbound',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final List inbound = response.data['data']['result'][0]['values'];
|
||||||
|
|
||||||
|
response = await client.get(
|
||||||
|
'/monitoring/metrics/droplet/bandwidth',
|
||||||
|
queryParameters: {
|
||||||
|
'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'host_id': '$serverId',
|
||||||
|
'interface': 'public',
|
||||||
|
'direction': 'outbound',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final List outbound = response.data['data']['result'][0]['values'];
|
||||||
|
|
||||||
|
response = await client.get(
|
||||||
|
'/monitoring/metrics/droplet/cpu',
|
||||||
|
queryParameters: {
|
||||||
|
'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
|
||||||
|
'host_id': '$serverId',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
metrics = ServerMetrics(
|
||||||
|
bandwidthIn: inbound
|
||||||
|
.map(
|
||||||
|
(final el) => TimeSeriesData(el[0], double.parse(el[1]) * 100000),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
bandwidthOut: outbound
|
||||||
|
.map(
|
||||||
|
(final el) => TimeSeriesData(el[0], double.parse(el[1]) * 100000),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
cpu: calculateCpuLoadMetrics(response.data['data']['result']),
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
stepsInSecond: step,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerMetadataEntity>> getMetadata(final int serverId) async {
|
||||||
|
List<ServerMetadataEntity> metadata = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get('/droplets/$serverId');
|
||||||
|
final droplet = response.data!['droplet'];
|
||||||
|
metadata = [
|
||||||
inex
commented
Review
.tr() should be called by UI layer. .tr() should be called by UI layer.
NaiJi
commented
Review
Will be a part of coming refactoring for business logic layer. Consider it a stub Will be a part of coming refactoring for business logic layer. Consider it a stub
|
|||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.id,
|
||||||
|
name: 'server.server_id'.tr(),
|
||||||
|
value: droplet['id'].toString(),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.status,
|
||||||
|
name: 'server.status'.tr(),
|
||||||
|
value: droplet['status'].toString().capitalize(),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.cpu,
|
||||||
|
name: 'server.cpu'.tr(),
|
||||||
|
value: 'server.core_count'.plural(droplet['vcpus']),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.ram,
|
||||||
|
name: 'server.ram'.tr(),
|
||||||
|
value: "${droplet['memory'].toString()} MB",
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.cost,
|
||||||
|
name: 'server.monthly_cost'.tr(),
|
||||||
|
value: droplet['size']['price_monthly'].toString(),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.location,
|
||||||
|
name: 'server.location'.tr(),
|
||||||
|
value:
|
||||||
|
'${droplet['region']['name']} ${getEmojiFlag(droplet['region']['slug'].toString()) ?? ''}',
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.other,
|
||||||
|
name: 'server.provider'.tr(),
|
||||||
|
value: displayProviderName,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerBasicInfo>> getServers() async {
|
||||||
|
List<ServerBasicInfo> servers = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get('/droplets');
|
||||||
|
servers = response.data!['droplets'].map<ServerBasicInfo>(
|
||||||
|
(final server) {
|
||||||
|
String ipv4 = '0.0.0.0';
|
||||||
|
if (server['networks']['v4'].isNotEmpty) {
|
||||||
|
for (final v4 in server['networks']['v4']) {
|
||||||
|
if (v4['type'].toString() == 'public') {
|
||||||
|
ipv4 = v4['ip_address'].toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerBasicInfo(
|
||||||
|
id: server['id'],
|
||||||
|
reverseDns: server['name'],
|
||||||
|
created: DateTime.now(),
|
||||||
|
ip: ipv4,
|
||||||
|
name: server['name'],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
print(servers);
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? getEmojiFlag(final String query) {
|
||||||
|
String? emoji;
|
||||||
|
|
||||||
|
switch (query.toLowerCase().substring(0, 3)) {
|
||||||
|
case 'fra':
|
||||||
|
emoji = '🇩🇪';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ams':
|
||||||
|
emoji = '🇳🇱';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sgp':
|
||||||
|
emoji = '🇸🇬';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'lon':
|
||||||
|
emoji = '🇬🇧';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tor':
|
||||||
|
emoji = '🇨🇦';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'blr':
|
||||||
|
emoji = '🇮🇳';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'nyc':
|
||||||
|
case 'sfo':
|
||||||
|
emoji = '🇺🇸';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerProviderLocation>> getAvailableLocations() async {
|
||||||
|
List<ServerProviderLocation> locations = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(
|
||||||
|
'/regions',
|
||||||
|
);
|
||||||
|
|
||||||
|
locations = response.data!['regions']
|
||||||
|
.map<ServerProviderLocation>(
|
||||||
|
(final location) => ServerProviderLocation(
|
||||||
|
title: location['slug'],
|
||||||
|
description: location['name'],
|
||||||
|
flag: getEmojiFlag(location['slug']),
|
||||||
|
identifier: location['slug'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerType>> getServerTypesByLocation({
|
||||||
|
required final ServerProviderLocation location,
|
||||||
|
}) async {
|
||||||
|
final List<ServerType> types = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(
|
||||||
|
'/sizes',
|
||||||
|
);
|
||||||
|
final rawSizes = response.data!['sizes'];
|
||||||
|
for (final rawSize in rawSizes) {
|
||||||
|
for (final rawRegion in rawSize['regions']) {
|
||||||
|
if (rawRegion.toString() == location.identifier) {
|
||||||
|
types.add(
|
||||||
|
ServerType(
|
||||||
|
title: rawSize['description'],
|
||||||
|
identifier: rawSize['slug'],
|
||||||
|
ram: rawSize['memory'].toDouble(),
|
||||||
|
cores: rawSize['vcpus'],
|
||||||
|
disk: DiskSize(byte: rawSize['disk'] * 1024 * 1024 * 1024),
|
||||||
|
price: Price(
|
||||||
|
value: rawSize['price_monthly'],
|
||||||
|
currency: 'USD',
|
||||||
|
),
|
||||||
|
location: location,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createReverseDns({
|
||||||
|
required final ServerHostingDetails serverDetails,
|
||||||
|
required final ServerDomain domain,
|
||||||
|
}) async {
|
||||||
|
/// TODO remove from provider interface
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProviderApiTokenValidation getApiTokenValidation() =>
|
||||||
|
ProviderApiTokenValidation(
|
||||||
|
regexp: RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'),
|
||||||
|
length: 71,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
||||||
|
|
||||||
|
class DigitalOceanApiFactory extends ServerProviderApiFactory
|
||||||
|
with VolumeProviderApiFactory {
|
||||||
|
DigitalOceanApiFactory({this.region});
|
||||||
|
|
||||||
|
final String? region;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServerProviderApi getServerProvider({
|
||||||
|
final ServerProviderApiSettings settings =
|
||||||
|
const ServerProviderApiSettings(),
|
||||||
|
}) =>
|
||||||
|
DigitalOceanApi(
|
||||||
|
region: settings.region ?? region,
|
||||||
|
hasLogger: settings.hasLogger,
|
||||||
|
isWithToken: settings.isWithToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumeProviderApi getVolumeProvider({
|
||||||
|
final ServerProviderApiSettings settings =
|
||||||
|
const ServerProviderApiSettings(),
|
||||||
|
}) =>
|
||||||
|
DigitalOceanApi(
|
||||||
|
region: settings.region ?? region,
|
||||||
|
hasLogger: settings.hasLogger,
|
||||||
|
isWithToken: settings.isWithToken,
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,28 +2,43 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/staging_options.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.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/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
HetznerApi({this.hasLogger = false, this.isWithToken = true});
|
HetznerApi({
|
||||||
|
this.region,
|
||||||
|
this.hasLogger = false,
|
||||||
|
this.isWithToken = true,
|
||||||
|
});
|
||||||
@override
|
@override
|
||||||
bool hasLogger;
|
bool hasLogger;
|
||||||
@override
|
@override
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
||||||
|
final String? region;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
|
||||||
if (isWithToken) {
|
if (isWithToken) {
|
||||||
final String? token = getIt<ApiConfigModel>().hetznerKey;
|
final String? token = getIt<ApiConfigModel>().serverProviderKey;
|
||||||
assert(token != null);
|
assert(token != null);
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
@ -36,7 +51,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String rootAddress = 'https://api.hetzner.cloud/v1';
|
String get rootAddress => 'https://api.hetzner.cloud/v1';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get infectProviderName => 'hetzner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get displayProviderName => 'Hetzner';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isApiTokenValid(final String token) async {
|
Future<bool> isApiTokenValid(final String token) async {
|
||||||
|
@ -71,19 +92,22 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RegExp getApiTokenValidation() =>
|
ProviderApiTokenValidation getApiTokenValidation() =>
|
||||||
RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
|
ProviderApiTokenValidation(
|
||||||
|
regexp: RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'),
|
||||||
|
length: 64,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<double?> getPricePerGb() async {
|
Future<Price?> getPricePerGb() async {
|
||||||
double? price;
|
double? price;
|
||||||
|
|
||||||
final Response dbGetResponse;
|
final Response pricingResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbGetResponse = await client.get('/pricing');
|
pricingResponse = await client.get('/pricing');
|
||||||
|
|
||||||
final volume = dbGetResponse.data['pricing']['volume'];
|
final volume = pricingResponse.data['pricing']['volume'];
|
||||||
final volumePrice = volume['price_per_gb_month']['gross'];
|
final volumePrice = volume['price_per_gb_month']['gross'];
|
||||||
price = double.parse(volumePrice);
|
price = double.parse(volumePrice);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -92,38 +116,43 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return price;
|
return price == null
|
||||||
|
? null
|
||||||
|
: Price(
|
||||||
|
value: price,
|
||||||
|
currency: 'EUR',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ServerVolume?> createVolume() async {
|
Future<ServerVolume?> createVolume() async {
|
||||||
ServerVolume? volume;
|
ServerVolume? volume;
|
||||||
|
|
||||||
final Response dbCreateResponse;
|
final Response createVolumeResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbCreateResponse = await client.post(
|
createVolumeResponse = await client.post(
|
||||||
'/volumes',
|
'/volumes',
|
||||||
data: {
|
data: {
|
||||||
'size': 10,
|
'size': 10,
|
||||||
'name': StringGenerators.dbStorageName(),
|
'name': StringGenerators.storageName(),
|
||||||
'labels': {'labelkey': 'value'},
|
'labels': {'labelkey': 'value'},
|
||||||
'location': 'fsn1',
|
'location': region,
|
||||||
'automount': false,
|
'automount': false,
|
||||||
'format': 'ext4'
|
'format': 'ext4'
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final dbId = dbCreateResponse.data['volume']['id'];
|
final volumeId = createVolumeResponse.data['volume']['id'];
|
||||||
final dbSize = dbCreateResponse.data['volume']['size'];
|
final volumeSize = createVolumeResponse.data['volume']['size'];
|
||||||
final dbServer = dbCreateResponse.data['volume']['server'];
|
final volumeServer = createVolumeResponse.data['volume']['server'];
|
||||||
final dbName = dbCreateResponse.data['volume']['name'];
|
final volumeName = createVolumeResponse.data['volume']['name'];
|
||||||
final dbDevice = dbCreateResponse.data['volume']['linux_device'];
|
final volumeDevice = createVolumeResponse.data['volume']['linux_device'];
|
||||||
volume = ServerVolume(
|
volume = ServerVolume(
|
||||||
id: dbId,
|
id: volumeId,
|
||||||
name: dbName,
|
name: volumeName,
|
||||||
sizeByte: dbSize,
|
sizeByte: volumeSize,
|
||||||
serverId: dbServer,
|
serverId: volumeServer,
|
||||||
linuxDevice: dbDevice,
|
linuxDevice: volumeDevice,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -138,28 +167,28 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
Future<List<ServerVolume>> getVolumes({final String? status}) async {
|
Future<List<ServerVolume>> getVolumes({final String? status}) async {
|
||||||
final List<ServerVolume> volumes = [];
|
final List<ServerVolume> volumes = [];
|
||||||
|
|
||||||
final Response dbGetResponse;
|
final Response getVolumesResonse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbGetResponse = await client.get(
|
getVolumesResonse = await client.get(
|
||||||
'/volumes',
|
'/volumes',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'status': status,
|
'status': status,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final List<dynamic> rawVolumes = dbGetResponse.data['volumes'];
|
final List<dynamic> rawVolumes = getVolumesResonse.data['volumes'];
|
||||||
for (final rawVolume in rawVolumes) {
|
for (final rawVolume in rawVolumes) {
|
||||||
final int dbId = rawVolume['id'];
|
final int volumeId = rawVolume['id'];
|
||||||
final int dbSize = rawVolume['size'] * 1024 * 1024 * 1024;
|
final int volumeSize = rawVolume['size'] * 1024 * 1024 * 1024;
|
||||||
final dbServer = rawVolume['server'];
|
final volumeServer = rawVolume['server'];
|
||||||
final String dbName = rawVolume['name'];
|
final String volumeName = rawVolume['name'];
|
||||||
final dbDevice = rawVolume['linux_device'];
|
final volumeDevice = rawVolume['linux_device'];
|
||||||
final volume = ServerVolume(
|
final volume = ServerVolume(
|
||||||
id: dbId,
|
id: volumeId,
|
||||||
name: dbName,
|
name: volumeName,
|
||||||
sizeByte: dbSize,
|
sizeByte: volumeSize,
|
||||||
serverId: dbServer,
|
serverId: volumeServer,
|
||||||
linuxDevice: dbDevice,
|
linuxDevice: volumeDevice,
|
||||||
);
|
);
|
||||||
volumes.add(volume);
|
volumes.add(volume);
|
||||||
}
|
}
|
||||||
|
@ -172,25 +201,26 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
return volumes;
|
return volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<ServerVolume?> getVolume(
|
||||||
Future<ServerVolume?> getVolume(final int id) async {
|
final String volumeId,
|
||||||
|
) async {
|
||||||
ServerVolume? volume;
|
ServerVolume? volume;
|
||||||
|
|
||||||
final Response dbGetResponse;
|
final Response getVolumeResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbGetResponse = await client.get('/volumes/$id');
|
getVolumeResponse = await client.get('/volumes/$volumeId');
|
||||||
final int dbId = dbGetResponse.data['volume']['id'];
|
final int responseVolumeId = getVolumeResponse.data['volume']['id'];
|
||||||
final int dbSize = dbGetResponse.data['volume']['size'];
|
final int volumeSize = getVolumeResponse.data['volume']['size'];
|
||||||
final int dbServer = dbGetResponse.data['volume']['server'];
|
final int volumeServer = getVolumeResponse.data['volume']['server'];
|
||||||
final String dbName = dbGetResponse.data['volume']['name'];
|
final String volumeName = getVolumeResponse.data['volume']['name'];
|
||||||
final dbDevice = dbGetResponse.data['volume']['linux_device'];
|
final volumeDevice = getVolumeResponse.data['volume']['linux_device'];
|
||||||
volume = ServerVolume(
|
volume = ServerVolume(
|
||||||
id: dbId,
|
id: responseVolumeId,
|
||||||
name: dbName,
|
name: volumeName,
|
||||||
sizeByte: dbSize,
|
sizeByte: volumeSize,
|
||||||
serverId: dbServer,
|
serverId: volumeServer,
|
||||||
linuxDevice: dbDevice,
|
linuxDevice: volumeDevice,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -202,10 +232,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteVolume(final int id) async {
|
Future<void> deleteVolume(final ServerVolume volume) async {
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
await client.delete('/volumes/$id');
|
await client.delete('/volumes/${volume.id}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -214,20 +244,24 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> attachVolume(final int volumeId, final int serverId) async {
|
Future<bool> attachVolume(
|
||||||
|
final ServerVolume volume,
|
||||||
|
final int serverId,
|
||||||
|
) async {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
final Response dbPostResponse;
|
final Response attachVolumeResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbPostResponse = await client.post(
|
attachVolumeResponse = await client.post(
|
||||||
'/volumes/$volumeId/actions/attach',
|
'/volumes/${volume.id}/actions/attach',
|
||||||
data: {
|
data: {
|
||||||
'automount': true,
|
'automount': true,
|
||||||
'server': serverId,
|
'server': serverId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
success = dbPostResponse.data['action']['status'].toString() != 'error';
|
success =
|
||||||
|
attachVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -238,14 +272,17 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> detachVolume(final int volumeId) async {
|
Future<bool> detachVolume(final ServerVolume volume) async {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
final Response dbPostResponse;
|
final Response detachVolumeResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbPostResponse = await client.post('/volumes/$volumeId/actions/detach');
|
detachVolumeResponse = await client.post(
|
||||||
success = dbPostResponse.data['action']['status'].toString() != 'error';
|
'/volumes/${volume.id}/actions/detach',
|
||||||
|
);
|
||||||
|
success =
|
||||||
|
detachVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -256,19 +293,23 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> resizeVolume(final int volumeId, final int sizeGb) async {
|
Future<bool> resizeVolume(
|
||||||
|
final ServerVolume volume,
|
||||||
|
final DiskSize size,
|
||||||
|
) async {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
final Response dbPostResponse;
|
final Response resizeVolumeResponse;
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
dbPostResponse = await client.post(
|
resizeVolumeResponse = await client.post(
|
||||||
'/volumes/$volumeId/actions/resize',
|
'/volumes/${volume.id}/actions/resize',
|
||||||
data: {
|
data: {
|
||||||
'size': sizeGb,
|
'size': size.gibibyte,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
success = dbPostResponse.data['action']['status'].toString() != 'error';
|
success =
|
||||||
|
resizeVolumeResponse.data['action']['status'].toString() != 'error';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -283,6 +324,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
required final String dnsApiToken,
|
required final String dnsApiToken,
|
||||||
required final User rootUser,
|
required final User rootUser,
|
||||||
required final String domainName,
|
required final String domainName,
|
||||||
|
required final String serverType,
|
||||||
}) async {
|
}) async {
|
||||||
ServerHostingDetails? details;
|
ServerHostingDetails? details;
|
||||||
|
|
||||||
|
@ -295,7 +337,8 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
dnsApiToken: dnsApiToken,
|
dnsApiToken: dnsApiToken,
|
||||||
rootUser: rootUser,
|
rootUser: rootUser,
|
||||||
domainName: domainName,
|
domainName: domainName,
|
||||||
dataBase: newVolume,
|
volume: newVolume,
|
||||||
|
serverType: serverType,
|
||||||
);
|
);
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
|
@ -305,48 +348,43 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
required final String dnsApiToken,
|
required final String dnsApiToken,
|
||||||
required final User rootUser,
|
required final User rootUser,
|
||||||
required final String domainName,
|
required final String domainName,
|
||||||
required final ServerVolume dataBase,
|
required final ServerVolume volume,
|
||||||
|
required final String serverType,
|
||||||
}) async {
|
}) async {
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
|
|
||||||
final String dbPassword = StringGenerators.dbPassword();
|
final String dbPassword = StringGenerators.dbPassword();
|
||||||
final int dbId = dataBase.id;
|
final int volumeId = volume.id;
|
||||||
|
|
||||||
final String apiToken = StringGenerators.apiToken();
|
final String apiToken = StringGenerators.apiToken();
|
||||||
|
|
||||||
final String hostname = getHostnameFromDomain(domainName);
|
final String hostname = getHostnameFromDomain(domainName);
|
||||||
|
const String infectBranch = 'providers/hetzner';
|
||||||
|
final String stagingAcme = StagingOptions.stagingAcme ? 'true' : 'false';
|
||||||
final String base64Password =
|
final String base64Password =
|
||||||
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
||||||
|
|
||||||
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 String 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=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
|
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | STAGING_ACME='$stagingAcme' PROVIDER=$infectProviderName NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
|
||||||
print(userdataString);
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
ServerHostingDetails? serverDetails;
|
ServerHostingDetails? serverDetails;
|
||||||
DioError? hetznerError;
|
DioError? hetznerError;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final Map<String, Object> data = {
|
||||||
|
'name': hostname,
|
||||||
|
'server_type': serverType,
|
||||||
|
'start_after_create': false,
|
||||||
|
'image': 'ubuntu-20.04',
|
||||||
|
'volumes': [volumeId],
|
||||||
|
'networks': [],
|
||||||
|
'user_data': userdataString,
|
||||||
|
'labels': {},
|
||||||
|
'automount': true,
|
||||||
|
'location': region!,
|
||||||
|
};
|
||||||
|
print('Decoded data: $data');
|
||||||
|
|
||||||
final Response serverCreateResponse = await client.post(
|
final Response serverCreateResponse = await client.post(
|
||||||
'/servers',
|
'/servers',
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -356,7 +394,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
id: serverCreateResponse.data['server']['id'],
|
id: serverCreateResponse.data['server']['id'],
|
||||||
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
||||||
createTime: DateTime.now(),
|
createTime: DateTime.now(),
|
||||||
volume: dataBase,
|
volume: volume,
|
||||||
apiToken: apiToken,
|
apiToken: apiToken,
|
||||||
provider: ServerProvider.hetzner,
|
provider: ServerProvider.hetzner,
|
||||||
);
|
);
|
||||||
|
@ -372,7 +410,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
await deleteVolume(dbId);
|
await deleteVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hetznerError != null) {
|
if (hetznerError != null) {
|
||||||
|
@ -459,14 +497,12 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
return server.copyWith(startTime: DateTime.now());
|
return server.copyWith(startTime: DateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getMetrics(
|
Future<Map<String, dynamic>> requestRawMetrics(
|
||||||
|
final int serverId,
|
||||||
final DateTime start,
|
final DateTime start,
|
||||||
final DateTime end,
|
final DateTime end,
|
||||||
final String type,
|
final String type,
|
||||||
) async {
|
) async {
|
||||||
final ServerHostingDetails? hetznerServer =
|
|
||||||
getIt<ApiConfigModel>().serverDetails;
|
|
||||||
|
|
||||||
Map<String, dynamic> metrics = {};
|
Map<String, dynamic> metrics = {};
|
||||||
final Dio client = await getClient();
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
|
@ -476,10 +512,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
'type': type
|
'type': type
|
||||||
};
|
};
|
||||||
final Response res = await client.get(
|
final Response res = await client.get(
|
||||||
'/servers/${hetznerServer!.id}/metrics',
|
'/servers/$serverId/metrics',
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
);
|
);
|
||||||
metrics = res.data;
|
metrics = res.data['metrics'];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -489,14 +525,115 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HetznerServerInfo> getInfo() async {
|
List<TimeSeriesData> serializeTimeSeries(
|
||||||
final ServerHostingDetails? hetznerServer =
|
final Map<String, dynamic> json,
|
||||||
getIt<ApiConfigModel>().serverDetails;
|
final String type,
|
||||||
final Dio client = await getClient();
|
) {
|
||||||
final Response response = await client.get('/servers/${hetznerServer!.id}');
|
final List list = json['time_series'][type]['values'];
|
||||||
close(client);
|
return list
|
||||||
|
.map((final el) => TimeSeriesData(el[0], double.parse(el[1])))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
return HetznerServerInfo.fromJson(response.data!['server']);
|
@override
|
||||||
|
Future<ServerMetrics?> getMetrics(
|
||||||
|
final int serverId,
|
||||||
|
final DateTime start,
|
||||||
|
final DateTime end,
|
||||||
|
) async {
|
||||||
|
ServerMetrics? metrics;
|
||||||
|
|
||||||
|
final Map<String, dynamic> rawCpuMetrics = await requestRawMetrics(
|
||||||
|
serverId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
'cpu',
|
||||||
|
);
|
||||||
|
final Map<String, dynamic> rawNetworkMetrics = await requestRawMetrics(
|
||||||
|
serverId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
'network',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rawNetworkMetrics.isEmpty || rawCpuMetrics.isEmpty) {
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics = ServerMetrics(
|
||||||
|
cpu: serializeTimeSeries(
|
||||||
|
rawCpuMetrics,
|
||||||
|
'cpu',
|
||||||
|
),
|
||||||
|
bandwidthIn: serializeTimeSeries(
|
||||||
|
rawNetworkMetrics,
|
||||||
|
'network.0.bandwidth.in',
|
||||||
|
),
|
||||||
|
bandwidthOut: serializeTimeSeries(
|
||||||
|
rawNetworkMetrics,
|
||||||
|
'network.0.bandwidth.out',
|
||||||
|
),
|
||||||
|
end: end,
|
||||||
|
start: start,
|
||||||
|
stepsInSecond: rawCpuMetrics['step'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerMetadataEntity>> getMetadata(final int serverId) async {
|
||||||
|
List<ServerMetadataEntity> metadata = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get('/servers/$serverId');
|
||||||
|
final hetznerInfo = HetznerServerInfo.fromJson(response.data!['server']);
|
||||||
|
metadata = [
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.id,
|
||||||
|
name: 'server.server_id'.tr(),
|
||||||
|
value: hetznerInfo.id.toString(),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.status,
|
||||||
|
name: 'server.status'.tr(),
|
||||||
|
value: hetznerInfo.status.toString().split('.')[1].capitalize(),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.cpu,
|
||||||
|
name: 'server.cpu'.tr(),
|
||||||
|
value: 'server.core_count'.plural(hetznerInfo.serverType.cores),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.ram,
|
||||||
|
name: 'server.ram'.tr(),
|
||||||
|
value: '${hetznerInfo.serverType.memory.toString()} GB',
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.cost,
|
||||||
|
name: 'server.monthly_cost'.tr(),
|
||||||
|
value: hetznerInfo.serverType.prices[1].monthly.toStringAsFixed(2),
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.location,
|
||||||
|
name: 'server.location'.tr(),
|
||||||
|
value:
|
||||||
|
'${hetznerInfo.location.city}, ${hetznerInfo.location.country}',
|
||||||
|
),
|
||||||
|
ServerMetadataEntity(
|
||||||
|
type: MetadataType.other,
|
||||||
|
name: 'server.provider'.tr(),
|
||||||
|
value: displayProviderName,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -521,7 +658,6 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
ip: server.publicNet.ipv4.ip,
|
ip: server.publicNet.ipv4.ip,
|
||||||
reverseDns: server.publicNet.ipv4.reverseDns,
|
reverseDns: server.publicNet.ipv4.reverseDns,
|
||||||
created: server.created,
|
created: server.created,
|
||||||
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -535,6 +671,96 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? getEmojiFlag(final String query) {
|
||||||
|
String? emoji;
|
||||||
|
|
||||||
|
switch (query.toLowerCase()) {
|
||||||
|
case 'de':
|
||||||
|
emoji = '🇩🇪';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fi':
|
||||||
|
emoji = '🇫🇮';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'us':
|
||||||
|
emoji = '🇺🇸';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerProviderLocation>> getAvailableLocations() async {
|
||||||
|
List<ServerProviderLocation> locations = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(
|
||||||
|
'/locations',
|
||||||
|
);
|
||||||
|
|
||||||
|
locations = response.data!['locations']
|
||||||
|
.map<ServerProviderLocation>(
|
||||||
|
(final location) => ServerProviderLocation(
|
||||||
|
title: location['city'],
|
||||||
|
description: location['description'],
|
||||||
|
flag: getEmojiFlag(location['country']),
|
||||||
|
identifier: location['name'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ServerType>> getServerTypesByLocation({
|
||||||
|
required final ServerProviderLocation location,
|
||||||
|
}) async {
|
||||||
|
final List<ServerType> types = [];
|
||||||
|
|
||||||
|
final Dio client = await getClient();
|
||||||
|
try {
|
||||||
|
final Response response = await client.get(
|
||||||
|
'/server_types',
|
||||||
|
);
|
||||||
|
final rawTypes = response.data!['server_types'];
|
||||||
|
for (final rawType in rawTypes) {
|
||||||
|
for (final rawPrice in rawType['prices']) {
|
||||||
|
if (rawPrice['location'].toString() == location.identifier) {
|
||||||
|
types.add(
|
||||||
|
ServerType(
|
||||||
|
title: rawType['description'],
|
||||||
|
identifier: rawType['name'],
|
||||||
|
ram: rawType['memory'],
|
||||||
|
cores: rawType['cores'],
|
||||||
|
disk: DiskSize(byte: rawType['disk'] * 1024 * 1024 * 1024),
|
||||||
|
price: Price(
|
||||||
|
value: double.parse(rawPrice['price_monthly']['gross']),
|
||||||
|
currency: 'EUR',
|
||||||
|
),
|
||||||
|
location: location,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createReverseDns({
|
Future<void> createReverseDns({
|
||||||
required final ServerHostingDetails serverDetails,
|
required final ServerHostingDetails serverDetails,
|
||||||
|
|
|
@ -1,25 +1,33 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
||||||
|
|
||||||
class HetznerApiFactory extends ServerProviderApiFactory
|
class HetznerApiFactory extends ServerProviderApiFactory
|
||||||
with VolumeProviderApiFactory {
|
with VolumeProviderApiFactory {
|
||||||
|
HetznerApiFactory({this.region});
|
||||||
|
|
||||||
|
final String? region;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ServerProviderApi getServerProvider({
|
ServerProviderApi getServerProvider({
|
||||||
final ProviderApiSettings settings = const ProviderApiSettings(),
|
final ServerProviderApiSettings settings =
|
||||||
|
const ServerProviderApiSettings(),
|
||||||
}) =>
|
}) =>
|
||||||
HetznerApi(
|
HetznerApi(
|
||||||
|
region: settings.region ?? region,
|
||||||
hasLogger: settings.hasLogger,
|
hasLogger: settings.hasLogger,
|
||||||
isWithToken: settings.isWithToken,
|
isWithToken: settings.isWithToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
VolumeProviderApi getVolumeProvider({
|
VolumeProviderApi getVolumeProvider({
|
||||||
final ProviderApiSettings settings = const ProviderApiSettings(),
|
final ServerProviderApiSettings settings =
|
||||||
|
const ServerProviderApiSettings(),
|
||||||
}) =>
|
}) =>
|
||||||
HetznerApi(
|
HetznerApi(
|
||||||
|
region: settings.region ?? region,
|
||||||
hasLogger: settings.hasLogger,
|
hasLogger: settings.hasLogger,
|
||||||
isWithToken: settings.isWithToken,
|
isWithToken: settings.isWithToken,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,10 +2,27 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.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/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
|
||||||
|
class ProviderApiTokenValidation {
|
||||||
|
ProviderApiTokenValidation({
|
||||||
|
required this.length,
|
||||||
|
required this.regexp,
|
||||||
|
});
|
||||||
|
final int length;
|
||||||
|
final RegExp regexp;
|
||||||
|
}
|
||||||
|
|
||||||
abstract class ServerProviderApi extends ApiMap {
|
abstract class ServerProviderApi extends ApiMap {
|
||||||
Future<List<ServerBasicInfo>> getServers();
|
Future<List<ServerBasicInfo>> getServers();
|
||||||
|
Future<List<ServerProviderLocation>> getAvailableLocations();
|
||||||
|
Future<List<ServerType>> getServerTypesByLocation({
|
||||||
|
required final ServerProviderLocation location,
|
||||||
|
});
|
||||||
|
|
||||||
Future<ServerHostingDetails> restart();
|
Future<ServerHostingDetails> restart();
|
||||||
Future<ServerHostingDetails> powerOn();
|
Future<ServerHostingDetails> powerOn();
|
||||||
|
@ -15,6 +32,7 @@ abstract class ServerProviderApi extends ApiMap {
|
||||||
required final String dnsApiToken,
|
required final String dnsApiToken,
|
||||||
required final User rootUser,
|
required final User rootUser,
|
||||||
required final String domainName,
|
required final String domainName,
|
||||||
|
required final String serverType,
|
||||||
});
|
});
|
||||||
Future<void> createReverseDns({
|
Future<void> createReverseDns({
|
||||||
required final ServerHostingDetails serverDetails,
|
required final ServerHostingDetails serverDetails,
|
||||||
|
@ -22,5 +40,19 @@ abstract class ServerProviderApi extends ApiMap {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<bool> isApiTokenValid(final String token);
|
Future<bool> isApiTokenValid(final String token);
|
||||||
RegExp getApiTokenValidation();
|
ProviderApiTokenValidation getApiTokenValidation();
|
||||||
|
Future<List<ServerMetadataEntity>> getMetadata(final int serverId);
|
||||||
|
Future<ServerMetrics?> getMetrics(
|
||||||
|
final int serverId,
|
||||||
|
final DateTime start,
|
||||||
|
final DateTime end,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Provider name key which lets infect understand what kind of installation
|
||||||
|
/// it requires, for example 'digitaloceal' for Digital Ocean
|
||||||
|
String get infectProviderName;
|
||||||
|
|
||||||
|
/// Actual provider name to render on information page for user,
|
||||||
|
/// for example 'Digital Ocean' for Digital Ocean
|
||||||
|
String get displayProviderName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
||||||
|
|
||||||
|
class ServerProviderApiSettings extends ProviderApiSettings {
|
||||||
|
const ServerProviderApiSettings({
|
||||||
|
this.region,
|
||||||
|
super.hasLogger = false,
|
||||||
|
super.isWithToken = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? region;
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
|
||||||
|
|
||||||
abstract class ServerProviderApiFactory {
|
abstract class ServerProviderApiFactory {
|
||||||
ServerProviderApi getServerProvider({
|
ServerProviderApi getServerProvider({
|
||||||
final ProviderApiSettings settings = const ProviderApiSettings(),
|
final ServerProviderApiSettings settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin VolumeProviderApiFactory {
|
mixin VolumeProviderApiFactory {
|
||||||
VolumeProviderApi getVolumeProvider({
|
VolumeProviderApi getVolumeProvider({
|
||||||
final ProviderApiSettings settings = const ProviderApiSettings(),
|
final ServerProviderApiSettings settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
|
|
||||||
mixin VolumeProviderApi on ApiMap {
|
mixin VolumeProviderApi on ApiMap {
|
||||||
Future<ServerVolume?> createVolume();
|
Future<ServerVolume?> createVolume();
|
||||||
Future<List<ServerVolume>> getVolumes({final String? status});
|
Future<List<ServerVolume>> getVolumes({final String? status});
|
||||||
Future<ServerVolume?> getVolume(final int id);
|
Future<bool> attachVolume(final ServerVolume volume, final int serverId);
|
||||||
Future<bool> attachVolume(final int volumeId, final int serverId);
|
Future<bool> detachVolume(final ServerVolume volume);
|
||||||
Future<bool> detachVolume(final int volumeId);
|
Future<bool> resizeVolume(final ServerVolume volume, final DiskSize size);
|
||||||
Future<bool> resizeVolume(final int volumeId, final int sizeGb);
|
Future<void> deleteVolume(final ServerVolume volume);
|
||||||
Future<void> deleteVolume(final int id);
|
Future<Price?> getPricePerGb();
|
||||||
Future<double?> getPricePerGb();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// Controls staging environment for network, is used during manual
|
||||||
|
/// integration testing and such
|
||||||
|
class StagingOptions {
|
||||||
|
/// Whether we request for staging temprorary certificates.
|
||||||
|
/// Hardcode to 'true' in the middle of testing to not
|
||||||
|
/// get your domain banned by constant certificate renewal
|
||||||
|
static bool get stagingAcme => false;
|
||||||
|
}
|
|
@ -5,17 +5,6 @@ enum LoadingStatus {
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InitializingSteps {
|
|
||||||
setHetznerKey,
|
|
||||||
setCloudFlareKey,
|
|
||||||
setDomainName,
|
|
||||||
setRootUser,
|
|
||||||
createServer,
|
|
||||||
checkCloudFlareDns,
|
|
||||||
startServer,
|
|
||||||
checkSystemDnsAndDkimSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Period {
|
enum Period {
|
||||||
hour,
|
hour,
|
||||||
day,
|
day,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:async';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/backup.dart';
|
import 'package:selfprivacy/logic/models/json/backup.dart';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
|
|
||||||
part 'dns_records_state.dart';
|
part 'dns_records_state.dart';
|
||||||
|
@ -19,11 +18,6 @@ class DnsRecordsCubit
|
||||||
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
|
||||||
);
|
);
|
||||||
|
|
||||||
DnsProviderApiFactory? dnsProviderApiFactory =
|
|
||||||
ApiFactoryCreator.createDnsProviderApiFactory(
|
|
||||||
DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!!
|
|
||||||
); // TODO: Remove when provider selection is implemented.
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
final ServerApi api = ServerApi();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -44,7 +38,8 @@ class DnsRecordsCubit
|
||||||
final String? ipAddress =
|
final String? ipAddress =
|
||||||
serverInstallationCubit.state.serverDetails?.ip4;
|
serverInstallationCubit.state.serverDetails?.ip4;
|
||||||
if (domain != null && ipAddress != null) {
|
if (domain != null && ipAddress != null) {
|
||||||
final List<DnsRecord> records = await dnsProviderApiFactory!
|
final List<DnsRecord> records = await ApiController
|
||||||
|
.currentDnsProviderApiFactory!
|
||||||
.getDnsProvider()
|
.getDnsProvider()
|
||||||
.getDnsRecords(domain: domain);
|
.getDnsRecords(domain: domain);
|
||||||
final String? dkimPublicKey =
|
final String? dkimPublicKey =
|
||||||
|
@ -124,7 +119,7 @@ class DnsRecordsCubit
|
||||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||||
final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4;
|
final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4;
|
||||||
final DnsProviderApi dnsProviderApi =
|
final DnsProviderApi dnsProviderApi =
|
||||||
dnsProviderApiFactory!.getDnsProvider();
|
ApiController.currentDnsProviderApiFactory!.getDnsProvider();
|
||||||
await dnsProviderApi.removeSimilarRecords(domain: domain!);
|
await dnsProviderApi.removeSimilarRecords(domain: domain!);
|
||||||
await dnsProviderApi.createMultipleDnsRecords(
|
await dnsProviderApi.createMultipleDnsRecords(
|
||||||
domain: domain,
|
domain: domain,
|
||||||
|
|
|
@ -7,15 +7,10 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
|
|
||||||
class DnsProviderFormCubit extends FormCubit {
|
class DnsProviderFormCubit extends FormCubit {
|
||||||
DnsProviderFormCubit(this.initializingCubit) {
|
DnsProviderFormCubit(this.initializingCubit) {
|
||||||
final RegExp regExp = initializingCubit.getDnsProviderApiTokenValidation();
|
|
||||||
apiKey = FieldCubit(
|
apiKey = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
|
||||||
regExp.hasMatch,
|
|
||||||
'validations.invalid_format'.tr(),
|
|
||||||
),
|
|
||||||
LengthStringNotEqualValidation(40)
|
LengthStringNotEqualValidation(40)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
|
||||||
|
@ -9,8 +10,7 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
emit(Loading(LoadingTypes.loadingDomain));
|
emit(Loading(LoadingTypes.loadingDomain));
|
||||||
final List<String> list = await serverInstallationCubit
|
final List<String> list = await ApiController.currentDnsProviderApiFactory!
|
||||||
.repository.dnsProviderApiFactory!
|
|
||||||
.getDnsProvider()
|
.getDnsProvider()
|
||||||
.domainList();
|
.domainList();
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
|
@ -31,8 +31,7 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
||||||
|
|
||||||
emit(Loading(LoadingTypes.saving));
|
emit(Loading(LoadingTypes.saving));
|
||||||
|
|
||||||
final String? zoneId = await serverInstallationCubit
|
final String? zoneId = await ApiController.currentDnsProviderApiFactory!
|
||||||
.repository.dnsProviderApiFactory!
|
|
||||||
.getDnsProvider()
|
.getDnsProvider()
|
||||||
.getZoneId(domainName);
|
.getZoneId(domainName);
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,16 @@ import 'dart:async';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
|
||||||
|
|
||||||
class ProviderFormCubit extends FormCubit {
|
class ProviderFormCubit extends FormCubit {
|
||||||
ProviderFormCubit(this.serverInstallationCubit) {
|
ProviderFormCubit(this.serverInstallationCubit) {
|
||||||
final RegExp regExp =
|
//final int tokenLength =
|
||||||
serverInstallationCubit.getServerProviderApiTokenValidation();
|
// serverInstallationCubit.serverProviderApiTokenValidation().length;
|
||||||
apiKey = FieldCubit(
|
apiKey = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
//LengthStringNotEqualValidation(tokenLength),
|
||||||
regExp.hasMatch,
|
|
||||||
'validations.invalid_format'.tr(),
|
|
||||||
),
|
|
||||||
LengthStringNotEqualValidation(64)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -26,7 +21,7 @@ class ProviderFormCubit extends FormCubit {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> onSubmit() async {
|
FutureOr<void> onSubmit() async {
|
||||||
serverInstallationCubit.setHetznerKey(apiKey.state.value);
|
serverInstallationCubit.setServerProviderKey(apiKey.state.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ServerInstallationCubit serverInstallationCubit;
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
@ -45,7 +40,7 @@ class ProviderFormCubit extends FormCubit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isKeyValid) {
|
if (!isKeyValid) {
|
||||||
apiKey.setError('initializing.hetzner_bad_key_error'.tr());
|
apiKey.setError('initializing.provider_bad_key_error'.tr());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart';
|
|
||||||
|
|
||||||
class MetricsLoadException implements Exception {
|
|
||||||
MetricsLoadException(this.message);
|
|
||||||
final String message;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HetznerMetricsRepository {
|
|
||||||
Future<HetznerMetricsLoaded> getMetrics(final Period period) async {
|
|
||||||
final DateTime end = DateTime.now();
|
|
||||||
DateTime start;
|
|
||||||
|
|
||||||
switch (period) {
|
|
||||||
case Period.hour:
|
|
||||||
start = end.subtract(const Duration(hours: 1));
|
|
||||||
break;
|
|
||||||
case Period.day:
|
|
||||||
start = end.subtract(const Duration(days: 1));
|
|
||||||
break;
|
|
||||||
case Period.month:
|
|
||||||
start = end.subtract(const Duration(days: 15));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final HetznerApi api = HetznerApi(hasLogger: false);
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> results = await Future.wait([
|
|
||||||
api.getMetrics(start, end, 'cpu'),
|
|
||||||
api.getMetrics(start, end, 'network'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
final cpuMetricsData = results[0]['metrics'];
|
|
||||||
final networkMetricsData = results[1]['metrics'];
|
|
||||||
|
|
||||||
if (cpuMetricsData == null || networkMetricsData == null) {
|
|
||||||
throw MetricsLoadException('Metrics data is null');
|
|
||||||
}
|
|
||||||
|
|
||||||
return HetznerMetricsLoaded(
|
|
||||||
period: period,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
stepInSeconds: cpuMetricsData['step'],
|
|
||||||
cpu: timeSeriesSerializer(cpuMetricsData, 'cpu'),
|
|
||||||
ppsIn: timeSeriesSerializer(networkMetricsData, 'network.0.pps.in'),
|
|
||||||
ppsOut: timeSeriesSerializer(networkMetricsData, 'network.0.pps.out'),
|
|
||||||
bandwidthIn:
|
|
||||||
timeSeriesSerializer(networkMetricsData, 'network.0.bandwidth.in'),
|
|
||||||
bandwidthOut: timeSeriesSerializer(
|
|
||||||
networkMetricsData,
|
|
||||||
'network.0.bandwidth.out',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TimeSeriesData> timeSeriesSerializer(
|
|
||||||
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();
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
part of 'hetzner_metrics_cubit.dart';
|
|
||||||
|
|
||||||
abstract class HetznerMetricsState extends Equatable {
|
|
||||||
const HetznerMetricsState();
|
|
||||||
|
|
||||||
abstract final Period period;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HetznerMetricsLoading extends HetznerMetricsState {
|
|
||||||
const HetznerMetricsLoading(this.period);
|
|
||||||
@override
|
|
||||||
final Period period;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [period];
|
|
||||||
}
|
|
||||||
|
|
||||||
class HetznerMetricsLoaded extends HetznerMetricsState {
|
|
||||||
const HetznerMetricsLoaded({
|
|
||||||
required this.period,
|
|
||||||
required this.start,
|
|
||||||
required this.end,
|
|
||||||
required this.stepInSeconds,
|
|
||||||
required this.cpu,
|
|
||||||
required this.ppsIn,
|
|
||||||
required this.ppsOut,
|
|
||||||
required this.bandwidthIn,
|
|
||||||
required this.bandwidthOut,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
final Period period;
|
|
||||||
final DateTime start;
|
|
||||||
final DateTime end;
|
|
||||||
final num stepInSeconds;
|
|
||||||
|
|
||||||
final List<TimeSeriesData> cpu;
|
|
||||||
final List<TimeSeriesData> ppsIn;
|
|
||||||
final List<TimeSeriesData> ppsOut;
|
|
||||||
final List<TimeSeriesData> bandwidthIn;
|
|
||||||
final List<TimeSeriesData> bandwidthOut;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [period, start, end];
|
|
||||||
}
|
|
|
@ -3,16 +3,16 @@ import 'dart:async';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart';
|
import 'package:selfprivacy/logic/cubit/metrics/metrics_repository.dart';
|
||||||
|
|
||||||
part 'hetzner_metrics_state.dart';
|
part 'metrics_state.dart';
|
||||||
|
|
||||||
class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
class MetricsCubit extends Cubit<MetricsState> {
|
||||||
HetznerMetricsCubit() : super(const HetznerMetricsLoading(Period.day));
|
MetricsCubit() : super(const MetricsLoading(Period.day));
|
||||||
|
|
||||||
final HetznerMetricsRepository repository = HetznerMetricsRepository();
|
final MetricsRepository repository = MetricsRepository();
|
||||||
|
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
||||||
|
|
||||||
void changePeriod(final Period period) async {
|
void changePeriod(final Period period) async {
|
||||||
closeTimer();
|
closeTimer();
|
||||||
emit(HetznerMetricsLoading(period));
|
emit(MetricsLoading(period));
|
||||||
load(period);
|
load(period);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +40,14 @@ class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
||||||
|
|
||||||
void load(final Period period) async {
|
void load(final Period period) async {
|
||||||
try {
|
try {
|
||||||
final HetznerMetricsLoaded newState = await repository.getMetrics(period);
|
final MetricsLoaded newState = await repository.getMetrics(period);
|
||||||
timer = Timer(
|
timer = Timer(
|
||||||
Duration(seconds: newState.stepInSeconds.toInt()),
|
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
|
||||||
() => load(newState.period),
|
() => load(newState.period),
|
||||||
);
|
);
|
||||||
emit(newState);
|
emit(newState);
|
||||||
} on StateError {
|
} on StateError {
|
||||||
print('Tried to emit Hetzner metrics when cubit is closed');
|
print('Tried to emit metrics when cubit is closed');
|
||||||
} on MetricsLoadException {
|
} on MetricsLoadException {
|
||||||
timer = Timer(
|
timer = Timer(
|
||||||
Duration(seconds: state.period.stepPeriodInSeconds),
|
Duration(seconds: state.period.stepPeriodInSeconds),
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
|
||||||
|
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
|
||||||
|
class MetricsLoadException implements Exception {
|
||||||
|
MetricsLoadException(this.message);
|
||||||
|
final String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsRepository {
|
||||||
|
Future<MetricsLoaded> getMetrics(final Period period) async {
|
||||||
|
final providerApiFactory = ApiController.currentServerProviderApiFactory;
|
||||||
|
if (providerApiFactory == null) {
|
||||||
|
throw MetricsLoadException('Server Provider data is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime end = DateTime.now();
|
||||||
|
DateTime start;
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case Period.hour:
|
||||||
|
start = end.subtract(const Duration(hours: 1));
|
||||||
|
break;
|
||||||
|
case Period.day:
|
||||||
|
start = end.subtract(const Duration(days: 1));
|
||||||
|
break;
|
||||||
|
case Period.month:
|
||||||
|
start = end.subtract(const Duration(days: 15));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final serverId = getIt<ApiConfigModel>().serverDetails!.id;
|
||||||
|
final ServerMetrics? metrics =
|
||||||
|
await providerApiFactory.getServerProvider().getMetrics(
|
||||||
|
serverId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (metrics == null) {
|
||||||
|
throw MetricsLoadException('Metrics data is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetricsLoaded(
|
||||||
|
period: period,
|
||||||
|
metrics: metrics,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
part of 'metrics_cubit.dart';
|
||||||
|
|
||||||
|
abstract class MetricsState extends Equatable {
|
||||||
|
const MetricsState();
|
||||||
|
|
||||||
|
abstract final Period period;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsLoading extends MetricsState {
|
||||||
|
const MetricsLoading(this.period);
|
||||||
|
@override
|
||||||
|
final Period period;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [period];
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsLoaded extends MetricsState {
|
||||||
|
const MetricsLoaded({
|
||||||
|
required this.period,
|
||||||
|
required this.metrics,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Period period;
|
||||||
|
|
||||||
|
final ServerMetrics metrics;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [period, metrics];
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
|
|
||||||
part 'provider_volume_state.dart';
|
part 'provider_volume_state.dart';
|
||||||
|
|
||||||
|
@ -14,26 +15,19 @@ class ApiProviderVolumeCubit
|
||||||
extends ServerInstallationDependendCubit<ApiProviderVolumeState> {
|
extends ServerInstallationDependendCubit<ApiProviderVolumeState> {
|
||||||
ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit)
|
ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit)
|
||||||
: super(serverInstallationCubit, const ApiProviderVolumeState.initial());
|
: super(serverInstallationCubit, const ApiProviderVolumeState.initial());
|
||||||
|
|
||||||
VolumeProviderApiFactory? providerApi;
|
|
||||||
|
|
||||||
final ServerApi serverApi = ServerApi();
|
final ServerApi serverApi = ServerApi();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||||
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
|
||||||
providerApi = serverDetails == null
|
|
||||||
? null
|
|
||||||
: VolumeApiFactoryCreator.createVolumeProviderApiFactory(
|
|
||||||
getIt<ApiConfigModel>().serverDetails!.provider,
|
|
||||||
);
|
|
||||||
_refetch();
|
_refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<double?> getPricePerGb() async =>
|
Future<Price?> getPricePerGb() async =>
|
||||||
providerApi!.getVolumeProvider().getPricePerGb();
|
ApiController.currentVolumeProviderApiFactory!
|
||||||
|
.getVolumeProvider()
|
||||||
|
.getPricePerGb();
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
||||||
|
@ -41,12 +35,14 @@ class ApiProviderVolumeCubit
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refetch() async {
|
Future<void> _refetch() async {
|
||||||
if (providerApi == null) {
|
if (ApiController.currentVolumeProviderApiFactory == null) {
|
||||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<ServerVolume> volumes =
|
final List<ServerVolume> volumes = await ApiController
|
||||||
await providerApi!.getVolumeProvider().getVolumes();
|
.currentVolumeProviderApiFactory!
|
||||||
|
.getVolumeProvider()
|
||||||
|
.getVolumes();
|
||||||
|
|
||||||
if (volumes.isEmpty) {
|
if (volumes.isEmpty) {
|
||||||
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
|
||||||
|
@ -57,31 +53,33 @@ class ApiProviderVolumeCubit
|
||||||
|
|
||||||
Future<void> attachVolume(final DiskVolume volume) async {
|
Future<void> attachVolume(final DiskVolume volume) async {
|
||||||
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
|
||||||
await providerApi!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.attachVolume(volume.providerVolume!.id, server.id);
|
.attachVolume(volume.providerVolume!, server.id);
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> detachVolume(final DiskVolume volume) async {
|
Future<void> detachVolume(final DiskVolume volume) async {
|
||||||
await providerApi!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.detachVolume(volume.providerVolume!.id);
|
.detachVolume(volume.providerVolume!);
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> resizeVolume(
|
Future<bool> resizeVolume(
|
||||||
final DiskVolume volume,
|
final DiskVolume volume,
|
||||||
final int newSizeGb,
|
final DiskSize newSize,
|
||||||
final Function() callback,
|
final Function() callback,
|
||||||
) async {
|
) async {
|
||||||
getIt<NavigationService>().showSnackBar(
|
getIt<NavigationService>().showSnackBar(
|
||||||
'Starting resize',
|
'Starting resize',
|
||||||
);
|
);
|
||||||
emit(state.copyWith(isResizing: true));
|
emit(state.copyWith(isResizing: true));
|
||||||
final bool resized = await providerApi!.getVolumeProvider().resizeVolume(
|
final bool resized = await ApiController.currentVolumeProviderApiFactory!
|
||||||
volume.providerVolume!.id,
|
.getVolumeProvider()
|
||||||
newSizeGb,
|
.resizeVolume(
|
||||||
|
volume.providerVolume!,
|
||||||
|
newSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!resized) {
|
if (!resized) {
|
||||||
|
@ -93,13 +91,13 @@ class ApiProviderVolumeCubit
|
||||||
}
|
}
|
||||||
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
getIt<NavigationService>().showSnackBar(
|
||||||
'Hetzner resized, waiting 10 seconds',
|
'Provider volume resized, waiting 10 seconds',
|
||||||
);
|
);
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
|
||||||
await ServerApi().resizeVolume(volume.name);
|
await ServerApi().resizeVolume(volume.name);
|
||||||
getIt<NavigationService>().showSnackBar(
|
getIt<NavigationService>().showSnackBar(
|
||||||
'Server api resized, waiting 20 seconds',
|
'Server volume resized, waiting 20 seconds',
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 20));
|
await Future.delayed(const Duration(seconds: 20));
|
||||||
|
@ -115,8 +113,10 @@ class ApiProviderVolumeCubit
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createVolume() async {
|
Future<void> createVolume() async {
|
||||||
final ServerVolume? volume =
|
final ServerVolume? volume = await ApiController
|
||||||
await providerApi!.getVolumeProvider().createVolume();
|
.currentVolumeProviderApiFactory!
|
||||||
|
.getVolumeProvider()
|
||||||
|
.createVolume();
|
||||||
|
|
||||||
final diskVolume = DiskVolume(providerVolume: volume);
|
final diskVolume = DiskVolume(providerVolume: volume);
|
||||||
await attachVolume(diskVolume);
|
await attachVolume(diskVolume);
|
||||||
|
@ -128,9 +128,9 @@ class ApiProviderVolumeCubit
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteVolume(final DiskVolume volume) async {
|
Future<void> deleteVolume(final DiskVolume volume) async {
|
||||||
await providerApi!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.deleteVolume(volume.providerVolume!.id);
|
.deleteVolume(volume.providerVolume!);
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart';
|
||||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||||
|
|
||||||
part 'server_detailed_info_state.dart';
|
part 'server_detailed_info_state.dart';
|
||||||
|
@ -22,7 +22,7 @@ class ServerDetailsCubit
|
||||||
final ServerDetailsRepositoryDto data = await repository.load();
|
final ServerDetailsRepositoryDto data = await repository.load();
|
||||||
emit(
|
emit(
|
||||||
Loaded(
|
Loaded(
|
||||||
serverInfo: data.hetznerServerInfo,
|
metadata: data.metadata,
|
||||||
autoUpgradeSettings: data.autoUpgradeSettings,
|
autoUpgradeSettings: data.autoUpgradeSettings,
|
||||||
serverTimezone: data.serverTimezone,
|
serverTimezone: data.serverTimezone,
|
||||||
checkTime: DateTime.now(),
|
checkTime: DateTime.now(),
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
import 'package:selfprivacy/logic/models/server_metadata.dart';
|
||||||
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
import 'package:selfprivacy/logic/models/timezone_settings.dart';
|
||||||
|
|
||||||
class ServerDetailsRepository {
|
class ServerDetailsRepository {
|
||||||
HetznerApi hetzner = HetznerApi();
|
|
||||||
ServerApi server = ServerApi();
|
ServerApi server = ServerApi();
|
||||||
|
|
||||||
Future<ServerDetailsRepositoryDto> load() async {
|
Future<ServerDetailsRepositoryDto> load() async {
|
||||||
|
final serverProviderApi = ApiController.currentServerProviderApiFactory;
|
||||||
final settings = await server.getSystemSettings();
|
final settings = await server.getSystemSettings();
|
||||||
|
final serverId = getIt<ApiConfigModel>().serverDetails!.id;
|
||||||
|
final metadata =
|
||||||
|
await serverProviderApi!.getServerProvider().getMetadata(serverId);
|
||||||
|
|
||||||
return ServerDetailsRepositoryDto(
|
return ServerDetailsRepositoryDto(
|
||||||
autoUpgradeSettings: settings.autoUpgradeSettings,
|
autoUpgradeSettings: settings.autoUpgradeSettings,
|
||||||
hetznerServerInfo: await hetzner.getInfo(),
|
metadata: metadata,
|
||||||
serverTimezone: TimeZoneSettings.fromString(
|
serverTimezone: TimeZoneSettings.fromString(
|
||||||
settings.timezone,
|
settings.timezone,
|
||||||
),
|
),
|
||||||
|
@ -36,13 +41,11 @@ class ServerDetailsRepository {
|
||||||
|
|
||||||
class ServerDetailsRepositoryDto {
|
class ServerDetailsRepositoryDto {
|
||||||
ServerDetailsRepositoryDto({
|
ServerDetailsRepositoryDto({
|
||||||
required this.hetznerServerInfo,
|
required this.metadata,
|
||||||
required this.serverTimezone,
|
required this.serverTimezone,
|
||||||
required this.autoUpgradeSettings,
|
required this.autoUpgradeSettings,
|
||||||
});
|
});
|
||||||
final HetznerServerInfo hetznerServerInfo;
|
final List<ServerMetadataEntity> metadata;
|
||||||
|
|
||||||
final TimeZoneSettings serverTimezone;
|
final TimeZoneSettings serverTimezone;
|
||||||
|
|
||||||
final AutoUpgradeSettings autoUpgradeSettings;
|
final AutoUpgradeSettings autoUpgradeSettings;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,19 @@ class Loading extends ServerDetailsState {}
|
||||||
|
|
||||||
class Loaded extends ServerDetailsState {
|
class Loaded extends ServerDetailsState {
|
||||||
const Loaded({
|
const Loaded({
|
||||||
required this.serverInfo,
|
required this.metadata,
|
||||||
required this.serverTimezone,
|
required this.serverTimezone,
|
||||||
required this.autoUpgradeSettings,
|
required this.autoUpgradeSettings,
|
||||||
required this.checkTime,
|
required this.checkTime,
|
||||||
});
|
});
|
||||||
final HetznerServerInfo serverInfo;
|
final List<ServerMetadataEntity> metadata;
|
||||||
|
|
||||||
final TimeZoneSettings serverTimezone;
|
final TimeZoneSettings serverTimezone;
|
||||||
|
|
||||||
final AutoUpgradeSettings autoUpgradeSettings;
|
final AutoUpgradeSettings autoUpgradeSettings;
|
||||||
final DateTime checkTime;
|
final DateTime checkTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [
|
List<Object> get props => [
|
||||||
serverInfo,
|
metadata,
|
||||||
serverTimezone,
|
serverTimezone,
|
||||||
autoUpgradeSettings,
|
autoUpgradeSettings,
|
||||||
checkTime,
|
checkTime,
|
||||||
|
|
|
@ -4,8 +4,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.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_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
@ -13,6 +17,8 @@ import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -51,40 +57,85 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegExp getServerProviderApiTokenValidation() =>
|
void setServerProviderType(final ServerProvider providerType) async {
|
||||||
repository.serverProviderApiFactory!
|
await repository.saveServerProviderType(providerType);
|
||||||
|
ApiController.initServerProviderApiFactory(
|
||||||
|
ServerProviderApiFactorySettings(
|
||||||
|
provider: providerType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProviderApiTokenValidation serverProviderApiTokenValidation() =>
|
||||||
|
ApiController.currentServerProviderApiFactory!
|
||||||
.getServerProvider()
|
.getServerProvider()
|
||||||
.getApiTokenValidation();
|
.getApiTokenValidation();
|
||||||
|
|
||||||
RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory!
|
RegExp getDnsProviderApiTokenValidation() =>
|
||||||
.getDnsProvider()
|
ApiController.currentDnsProviderApiFactory!
|
||||||
.getApiTokenValidation();
|
.getDnsProvider()
|
||||||
|
.getApiTokenValidation();
|
||||||
|
|
||||||
Future<bool> isServerProviderApiTokenValid(
|
Future<bool> isServerProviderApiTokenValid(
|
||||||
final String providerToken,
|
final String providerToken,
|
||||||
) async =>
|
) async =>
|
||||||
repository.serverProviderApiFactory!
|
ApiController.currentServerProviderApiFactory!
|
||||||
.getServerProvider(
|
.getServerProvider(
|
||||||
settings: const ProviderApiSettings(isWithToken: false),
|
settings: const ServerProviderApiSettings(
|
||||||
|
isWithToken: false,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.isApiTokenValid(providerToken);
|
.isApiTokenValid(providerToken);
|
||||||
|
|
||||||
Future<bool> isDnsProviderApiTokenValid(
|
Future<bool> isDnsProviderApiTokenValid(
|
||||||
final String providerToken,
|
final String providerToken,
|
||||||
) async =>
|
) async {
|
||||||
repository.dnsProviderApiFactory!
|
if (ApiController.currentDnsProviderApiFactory == null) {
|
||||||
.getDnsProvider(
|
// No other DNS provider is supported for now,
|
||||||
settings: const DnsProviderApiSettings(isWithToken: false),
|
// so it's safe to hardcode Cloudflare
|
||||||
)
|
ApiController.initDnsProviderApiFactory(
|
||||||
.isApiTokenValid(providerToken);
|
DnsProviderApiFactorySettings(
|
||||||
|
provider: DnsProvider.cloudflare,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void setHetznerKey(final String hetznerKey) async {
|
return ApiController.currentDnsProviderApiFactory!
|
||||||
await repository.saveHetznerKey(hetznerKey);
|
.getDnsProvider(
|
||||||
|
settings: const DnsProviderApiSettings(isWithToken: false),
|
||||||
|
)
|
||||||
|
.isApiTokenValid(providerToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ServerProviderLocation>> fetchAvailableLocations() async {
|
||||||
|
if (ApiController.currentServerProviderApiFactory == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiController.currentServerProviderApiFactory!
|
||||||
|
.getServerProvider()
|
||||||
|
.getAvailableLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ServerType>> fetchAvailableTypesByLocation(
|
||||||
|
final ServerProviderLocation location,
|
||||||
|
) async {
|
||||||
|
if (ApiController.currentServerProviderApiFactory == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiController.currentServerProviderApiFactory!
|
||||||
|
.getServerProvider()
|
||||||
|
.getServerTypesByLocation(location: location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setServerProviderKey(final String serverProviderKey) async {
|
||||||
|
await repository.saveServerProviderKey(serverProviderKey);
|
||||||
|
|
||||||
if (state is ServerInstallationRecovery) {
|
if (state is ServerInstallationRecovery) {
|
||||||
emit(
|
emit(
|
||||||
(state as ServerInstallationRecovery).copyWith(
|
(state as ServerInstallationRecovery).copyWith(
|
||||||
providerApiToken: hetznerKey,
|
providerApiToken: serverProviderKey,
|
||||||
currentStep: RecoveryStep.serverSelection,
|
currentStep: RecoveryStep.serverSelection,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -93,7 +144,33 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
(state as ServerInstallationNotFinished).copyWith(
|
(state as ServerInstallationNotFinished).copyWith(
|
||||||
providerApiToken: hetznerKey,
|
providerApiToken: serverProviderKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setServerType(final ServerType serverType) async {
|
||||||
|
await repository.saveServerType(serverType);
|
||||||
|
|
||||||
|
ApiController.initServerProviderApiFactory(
|
||||||
|
ServerProviderApiFactorySettings(
|
||||||
|
provider: getIt<ApiConfigModel>().serverProvider!,
|
||||||
|
location: serverType.location.identifier,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// All server providers support volumes for now,
|
||||||
|
// so it's safe to initialize.
|
||||||
|
ApiController.initVolumeProviderApiFactory(
|
||||||
|
ServerProviderApiFactorySettings(
|
||||||
|
provider: getIt<ApiConfigModel>().serverProvider!,
|
||||||
|
location: serverType.location.identifier,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
(state as ServerInstallationNotFinished).copyWith(
|
||||||
|
serverTypeIdentificator: serverType.identifier,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -104,6 +181,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await repository.saveCloudFlareKey(cloudFlareKey);
|
await repository.saveCloudFlareKey(cloudFlareKey);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
(state as ServerInstallationNotFinished)
|
(state as ServerInstallationNotFinished)
|
||||||
.copyWith(cloudFlareKey: cloudFlareKey),
|
.copyWith(cloudFlareKey: cloudFlareKey),
|
||||||
|
@ -248,14 +326,13 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
timer = Timer(pauseDuration, () async {
|
timer = Timer(pauseDuration, () async {
|
||||||
final ServerHostingDetails hetznerServerDetails =
|
final ServerHostingDetails serverDetails = await repository.restart();
|
||||||
await repository.restart();
|
|
||||||
await repository.saveIsServerResetedFirstTime(true);
|
await repository.saveIsServerResetedFirstTime(true);
|
||||||
await repository.saveServerDetails(hetznerServerDetails);
|
await repository.saveServerDetails(serverDetails);
|
||||||
|
|
||||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||||
isServerResetedFirstTime: true,
|
isServerResetedFirstTime: true,
|
||||||
serverDetails: hetznerServerDetails,
|
serverDetails: serverDetails,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -290,14 +367,13 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
timer = Timer(pauseDuration, () async {
|
timer = Timer(pauseDuration, () async {
|
||||||
final ServerHostingDetails hetznerServerDetails =
|
final ServerHostingDetails serverDetails = await repository.restart();
|
||||||
await repository.restart();
|
|
||||||
await repository.saveIsServerResetedSecondTime(true);
|
await repository.saveIsServerResetedSecondTime(true);
|
||||||
await repository.saveServerDetails(hetznerServerDetails);
|
await repository.saveServerDetails(serverDetails);
|
||||||
|
|
||||||
final ServerInstallationNotFinished newState = dataState.copyWith(
|
final ServerInstallationNotFinished newState = dataState.copyWith(
|
||||||
isServerResetedSecondTime: true,
|
isServerResetedSecondTime: true,
|
||||||
serverDetails: hetznerServerDetails,
|
serverDetails: serverDetails,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -423,11 +499,21 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
token,
|
token,
|
||||||
dataState.recoveryCapabilities,
|
dataState.recoveryCapabilities,
|
||||||
);
|
);
|
||||||
|
final ServerProvider provider = await ServerApi(
|
||||||
|
customToken: serverDetails.apiToken,
|
||||||
|
isWithToken: true,
|
||||||
|
).getServerProviderType();
|
||||||
|
if (provider == ServerProvider.unknown) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('recovering.generic_error'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
await repository.saveServerDetails(serverDetails);
|
await repository.saveServerDetails(serverDetails);
|
||||||
|
setServerProviderType(provider);
|
||||||
emit(
|
emit(
|
||||||
dataState.copyWith(
|
dataState.copyWith(
|
||||||
serverDetails: serverDetails,
|
serverDetails: serverDetails,
|
||||||
currentStep: RecoveryStep.hetznerToken,
|
currentStep: RecoveryStep.serverProviderToken,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on ServerAuthorizationException {
|
} on ServerAuthorizationException {
|
||||||
|
@ -503,8 +589,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ServerBasicInfoWithValidators>>
|
Future<List<ServerBasicInfoWithValidators>> getAvailableServers() async {
|
||||||
getServersOnHetznerAccount() async {
|
|
||||||
final ServerInstallationRecovery dataState =
|
final ServerInstallationRecovery dataState =
|
||||||
state as ServerInstallationRecovery;
|
state as ServerInstallationRecovery;
|
||||||
final List<ServerBasicInfo> servers =
|
final List<ServerBasicInfo> servers =
|
||||||
|
@ -515,7 +600,9 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
serverBasicInfo: server,
|
serverBasicInfo: server,
|
||||||
isIpValid: server.ip == dataState.serverDetails?.ip4,
|
isIpValid: server.ip == dataState.serverDetails?.ip4,
|
||||||
isReverseDnsValid:
|
isReverseDnsValid:
|
||||||
server.reverseDns == dataState.serverDomain?.domainName,
|
server.reverseDns == dataState.serverDomain?.domainName ||
|
||||||
|
server.reverseDns ==
|
||||||
|
dataState.serverDomain?.domainName.split('.')[0],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return validated.toList();
|
return validated.toList();
|
||||||
|
@ -533,7 +620,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
id: server.id,
|
id: server.id,
|
||||||
createTime: server.created,
|
createTime: server.created,
|
||||||
volume: ServerVolume(
|
volume: ServerVolume(
|
||||||
id: server.volumeId,
|
id: 0,
|
||||||
name: 'recovered_volume',
|
name: 'recovered_volume',
|
||||||
sizeByte: 0,
|
sizeByte: 0,
|
||||||
serverId: server.id,
|
serverId: server.id,
|
||||||
|
@ -612,7 +699,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
|
|
||||||
void clearAppConfig() {
|
void clearAppConfig() {
|
||||||
closeTimer();
|
closeTimer();
|
||||||
|
ApiController.clearProviderApiFactories();
|
||||||
repository.clearAppConfig();
|
repository.clearAppConfig();
|
||||||
emit(const ServerInstallationEmpty());
|
emit(const ServerInstallationEmpty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:pub_semver/pub_semver.dart';
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.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_details.dart';
|
||||||
|
@ -24,8 +24,8 @@ import 'package:selfprivacy/logic/models/json/device_token.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/message.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
|
|
||||||
class IpNotFoundException implements Exception {
|
class IpNotFoundException implements Exception {
|
||||||
|
@ -41,41 +41,52 @@ class ServerAuthorizationException implements Exception {
|
||||||
class ServerInstallationRepository {
|
class ServerInstallationRepository {
|
||||||
Box box = Hive.box(BNames.serverInstallationBox);
|
Box box = Hive.box(BNames.serverInstallationBox);
|
||||||
Box<User> usersBox = Hive.box(BNames.usersBox);
|
Box<User> usersBox = Hive.box(BNames.usersBox);
|
||||||
ServerProviderApiFactory? serverProviderApiFactory =
|
|
||||||
ApiFactoryCreator.createServerProviderApiFactory(
|
|
||||||
ServerProvider.hetzner, // TODO: HARDCODE FOR NOW!!!
|
|
||||||
); // TODO: Remove when provider selection is implemented.
|
|
||||||
DnsProviderApiFactory? dnsProviderApiFactory =
|
|
||||||
ApiFactoryCreator.createDnsProviderApiFactory(
|
|
||||||
DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!!
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<ServerInstallationState> load() async {
|
Future<ServerInstallationState> load() async {
|
||||||
final String? providerApiToken = getIt<ApiConfigModel>().hetznerKey;
|
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey;
|
||||||
|
final String? location = getIt<ApiConfigModel>().serverLocation;
|
||||||
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
|
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
|
||||||
|
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType;
|
||||||
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
||||||
|
final ServerProvider? serverProvider =
|
||||||
|
getIt<ApiConfigModel>().serverProvider;
|
||||||
final BackblazeCredential? backblazeCredential =
|
final BackblazeCredential? backblazeCredential =
|
||||||
getIt<ApiConfigModel>().backblazeCredential;
|
getIt<ApiConfigModel>().backblazeCredential;
|
||||||
final ServerHostingDetails? serverDetails =
|
final ServerHostingDetails? serverDetails =
|
||||||
getIt<ApiConfigModel>().serverDetails;
|
getIt<ApiConfigModel>().serverDetails;
|
||||||
|
|
||||||
if (serverDetails != null &&
|
if (serverProvider != null ||
|
||||||
serverDetails.provider != ServerProvider.unknown) {
|
(serverDetails != null &&
|
||||||
serverProviderApiFactory =
|
serverDetails.provider != ServerProvider.unknown)) {
|
||||||
ApiFactoryCreator.createServerProviderApiFactory(
|
ApiController.initServerProviderApiFactory(
|
||||||
serverDetails.provider,
|
ServerProviderApiFactorySettings(
|
||||||
|
provider: serverProvider ?? serverDetails!.provider,
|
||||||
|
location: location,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// All current providers support volumes
|
||||||
|
// so it's safe to hardcode for now
|
||||||
|
ApiController.initVolumeProviderApiFactory(
|
||||||
|
ServerProviderApiFactorySettings(
|
||||||
|
provider: serverProvider ?? serverDetails!.provider,
|
||||||
|
location: location,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDomain != null && serverDomain.provider != DnsProvider.unknown) {
|
if (serverDomain != null && serverDomain.provider != DnsProvider.unknown) {
|
||||||
dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory(
|
ApiController.initDnsProviderApiFactory(
|
||||||
serverDomain.provider,
|
DnsProviderApiFactorySettings(
|
||||||
|
provider: serverDomain.provider,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
||||||
return ServerInstallationFinished(
|
return ServerInstallationFinished(
|
||||||
providerApiToken: providerApiToken!,
|
providerApiToken: providerApiToken!,
|
||||||
|
serverTypeIdentificator: serverTypeIdentificator ?? '',
|
||||||
cloudFlareKey: cloudflareToken!,
|
cloudFlareKey: cloudflareToken!,
|
||||||
serverDomain: serverDomain!,
|
serverDomain: serverDomain!,
|
||||||
backblazeCredential: backblazeCredential!,
|
backblazeCredential: backblazeCredential!,
|
||||||
|
@ -126,13 +137,13 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
RecoveryStep _getCurrentRecoveryStep(
|
RecoveryStep _getCurrentRecoveryStep(
|
||||||
final String? hetznerToken,
|
final String? serverProviderToken,
|
||||||
final String? cloudflareToken,
|
final String? cloudflareToken,
|
||||||
final ServerDomain serverDomain,
|
final ServerDomain serverDomain,
|
||||||
final ServerHostingDetails? serverDetails,
|
final ServerHostingDetails? serverDetails,
|
||||||
) {
|
) {
|
||||||
if (serverDetails != null) {
|
if (serverDetails != null) {
|
||||||
if (hetznerToken != null) {
|
if (serverProviderToken != null) {
|
||||||
if (serverDetails.provider != ServerProvider.unknown) {
|
if (serverDetails.provider != ServerProvider.unknown) {
|
||||||
if (serverDomain.provider != DnsProvider.unknown) {
|
if (serverDomain.provider != DnsProvider.unknown) {
|
||||||
return RecoveryStep.backblazeToken;
|
return RecoveryStep.backblazeToken;
|
||||||
|
@ -141,7 +152,7 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
return RecoveryStep.serverSelection;
|
return RecoveryStep.serverSelection;
|
||||||
}
|
}
|
||||||
return RecoveryStep.hetznerToken;
|
return RecoveryStep.serverProviderToken;
|
||||||
}
|
}
|
||||||
return RecoveryStep.selecting;
|
return RecoveryStep.selecting;
|
||||||
}
|
}
|
||||||
|
@ -152,18 +163,20 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ServerHostingDetails> startServer(
|
Future<ServerHostingDetails> startServer(
|
||||||
final ServerHostingDetails hetznerServer,
|
final ServerHostingDetails server,
|
||||||
) async {
|
) async {
|
||||||
ServerHostingDetails serverDetails;
|
ServerHostingDetails serverDetails;
|
||||||
|
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
serverDetails = await ApiController.currentServerProviderApiFactory!
|
||||||
serverDetails = await api.powerOn();
|
.getServerProvider()
|
||||||
|
.powerOn();
|
||||||
|
|
||||||
return serverDetails;
|
return serverDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getDomainId(final String token, final String domain) async {
|
Future<String?> getDomainId(final String token, final String domain) async {
|
||||||
final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(
|
final DnsProviderApi dnsProviderApi =
|
||||||
|
ApiController.currentDnsProviderApiFactory!.getDnsProvider(
|
||||||
settings: DnsProviderApiSettings(
|
settings: DnsProviderApiSettings(
|
||||||
isWithToken: false,
|
isWithToken: false,
|
||||||
customToken: token,
|
customToken: token,
|
||||||
|
@ -232,12 +245,14 @@ class ServerInstallationRepository {
|
||||||
required final Future<void> Function(ServerHostingDetails serverDetails)
|
required final Future<void> Function(ServerHostingDetails serverDetails)
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}) async {
|
}) async {
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
final ServerProviderApi api =
|
||||||
|
ApiController.currentServerProviderApiFactory!.getServerProvider();
|
||||||
try {
|
try {
|
||||||
final ServerHostingDetails? serverDetails = await api.createServer(
|
final ServerHostingDetails? serverDetails = await api.createServer(
|
||||||
dnsApiToken: cloudFlareKey,
|
dnsApiToken: cloudFlareKey,
|
||||||
rootUser: rootUser,
|
rootUser: rootUser,
|
||||||
domainName: domainName,
|
domainName: domainName,
|
||||||
|
serverType: getIt<ApiConfigModel>().serverType!,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (serverDetails == null) {
|
if (serverDetails == null) {
|
||||||
|
@ -248,82 +263,62 @@ class ServerInstallationRepository {
|
||||||
onSuccess(serverDetails);
|
onSuccess(serverDetails);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
if (e.response!.data['error']['code'] == 'uniqueness_error') {
|
if (e.response!.data['error']['code'] == 'uniqueness_error') {
|
||||||
final NavigationService nav = getIt.get<NavigationService>();
|
showPopUpAlert(
|
||||||
nav.showPopUpDialog(
|
alertTitle: 'modals.already_exists'.tr(),
|
||||||
BrandAlert(
|
description: 'modals.destroy_server'.tr(),
|
||||||
title: 'modals.already_exists'.tr(),
|
actionButtonTitle: 'modals.yes'.tr(),
|
||||||
contentText: 'modals.destroy_server'.tr(),
|
actionButtonOnPressed: () async {
|
||||||
actions: [
|
await api.deleteServer(
|
||||||
ActionButton(
|
domainName: domainName,
|
||||||
text: 'basis.delete'.tr(),
|
);
|
||||||
isRed: true,
|
|
||||||
onPressed: () async {
|
|
||||||
await api.deleteServer(
|
|
||||||
domainName: domainName,
|
|
||||||
);
|
|
||||||
|
|
||||||
ServerHostingDetails? serverDetails;
|
ServerHostingDetails? serverDetails;
|
||||||
try {
|
try {
|
||||||
serverDetails = await api.createServer(
|
serverDetails = await api.createServer(
|
||||||
dnsApiToken: cloudFlareKey,
|
dnsApiToken: cloudFlareKey,
|
||||||
rootUser: rootUser,
|
rootUser: rootUser,
|
||||||
domainName: domainName,
|
domainName: domainName,
|
||||||
);
|
serverType: getIt<ApiConfigModel>().serverType!,
|
||||||
} catch (e) {
|
);
|
||||||
print(e);
|
} catch (e) {
|
||||||
}
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (serverDetails == null) {
|
if (serverDetails == null) {
|
||||||
print('Server is not initialized!');
|
print('Server is not initialized!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await saveServerDetails(serverDetails);
|
await saveServerDetails(serverDetails);
|
||||||
onSuccess(serverDetails);
|
onSuccess(serverDetails);
|
||||||
},
|
},
|
||||||
),
|
cancelButtonOnPressed: onCancel,
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
onPressed: onCancel,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final NavigationService nav = getIt.get<NavigationService>();
|
showPopUpAlert(
|
||||||
nav.showPopUpDialog(
|
alertTitle: 'modals.unexpected_error'.tr(),
|
||||||
BrandAlert(
|
description: 'modals.try_again'.tr(),
|
||||||
title: 'modals.unexpected_error'.tr(),
|
actionButtonTitle: 'modals.yes'.tr(),
|
||||||
contentText: 'modals.try_again'.tr(),
|
actionButtonOnPressed: () async {
|
||||||
actions: [
|
ServerHostingDetails? serverDetails;
|
||||||
ActionButton(
|
try {
|
||||||
text: 'modals.yes'.tr(),
|
serverDetails = await api.createServer(
|
||||||
isRed: true,
|
dnsApiToken: cloudFlareKey,
|
||||||
onPressed: () async {
|
rootUser: rootUser,
|
||||||
ServerHostingDetails? serverDetails;
|
domainName: domainName,
|
||||||
try {
|
serverType: getIt<ApiConfigModel>().serverType!,
|
||||||
serverDetails = await api.createServer(
|
);
|
||||||
dnsApiToken: cloudFlareKey,
|
} catch (e) {
|
||||||
rootUser: rootUser,
|
print(e);
|
||||||
domainName: domainName,
|
}
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverDetails == null) {
|
if (serverDetails == null) {
|
||||||
print('Server is not initialized!');
|
print('Server is not initialized!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await saveServerDetails(serverDetails);
|
await saveServerDetails(serverDetails);
|
||||||
onSuccess(serverDetails);
|
onSuccess(serverDetails);
|
||||||
},
|
},
|
||||||
),
|
cancelButtonOnPressed: onCancel,
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
onPressed: onCancel,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,9 +330,9 @@ class ServerInstallationRepository {
|
||||||
required final void Function() onCancel,
|
required final void Function() onCancel,
|
||||||
}) async {
|
}) async {
|
||||||
final DnsProviderApi dnsProviderApi =
|
final DnsProviderApi dnsProviderApi =
|
||||||
dnsProviderApiFactory!.getDnsProvider();
|
ApiController.currentDnsProviderApiFactory!.getDnsProvider();
|
||||||
final ServerProviderApi serverApi =
|
final ServerProviderApi serverApi =
|
||||||
serverProviderApiFactory!.getServerProvider();
|
ApiController.currentServerProviderApiFactory!.getServerProvider();
|
||||||
|
|
||||||
await dnsProviderApi.removeSimilarRecords(
|
await dnsProviderApi.removeSimilarRecords(
|
||||||
ip4: serverDetails.ip4,
|
ip4: serverDetails.ip4,
|
||||||
|
@ -350,31 +345,19 @@ class ServerInstallationRepository {
|
||||||
domain: domain,
|
domain: domain,
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
final NavigationService nav = getIt.get<NavigationService>();
|
showPopUpAlert(
|
||||||
nav.showPopUpDialog(
|
alertTitle: e.response!.data['errors'][0]['code'] == 1038
|
||||||
BrandAlert(
|
? 'modals.you_cant_use_this_api'.tr()
|
||||||
title: e.response!.data['errors'][0]['code'] == 1038
|
: 'domain.error'.tr(),
|
||||||
? 'modals.you_cant_use_this_api'.tr()
|
description: 'modals.delete_server_volume'.tr(),
|
||||||
: 'domain.error'.tr(),
|
cancelButtonOnPressed: onCancel,
|
||||||
contentText: 'modals.delete_server_volume'.tr(),
|
actionButtonTitle: 'basis.delete'.tr(),
|
||||||
actions: [
|
actionButtonOnPressed: () async {
|
||||||
ActionButton(
|
await serverApi.deleteServer(
|
||||||
text: 'basis.delete'.tr(),
|
domainName: domain.domainName,
|
||||||
isRed: true,
|
);
|
||||||
onPressed: () async {
|
onCancel();
|
||||||
await serverApi.deleteServer(
|
},
|
||||||
domainName: domain.domainName,
|
|
||||||
);
|
|
||||||
|
|
||||||
onCancel();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
onPressed: onCancel,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -389,7 +372,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> createDkimRecord(final ServerDomain cloudFlareDomain) async {
|
Future<void> createDkimRecord(final ServerDomain cloudFlareDomain) async {
|
||||||
final DnsProviderApi dnsProviderApi =
|
final DnsProviderApi dnsProviderApi =
|
||||||
dnsProviderApiFactory!.getDnsProvider();
|
ApiController.currentDnsProviderApiFactory!.getDnsProvider();
|
||||||
final ServerApi api = ServerApi();
|
final ServerApi api = ServerApi();
|
||||||
|
|
||||||
late DnsRecord record;
|
late DnsRecord record;
|
||||||
|
@ -408,15 +391,15 @@ class ServerInstallationRepository {
|
||||||
return api.isHttpServerWorking();
|
return api.isHttpServerWorking();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ServerHostingDetails> restart() async {
|
Future<ServerHostingDetails> restart() async =>
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
ApiController.currentServerProviderApiFactory!
|
||||||
return api.restart();
|
.getServerProvider()
|
||||||
}
|
.restart();
|
||||||
|
|
||||||
Future<ServerHostingDetails> powerOn() async {
|
Future<ServerHostingDetails> powerOn() async =>
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
ApiController.currentServerProviderApiFactory!
|
||||||
return api.powerOn();
|
.getServerProvider()
|
||||||
}
|
.powerOn();
|
||||||
|
|
||||||
Future<ServerRecoveryCapabilities> getRecoveryCapabilities(
|
Future<ServerRecoveryCapabilities> getRecoveryCapabilities(
|
||||||
final ServerDomain serverDomain,
|
final ServerDomain serverDomain,
|
||||||
|
@ -651,10 +634,10 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ServerBasicInfo>> getServersOnProviderAccount() async {
|
Future<List<ServerBasicInfo>> getServersOnProviderAccount() async =>
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
ApiController.currentServerProviderApiFactory!
|
||||||
return api.getServers();
|
.getServerProvider()
|
||||||
}
|
.getServers();
|
||||||
|
|
||||||
Future<void> saveServerDetails(
|
Future<void> saveServerDetails(
|
||||||
final ServerHostingDetails serverDetails,
|
final ServerHostingDetails serverDetails,
|
||||||
|
@ -667,12 +650,24 @@ class ServerInstallationRepository {
|
||||||
getIt<ApiConfigModel>().init();
|
getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveHetznerKey(final String key) async {
|
Future<void> saveServerProviderType(final ServerProvider type) async {
|
||||||
print('saved');
|
await getIt<ApiConfigModel>().storeServerProviderType(type);
|
||||||
await getIt<ApiConfigModel>().storeHetznerKey(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteHetznerKey() async {
|
Future<void> saveServerProviderKey(final String key) async {
|
||||||
|
await getIt<ApiConfigModel>().storeServerProviderKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveServerType(final ServerType serverType) async {
|
||||||
|
await getIt<ApiConfigModel>().storeServerTypeIdentifier(
|
||||||
|
serverType.identifier,
|
||||||
|
);
|
||||||
|
await getIt<ApiConfigModel>().storeServerLocation(
|
||||||
|
serverType.location.identifier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteServerProviderKey() async {
|
||||||
await box.delete(BNames.hetznerKey);
|
await box.delete(BNames.hetznerKey);
|
||||||
getIt<ApiConfigModel>().init();
|
getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
@ -731,13 +726,11 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteServer(final ServerDomain serverDomain) async {
|
Future<void> deleteServer(final ServerDomain serverDomain) async {
|
||||||
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
|
await ApiController.currentServerProviderApiFactory!
|
||||||
final DnsProviderApi dnsProviderApi =
|
.getServerProvider()
|
||||||
dnsProviderApiFactory!.getDnsProvider();
|
.deleteServer(
|
||||||
|
domainName: serverDomain.domainName,
|
||||||
await api.deleteServer(
|
);
|
||||||
domainName: serverDomain.domainName,
|
|
||||||
);
|
|
||||||
|
|
||||||
await box.put(BNames.hasFinalChecked, false);
|
await box.put(BNames.hasFinalChecked, false);
|
||||||
await box.put(BNames.isServerStarted, false);
|
await box.put(BNames.isServerStarted, false);
|
||||||
|
@ -746,7 +739,9 @@ class ServerInstallationRepository {
|
||||||
await box.put(BNames.isLoading, false);
|
await box.put(BNames.isLoading, false);
|
||||||
await box.put(BNames.serverDetails, null);
|
await box.put(BNames.serverDetails, null);
|
||||||
|
|
||||||
await dnsProviderApi.removeSimilarRecords(domain: serverDomain);
|
await ApiController.currentDnsProviderApiFactory!
|
||||||
|
.getDnsProvider()
|
||||||
|
.removeSimilarRecords(domain: serverDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteServerRelatedRecords() async {
|
Future<void> deleteServerRelatedRecords() async {
|
||||||
|
|
|
@ -3,6 +3,7 @@ part of '../server_installation/server_installation_cubit.dart';
|
||||||
abstract class ServerInstallationState extends Equatable {
|
abstract class ServerInstallationState extends Equatable {
|
||||||
const ServerInstallationState({
|
const ServerInstallationState({
|
||||||
required this.providerApiToken,
|
required this.providerApiToken,
|
||||||
|
required this.serverTypeIdentificator,
|
||||||
required this.cloudFlareKey,
|
required this.cloudFlareKey,
|
||||||
required this.backblazeCredential,
|
required this.backblazeCredential,
|
||||||
required this.serverDomain,
|
required this.serverDomain,
|
||||||
|
@ -16,6 +17,7 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
providerApiToken,
|
providerApiToken,
|
||||||
|
serverTypeIdentificator,
|
||||||
cloudFlareKey,
|
cloudFlareKey,
|
||||||
backblazeCredential,
|
backblazeCredential,
|
||||||
serverDomain,
|
serverDomain,
|
||||||
|
@ -27,6 +29,7 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
|
|
||||||
final String? providerApiToken;
|
final String? providerApiToken;
|
||||||
final String? cloudFlareKey;
|
final String? cloudFlareKey;
|
||||||
|
final String? serverTypeIdentificator;
|
||||||
final BackblazeCredential? backblazeCredential;
|
final BackblazeCredential? backblazeCredential;
|
||||||
final ServerDomain? serverDomain;
|
final ServerDomain? serverDomain;
|
||||||
final User? rootUser;
|
final User? rootUser;
|
||||||
|
@ -35,7 +38,8 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
final bool isServerResetedFirstTime;
|
final bool isServerResetedFirstTime;
|
||||||
final bool isServerResetedSecondTime;
|
final bool isServerResetedSecondTime;
|
||||||
|
|
||||||
bool get isServerProviderFilled => providerApiToken != null;
|
bool get isServerProviderApiKeyFilled => providerApiToken != null;
|
||||||
|
bool get isServerTypeFilled => serverTypeIdentificator != null;
|
||||||
bool get isDnsProviderFilled => cloudFlareKey != null;
|
bool get isDnsProviderFilled => cloudFlareKey != null;
|
||||||
bool get isBackupsProviderFilled => backblazeCredential != null;
|
bool get isBackupsProviderFilled => backblazeCredential != null;
|
||||||
bool get isDomainSelected => serverDomain != null;
|
bool get isDomainSelected => serverDomain != null;
|
||||||
|
@ -58,7 +62,8 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
|
|
||||||
List<bool?> get _fulfilementList {
|
List<bool?> get _fulfilementList {
|
||||||
final List<bool> res = [
|
final List<bool> res = [
|
||||||
isServerProviderFilled,
|
isServerProviderApiKeyFilled,
|
||||||
|
isServerTypeFilled,
|
||||||
isDnsProviderFilled,
|
isDnsProviderFilled,
|
||||||
isBackupsProviderFilled,
|
isBackupsProviderFilled,
|
||||||
isDomainSelected,
|
isDomainSelected,
|
||||||
|
@ -81,6 +86,7 @@ class TimerState extends ServerInstallationNotFinished {
|
||||||
this.duration,
|
this.duration,
|
||||||
}) : super(
|
}) : super(
|
||||||
providerApiToken: dataState.providerApiToken,
|
providerApiToken: dataState.providerApiToken,
|
||||||
|
serverTypeIdentificator: dataState.serverTypeIdentificator,
|
||||||
cloudFlareKey: dataState.cloudFlareKey,
|
cloudFlareKey: dataState.cloudFlareKey,
|
||||||
backblazeCredential: dataState.backblazeCredential,
|
backblazeCredential: dataState.backblazeCredential,
|
||||||
serverDomain: dataState.serverDomain,
|
serverDomain: dataState.serverDomain,
|
||||||
|
@ -106,7 +112,8 @@ class TimerState extends ServerInstallationNotFinished {
|
||||||
|
|
||||||
enum ServerSetupProgress {
|
enum ServerSetupProgress {
|
||||||
nothingYet,
|
nothingYet,
|
||||||
hetznerFilled,
|
serverProviderFilled,
|
||||||
|
servertTypeFilled,
|
||||||
cloudFlareFilled,
|
cloudFlareFilled,
|
||||||
backblazeFilled,
|
backblazeFilled,
|
||||||
domainFilled,
|
domainFilled,
|
||||||
|
@ -125,6 +132,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.dnsMatches,
|
required this.dnsMatches,
|
||||||
super.providerApiToken,
|
super.providerApiToken,
|
||||||
|
super.serverTypeIdentificator,
|
||||||
super.cloudFlareKey,
|
super.cloudFlareKey,
|
||||||
super.backblazeCredential,
|
super.backblazeCredential,
|
||||||
super.serverDomain,
|
super.serverDomain,
|
||||||
|
@ -137,6 +145,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
providerApiToken,
|
providerApiToken,
|
||||||
|
serverTypeIdentificator,
|
||||||
cloudFlareKey,
|
cloudFlareKey,
|
||||||
backblazeCredential,
|
backblazeCredential,
|
||||||
serverDomain,
|
serverDomain,
|
||||||
|
@ -150,6 +159,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
|
||||||
|
|
||||||
ServerInstallationNotFinished copyWith({
|
ServerInstallationNotFinished copyWith({
|
||||||
final String? providerApiToken,
|
final String? providerApiToken,
|
||||||
|
final String? serverTypeIdentificator,
|
||||||
final String? cloudFlareKey,
|
final String? cloudFlareKey,
|
||||||
final BackblazeCredential? backblazeCredential,
|
final BackblazeCredential? backblazeCredential,
|
||||||
final ServerDomain? serverDomain,
|
final ServerDomain? serverDomain,
|
||||||
|
@ -163,6 +173,8 @@ class ServerInstallationNotFinished extends ServerInstallationState {
|
||||||
}) =>
|
}) =>
|
||||||
ServerInstallationNotFinished(
|
ServerInstallationNotFinished(
|
||||||
providerApiToken: providerApiToken ?? this.providerApiToken,
|
providerApiToken: providerApiToken ?? this.providerApiToken,
|
||||||
|
serverTypeIdentificator:
|
||||||
|
serverTypeIdentificator ?? this.serverTypeIdentificator,
|
||||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||||
serverDomain: serverDomain ?? this.serverDomain,
|
serverDomain: serverDomain ?? this.serverDomain,
|
||||||
|
@ -179,6 +191,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
|
||||||
|
|
||||||
ServerInstallationFinished finish() => ServerInstallationFinished(
|
ServerInstallationFinished finish() => ServerInstallationFinished(
|
||||||
providerApiToken: providerApiToken!,
|
providerApiToken: providerApiToken!,
|
||||||
|
serverTypeIdentificator: serverTypeIdentificator ?? '',
|
||||||
cloudFlareKey: cloudFlareKey!,
|
cloudFlareKey: cloudFlareKey!,
|
||||||
backblazeCredential: backblazeCredential!,
|
backblazeCredential: backblazeCredential!,
|
||||||
serverDomain: serverDomain!,
|
serverDomain: serverDomain!,
|
||||||
|
@ -194,6 +207,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
|
||||||
const ServerInstallationEmpty()
|
const ServerInstallationEmpty()
|
||||||
: super(
|
: super(
|
||||||
providerApiToken: null,
|
providerApiToken: null,
|
||||||
|
serverTypeIdentificator: null,
|
||||||
cloudFlareKey: null,
|
cloudFlareKey: null,
|
||||||
backblazeCredential: null,
|
backblazeCredential: null,
|
||||||
serverDomain: null,
|
serverDomain: null,
|
||||||
|
@ -210,6 +224,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
|
||||||
class ServerInstallationFinished extends ServerInstallationState {
|
class ServerInstallationFinished extends ServerInstallationState {
|
||||||
const ServerInstallationFinished({
|
const ServerInstallationFinished({
|
||||||
required String super.providerApiToken,
|
required String super.providerApiToken,
|
||||||
|
required String super.serverTypeIdentificator,
|
||||||
required String super.cloudFlareKey,
|
required String super.cloudFlareKey,
|
||||||
required BackblazeCredential super.backblazeCredential,
|
required BackblazeCredential super.backblazeCredential,
|
||||||
required ServerDomain super.serverDomain,
|
required ServerDomain super.serverDomain,
|
||||||
|
@ -223,6 +238,7 @@ class ServerInstallationFinished extends ServerInstallationState {
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
providerApiToken,
|
providerApiToken,
|
||||||
|
serverTypeIdentificator,
|
||||||
cloudFlareKey,
|
cloudFlareKey,
|
||||||
backblazeCredential,
|
backblazeCredential,
|
||||||
serverDomain,
|
serverDomain,
|
||||||
|
@ -238,7 +254,7 @@ enum RecoveryStep {
|
||||||
recoveryKey,
|
recoveryKey,
|
||||||
newDeviceKey,
|
newDeviceKey,
|
||||||
oldToken,
|
oldToken,
|
||||||
hetznerToken,
|
serverProviderToken,
|
||||||
serverSelection,
|
serverSelection,
|
||||||
cloudflareToken,
|
cloudflareToken,
|
||||||
backblazeToken,
|
backblazeToken,
|
||||||
|
@ -261,6 +277,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
||||||
required this.currentStep,
|
required this.currentStep,
|
||||||
required this.recoveryCapabilities,
|
required this.recoveryCapabilities,
|
||||||
super.providerApiToken,
|
super.providerApiToken,
|
||||||
|
super.serverTypeIdentificator,
|
||||||
super.cloudFlareKey,
|
super.cloudFlareKey,
|
||||||
super.backblazeCredential,
|
super.backblazeCredential,
|
||||||
super.serverDomain,
|
super.serverDomain,
|
||||||
|
@ -277,6 +294,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
providerApiToken,
|
providerApiToken,
|
||||||
|
serverTypeIdentificator,
|
||||||
cloudFlareKey,
|
cloudFlareKey,
|
||||||
backblazeCredential,
|
backblazeCredential,
|
||||||
serverDomain,
|
serverDomain,
|
||||||
|
@ -289,6 +307,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
||||||
|
|
||||||
ServerInstallationRecovery copyWith({
|
ServerInstallationRecovery copyWith({
|
||||||
final String? providerApiToken,
|
final String? providerApiToken,
|
||||||
|
final String? serverTypeIdentificator,
|
||||||
final String? cloudFlareKey,
|
final String? cloudFlareKey,
|
||||||
final BackblazeCredential? backblazeCredential,
|
final BackblazeCredential? backblazeCredential,
|
||||||
final ServerDomain? serverDomain,
|
final ServerDomain? serverDomain,
|
||||||
|
@ -299,6 +318,8 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
||||||
}) =>
|
}) =>
|
||||||
ServerInstallationRecovery(
|
ServerInstallationRecovery(
|
||||||
providerApiToken: providerApiToken ?? this.providerApiToken,
|
providerApiToken: providerApiToken ?? this.providerApiToken,
|
||||||
|
serverTypeIdentificator:
|
||||||
|
serverTypeIdentificator ?? this.serverTypeIdentificator,
|
||||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||||
serverDomain: serverDomain ?? this.serverDomain,
|
serverDomain: serverDomain ?? this.serverDomain,
|
||||||
|
@ -310,6 +331,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
|
||||||
|
|
||||||
ServerInstallationFinished finish() => ServerInstallationFinished(
|
ServerInstallationFinished finish() => ServerInstallationFinished(
|
||||||
providerApiToken: providerApiToken!,
|
providerApiToken: providerApiToken!,
|
||||||
|
serverTypeIdentificator: serverTypeIdentificator ?? '',
|
||||||
cloudFlareKey: cloudFlareKey!,
|
cloudFlareKey: cloudFlareKey!,
|
||||||
backblazeCredential: backblazeCredential!,
|
backblazeCredential: backblazeCredential!,
|
||||||
serverDomain: serverDomain!,
|
serverDomain: serverDomain!,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
|
||||||
|
|
|
@ -9,22 +9,33 @@ class ApiConfigModel {
|
||||||
final Box _box = Hive.box(BNames.serverInstallationBox);
|
final Box _box = Hive.box(BNames.serverInstallationBox);
|
||||||
|
|
||||||
ServerHostingDetails? get serverDetails => _serverDetails;
|
ServerHostingDetails? get serverDetails => _serverDetails;
|
||||||
String? get hetznerKey => _hetznerKey;
|
String? get serverProviderKey => _serverProviderKey;
|
||||||
|
String? get serverLocation => _serverLocation;
|
||||||
|
String? get serverType => _serverType;
|
||||||
String? get cloudFlareKey => _cloudFlareKey;
|
String? get cloudFlareKey => _cloudFlareKey;
|
||||||
|
ServerProvider? get serverProvider => _serverProvider;
|
||||||
BackblazeCredential? get backblazeCredential => _backblazeCredential;
|
BackblazeCredential? get backblazeCredential => _backblazeCredential;
|
||||||
ServerDomain? get serverDomain => _serverDomain;
|
ServerDomain? get serverDomain => _serverDomain;
|
||||||
BackblazeBucket? get backblazeBucket => _backblazeBucket;
|
BackblazeBucket? get backblazeBucket => _backblazeBucket;
|
||||||
|
|
||||||
String? _hetznerKey;
|
String? _serverProviderKey;
|
||||||
|
String? _serverLocation;
|
||||||
String? _cloudFlareKey;
|
String? _cloudFlareKey;
|
||||||
|
String? _serverType;
|
||||||
|
ServerProvider? _serverProvider;
|
||||||
ServerHostingDetails? _serverDetails;
|
ServerHostingDetails? _serverDetails;
|
||||||
BackblazeCredential? _backblazeCredential;
|
BackblazeCredential? _backblazeCredential;
|
||||||
ServerDomain? _serverDomain;
|
ServerDomain? _serverDomain;
|
||||||
BackblazeBucket? _backblazeBucket;
|
BackblazeBucket? _backblazeBucket;
|
||||||
|
|
||||||
Future<void> storeHetznerKey(final String value) async {
|
Future<void> storeServerProviderType(final ServerProvider value) async {
|
||||||
|
await _box.put(BNames.serverProvider, value);
|
||||||
|
_serverProvider = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> storeServerProviderKey(final String value) async {
|
||||||
await _box.put(BNames.hetznerKey, value);
|
await _box.put(BNames.hetznerKey, value);
|
||||||
_hetznerKey = value;
|
_serverProviderKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeCloudFlareKey(final String value) async {
|
Future<void> storeCloudFlareKey(final String value) async {
|
||||||
|
@ -32,6 +43,16 @@ class ApiConfigModel {
|
||||||
_cloudFlareKey = value;
|
_cloudFlareKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> storeServerTypeIdentifier(final String typeIdentifier) async {
|
||||||
|
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
|
||||||
|
_serverType = typeIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> storeServerLocation(final String serverLocation) async {
|
||||||
|
await _box.put(BNames.serverLocation, serverLocation);
|
||||||
|
_serverLocation = serverLocation;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
|
Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
|
||||||
await _box.put(BNames.backblazeCredential, value);
|
await _box.put(BNames.backblazeCredential, value);
|
||||||
_backblazeCredential = value;
|
_backblazeCredential = value;
|
||||||
|
@ -53,20 +74,26 @@ class ApiConfigModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_hetznerKey = null;
|
_serverProviderKey = null;
|
||||||
|
_serverLocation = null;
|
||||||
_cloudFlareKey = null;
|
_cloudFlareKey = null;
|
||||||
_backblazeCredential = null;
|
_backblazeCredential = null;
|
||||||
_serverDomain = null;
|
_serverDomain = null;
|
||||||
_serverDetails = null;
|
_serverDetails = null;
|
||||||
_backblazeBucket = null;
|
_backblazeBucket = null;
|
||||||
|
_serverType = null;
|
||||||
|
_serverProvider = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_hetznerKey = _box.get(BNames.hetznerKey);
|
_serverProviderKey = _box.get(BNames.hetznerKey);
|
||||||
|
_serverLocation = _box.get(BNames.serverLocation);
|
||||||
_cloudFlareKey = _box.get(BNames.cloudFlareKey);
|
_cloudFlareKey = _box.get(BNames.cloudFlareKey);
|
||||||
_backblazeCredential = _box.get(BNames.backblazeCredential);
|
_backblazeCredential = _box.get(BNames.backblazeCredential);
|
||||||
_serverDomain = _box.get(BNames.serverDomain);
|
_serverDomain = _box.get(BNames.serverDomain);
|
||||||
_serverDetails = _box.get(BNames.serverDetails);
|
_serverDetails = _box.get(BNames.serverDetails);
|
||||||
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
_backblazeBucket = _box.get(BNames.backblazeBucket);
|
||||||
|
_serverType = _box.get(BNames.serverTypeIdentifier);
|
||||||
|
_serverProvider = _box.get(BNames.serverProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
class TimeSeriesData {
|
|
||||||
TimeSeriesData(
|
|
||||||
this.secondsSinceEpoch,
|
|
||||||
this.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
final int secondsSinceEpoch;
|
|
||||||
DateTime get time =>
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
|
|
||||||
final double value;
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
|
||||||
|
|
||||||
part 'server_details.g.dart';
|
part 'server_details.g.dart';
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ class ServerVolume {
|
||||||
required this.sizeByte,
|
required this.sizeByte,
|
||||||
required this.serverId,
|
required this.serverId,
|
||||||
required this.linuxDevice,
|
required this.linuxDevice,
|
||||||
|
this.uuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
|
@ -70,6 +72,8 @@ class ServerVolume {
|
||||||
int? serverId;
|
int? serverId;
|
||||||
@HiveField(5, defaultValue: null)
|
@HiveField(5, defaultValue: null)
|
||||||
String? linuxDevice;
|
String? linuxDevice;
|
||||||
|
@HiveField(6, defaultValue: null)
|
||||||
|
String? uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HiveType(typeId: 101)
|
@HiveType(typeId: 101)
|
||||||
|
@ -78,4 +82,17 @@ enum ServerProvider {
|
||||||
unknown,
|
unknown,
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
hetzner,
|
hetzner,
|
||||||
|
@HiveField(2)
|
||||||
|
digitalOcean;
|
||||||
|
|
||||||
|
factory ServerProvider.fromGraphQL(final Enum$ServerProvider provider) {
|
||||||
|
switch (provider) {
|
||||||
|
case Enum$ServerProvider.HETZNER:
|
||||||
|
return hetzner;
|
||||||
|
case Enum$ServerProvider.DIGITALOCEAN:
|
||||||
|
return digitalOcean;
|
||||||
|
default:
|
||||||
|
return unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,13 +76,14 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||||
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
|
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
|
||||||
serverId: fields[4] as int?,
|
serverId: fields[4] as int?,
|
||||||
linuxDevice: fields[5] as String?,
|
linuxDevice: fields[5] as String?,
|
||||||
|
uuid: fields[6] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, ServerVolume obj) {
|
void write(BinaryWriter writer, ServerVolume obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(5)
|
..writeByte(6)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
..write(obj.id)
|
..write(obj.id)
|
||||||
..writeByte(2)
|
..writeByte(2)
|
||||||
|
@ -92,7 +93,9 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
|
||||||
..writeByte(4)
|
..writeByte(4)
|
||||||
..write(obj.serverId)
|
..write(obj.serverId)
|
||||||
..writeByte(5)
|
..writeByte(5)
|
||||||
..write(obj.linuxDevice);
|
..write(obj.linuxDevice)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -117,6 +120,8 @@ class ServerProviderAdapter extends TypeAdapter<ServerProvider> {
|
||||||
return ServerProvider.unknown;
|
return ServerProvider.unknown;
|
||||||
case 1:
|
case 1:
|
||||||
return ServerProvider.hetzner;
|
return ServerProvider.hetzner;
|
||||||
|
case 2:
|
||||||
|
return ServerProvider.digitalOcean;
|
||||||
default:
|
default:
|
||||||
return ServerProvider.unknown;
|
return ServerProvider.unknown;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +136,9 @@ class ServerProviderAdapter extends TypeAdapter<ServerProvider> {
|
||||||
case ServerProvider.hetzner:
|
case ServerProvider.hetzner:
|
||||||
writer.writeByte(1);
|
writer.writeByte(1);
|
||||||
break;
|
break;
|
||||||
|
case ServerProvider.digitalOcean:
|
||||||
|
writer.writeByte(2);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
class TimeSeriesData {
|
||||||
|
TimeSeriesData(
|
||||||
|
this.secondsSinceEpoch,
|
||||||
|
this.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int secondsSinceEpoch;
|
||||||
|
DateTime get time =>
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
|
||||||
|
final double value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerMetrics {
|
||||||
|
ServerMetrics({
|
||||||
|
required this.stepsInSecond,
|
||||||
|
required this.cpu,
|
||||||
|
required this.bandwidthIn,
|
||||||
|
required this.bandwidthOut,
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
});
|
||||||
|
|
||||||
|
final num stepsInSecond;
|
||||||
|
final List<TimeSeriesData> cpu;
|
||||||
|
final List<TimeSeriesData> bandwidthIn;
|
||||||
|
final List<TimeSeriesData> bandwidthOut;
|
||||||
|
|
||||||
|
final DateTime start;
|
||||||
|
final DateTime end;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Price {
|
||||||
|
Price({
|
||||||
|
required this.value,
|
||||||
|
required this.currency,
|
||||||
|
});
|
||||||
|
|
||||||
|
double value;
|
||||||
|
String currency;
|
||||||
inex
commented
Review
Currency should be a smart enum. Currency should be a smart enum.
NaiJi
commented
Review
It's beyond the scope of Digital Ocean implementation, though it indeed is better to be done asap, it will be planned. It's beyond the scope of Digital Ocean implementation, though it indeed is better to be done asap, it will be planned.
|
|||||||
|
}
|
|
@ -5,14 +5,12 @@ class ServerBasicInfo {
|
||||||
required this.reverseDns,
|
required this.reverseDns,
|
||||||
required this.ip,
|
required this.ip,
|
||||||
required this.created,
|
required this.created,
|
||||||
required this.volumeId,
|
|
||||||
});
|
});
|
||||||
final int id;
|
final int id;
|
||||||
final String name;
|
final String name;
|
||||||
final String reverseDns;
|
final String reverseDns;
|
||||||
final String ip;
|
final String ip;
|
||||||
final DateTime created;
|
final DateTime created;
|
||||||
final int volumeId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
||||||
|
@ -26,7 +24,6 @@ class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
||||||
reverseDns: serverBasicInfo.reverseDns,
|
reverseDns: serverBasicInfo.reverseDns,
|
||||||
ip: serverBasicInfo.ip,
|
ip: serverBasicInfo.ip,
|
||||||
created: serverBasicInfo.created,
|
created: serverBasicInfo.created,
|
||||||
volumeId: serverBasicInfo.volumeId,
|
|
||||||
isIpValid: isIpValid,
|
isIpValid: isIpValid,
|
||||||
isReverseDnsValid: isReverseDnsValid,
|
isReverseDnsValid: isReverseDnsValid,
|
||||||
);
|
);
|
||||||
|
@ -37,7 +34,6 @@ class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
||||||
required super.reverseDns,
|
required super.reverseDns,
|
||||||
required super.ip,
|
required super.ip,
|
||||||
required super.created,
|
required super.created,
|
||||||
required super.volumeId,
|
|
||||||
required this.isIpValid,
|
required this.isIpValid,
|
||||||
required this.isReverseDnsValid,
|
required this.isReverseDnsValid,
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum MetadataType {
|
||||||
|
id(icon: Icons.numbers_outlined),
|
||||||
|
status(icon: Icons.mode_standby_outlined),
|
||||||
|
cpu(icon: Icons.memory_outlined),
|
||||||
|
ram(icon: Icons.memory_outlined),
|
||||||
|
cost(icon: Icons.payments_outlined),
|
||||||
|
location(icon: Icons.location_on_outlined),
|
||||||
|
|
||||||
|
other(icon: Icons.info_outlined);
|
||||||
|
|
||||||
|
const MetadataType({
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerMetadataEntity {
|
||||||
|
ServerMetadataEntity({
|
||||||
|
required this.name,
|
||||||
|
required this.value,
|
||||||
|
this.type = MetadataType.other,
|
||||||
|
});
|
||||||
|
final MetadataType type;
|
||||||
|
final String name;
|
||||||
|
final String value;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
class ServerProviderLocation {
|
||||||
|
ServerProviderLocation({
|
||||||
|
required this.title,
|
||||||
|
required this.identifier,
|
||||||
|
this.description,
|
||||||
|
this.flag,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String identifier;
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
/// as emoji
|
||||||
|
final String? flag;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
|
||||||
|
class ServerType {
|
||||||
|
ServerType({
|
||||||
|
required this.title,
|
||||||
|
required this.identifier,
|
||||||
|
required this.ram,
|
||||||
|
required this.cores,
|
||||||
|
required this.disk,
|
||||||
|
required this.price,
|
||||||
|
required this.location,
|
||||||
|
});
|
||||||
|
final String title;
|
||||||
|
final String identifier;
|
||||||
|
final double ram;
|
||||||
|
final DiskSize disk;
|
||||||
|
final int cores;
|
||||||
|
final Price price;
|
||||||
|
final ServerProviderLocation location;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
|
@ -2,18 +2,16 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
||||||
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
|
||||||
class JobsContent extends StatelessWidget {
|
class JobsContent extends StatelessWidget {
|
||||||
const JobsContent({super.key});
|
const JobsContent({super.key});
|
||||||
|
@ -47,26 +45,16 @@ class JobsContent extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
title: 'jobs.reboot_server'.tr(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final NavigationService nav = getIt<NavigationService>();
|
showPopUpAlert(
|
||||||
nav.showPopUpDialog(
|
alertTitle: 'jobs.reboot_server'.tr(),
|
||||||
BrandAlert(
|
description: 'modals.are_you_sure'.tr(),
|
||||||
title: 'jobs.reboot_server'.tr(),
|
actionButtonTitle: 'modals.reboot'.tr(),
|
||||||
contentText: 'modals.are_you_sure'.tr(),
|
actionButtonOnPressed: () =>
|
||||||
actions: [
|
{context.read<JobsCubit>().rebootServer()},
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
onPressed: () =>
|
|
||||||
{context.read<JobsCubit>().rebootServer()},
|
|
||||||
text: 'modals.reboot'.tr(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: 'jobs.reboot_server'.tr(),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
||||||
|
|
||||||
Future<T?> showBrandBottomSheet<T>({
|
Future<T?> showBrandBottomSheet<T>({
|
||||||
required final BuildContext context,
|
required final BuildContext context,
|
||||||
|
@ -12,3 +16,30 @@ Future<T?> showBrandBottomSheet<T>({
|
||||||
shadow: const BoxShadow(color: Colors.transparent),
|
shadow: const BoxShadow(color: Colors.transparent),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void showPopUpAlert({
|
||||||
|
required final String description,
|
||||||
|
required final String actionButtonTitle,
|
||||||
|
required final void Function() actionButtonOnPressed,
|
||||||
|
final void Function()? cancelButtonOnPressed,
|
||||||
|
final String? alertTitle,
|
||||||
|
final String? cancelButtonTitle,
|
||||||
|
}) {
|
||||||
|
getIt.get<NavigationService>().showPopUpDialog(
|
||||||
|
BrandAlert(
|
||||||
|
title: alertTitle ?? 'basis.alert'.tr(),
|
||||||
|
contentText: description,
|
||||||
|
actions: [
|
||||||
|
ActionButton(
|
||||||
|
text: actionButtonTitle,
|
||||||
|
isRed: true,
|
||||||
|
onPressed: actionButtonOnPressed,
|
||||||
|
),
|
||||||
|
ActionButton(
|
||||||
|
text: cancelButtonTitle ?? 'basis.cancel'.tr(),
|
||||||
|
onPressed: cancelButtonOnPressed,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/backup.dart';
|
import 'package:selfprivacy/logic/models/json/backup.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
|
||||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@ -157,28 +155,17 @@ class _BackupDetailsState extends State<BackupDetails>
|
||||||
onTap: preventActions
|
onTap: preventActions
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
final NavigationService nav =
|
showPopUpAlert(
|
||||||
getIt<NavigationService>();
|
alertTitle: 'backup.restoring'.tr(),
|
||||||
nav.showPopUpDialog(
|
description: 'backup.restore_alert'.tr(
|
||||||
BrandAlert(
|
args: [backup.time.toString()],
|
||||||
title: 'backup.restoring'.tr(),
|
|
||||||
contentText: 'backup.restore_alert'.tr(
|
|
||||||
args: [backup.time.toString()],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
onPressed: () => {
|
|
||||||
context
|
|
||||||
.read<BackupsCubit>()
|
|
||||||
.restoreBackup(backup.id)
|
|
||||||
},
|
|
||||||
text: 'modals.yes'.tr(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
actionButtonTitle: 'modals.yes'.tr(),
|
||||||
|
actionButtonOnPressed: () => {
|
||||||
|
context
|
||||||
|
.read<BackupsCubit>()
|
||||||
|
.restoreBackup(backup.id)
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info/package_info.dart';
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/pages/devices/devices.dart';
|
import 'package:selfprivacy/ui/pages/devices/devices.dart';
|
||||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
|
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||||
import 'package:selfprivacy/ui/pages/users/users.dart';
|
import 'package:selfprivacy/ui/pages/users/users.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
String bottomTitle(
|
String bottomTitle(
|
||||||
|
@ -11,7 +11,7 @@ String bottomTitle(
|
||||||
final day = DateFormat('MMMd');
|
final day = DateFormat('MMMd');
|
||||||
String res;
|
String res;
|
||||||
|
|
||||||
if (value <= 0) {
|
if (value <= 0 || value >= data.length) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ part of '../server_details_screen.dart';
|
||||||
class _Chart extends StatelessWidget {
|
class _Chart extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final HetznerMetricsCubit cubit = context.watch<HetznerMetricsCubit>();
|
final MetricsCubit cubit = context.watch<MetricsCubit>();
|
||||||
final Period period = cubit.state.period;
|
final Period period = cubit.state.period;
|
||||||
final HetznerMetricsState state = cubit.state;
|
final MetricsState state = cubit.state;
|
||||||
List<Widget> charts;
|
List<Widget> charts;
|
||||||
if (state is HetznerMetricsLoaded || state is HetznerMetricsLoading) {
|
if (state is MetricsLoaded || state is MetricsLoading) {
|
||||||
charts = [
|
charts = [
|
||||||
FilledCard(
|
FilledCard(
|
||||||
clipped: false,
|
clipped: false,
|
||||||
|
@ -26,10 +26,10 @@ class _Chart extends StatelessWidget {
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (state is HetznerMetricsLoaded) getCpuChart(state),
|
if (state is MetricsLoaded) getCpuChart(state),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
opacity: state is HetznerMetricsLoading ? 1 : 0,
|
opacity: state is MetricsLoading ? 1 : 0,
|
||||||
child: const _GraphLoadingCardContent(),
|
child: const _GraphLoadingCardContent(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -72,10 +72,10 @@ class _Chart extends StatelessWidget {
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (state is HetznerMetricsLoaded) getBandwidthChart(state),
|
if (state is MetricsLoaded) getBandwidthChart(state),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
opacity: state is HetznerMetricsLoading ? 1 : 0,
|
opacity: state is MetricsLoading ? 1 : 0,
|
||||||
child: const _GraphLoadingCardContent(),
|
child: const _GraphLoadingCardContent(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -122,29 +122,29 @@ class _Chart extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getCpuChart(final HetznerMetricsLoaded state) {
|
Widget getCpuChart(final MetricsLoaded state) {
|
||||||
final data = state.cpu;
|
final data = state.metrics.cpu;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: CpuChart(
|
child: CpuChart(
|
||||||
data: data,
|
data: data,
|
||||||
period: state.period,
|
period: state.period,
|
||||||
start: state.start,
|
start: state.metrics.start,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getBandwidthChart(final HetznerMetricsLoaded state) {
|
Widget getBandwidthChart(final MetricsLoaded state) {
|
||||||
final ppsIn = state.bandwidthIn;
|
final ppsIn = state.metrics.bandwidthIn;
|
||||||
final ppsOut = state.bandwidthOut;
|
final ppsOut = state.metrics.bandwidthOut;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: NetworkChart(
|
child: NetworkChart(
|
||||||
listData: [ppsIn, ppsOut],
|
listData: [ppsIn, ppsOut],
|
||||||
period: state.period,
|
period: state.period,
|
||||||
start: state.start,
|
start: state.metrics.start,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ class CpuChart extends StatelessWidget {
|
||||||
],
|
],
|
||||||
minY: 0,
|
minY: 0,
|
||||||
maxY: 100,
|
maxY: 100,
|
||||||
minX: data.length - 200,
|
minX: 0,
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
||||||
|
|
||||||
class NetworkChart extends StatelessWidget {
|
class NetworkChart extends StatelessWidget {
|
||||||
|
@ -116,7 +116,7 @@ class NetworkChart extends StatelessWidget {
|
||||||
...listData[1].map((final e) => e.value)
|
...listData[1].map((final e) => e.value)
|
||||||
].reduce(max) *
|
].reduce(max) *
|
||||||
1.2,
|
1.2,
|
||||||
minX: listData[0].length - 200,
|
minX: 0,
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
|
@ -21,7 +21,6 @@ import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
||||||
import 'package:selfprivacy/utils/extensions/duration.dart';
|
import 'package:selfprivacy/utils/extensions/duration.dart';
|
||||||
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:timezone/timezone.dart';
|
import 'package:timezone/timezone.dart';
|
||||||
|
@ -92,7 +91,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final context) => HetznerMetricsCubit()..restart(),
|
create: (final context) => MetricsCubit()..restart(),
|
||||||
child: _Chart(),
|
child: _Chart(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
|
@ -10,7 +10,6 @@ class _TextDetails extends StatelessWidget {
|
||||||
} else if (details is ServerDetailsNotReady) {
|
} else if (details is ServerDetailsNotReady) {
|
||||||
return _TempMessage(message: 'basis.no_data'.tr());
|
return _TempMessage(message: 'basis.no_data'.tr());
|
||||||
} else if (details is Loaded) {
|
} else if (details is Loaded) {
|
||||||
final data = details.serverInfo;
|
|
||||||
return FilledCard(
|
return FilledCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -24,37 +23,15 @@ class _TextDetails extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTileOnSurfaceVariant(
|
...details.metadata
|
||||||
leadingIcon: Icons.numbers_outlined,
|
.map(
|
||||||
title: data.id.toString(),
|
(final metadata) => ListTileOnSurfaceVariant(
|
||||||
subtitle: 'server.server_id'.tr(),
|
leadingIcon: metadata.type.icon,
|
||||||
),
|
title: metadata.name,
|
||||||
ListTileOnSurfaceVariant(
|
subtitle: metadata.value,
|
||||||
leadingIcon: Icons.mode_standby_outlined,
|
),
|
||||||
title: data.status.toString().split('.')[1].capitalize(),
|
)
|
||||||
subtitle: 'server.status'.tr(),
|
.toList(),
|
||||||
),
|
|
||||||
ListTileOnSurfaceVariant(
|
|
||||||
leadingIcon: Icons.memory_outlined,
|
|
||||||
title: 'server.core_count'.plural(data.serverType.cores),
|
|
||||||
subtitle: 'server.cpu'.tr(),
|
|
||||||
),
|
|
||||||
ListTileOnSurfaceVariant(
|
|
||||||
leadingIcon: Icons.memory_outlined,
|
|
||||||
title: '${data.serverType.memory.toString()} GB',
|
|
||||||
subtitle: 'server.ram'.tr(),
|
|
||||||
),
|
|
||||||
ListTileOnSurfaceVariant(
|
|
||||||
leadingIcon: Icons.euro_outlined,
|
|
||||||
title: data.serverType.prices[1].monthly.toStringAsFixed(2),
|
|
||||||
subtitle: 'server.monthly_cost'.tr(),
|
|
||||||
),
|
|
||||||
// Server location
|
|
||||||
ListTileOnSurfaceVariant(
|
|
||||||
leadingIcon: Icons.location_on_outlined,
|
|
||||||
title: '${data.location.city}, ${data.location.country}',
|
|
||||||
subtitle: 'server.location'.tr(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_depe
|
||||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
|
@ -74,7 +75,7 @@ class _ExtendingVolumePageState extends State<ExtendingVolumePage> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_euroPerGb = snapshot.data as double;
|
_euroPerGb = (snapshot.data as Price).value;
|
||||||
_sizeController.text = _currentSliderGbValue.truncate().toString();
|
_sizeController.text = _currentSliderGbValue.truncate().toString();
|
||||||
_priceController.text =
|
_priceController.text =
|
||||||
(_euroPerGb * double.parse(_sizeController.text))
|
(_euroPerGb * double.parse(_sizeController.text))
|
||||||
|
@ -152,7 +153,7 @@ class _ExtendingVolumePageState extends State<ExtendingVolumePage> {
|
||||||
: () {
|
: () {
|
||||||
context.read<ApiProviderVolumeCubit>().resizeVolume(
|
context.read<ApiProviderVolumeCubit>().resizeVolume(
|
||||||
widget.diskVolumeToResize,
|
widget.diskVolumeToResize,
|
||||||
_currentSliderGbValue.round(),
|
DiskSize.fromGibibyte(_currentSliderGbValue),
|
||||||
context.read<ApiServerVolumeCubit>().reload,
|
context.read<ApiServerVolumeCubit>().reload,
|
||||||
);
|
);
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
|
|
@ -2,12 +2,12 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
|
@ -17,6 +17,8 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
||||||
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@ -33,7 +35,8 @@ class InitializingPage extends StatelessWidget {
|
||||||
Widget? actualInitializingPage;
|
Widget? actualInitializingPage;
|
||||||
if (cubit.state is! ServerInstallationFinished) {
|
if (cubit.state is! ServerInstallationFinished) {
|
||||||
actualInitializingPage = [
|
actualInitializingPage = [
|
||||||
() => _stepHetzner(cubit),
|
() => _stepServerProviderToken(cubit),
|
||||||
|
() => _stepServerType(cubit),
|
||||||
() => _stepCloudflare(cubit),
|
() => _stepCloudflare(cubit),
|
||||||
() => _stepBackblaze(cubit),
|
() => _stepBackblaze(cubit),
|
||||||
() => _stepDomain(cubit),
|
() => _stepDomain(cubit),
|
||||||
|
@ -67,7 +70,8 @@ class InitializingPage extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: ProgressBar(
|
: ProgressBar(
|
||||||
steps: const [
|
steps: const [
|
||||||
'Hetzner',
|
'Hosting',
|
||||||
|
'Server Type',
|
||||||
'CloudFlare',
|
'CloudFlare',
|
||||||
'Backblaze',
|
'Backblaze',
|
||||||
'Domain',
|
'Domain',
|
||||||
|
@ -78,6 +82,11 @@ class InitializingPage extends StatelessWidget {
|
||||||
activeIndex: cubit.state.porgressBar,
|
activeIndex: cubit.state.porgressBar,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (cubit.state.porgressBar ==
|
||||||
|
ServerSetupProgress.serverProviderFilled.index)
|
||||||
|
BrandText.h2(
|
||||||
|
'initializing.choose_location_type'.tr(),
|
||||||
|
),
|
||||||
_addCard(
|
_addCard(
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
@ -136,55 +145,34 @@ class InitializingPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepHetzner(final ServerInstallationCubit serverInstallationCubit) =>
|
Widget _stepServerProviderToken(
|
||||||
|
final ServerInstallationCubit serverInstallationCubit,
|
||||||
|
) =>
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final context) => ProviderFormCubit(
|
create: (final context) => ProviderFormCubit(serverInstallationCubit),
|
||||||
serverInstallationCubit,
|
|
||||||
),
|
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (final context) {
|
builder: (final context) {
|
||||||
final formCubitState = context.watch<ProviderFormCubit>().state;
|
final providerCubit = context.watch<ProviderFormCubit>();
|
||||||
return Column(
|
return ServerProviderPicker(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
formCubit: providerCubit,
|
||||||
children: [
|
serverInstallationCubit: serverInstallationCubit,
|
||||||
Image.asset(
|
|
||||||
'assets/images/logos/hetzner.png',
|
|
||||||
width: 150,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandText.h2('initializing.connect_to_server'.tr()),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandText.body2('initializing.place_where_data'.tr()),
|
|
||||||
const Spacer(),
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit: context.read<ProviderFormCubit>().apiKey,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Hetzner API Token',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: formCubitState.isSubmitting
|
|
||||||
? null
|
|
||||||
: () => context.read<ProviderFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.connect'.tr(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandButton.text(
|
|
||||||
onPressed: () => _showModal(
|
|
||||||
context,
|
|
||||||
const _HowTo(fileName: 'how_hetzner'),
|
|
||||||
),
|
|
||||||
title: 'initializing.how'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Widget _stepServerType(
|
||||||
|
final ServerInstallationCubit serverInstallationCubit,
|
||||||
|
) =>
|
||||||
|
BlocProvider(
|
||||||
|
create: (final context) => ProviderFormCubit(serverInstallationCubit),
|
||||||
|
child: Builder(
|
||||||
|
builder: (final context) => ServerTypePicker(
|
||||||
|
serverInstallationCubit: serverInstallationCubit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
void _showModal(final BuildContext context, final Widget widget) {
|
void _showModal(final BuildContext context, final Widget widget) {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -198,49 +186,44 @@ class InitializingPage extends StatelessWidget {
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final context) => DnsProviderFormCubit(initializingCubit),
|
create: (final context) => DnsProviderFormCubit(initializingCubit),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (final context) {
|
builder: (final context) => Column(
|
||||||
final formCubitState = context.watch<DnsProviderFormCubit>().state;
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
return Column(
|
Image.asset(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
'assets/images/logos/cloudflare.png',
|
||||||
children: [
|
width: 150,
|
||||||
Image.asset(
|
),
|
||||||
'assets/images/logos/cloudflare.png',
|
const SizedBox(height: 10),
|
||||||
width: 150,
|
BrandText.h2('initializing.connect_cloudflare'.tr()),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
BrandText.body2('initializing.manage_domain_dns'.tr()),
|
||||||
|
const Spacer(),
|
||||||
|
CubitFormTextField(
|
||||||
|
formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'initializing.cloudflare_api_token'.tr(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
BrandText.h2('initializing.connect_cloudflare'.tr()),
|
const Spacer(),
|
||||||
const SizedBox(height: 10),
|
BrandButton.rised(
|
||||||
BrandText.body2('initializing.manage_domain_dns'.tr()),
|
onPressed: () =>
|
||||||
const Spacer(),
|
context.read<DnsProviderFormCubit>().trySubmit(),
|
||||||
CubitFormTextField(
|
text: 'basis.connect'.tr(),
|
||||||
formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
|
),
|
||||||
textAlign: TextAlign.center,
|
const SizedBox(height: 10),
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
BrandButton.text(
|
||||||
decoration: InputDecoration(
|
onPressed: () => _showModal(
|
||||||
hintText: 'initializing.cloudflare_api_token'.tr(),
|
context,
|
||||||
|
const _HowTo(
|
||||||
|
fileName: 'how_cloudflare',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
title: 'initializing.how'.tr(),
|
||||||
BrandButton.rised(
|
),
|
||||||
onPressed: formCubitState.isSubmitting
|
],
|
||||||
? null
|
),
|
||||||
: () => context.read<DnsProviderFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.connect'.tr(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandButton.text(
|
|
||||||
onPressed: () => _showModal(
|
|
||||||
context,
|
|
||||||
const _HowTo(
|
|
||||||
fileName: 'how_cloudflare',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: 'initializing.how'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
|
|
||||||
|
class ServerProviderPicker extends StatefulWidget {
|
||||||
|
const ServerProviderPicker({
|
||||||
|
required this.formCubit,
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProviderFormCubit formCubit;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServerProviderPicker> createState() => _ServerProviderPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerProviderPickerState extends State<ServerProviderPicker> {
|
||||||
|
ServerProvider selectedProvider = ServerProvider.unknown;
|
||||||
|
|
||||||
|
void setProvider(final ServerProvider provider) {
|
||||||
|
setState(() {
|
||||||
|
selectedProvider = provider;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
switch (selectedProvider) {
|
||||||
|
case ServerProvider.unknown:
|
||||||
|
return ProviderSelectionPage(
|
||||||
|
serverInstallationCubit: widget.serverInstallationCubit,
|
||||||
|
callback: setProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
case ServerProvider.hetzner:
|
||||||
|
return ProviderInputDataPage(
|
||||||
|
providerCubit: widget.formCubit,
|
||||||
|
providerInfo: ProviderPageInfo(
|
||||||
|
providerType: ServerProvider.hetzner,
|
||||||
|
pathToHow: 'hetzner_how',
|
||||||
|
image: Image.asset(
|
||||||
|
'assets/images/logos/hetzner.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
case ServerProvider.digitalOcean:
|
||||||
|
return ProviderInputDataPage(
|
||||||
|
providerCubit: widget.formCubit,
|
||||||
|
providerInfo: ProviderPageInfo(
|
||||||
|
providerType: ServerProvider.digitalOcean,
|
||||||
|
pathToHow: 'hetzner_how',
|
||||||
|
image: Image.asset(
|
||||||
|
'assets/images/logos/digital_ocean.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderPageInfo {
|
||||||
|
const ProviderPageInfo({
|
||||||
|
required this.providerType,
|
||||||
|
required this.pathToHow,
|
||||||
|
required this.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String pathToHow;
|
||||||
|
final Image image;
|
||||||
|
final ServerProvider providerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderInputDataPage extends StatelessWidget {
|
||||||
|
const ProviderInputDataPage({
|
||||||
|
required this.providerInfo,
|
||||||
|
required this.providerCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProviderPageInfo providerInfo;
|
||||||
|
final ProviderFormCubit providerCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
providerInfo.image,
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'initializing.connect_to_server'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
CubitFormTextField(
|
||||||
|
formFieldCubit: providerCubit.apiKey,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Provider API Token',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
FilledButton(
|
||||||
|
title: 'basis.connect'.tr(),
|
||||||
|
onPressed: () => providerCubit.trySubmit(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
OutlinedButton(
|
||||||
|
child: Text('initializing.how'.tr()),
|
||||||
|
onPressed: () => showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (final BuildContext context) => BrandBottomSheet(
|
||||||
|
isExpended: true,
|
||||||
|
child: Padding(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
children: [
|
||||||
|
BrandMarkdown(
|
||||||
|
fileName: providerInfo.pathToHow,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderSelectionPage extends StatelessWidget {
|
||||||
|
const ProviderSelectionPage({
|
||||||
|
required this.callback,
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Function callback;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'initializing.select_provider'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'initializing.place_where_data'.tr(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 320,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
serverInstallationCubit
|
||||||
|
.setServerProviderType(ServerProvider.hetzner);
|
||||||
|
callback(ServerProvider.hetzner);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logos/hetzner.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
serverInstallationCubit
|
||||||
|
.setServerProviderType(ServerProvider.digitalOcean);
|
||||||
|
callback(ServerProvider.digitalOcean);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logos/digital_ocean.png',
|
||||||
|
width: 150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
|
|
||||||
|
class ServerTypePicker extends StatefulWidget {
|
||||||
|
const ServerTypePicker({
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServerTypePicker> createState() => _ServerTypePickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerTypePickerState extends State<ServerTypePicker> {
|
||||||
|
ServerProviderLocation? serverProviderLocation;
|
||||||
|
ServerType? serverType;
|
||||||
|
|
||||||
|
void setServerProviderLocation(final ServerProviderLocation? location) {
|
||||||
|
setState(() {
|
||||||
|
serverProviderLocation = location;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
if (serverProviderLocation == null) {
|
||||||
|
return SelectLocationPage(
|
||||||
|
serverInstallationCubit: widget.serverInstallationCubit,
|
||||||
|
callback: setServerProviderLocation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelectTypePage(
|
||||||
|
location: serverProviderLocation!,
|
||||||
|
serverInstallationCubit: widget.serverInstallationCubit,
|
||||||
|
backToLocationPickingCallback: () {
|
||||||
|
setServerProviderLocation(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectLocationPage extends StatelessWidget {
|
||||||
|
const SelectLocationPage({
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
required this.callback,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Function callback;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => FutureBuilder(
|
||||||
|
future: serverInstallationCubit.fetchAvailableLocations(),
|
||||||
|
builder: (
|
||||||
|
final BuildContext context,
|
||||||
|
final AsyncSnapshot<Object?> snapshot,
|
||||||
|
) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
|
||||||
|
return Text('initializing.no_locations_found'.tr());
|
||||||
|
}
|
||||||
|
return ListView(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
children: [
|
||||||
|
...(snapshot.data! as List<ServerProviderLocation>).map(
|
||||||
|
(final location) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
callback(location);
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (location.flag != null) Text(location.flag!),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(location.title),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (location.description != null)
|
||||||
|
Text(location.description!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectTypePage extends StatelessWidget {
|
||||||
|
const SelectTypePage({
|
||||||
|
required this.backToLocationPickingCallback,
|
||||||
|
required this.location,
|
||||||
|
required this.serverInstallationCubit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ServerProviderLocation location;
|
||||||
|
final ServerInstallationCubit serverInstallationCubit;
|
||||||
|
final Function backToLocationPickingCallback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => FutureBuilder(
|
||||||
|
future: serverInstallationCubit.fetchAvailableTypesByLocation(location),
|
||||||
|
builder: (
|
||||||
|
final BuildContext context,
|
||||||
|
final AsyncSnapshot<Object?> snapshot,
|
||||||
|
) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
if ((snapshot.data as List<ServerType>).isEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'initializing.no_server_types_found'.tr(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: () {
|
||||||
|
backToLocationPickingCallback();
|
||||||
|
},
|
||||||
|
text: 'initializing.back_to_locations'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ListView(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
children: [
|
||||||
|
...(snapshot.data! as List<ServerType>).map(
|
||||||
|
(final type) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
serverInstallationCubit.setServerType(type);
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
type.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'cores: ${type.cores.toString()}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'ram: ${type.ram.toString()}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'disk: ${type.disk.gibibyte.toString()}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'price: ${type.price.value.toString()} ${type.price.currency}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -45,9 +45,8 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder<List<ServerBasicInfoWithValidators>>(
|
FutureBuilder<List<ServerBasicInfoWithValidators>>(
|
||||||
future: context
|
future:
|
||||||
.read<ServerInstallationCubit>()
|
context.read<ServerInstallationCubit>().getAvailableServers(),
|
||||||
.getServersOnHetznerAccount(),
|
|
||||||
builder: (final context, final snapshot) {
|
builder: (final context, final snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final servers = snapshot.data;
|
final servers = snapshot.data;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_new_device_key.
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_backblaze.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_backblaze.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_server.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_confirm_server.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_hentzner_connected.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_server_provider_connected.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@ -47,8 +47,8 @@ class RecoveryRouting extends StatelessWidget {
|
||||||
case RecoveryStep.oldToken:
|
case RecoveryStep.oldToken:
|
||||||
currentPage = const RecoverByOldToken();
|
currentPage = const RecoverByOldToken();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.hetznerToken:
|
case RecoveryStep.serverProviderToken:
|
||||||
currentPage = const RecoveryHetznerConnected();
|
currentPage = const RecoveryServerProviderConnected();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.serverSelection:
|
case RecoveryStep.serverSelection:
|
||||||
currentPage = const RecoveryConfirmServer();
|
currentPage = const RecoveryConfirmServer();
|
||||||
|
|
|
@ -10,8 +10,8 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
|
|
||||||
class RecoveryHetznerConnected extends StatelessWidget {
|
class RecoveryServerProviderConnected extends StatelessWidget {
|
||||||
const RecoveryHetznerConnected({super.key});
|
const RecoveryServerProviderConnected({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
|
@ -26,8 +26,8 @@ class RecoveryHetznerConnected extends StatelessWidget {
|
||||||
context.watch<ProviderFormCubit>().state;
|
context.watch<ProviderFormCubit>().state;
|
||||||
|
|
||||||
return BrandHeroScreen(
|
return BrandHeroScreen(
|
||||||
heroTitle: 'recovering.hetzner_connected'.tr(),
|
heroTitle: 'recovering.server_provider_connected'.tr(),
|
||||||
heroSubtitle: 'recovering.hetzner_connected_description'.tr(
|
heroSubtitle: 'recovering.server_provider_connected_description'.tr(
|
||||||
args: [appConfig.state.serverDomain?.domainName ?? 'your domain'],
|
args: [appConfig.state.serverDomain?.domainName ?? 'your domain'],
|
||||||
),
|
),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
|
@ -40,7 +40,8 @@ class RecoveryHetznerConnected extends StatelessWidget {
|
||||||
formFieldCubit: context.read<ProviderFormCubit>().apiKey,
|
formFieldCubit: context.read<ProviderFormCubit>().apiKey,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: 'recovering.hetzner_connected_placeholder'.tr(),
|
labelText:
|
||||||
|
'recovering.server_provider_connected_placeholder'.tr(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
|
@ -101,10 +101,10 @@ class StringGenerators {
|
||||||
hasSymbols: true,
|
hasSymbols: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
static StringGeneratorFunction dbStorageName = () => getRandomString(
|
static StringGeneratorFunction storageName = () => getRandomString(
|
||||||
6,
|
6,
|
||||||
hasLowercaseLetters: true,
|
hasLowercaseLetters: true,
|
||||||
hasUppercaseLetters: true,
|
hasUppercaseLetters: false,
|
||||||
hasNumbers: true,
|
hasNumbers: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
32
pubspec.lock
|
@ -35,7 +35,7 @@ packages:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
version: "2.9.0"
|
||||||
auto_size_text:
|
auto_size_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -126,7 +126,7 @@ packages:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -147,7 +147,7 @@ packages:
|
||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -350,7 +350,7 @@ packages:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.1"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -631,12 +631,12 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.3"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.4"
|
version: "0.13.5"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -762,21 +762,21 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.11"
|
version: "0.12.12"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4"
|
version: "0.1.5"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -846,7 +846,7 @@ packages:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.1"
|
version: "1.8.2"
|
||||||
path_drawing:
|
path_drawing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1159,7 +1159,7 @@ packages:
|
||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.9.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1187,7 +1187,7 @@ packages:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
system_theme:
|
system_theme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1208,28 +1208,28 @@ packages:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
test:
|
test:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.21.1"
|
version: "1.21.4"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.9"
|
version: "0.4.12"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.13"
|
version: "0.4.16"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -33,6 +33,7 @@ dependencies:
|
||||||
gtk_theme_fl: ^0.0.1
|
gtk_theme_fl: ^0.0.1
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
|
http: ^0.13.5
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
ionicons: ^0.1.2
|
ionicons: ^0.1.2
|
||||||
json_annotation: ^4.6.0
|
json_annotation: ^4.6.0
|
||||||
|
|
Use final and declare the type. Implication for some reason ignores our GraphQL model and we cannot use properly parsedData without type declaration.