From ee160042f85b102ef0eee668f33a5b77bc7931cc Mon Sep 17 00:00:00 2001 From: NaiJi Date: Tue, 11 Oct 2022 16:43:12 +0000 Subject: [PATCH 01/52] feat(digital-ocean): Add Digital Ocean logo asset --- assets/images/logos/digital_ocean.png | Bin 0 -> 36947 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/logos/digital_ocean.png diff --git a/assets/images/logos/digital_ocean.png b/assets/images/logos/digital_ocean.png new file mode 100644 index 0000000000000000000000000000000000000000..04f5efd557c386f828a6aa90cc44119bd88b92d7 GIT binary patch literal 36947 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%0D(&a2J_ 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

fhZb)e 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 O0UftHiLJEpivJsKk2Wy? literal 0 HcmV?d00001 From 70330c59aba34df24ae30d9e69c82f6b329daf37 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Tue, 11 Oct 2022 20:11:13 +0000 Subject: [PATCH 02/52] feat(digital-ocean): Implement provider picker pages --- assets/translations/en.json | 3 +- assets/translations/ru.json | 2 +- .../rest_maps/api_factory_creator.dart | 5 + .../digital_ocean/digital_ocean.dart | 559 ++++++++++++++++++ .../digital_ocean/digital_ocean_factory.dart | 26 + .../server_providers/hetzner/hetzner.dart | 2 +- .../initializing/provider_form_cubit.dart | 4 +- .../server_installation_cubit.dart | 18 +- .../server_installation_repository.dart | 22 +- .../server_installation_state.dart | 2 +- lib/logic/get_it/api_config.dart | 12 +- lib/logic/models/hive/server_details.dart | 2 + lib/main.dart | 2 +- .../not_ready_card/not_ready_card.dart | 2 +- lib/ui/pages/more/more.dart | 2 +- .../{ => initializing}/initializing.dart | 49 +- .../setup/initializing/provider_picker.dart | 209 +++++++ .../setup/recovering/recovery_routing.dart | 2 +- 18 files changed, 843 insertions(+), 80 deletions(-) create mode 100644 lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart create mode 100644 lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart rename lib/ui/pages/setup/{ => initializing}/initializing.dart (92%) create mode 100644 lib/ui/pages/setup/initializing/provider_picker.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index a0ce11e6..bf8e4e2c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -264,9 +264,10 @@ }, "initializing": { "connect_to_server": "Connect a server", + "select_provider": "Select your provider", "place_where_data": "A place where your data and SelfPrivacy services will reside:", "how": "How to obtain API token", - "hetzner_bad_key_error": "Hetzner API key is invalid", + "provider_bad_key_error": "Provider API key is invalid", "cloudflare_bad_key_error": "Cloudflare API key is invalid", "backblaze_bad_key_error": "Backblaze storage information is invalid", "connect_cloudflare": "Connect CloudFlare", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 04b832da..604edf35 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -266,7 +266,7 @@ "connect_to_server": "Подключите сервер", "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", "how": "Как получить API Token", - "hetzner_bad_key_error": "Hetzner API ключ неверен", + "provider_bad_key_error": "API ключ провайдера неверен", "cloudflare_bad_key_error": "Cloudflare API ключ неверен", "backblaze_bad_key_error": "Информация о Backblaze хранилище неверна", "connect_cloudflare": "Подключите CloudFlare", diff --git a/lib/logic/api_maps/rest_maps/api_factory_creator.dart b/lib/logic/api_maps/rest_maps/api_factory_creator.dart index 18b4ea33..a144c647 100644 --- a/lib/logic/api_maps/rest_maps/api_factory_creator.dart +++ b/lib/logic/api_maps/rest_maps/api_factory_creator.dart @@ -1,5 +1,6 @@ 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/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/server_provider_factory.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; @@ -17,6 +18,8 @@ class ApiFactoryCreator { switch (provider) { case ServerProvider.hetzner: return HetznerApiFactory(); + case ServerProvider.digitalOcean: + return DigitalOceanApiFactory(); case ServerProvider.unknown: throw UnknownApiProviderException('Unknown server provider'); } @@ -41,6 +44,8 @@ class VolumeApiFactoryCreator { switch (provider) { case ServerProvider.hetzner: return HetznerApiFactory(); + case ServerProvider.digitalOcean: + return DigitalOceanApiFactory(); case ServerProvider.unknown: throw UnknownApiProviderException('Unknown volume provider'); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart new file mode 100644 index 00000000..159b68d8 --- /dev/null +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -0,0 +1,559 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.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/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; + +class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { + DigitalOceanApi( + {final this.hasLogger = false, final this.isWithToken = true}); + @override + bool hasLogger; + @override + bool isWithToken; + + @override + BaseOptions get options { + final BaseOptions options = BaseOptions(baseUrl: rootAddress); + if (isWithToken) { + final String? token = getIt().serverProviderKey; + assert(token != null); + options.headers = {'Authorization': 'Bearer $token'}; + } + + if (validateStatus != null) { + options.validateStatus = validateStatus!; + } + + return options; + } + + @override + String rootAddress = 'https://api.digitalocean.com/v2'; + + @override + Future isApiTokenValid(final String token) async { + bool isValid = false; + Response? response; + final Dio client = await getClient(); + try { + response = await client.get( + '/servers', + 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; + } + + @override + RegExp getApiTokenValidation() => + RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); + + @override + Future getPricePerGb() async { + double? price; + + final Response dbGetResponse; + final Dio client = await getClient(); + try { + dbGetResponse = await client.get('/pricing'); + + final volume = dbGetResponse.data['pricing']['volume']; + final volumePrice = volume['price_per_gb_month']['gross']; + price = double.parse(volumePrice); + } catch (e) { + print(e); + } finally { + client.close(); + } + + return price; + } + + @override + Future createVolume() async { + ServerVolume? volume; + + final Response dbCreateResponse; + final Dio client = await getClient(); + try { + dbCreateResponse = await client.post( + '/volumes', + data: { + 'size': 10, + 'name': StringGenerators.dbStorageName(), + 'labels': {'labelkey': 'value'}, + 'location': 'fsn1', + 'automount': false, + 'format': 'ext4' + }, + ); + final dbId = dbCreateResponse.data['volume']['id']; + final dbSize = dbCreateResponse.data['volume']['size']; + final dbServer = dbCreateResponse.data['volume']['server']; + final dbName = dbCreateResponse.data['volume']['name']; + final dbDevice = dbCreateResponse.data['volume']['linux_device']; + volume = ServerVolume( + id: dbId, + name: dbName, + sizeByte: dbSize, + serverId: dbServer, + linuxDevice: dbDevice, + ); + } catch (e) { + print(e); + } finally { + client.close(); + } + + return volume; + } + + @override + Future> getVolumes({final String? status}) async { + final List volumes = []; + + final Response dbGetResponse; + final Dio client = await getClient(); + try { + dbGetResponse = await client.get( + '/volumes', + queryParameters: { + 'status': status, + }, + ); + final List rawVolumes = dbGetResponse.data['volumes']; + for (final rawVolume in rawVolumes) { + final int dbId = rawVolume['id']; + final int dbSize = rawVolume['size'] * 1024 * 1024 * 1024; + final dbServer = rawVolume['server']; + final String dbName = rawVolume['name']; + final dbDevice = rawVolume['linux_device']; + final volume = ServerVolume( + id: dbId, + name: dbName, + sizeByte: dbSize, + serverId: dbServer, + linuxDevice: dbDevice, + ); + volumes.add(volume); + } + } catch (e) { + print(e); + } finally { + client.close(); + } + + return volumes; + } + + @override + Future getVolume(final int id) async { + ServerVolume? volume; + + final Response dbGetResponse; + final Dio client = await getClient(); + try { + dbGetResponse = await client.get('/volumes/$id'); + final int dbId = dbGetResponse.data['volume']['id']; + final int dbSize = dbGetResponse.data['volume']['size']; + final int dbServer = dbGetResponse.data['volume']['server']; + final String dbName = dbGetResponse.data['volume']['name']; + final dbDevice = dbGetResponse.data['volume']['linux_device']; + volume = ServerVolume( + id: dbId, + name: dbName, + sizeByte: dbSize, + serverId: dbServer, + linuxDevice: dbDevice, + ); + } catch (e) { + print(e); + } finally { + client.close(); + } + + return volume; + } + + @override + Future deleteVolume(final int id) async { + final Dio client = await getClient(); + try { + await client.delete('/volumes/$id'); + } catch (e) { + print(e); + } finally { + client.close(); + } + } + + @override + Future attachVolume(final int volumeId, final int serverId) async { + bool success = false; + + final Response dbPostResponse; + final Dio client = await getClient(); + try { + dbPostResponse = await client.post( + '/volumes/$volumeId/actions/attach', + data: { + 'automount': true, + 'server': serverId, + }, + ); + success = dbPostResponse.data['action']['status'].toString() != 'error'; + } catch (e) { + print(e); + } finally { + client.close(); + } + + return success; + } + + @override + Future detachVolume(final int volumeId) async { + bool success = false; + + final Response dbPostResponse; + final Dio client = await getClient(); + try { + dbPostResponse = await client.post('/volumes/$volumeId/actions/detach'); + success = dbPostResponse.data['action']['status'].toString() != 'error'; + } catch (e) { + print(e); + } finally { + client.close(); + } + + return success; + } + + @override + Future resizeVolume(final int volumeId, final int sizeGb) async { + bool success = false; + + final Response dbPostResponse; + final Dio client = await getClient(); + try { + dbPostResponse = await client.post( + '/volumes/$volumeId/actions/resize', + data: { + 'size': sizeGb, + }, + ); + success = dbPostResponse.data['action']['status'].toString() != 'error'; + } catch (e) { + print(e); + } finally { + client.close(); + } + + return success; + } + + @override + Future createServer({ + required final String dnsApiToken, + required final User rootUser, + required final String domainName, + }) async { + ServerHostingDetails? details; + + final ServerVolume? newVolume = await createVolume(); + if (newVolume == null) { + return details; + } + + details = await createServerWithVolume( + dnsApiToken: dnsApiToken, + rootUser: rootUser, + domainName: domainName, + dataBase: newVolume, + ); + + return details; + } + + Future createServerWithVolume({ + required final String dnsApiToken, + required final User rootUser, + required final String domainName, + required final ServerVolume dataBase, + }) async { + final Dio client = await getClient(); + + final String dbPassword = StringGenerators.dbPassword(); + final int dbId = dataBase.id; + + final String apiToken = StringGenerators.apiToken(); + + final String hostname = getHostnameFromDomain(domainName); + + final String base64Password = + 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 = + "#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"; + print(userdataString); + + final Map 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; + DioError? hetznerError; + bool success = false; + + try { + final Response serverCreateResponse = await client.post( + '/servers', + data: data, + ); + print(serverCreateResponse.data); + serverDetails = ServerHostingDetails( + id: serverCreateResponse.data['server']['id'], + ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'], + createTime: DateTime.now(), + volume: dataBase, + apiToken: apiToken, + provider: ServerProvider.hetzner, + ); + success = true; + } on DioError catch (e) { + print(e); + hetznerError = e; + } catch (e) { + print(e); + } finally { + client.close(); + } + + if (!success) { + await Future.delayed(const Duration(seconds: 10)); + await deleteVolume(dbId); + } + + if (hetznerError != null) { + throw hetznerError; + } + + return serverDetails; + } + + 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 deleteServer({ + required final String domainName, + }) async { + final Dio client = await getClient(); + + final String hostname = getHostnameFromDomain(domainName); + + final Response serversReponse = await client.get('/servers'); + final List servers = serversReponse.data['servers']; + final Map server = servers.firstWhere((final el) => el['name'] == hostname); + final List volumes = server['volumes']; + final List laterFutures = []; + + for (final volumeId in volumes) { + await client.post('/volumes/$volumeId/actions/detach'); + } + await Future.delayed(const Duration(seconds: 10)); + + for (final volumeId in volumes) { + laterFutures.add(client.delete('/volumes/$volumeId')); + } + laterFutures.add(client.delete('/servers/${server['id']}')); + + await Future.wait(laterFutures); + close(client); + } + + @override + Future restart() async { + final ServerHostingDetails server = getIt().serverDetails!; + + final Dio client = await getClient(); + try { + await client.post('/servers/${server.id}/actions/reset'); + } catch (e) { + print(e); + } finally { + close(client); + } + + return server.copyWith(startTime: DateTime.now()); + } + + @override + Future powerOn() async { + final ServerHostingDetails server = getIt().serverDetails!; + + final Dio client = await getClient(); + try { + await client.post('/servers/${server.id}/actions/poweron'); + } catch (e) { + print(e); + } finally { + close(client); + } + + return server.copyWith(startTime: DateTime.now()); + } + + Future> getMetrics( + final DateTime start, + final DateTime end, + final String type, + ) async { + final ServerHostingDetails? hetznerServer = + getIt().serverDetails; + + Map metrics = {}; + final Dio client = await getClient(); + try { + final Map queryParameters = { + 'start': start.toUtc().toIso8601String(), + 'end': end.toUtc().toIso8601String(), + 'type': type + }; + final Response res = await client.get( + '/servers/${hetznerServer!.id}/metrics', + queryParameters: queryParameters, + ); + metrics = res.data; + } catch (e) { + print(e); + } finally { + close(client); + } + + return metrics; + } + + Future getInfo() async { + final ServerHostingDetails? hetznerServer = + getIt().serverDetails; + final Dio client = await getClient(); + final Response response = await client.get('/servers/${hetznerServer!.id}'); + close(client); + + return HetznerServerInfo.fromJson(response.data!['server']); + } + + @override + Future> getServers() async { + List servers = []; + + final Dio client = await getClient(); + try { + final Response response = await client.get('/servers'); + servers = response.data!['servers'] + .map( + (final e) => HetznerServerInfo.fromJson(e), + ) + .toList() + .where( + (final server) => server.publicNet.ipv4 != null, + ) + .map( + (final server) => ServerBasicInfo( + id: server.id, + name: server.name, + ip: server.publicNet.ipv4.ip, + reverseDns: server.publicNet.ipv4.reverseDns, + created: server.created, + volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0, + ), + ) + .toList(); + } catch (e) { + print(e); + } finally { + close(client); + } + + print(servers); + return servers; + } + + @override + Future createReverseDns({ + required final ServerHostingDetails serverDetails, + required final ServerDomain domain, + }) async { + final Dio client = await getClient(); + try { + await client.post( + '/servers/${serverDetails.id}/actions/change_dns_ptr', + data: { + 'ip': serverDetails.ip4, + 'dns_ptr': domain.domainName, + }, + ); + } catch (e) { + print(e); + } finally { + close(client); + } + } +} diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart new file mode 100644 index 00000000..565f7c84 --- /dev/null +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart @@ -0,0 +1,26 @@ +import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart'; +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_factory.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart'; + +class DigitalOceanApiFactory extends ServerProviderApiFactory + with VolumeProviderApiFactory { + @override + ServerProviderApi getServerProvider({ + final ProviderApiSettings settings = const ProviderApiSettings(), + }) => + DigitalOceanApi( + hasLogger: settings.hasLogger, + isWithToken: settings.isWithToken, + ); + + @override + VolumeProviderApi getVolumeProvider({ + final ProviderApiSettings settings = const ProviderApiSettings(), + }) => + DigitalOceanApi( + hasLogger: settings.hasLogger, + isWithToken: settings.isWithToken, + ); +} diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 8340676d..f26646ce 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -23,7 +23,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { BaseOptions get options { final BaseOptions options = BaseOptions(baseUrl: rootAddress); if (isWithToken) { - final String? token = getIt().hetznerKey; + final String? token = getIt().serverProviderKey; assert(token != null); options.headers = {'Authorization': 'Bearer $token'}; } diff --git a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart index 44f40b57..727daea8 100644 --- a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart @@ -26,7 +26,7 @@ class ProviderFormCubit extends FormCubit { @override FutureOr onSubmit() async { - serverInstallationCubit.setHetznerKey(apiKey.state.value); + serverInstallationCubit.setServerProviderKey(apiKey.state.value); } final ServerInstallationCubit serverInstallationCubit; @@ -45,7 +45,7 @@ class ProviderFormCubit extends FormCubit { } if (!isKeyValid) { - apiKey.setError('initializing.hetzner_bad_key_error'.tr()); + apiKey.setError('initializing.provider_bad_key_error'.tr()); return false; } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 9cb68359..b32fe79a 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; @@ -51,6 +52,13 @@ class ServerInstallationCubit extends Cubit { } } + void setServerProviderType(final ServerProvider providerType) { + repository.serverProviderApiFactory = + ApiFactoryCreator.createServerProviderApiFactory( + providerType, + ); + } + RegExp getServerProviderApiTokenValidation() => repository.serverProviderApiFactory! .getServerProvider() @@ -78,13 +86,13 @@ class ServerInstallationCubit extends Cubit { ) .isApiTokenValid(providerToken); - void setHetznerKey(final String hetznerKey) async { - await repository.saveHetznerKey(hetznerKey); + void setServerProviderKey(final String serverProviderKey) async { + await repository.saveServerProviderKey(serverProviderKey); if (state is ServerInstallationRecovery) { emit( (state as ServerInstallationRecovery).copyWith( - providerApiToken: hetznerKey, + providerApiToken: serverProviderKey, currentStep: RecoveryStep.serverSelection, ), ); @@ -93,7 +101,7 @@ class ServerInstallationCubit extends Cubit { emit( (state as ServerInstallationNotFinished).copyWith( - providerApiToken: hetznerKey, + providerApiToken: serverProviderKey, ), ); } @@ -427,7 +435,7 @@ class ServerInstallationCubit extends Cubit { emit( dataState.copyWith( serverDetails: serverDetails, - currentStep: RecoveryStep.hetznerToken, + currentStep: RecoveryStep.serverProviderToken, ), ); } on ServerAuthorizationException { diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index a83e9deb..39593b1f 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -39,17 +39,14 @@ class ServerAuthorizationException implements Exception { class ServerInstallationRepository { Box box = Hive.box(BNames.serverInstallationBox); Box usersBox = Hive.box(BNames.usersBox); - ServerProviderApiFactory? serverProviderApiFactory = - ApiFactoryCreator.createServerProviderApiFactory( - ServerProvider.hetzner, // TODO: HARDCODE FOR NOW!!! - ); // TODO: Remove when provider selection is implemented. + ServerProviderApiFactory? serverProviderApiFactory; DnsProviderApiFactory? dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory( DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!! ); Future load() async { - final String? providerApiToken = getIt().hetznerKey; + final String? providerApiToken = getIt().serverProviderKey; final String? cloudflareToken = getIt().cloudFlareKey; final ServerDomain? serverDomain = getIt().serverDomain; final BackblazeCredential? backblazeCredential = @@ -124,13 +121,13 @@ class ServerInstallationRepository { } RecoveryStep _getCurrentRecoveryStep( - final String? hetznerToken, + final String? serverProviderToken, final String? cloudflareToken, final ServerDomain serverDomain, final ServerHostingDetails? serverDetails, ) { if (serverDetails != null) { - if (hetznerToken != null) { + if (serverProviderToken != null) { if (serverDetails.provider != ServerProvider.unknown) { if (serverDomain.provider != DnsProvider.unknown) { return RecoveryStep.backblazeToken; @@ -139,7 +136,7 @@ class ServerInstallationRepository { } return RecoveryStep.serverSelection; } - return RecoveryStep.hetznerToken; + return RecoveryStep.serverProviderToken; } return RecoveryStep.selecting; } @@ -150,7 +147,7 @@ class ServerInstallationRepository { } Future startServer( - final ServerHostingDetails hetznerServer, + final ServerHostingDetails server, ) async { ServerHostingDetails serverDetails; @@ -670,12 +667,11 @@ class ServerInstallationRepository { getIt().init(); } - Future saveHetznerKey(final String key) async { - print('saved'); - await getIt().storeHetznerKey(key); + Future saveServerProviderKey(final String key) async { + await getIt().storeServerProviderKey(key); } - Future deleteHetznerKey() async { + Future deleteServerProviderKey() async { await box.delete(BNames.hetznerKey); getIt().init(); } diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index bb04c07d..82eda971 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -238,7 +238,7 @@ enum RecoveryStep { recoveryKey, newDeviceKey, oldToken, - hetznerToken, + serverProviderToken, serverSelection, cloudflareToken, backblazeToken, diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index ec2feb55..6a81141f 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -9,22 +9,22 @@ class ApiConfigModel { final Box _box = Hive.box(BNames.serverInstallationBox); ServerHostingDetails? get serverDetails => _serverDetails; - String? get hetznerKey => _hetznerKey; + String? get serverProviderKey => _serverProviderKey; String? get cloudFlareKey => _cloudFlareKey; BackblazeCredential? get backblazeCredential => _backblazeCredential; ServerDomain? get serverDomain => _serverDomain; BackblazeBucket? get backblazeBucket => _backblazeBucket; - String? _hetznerKey; + String? _serverProviderKey; String? _cloudFlareKey; ServerHostingDetails? _serverDetails; BackblazeCredential? _backblazeCredential; ServerDomain? _serverDomain; BackblazeBucket? _backblazeBucket; - Future storeHetznerKey(final String value) async { + Future storeServerProviderKey(final String value) async { await _box.put(BNames.hetznerKey, value); - _hetznerKey = value; + _serverProviderKey = value; } Future storeCloudFlareKey(final String value) async { @@ -53,7 +53,7 @@ class ApiConfigModel { } void clear() { - _hetznerKey = null; + _serverProviderKey = null; _cloudFlareKey = null; _backblazeCredential = null; _serverDomain = null; @@ -62,7 +62,7 @@ class ApiConfigModel { } void init() { - _hetznerKey = _box.get(BNames.hetznerKey); + _serverProviderKey = _box.get(BNames.hetznerKey); _cloudFlareKey = _box.get(BNames.cloudFlareKey); _backblazeCredential = _box.get(BNames.backblazeCredential); _serverDomain = _box.get(BNames.serverDomain); diff --git a/lib/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index 3791c664..27e9829a 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -78,4 +78,6 @@ enum ServerProvider { unknown, @HiveField(1) hetzner, + @HiveField(2) + digitalOcean, } diff --git a/lib/main.dart b/lib/main.dart index f2c36392..8b521f8a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/hive_config.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/root_route.dart'; import 'package:wakelock/wakelock.dart'; diff --git a/lib/ui/components/not_ready_card/not_ready_card.dart b/lib/ui/components/not_ready_card/not_ready_card.dart index faa23381..fededcaf 100644 --- a/lib/ui/components/not_ready_card/not_ready_card.dart +++ b/lib/ui/components/not_ready_card/not_ready_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.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:easy_localization/easy_localization.dart'; diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 5d487717..309746c5 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.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/recovery_key/recovery_key.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/root_route.dart'; import 'package:selfprivacy/ui/pages/users/users.dart'; diff --git a/lib/ui/pages/setup/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart similarity index 92% rename from lib/ui/pages/setup/initializing.dart rename to lib/ui/pages/setup/initializing/initializing.dart index 02af3a59..2904cfb8 100644 --- a/lib/ui/pages/setup/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -17,6 +17,7 @@ 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/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing/provider_picker.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; @@ -139,52 +140,8 @@ class InitializingPage extends StatelessWidget { } Widget _stepHetzner(final ServerInstallationCubit serverInstallationCubit) => - BlocProvider( - create: (final context) => ProviderFormCubit( - serverInstallationCubit, - ), - child: Builder( - builder: (final context) { - final formCubitState = context.watch().state; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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().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().trySubmit(), - text: 'basis.connect'.tr(), - ), - const SizedBox(height: 10), - BrandButton.text( - onPressed: () => _showModal( - context, - const _HowTo(fileName: 'how_hetzner'), - ), - title: 'initializing.how'.tr(), - ), - ], - ); - }, - ), + ProviderPicker( + serverInstallationCubit: serverInstallationCubit, ); void _showModal(final BuildContext context, final Widget widget) { diff --git a/lib/ui/pages/setup/initializing/provider_picker.dart b/lib/ui/pages/setup/initializing/provider_picker.dart new file mode 100644 index 00000000..c3cada82 --- /dev/null +++ b/lib/ui/pages/setup/initializing/provider_picker.dart @@ -0,0 +1,209 @@ +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 ProviderPicker extends StatefulWidget { + const ProviderPicker({ + required this.serverInstallationCubit, + super.key, + }); + + final ServerInstallationCubit serverInstallationCubit; + + @override + State createState() => _ProviderPickerState(); +} + +class _ProviderPickerState extends State { + 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( + callback: setProvider, + ); + + case ServerProvider.hetzner: + return ProviderInputDataPage( + serverInstallationCubit: widget.serverInstallationCubit, + providerInfo: ProviderPageInfo( + providerType: ServerProvider.hetzner, + pathToHow: 'hetzner_how', + image: Image.asset( + 'assets/images/logos/hetzner.png', + width: 150, + ), + ), + ); + + case ServerProvider.digitalOcean: + return ProviderInputDataPage( + serverInstallationCubit: widget.serverInstallationCubit, + 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.serverInstallationCubit, + super.key, + }); + + final ProviderPageInfo providerInfo; + final ServerInstallationCubit serverInstallationCubit; + + @override + Widget build(final BuildContext context) => BlocProvider( + create: (final context) => ProviderFormCubit( + serverInstallationCubit, + ), + child: Builder( + builder: (final context) { + final formCubitState = context.watch().state; + return 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: context.read().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: () => formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + ), + const SizedBox(height: 10), + OutlinedButton( + child: Text('initializing.how'.tr()), + onPressed: () => showModalBottomSheet( + 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, + super.key, + }); + + final Function callback; + + @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: () { + context.read().setServerProviderType(ServerProvider.hetzner); + callback(ServerProvider.hetzner); + }, + child: Image.asset( + 'assets/images/logos/hetzner.png', + width: 150, + ), + ), + const SizedBox( + width: 20, + ), + InkWell( + onTap: () { + context.read().setServerProviderType(ServerProvider.digitalOcean); + callback(ServerProvider.digitalOcean); + }, + child: Image.asset( + 'assets/images/logos/digital_ocean.png', + width: 150, + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/ui/pages/setup/recovering/recovery_routing.dart b/lib/ui/pages/setup/recovering/recovery_routing.dart index 028b8618..4442d2be 100644 --- a/lib/ui/pages/setup/recovering/recovery_routing.dart +++ b/lib/ui/pages/setup/recovering/recovery_routing.dart @@ -47,7 +47,7 @@ class RecoveryRouting extends StatelessWidget { case RecoveryStep.oldToken: currentPage = const RecoverByOldToken(); break; - case RecoveryStep.hetznerToken: + case RecoveryStep.serverProviderToken: currentPage = const RecoveryHetznerConnected(); break; case RecoveryStep.serverSelection: From 79e9334aca6fb1e7198c2987a99758bf80ebf757 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 12 Oct 2022 01:42:45 +0000 Subject: [PATCH 03/52] feat(pricing): Replace raw double with simple type Price --- .../digital_ocean/digital_ocean.dart | 38 ++++++------------- .../server_providers/hetzner/hetzner.dart | 10 ++++- .../server_providers/server_provider.dart | 4 +- .../server_providers/volume_provider.dart | 3 +- .../provider_volume_cubit.dart | 3 +- lib/logic/models/price.dart | 9 +++++ .../server_storage/extending_volume.dart | 3 +- .../setup/initializing/provider_picker.dart | 8 +++- 8 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 lib/logic/models/price.dart diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 159b68d8..16c87750 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -9,12 +9,15 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { - DigitalOceanApi( - {final this.hasLogger = false, final this.isWithToken = true}); + DigitalOceanApi({ + this.hasLogger = false, + this.isWithToken = true, + }); @override bool hasLogger; @override @@ -46,7 +49,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { response = await client.get( - '/servers', + '/account', options: Options( headers: {'Authorization': 'Bearer $token'}, ), @@ -71,30 +74,13 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return isValid; } + /// Hardcoded on their documentation and there is no pricing API at all + /// Probably we should scrap the doc page manually @override - RegExp getApiTokenValidation() => - RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); - - @override - Future getPricePerGb() async { - double? price; - - final Response dbGetResponse; - final Dio client = await getClient(); - try { - dbGetResponse = await client.get('/pricing'); - - final volume = dbGetResponse.data['pricing']['volume']; - final volumePrice = volume['price_per_gb_month']['gross']; - price = double.parse(volumePrice); - } catch (e) { - print(e); - } finally { - client.close(); - } - - return price; - } + Future getPricePerGb() async => Price( + value: 0.10, + currency: 'USD', + ); @override Future createVolume() async { diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index f26646ce..3b8ff016 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/utils/password_generator.dart'; @@ -75,7 +76,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); @override - Future getPricePerGb() async { + Future getPricePerGb() async { double? price; final Response dbGetResponse; @@ -92,7 +93,12 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { client.close(); } - return price; + return price == null + ? null + : Price( + value: price, + currency: 'EUR', + ); } @override diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 010ebd75..c2bfd533 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -22,5 +22,7 @@ abstract class ServerProviderApi extends ApiMap { }); Future isApiTokenValid(final String token); - RegExp getApiTokenValidation(); + RegExp getApiTokenValidation() => RegExp( + r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]', + ); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart index bdff72f2..d6feae2c 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart @@ -1,5 +1,6 @@ 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/price.dart'; mixin VolumeProviderApi on ApiMap { Future createVolume(); @@ -9,5 +10,5 @@ mixin VolumeProviderApi on ApiMap { Future detachVolume(final int volumeId); Future resizeVolume(final int volumeId, final int sizeGb); Future deleteVolume(final int id); - Future getPricePerGb(); + Future getPricePerGb(); } diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index b0500452..f6a2e7a6 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; +import 'package:selfprivacy/logic/models/price.dart'; part 'provider_volume_state.dart'; @@ -32,7 +33,7 @@ class ApiProviderVolumeCubit } } - Future getPricePerGb() async => + Future getPricePerGb() async => providerApi!.getVolumeProvider().getPricePerGb(); Future refresh() async { diff --git a/lib/logic/models/price.dart b/lib/logic/models/price.dart new file mode 100644 index 00000000..1da2677d --- /dev/null +++ b/lib/logic/models/price.dart @@ -0,0 +1,9 @@ +class Price { + Price({ + required this.value, + required this.currency, + }); + + double value; + String currency; +} diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index e465c2b2..8558c526 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -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/server_volumes/server_volume_cubit.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_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; @@ -67,7 +68,7 @@ class _ExtendingVolumePageState extends State { ], ); } - _euroPerGb = snapshot.data as double; + _euroPerGb = (snapshot.data as Price).value; _sizeController.text = _currentSliderGbValue.truncate().toString(); _priceController.text = (_euroPerGb * double.parse(_sizeController.text)) diff --git a/lib/ui/pages/setup/initializing/provider_picker.dart b/lib/ui/pages/setup/initializing/provider_picker.dart index c3cada82..6681a07b 100644 --- a/lib/ui/pages/setup/initializing/provider_picker.dart +++ b/lib/ui/pages/setup/initializing/provider_picker.dart @@ -180,7 +180,9 @@ class ProviderSelectionPage extends StatelessWidget { children: [ InkWell( onTap: () { - context.read().setServerProviderType(ServerProvider.hetzner); + context + .read() + .setServerProviderType(ServerProvider.hetzner); callback(ServerProvider.hetzner); }, child: Image.asset( @@ -193,7 +195,9 @@ class ProviderSelectionPage extends StatelessWidget { ), InkWell( onTap: () { - context.read().setServerProviderType(ServerProvider.digitalOcean); + context + .read() + .setServerProviderType(ServerProvider.digitalOcean); callback(ServerProvider.digitalOcean); }, child: Image.asset( From f40ed08b027512977d086d074f7f83c26886dc5d Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 12 Oct 2022 04:55:30 +0000 Subject: [PATCH 04/52] feat(volume): Implement volume endpoints for Digital Ocean volumeId type in VolumeApiProvider interfaces is now replaced with String from int to support Digital Ocean's UUID notation --- .../digital_ocean/digital_ocean.dart | 132 ++++++++++-------- .../server_providers/hetzner/hetzner.dart | 23 ++- .../server_providers/volume_provider.dart | 10 +- lib/logic/models/hive/server_details.dart | 3 + 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 16c87750..f24f49ca 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -23,6 +23,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @override bool isWithToken; + final String region = 'fra1'; + @override BaseOptions get options { final BaseOptions options = BaseOptions(baseUrl: rootAddress); @@ -92,25 +94,22 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { dbCreateResponse = await client.post( '/volumes', data: { - 'size': 10, + 'size_gigabytes': 10, 'name': StringGenerators.dbStorageName(), 'labels': {'labelkey': 'value'}, - 'location': 'fsn1', - 'automount': false, - 'format': 'ext4' + 'region': region, + 'filesystem_type': 'ext4', }, ); final dbId = dbCreateResponse.data['volume']['id']; - final dbSize = dbCreateResponse.data['volume']['size']; - final dbServer = dbCreateResponse.data['volume']['server']; + final dbSize = dbCreateResponse.data['volume']['size_gigabytes']; final dbName = dbCreateResponse.data['volume']['name']; - final dbDevice = dbCreateResponse.data['volume']['linux_device']; volume = ServerVolume( id: dbId, name: dbName, sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, + serverId: null, + linuxDevice: null, ); } catch (e) { print(e); @@ -135,18 +134,19 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { }, ); final List rawVolumes = dbGetResponse.data['volumes']; + int id = 0; for (final rawVolume in rawVolumes) { - final int dbId = rawVolume['id']; - final int dbSize = rawVolume['size'] * 1024 * 1024 * 1024; - final dbServer = rawVolume['server']; + final dbId = rawVolume['id']; + final int dbSize = rawVolume['size_gigabytes'] * 1024 * 1024 * 1024; + final dbDropletIds = rawVolume['droplet_ids']; final String dbName = rawVolume['name']; - final dbDevice = rawVolume['linux_device']; final volume = ServerVolume( - id: dbId, + id: id++, name: dbName, sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, + serverId: dbDropletIds.isNotEmpty ? dbDropletIds[0] : null, + linuxDevice: null, + uuid: dbId, ); volumes.add(volume); } @@ -160,39 +160,29 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future getVolume(final int id) async { - ServerVolume? volume; - final Response dbGetResponse; - final Dio client = await getClient(); - try { - dbGetResponse = await client.get('/volumes/$id'); - final int dbId = dbGetResponse.data['volume']['id']; - final int dbSize = dbGetResponse.data['volume']['size']; - final int dbServer = dbGetResponse.data['volume']['server']; - final String dbName = dbGetResponse.data['volume']['name']; - final dbDevice = dbGetResponse.data['volume']['linux_device']; - volume = ServerVolume( - id: dbId, - name: dbName, - sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, - ); - } catch (e) { - print(e); - } finally { - client.close(); + /// volumeId is storage's UUID for Digital Ocean + Future getVolume(final String volumeId) async { + ServerVolume? neededVolume; + + final List volumes = await getVolumes(); + + for (final volume in volumes) { + if (volume.uuid == volumeId) { + neededVolume = volume; + } } - return volume; + return neededVolume; } @override - Future deleteVolume(final int id) async { + + /// volumeId is storage's UUID for Digital Ocean + Future deleteVolume(final String volumeId) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$id'); + await client.delete('/volumes/$volumeId'); } catch (e) { print(e); } finally { @@ -201,20 +191,30 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future attachVolume(final int volumeId, final int serverId) async { + + /// volumeId is storage's UUID for Digital Ocean + Future attachVolume(final String volumeId, final int serverId) async { bool success = false; + final ServerVolume? volumeToAttach = await getVolume(volumeId); + if (volumeToAttach == null) { + return success; + } + final Response dbPostResponse; final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volumeId/actions/attach', + '/volumes/actions', data: { - 'automount': true, - 'server': serverId, + 'type': 'attach', + 'volume_name': volumeToAttach.name, + 'region': region, + 'droplet_id': serverId, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + dbPostResponse.data['action']['status'].toString() == 'completed'; } catch (e) { print(e); } finally { @@ -225,14 +225,29 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future detachVolume(final int volumeId) async { + + /// volumeId is storage's UUID for Digital Ocean + Future detachVolume(final String volumeId) async { bool success = false; + final ServerVolume? volumeToAttach = await getVolume(volumeId); + if (volumeToAttach == null) { + return success; + } + final Response dbPostResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post('/volumes/$volumeId/actions/detach'); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + dbPostResponse = await client.post( + '/volumes/actions', + data: { + 'type': 'detach', + 'droplet_id': volumeToAttach.serverId, + 'region': region, + }, + ); + success = + dbPostResponse.data['action']['status'].toString() == 'completed'; } catch (e) { print(e); } finally { @@ -243,19 +258,24 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future resizeVolume(final int volumeId, final int sizeGb) async { + + /// volumeId is storage's UUID for Digital Ocean + Future resizeVolume(final String volumeId, final int sizeGb) async { bool success = false; final Response dbPostResponse; final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volumeId/actions/resize', + '/volumes/actions', data: { - 'size': sizeGb, + 'type': 'resize', + 'size_gigabytes': sizeGb, + 'region': region, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + dbPostResponse.data['action']['status'].toString() == 'completed'; } catch (e) { print(e); } finally { @@ -300,17 +320,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final int dbId = dataBase.id; final String apiToken = StringGenerators.apiToken(); - final String hostname = getHostnameFromDomain(domainName); final String base64Password = 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 = "#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"; print(userdataString); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 3b8ff016..dceb5a47 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -179,13 +179,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future getVolume(final int id) async { + Future getVolume(final String volumeId) async { ServerVolume? volume; final Response dbGetResponse; final Dio client = await getClient(); try { - dbGetResponse = await client.get('/volumes/$id'); + dbGetResponse = await client.get('/volumes/$volumeId'); final int dbId = dbGetResponse.data['volume']['id']; final int dbSize = dbGetResponse.data['volume']['size']; final int dbServer = dbGetResponse.data['volume']['server']; @@ -208,10 +208,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future deleteVolume(final int id) async { + Future deleteVolume(final String volumeId) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$id'); + await client.delete('/volumes/$volumeId'); } catch (e) { print(e); } finally { @@ -220,7 +220,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future attachVolume(final int volumeId, final int serverId) async { + Future attachVolume(final String volumeId, final int serverId) async { bool success = false; final Response dbPostResponse; @@ -244,7 +244,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future detachVolume(final int volumeId) async { + Future detachVolume(final String volumeId) async { bool success = false; final Response dbPostResponse; @@ -262,7 +262,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future resizeVolume(final int volumeId, final int sizeGb) async { + Future resizeVolume(final String volumeId, final int sizeGb) async { bool success = false; final Response dbPostResponse; @@ -319,20 +319,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final int dbId = dataBase.id; final String apiToken = StringGenerators.apiToken(); - final String hostname = getHostnameFromDomain(domainName); final String base64Password = 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 = "#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"; - print(userdataString); final Map data = { 'name': hostname, @@ -378,7 +371,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { if (!success) { await Future.delayed(const Duration(seconds: 10)); - await deleteVolume(dbId); + await deleteVolume(dbId.toString()); } if (hetznerError != null) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart index d6feae2c..52458217 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart @@ -5,10 +5,10 @@ import 'package:selfprivacy/logic/models/price.dart'; mixin VolumeProviderApi on ApiMap { Future createVolume(); Future> getVolumes({final String? status}); - Future getVolume(final int id); - Future attachVolume(final int volumeId, final int serverId); - Future detachVolume(final int volumeId); - Future resizeVolume(final int volumeId, final int sizeGb); - Future deleteVolume(final int id); + Future getVolume(final String volumeId); + Future attachVolume(final String volumeId, final int serverId); + Future detachVolume(final String volumeId); + Future resizeVolume(final String volumeId, final int sizeGb); + Future deleteVolume(final String volumeId); Future getPricePerGb(); } diff --git a/lib/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index 27e9829a..faaf37b4 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -58,6 +58,7 @@ class ServerVolume { required this.sizeByte, required this.serverId, required this.linuxDevice, + this.uuid, }); @HiveField(1) @@ -70,6 +71,8 @@ class ServerVolume { int? serverId; @HiveField(5, defaultValue: null) String? linuxDevice; + @HiveField(6, defaultValue: null) + String? uuid; } @HiveType(typeId: 101) From 2f599546413f663ba5c58a497444be38dfcff026 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 13 Oct 2022 21:15:42 +0000 Subject: [PATCH 05/52] feat(region): Remove hardcode for region of server installation --- .../cloudflare/cloudflare_factory.dart | 1 + .../dns_provider_api_settings.dart | 10 ++++++++ .../dns_providers/dns_provider_factory.dart | 13 ++--------- .../rest_maps/provider_api_settings.dart | 5 +++- .../digital_ocean/digital_ocean.dart | 8 ++++--- .../digital_ocean/digital_ocean_factory.dart | 8 ++++--- .../server_providers/hetzner/hetzner.dart | 8 ++++--- .../hetzner/hetzner_factory.dart | 8 ++++--- .../server_provider_api_settings.dart | 11 +++++++++ .../server_provider_factory.dart | 6 ++--- .../hetzner_metrics_repository.dart | 2 +- .../provider_volume_cubit.dart | 23 ++++++++++--------- .../server_detailed_info_repository.dart | 2 +- .../server_installation_cubit.dart | 6 +++-- .../server_installation_repository.dart | 16 +++++++------ 15 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart create mode 100644 lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart index 9266471b..ccb58e6a 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart @@ -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/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'; class CloudflareApiFactory extends DnsProviderApiFactory { diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart new file mode 100644 index 00000000..6b737df5 --- /dev/null +++ b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart @@ -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; +} diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart index 01f59e98..fb573135 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart @@ -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/provider_api_settings.dart'; - -class DnsProviderApiSettings extends ProviderApiSettings { - const DnsProviderApiSettings({ - final super.hasLogger = false, - final super.isWithToken = true, - final this.customToken, - }); - final String? customToken; -} +import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart'; abstract class DnsProviderApiFactory { DnsProviderApi getDnsProvider({ - final DnsProviderApiSettings settings = const DnsProviderApiSettings(), + final DnsProviderApiSettings settings, }); } diff --git a/lib/logic/api_maps/rest_maps/provider_api_settings.dart b/lib/logic/api_maps/rest_maps/provider_api_settings.dart index 4350fbe7..9e601d2a 100644 --- a/lib/logic/api_maps/rest_maps/provider_api_settings.dart +++ b/lib/logic/api_maps/rest_maps/provider_api_settings.dart @@ -1,5 +1,8 @@ class ProviderApiSettings { - const ProviderApiSettings({this.hasLogger = false, this.isWithToken = true}); + const ProviderApiSettings({ + this.hasLogger = false, + this.isWithToken = true, + }); final bool hasLogger; final bool isWithToken; } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index f24f49ca..2354e5ab 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -15,6 +15,7 @@ import 'package:selfprivacy/utils/password_generator.dart'; class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { DigitalOceanApi({ + required this.region, this.hasLogger = false, this.isWithToken = true, }); @@ -23,7 +24,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @override bool isWithToken; - final String region = 'fra1'; + final String region; @override BaseOptions get options { @@ -318,6 +319,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String dbPassword = StringGenerators.dbPassword(); final int dbId = dataBase.id; + final String? dbUuid = dataBase.uuid; final String apiToken = StringGenerators.apiToken(); final String hostname = getHostnameFromDomain(domainName); @@ -334,7 +336,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'server_type': 'cx11', 'start_after_create': false, 'image': 'ubuntu-20.04', - 'volumes': [dbId], + 'volumes': dbUuid == null ? [dbId] : [dbUuid], 'networks': [], 'user_data': userdataString, 'labels': {}, @@ -373,7 +375,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { if (!success) { await Future.delayed(const Duration(seconds: 10)); - await deleteVolume(dbId); + await deleteVolume(dbUuid ?? dbId.toString()); } if (hetznerError != null) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart index 565f7c84..7508e04e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart @@ -1,6 +1,6 @@ -import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart'; 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'; @@ -8,18 +8,20 @@ class DigitalOceanApiFactory extends ServerProviderApiFactory with VolumeProviderApiFactory { @override ServerProviderApi getServerProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }) => DigitalOceanApi( + region: settings.region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); @override VolumeProviderApi getVolumeProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }) => DigitalOceanApi( + region: settings.region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index dceb5a47..fe577c03 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -14,12 +14,14 @@ import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class HetznerApi extends ServerProviderApi with VolumeProviderApi { - HetznerApi({final this.hasLogger = false, final this.isWithToken = true}); + HetznerApi({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); @@ -114,7 +116,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 'size': 10, 'name': StringGenerators.dbStorageName(), 'labels': {'labelkey': 'value'}, - 'location': 'fsn1', + 'location': region, 'automount': false, 'format': 'ext4' }, @@ -337,7 +339,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 'user_data': userdataString, 'labels': {}, 'automount': true, - 'location': 'fsn1' + 'location': region, }; print('Decoded data: $data'); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart index 60f61d1b..f303966b 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart @@ -1,6 +1,6 @@ -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/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'; @@ -8,18 +8,20 @@ class HetznerApiFactory extends ServerProviderApiFactory with VolumeProviderApiFactory { @override ServerProviderApi getServerProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }) => HetznerApi( + region: settings.region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); @override VolumeProviderApi getVolumeProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }) => HetznerApi( + region: settings.region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart new file mode 100644 index 00000000..b1513bf6 --- /dev/null +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart @@ -0,0 +1,11 @@ +import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart'; + +class ServerProviderApiSettings extends ProviderApiSettings { + const ServerProviderApiSettings({ + required this.region, + super.hasLogger = false, + super.isWithToken = true, + }); + + final String region; +} diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart index 10f4c40f..4fad8797 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart @@ -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_api_settings.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart'; abstract class ServerProviderApiFactory { ServerProviderApi getServerProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }); } mixin VolumeProviderApiFactory { VolumeProviderApi getVolumeProvider({ - final ProviderApiSettings settings = const ProviderApiSettings(), + required final ServerProviderApiSettings settings, }); } diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart index 134c955e..5b363406 100644 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart +++ b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart @@ -26,7 +26,7 @@ class HetznerMetricsRepository { break; } - final HetznerApi api = HetznerApi(hasLogger: false); + final HetznerApi api = HetznerApi(hasLogger: false, region: 'fra1',); final List> results = await Future.wait([ api.getMetrics(start, end, 'cpu'), diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index f6a2e7a6..1eb47bf3 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.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/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; @@ -34,7 +35,7 @@ class ApiProviderVolumeCubit } Future getPricePerGb() async => - providerApi!.getVolumeProvider().getPricePerGb(); + providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getPricePerGb(); Future refresh() async { emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false)); @@ -47,7 +48,7 @@ class ApiProviderVolumeCubit } final List volumes = - await providerApi!.getVolumeProvider().getVolumes(); + await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getVolumes(); if (volumes.isEmpty) { return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); @@ -59,15 +60,15 @@ class ApiProviderVolumeCubit Future attachVolume(final DiskVolume volume) async { final ServerHostingDetails server = getIt().serverDetails!; await providerApi! - .getVolumeProvider() - .attachVolume(volume.providerVolume!.id, server.id); + .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .attachVolume(volume.providerVolume!.id.toString(), server.id); refresh(); } Future detachVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider() - .detachVolume(volume.providerVolume!.id); + .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .detachVolume(volume.providerVolume!.id.toString()); refresh(); } @@ -80,8 +81,8 @@ class ApiProviderVolumeCubit 'Starting resize', ); emit(state.copyWith(isResizing: true)); - final bool resized = await providerApi!.getVolumeProvider().resizeVolume( - volume.providerVolume!.id, + final bool resized = await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).resizeVolume( + volume.providerVolume!.id.toString(), newSizeGb, ); @@ -117,7 +118,7 @@ class ApiProviderVolumeCubit Future createVolume() async { final ServerVolume? volume = - await providerApi!.getVolumeProvider().createVolume(); + await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).createVolume(); final diskVolume = DiskVolume(providerVolume: volume); await attachVolume(diskVolume); @@ -130,8 +131,8 @@ class ApiProviderVolumeCubit Future deleteVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider() - .deleteVolume(volume.providerVolume!.id); + .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .deleteVolume(volume.providerVolume!.id.toString()); refresh(); } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index dada896b..d4149f61 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; class ServerDetailsRepository { - HetznerApi hetzner = HetznerApi(); + HetznerApi hetzner = HetznerApi(region: 'fra1'); ServerApi server = ServerApi(); Future load() async { diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index b32fe79a..8e121044 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -5,8 +5,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.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/provider_api_settings.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/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; @@ -61,7 +63,7 @@ class ServerInstallationCubit extends Cubit { RegExp getServerProviderApiTokenValidation() => repository.serverProviderApiFactory! - .getServerProvider() + .getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) .getApiTokenValidation(); RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory! @@ -73,7 +75,7 @@ class ServerInstallationCubit extends Cubit { ) async => repository.serverProviderApiFactory! .getServerProvider( - settings: const ProviderApiSettings(isWithToken: false), + settings: const ServerProviderApiSettings(region: 'fra1', isWithToken: false), ) .isApiTokenValid(providerToken); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 39593b1f..10306c61 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -11,9 +11,11 @@ import 'package:selfprivacy/config/get_it_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/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/server.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/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; @@ -151,7 +153,7 @@ class ServerInstallationRepository { ) async { ServerHostingDetails serverDetails; - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); serverDetails = await api.powerOn(); return serverDetails; @@ -227,7 +229,7 @@ class ServerInstallationRepository { required final Future Function(ServerHostingDetails serverDetails) onSuccess, }) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); try { final ServerHostingDetails? serverDetails = await api.createServer( dnsApiToken: cloudFlareKey, @@ -332,7 +334,7 @@ class ServerInstallationRepository { final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); final ServerProviderApi serverApi = - serverProviderApiFactory!.getServerProvider(); + serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); await dnsProviderApi.removeSimilarRecords( ip4: serverDetails.ip4, @@ -404,12 +406,12 @@ class ServerInstallationRepository { } Future restart() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); return api.restart(); } Future powerOn() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); return api.powerOn(); } @@ -652,7 +654,7 @@ class ServerInstallationRepository { } Future> getServersOnProviderAccount() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); return api.getServers(); } @@ -730,7 +732,7 @@ class ServerInstallationRepository { } Future deleteServer(final ServerDomain serverDomain) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); From e032bd8a7880eb5e8292be55ff2fffdb391705f4 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 13 Oct 2022 23:13:56 +0000 Subject: [PATCH 06/52] feat(region): Implement endpoints for listing available provider regions --- .../digital_ocean/digital_ocean.dart | 64 +++++++++++++++++++ .../server_providers/hetzner/hetzner.dart | 53 ++++++++++++++- .../server_providers/server_provider.dart | 2 + .../hetzner_metrics_repository.dart | 5 +- .../provider_volume_cubit.dart | 53 ++++++++++++--- .../server_installation_cubit.dart | 9 ++- .../server_installation_repository.dart | 42 ++++++++++-- .../models/server_provider_location.dart | 13 ++++ 8 files changed, 220 insertions(+), 21 deletions(-) create mode 100644 lib/logic/models/server_provider_location.dart diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 2354e5ab..798fc54b 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -11,6 +11,7 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @@ -538,6 +539,69 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return servers; } + String? getEmojiFlag(final String query) { + String? emoji; + + switch (query.toLowerCase().substring(0, 2)) { + 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> getAvailableLocations() async { + List locations = []; + + final Dio client = await getClient(); + try { + final Response response = await client.post( + '/locations', + ); + + locations = response.data!['locations'].map( + (final location) => ServerProviderLocation( + title: location['slug'], + description: location['name'], + flag: getEmojiFlag(location['slug']), + ), + ); + } catch (e) { + print(e); + } finally { + close(client); + } + + return locations; + } + @override Future createReverseDns({ required final ServerHostingDetails serverDetails, diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index fe577c03..2c0b70db 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -11,10 +11,15 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class HetznerApi extends ServerProviderApi with VolumeProviderApi { - HetznerApi({required this.region, this.hasLogger = false, this.isWithToken = true,}); + HetznerApi({ + required this.region, + this.hasLogger = false, + this.isWithToken = true, + }); @override bool hasLogger; @override @@ -536,6 +541,52 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 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> getAvailableLocations() async { + List locations = []; + + final Dio client = await getClient(); + try { + final Response response = await client.post( + '/locations', + ); + + locations = response.data!['locations'].map( + (final location) => ServerProviderLocation( + title: location['city'], + description: location['description'], + flag: getEmojiFlag(location['country']), + ), + ); + } catch (e) { + print(e); + } finally { + close(client); + } + + return locations; + } + @override Future createReverseDns({ required final ServerHostingDetails serverDetails, diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index c2bfd533..d4b61721 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -3,9 +3,11 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/logic/models/server_provider_location.dart'; abstract class ServerProviderApi extends ApiMap { Future> getServers(); + Future> getAvailableLocations(); Future restart(); Future powerOn(); diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart index 5b363406..18137fdc 100644 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart +++ b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart @@ -26,7 +26,10 @@ class HetznerMetricsRepository { break; } - final HetznerApi api = HetznerApi(hasLogger: false, region: 'fra1',); + final HetznerApi api = HetznerApi( + hasLogger: false, + region: 'fra1', + ); final List> results = await Future.wait([ api.getMetrics(start, end, 'cpu'), diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 1eb47bf3..95b0d362 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -34,8 +34,13 @@ class ApiProviderVolumeCubit } } - Future getPricePerGb() async => - providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getPricePerGb(); + Future getPricePerGb() async => providerApi! + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) + .getPricePerGb(); Future refresh() async { emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false)); @@ -47,8 +52,13 @@ class ApiProviderVolumeCubit return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); } - final List volumes = - await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getVolumes(); + final List volumes = await providerApi! + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) + .getVolumes(); if (volumes.isEmpty) { return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); @@ -60,14 +70,22 @@ class ApiProviderVolumeCubit Future attachVolume(final DiskVolume volume) async { final ServerHostingDetails server = getIt().serverDetails!; await providerApi! - .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) .attachVolume(volume.providerVolume!.id.toString(), server.id); refresh(); } Future detachVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) .detachVolume(volume.providerVolume!.id.toString()); refresh(); } @@ -81,7 +99,13 @@ class ApiProviderVolumeCubit 'Starting resize', ); emit(state.copyWith(isResizing: true)); - final bool resized = await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).resizeVolume( + final bool resized = await providerApi! + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) + .resizeVolume( volume.providerVolume!.id.toString(), newSizeGb, ); @@ -117,8 +141,13 @@ class ApiProviderVolumeCubit } Future createVolume() async { - final ServerVolume? volume = - await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).createVolume(); + final ServerVolume? volume = await providerApi! + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) + .createVolume(); final diskVolume = DiskVolume(providerVolume: volume); await attachVolume(diskVolume); @@ -131,7 +160,11 @@ class ApiProviderVolumeCubit Future deleteVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .getVolumeProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) .deleteVolume(volume.providerVolume!.id.toString()); refresh(); } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 8e121044..99dd1d82 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -63,7 +63,11 @@ class ServerInstallationCubit extends Cubit { RegExp getServerProviderApiTokenValidation() => repository.serverProviderApiFactory! - .getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),) + .getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ) .getApiTokenValidation(); RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory! @@ -75,7 +79,8 @@ class ServerInstallationCubit extends Cubit { ) async => repository.serverProviderApiFactory! .getServerProvider( - settings: const ServerProviderApiSettings(region: 'fra1', isWithToken: false), + settings: const ServerProviderApiSettings( + region: 'fra1', isWithToken: false), ) .isApiTokenValid(providerToken); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 10306c61..4fe10b1f 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -153,7 +153,11 @@ class ServerInstallationRepository { ) async { ServerHostingDetails serverDetails; - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); serverDetails = await api.powerOn(); return serverDetails; @@ -229,7 +233,11 @@ class ServerInstallationRepository { required final Future Function(ServerHostingDetails serverDetails) onSuccess, }) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); try { final ServerHostingDetails? serverDetails = await api.createServer( dnsApiToken: cloudFlareKey, @@ -334,7 +342,11 @@ class ServerInstallationRepository { final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); final ServerProviderApi serverApi = - serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); await dnsProviderApi.removeSimilarRecords( ip4: serverDetails.ip4, @@ -406,12 +418,20 @@ class ServerInstallationRepository { } Future restart() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); return api.restart(); } Future powerOn() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); return api.powerOn(); } @@ -654,7 +674,11 @@ class ServerInstallationRepository { } Future> getServersOnProviderAccount() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); return api.getServers(); } @@ -732,7 +756,11 @@ class ServerInstallationRepository { } Future deleteServer(final ServerDomain serverDomain) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( + settings: const ServerProviderApiSettings( + region: 'fra1', + ), + ); final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); diff --git a/lib/logic/models/server_provider_location.dart b/lib/logic/models/server_provider_location.dart new file mode 100644 index 00000000..dea063a4 --- /dev/null +++ b/lib/logic/models/server_provider_location.dart @@ -0,0 +1,13 @@ +class ServerProviderLocation { + ServerProviderLocation({ + required this.title, + this.description, + this.flag, + }); + + final String title; + final String? description; + + /// as emoji + final String? flag; +} From b30e3723228cf14ba1fdafdb1485e3dafa2fe003 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 14 Oct 2022 19:00:44 +0000 Subject: [PATCH 07/52] feat(region): Implement endpoints for listing available types by region --- .../digital_ocean/digital_ocean.dart | 45 ++++++++++++++++++- .../server_providers/hetzner/hetzner.dart | 45 ++++++++++++++++++- .../server_providers/server_provider.dart | 4 ++ .../models/server_provider_location.dart | 2 + lib/logic/models/server_type.dart | 19 ++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 lib/logic/models/server_type.dart diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 798fc54b..67811f26 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -5,6 +5,7 @@ import 'package:dio/dio.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/models/disk_size.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/hive/server_details.dart'; @@ -12,6 +13,7 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @@ -582,7 +584,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - final Response response = await client.post( + final Response response = await client.get( '/locations', ); @@ -591,6 +593,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { title: location['slug'], description: location['name'], flag: getEmojiFlag(location['slug']), + identifier: location['slug'], ), ); } catch (e) { @@ -602,6 +605,46 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return locations; } + @override + Future> getServerTypesByLocation({ + required final ServerProviderLocation location, + }) async { + final List 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'], + cores: rawSize['vcpus'], + disk: DiskSize(byte: rawSize['disk'] * 1024 * 1024 * 1024), + price: Price( + value: rawSize['price_monthly'], + currency: 'USD', + ), + ), + ); + } + } + } + } catch (e) { + print(e); + } finally { + close(client); + } + + return types; + } + @override Future createReverseDns({ required final ServerHostingDetails serverDetails, diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 2c0b70db..8e31521e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -5,6 +5,7 @@ import 'package:dio/dio.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/models/disk_size.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/hive/server_details.dart'; @@ -12,6 +13,7 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/utils/password_generator.dart'; class HetznerApi extends ServerProviderApi with VolumeProviderApi { @@ -567,7 +569,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - final Response response = await client.post( + final Response response = await client.get( '/locations', ); @@ -576,6 +578,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { title: location['city'], description: location['description'], flag: getEmojiFlag(location['country']), + identifier: location['name'], ), ); } catch (e) { @@ -587,6 +590,46 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return locations; } + @override + Future> getServerTypesByLocation({ + required final ServerProviderLocation location, + }) async { + final List 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: rawPrice['price_monthly']['gross'], + currency: 'EUR', + ), + ), + ); + } + } + } + } catch (e) { + print(e); + } finally { + close(client); + } + + return types; + } + @override Future createReverseDns({ required final ServerHostingDetails serverDetails, diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index d4b61721..ffc1d2d9 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -4,10 +4,14 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; abstract class ServerProviderApi extends ApiMap { Future> getServers(); Future> getAvailableLocations(); + Future> getServerTypesByLocation({ + required final ServerProviderLocation location, + }); Future restart(); Future powerOn(); diff --git a/lib/logic/models/server_provider_location.dart b/lib/logic/models/server_provider_location.dart index dea063a4..f76c226f 100644 --- a/lib/logic/models/server_provider_location.dart +++ b/lib/logic/models/server_provider_location.dart @@ -1,11 +1,13 @@ class ServerProviderLocation { ServerProviderLocation({ required this.title, + required this.identifier, this.description, this.flag, }); final String title; + final String identifier; final String? description; /// as emoji diff --git a/lib/logic/models/server_type.dart b/lib/logic/models/server_type.dart new file mode 100644 index 00000000..8fb5df98 --- /dev/null +++ b/lib/logic/models/server_type.dart @@ -0,0 +1,19 @@ +import 'package:selfprivacy/logic/models/disk_size.dart'; +import 'package:selfprivacy/logic/models/price.dart'; + +class ServerType { + ServerType({ + required this.title, + required this.identifier, + required this.ram, + required this.cores, + required this.disk, + required this.price, + }); + final String title; + final String identifier; + final double ram; + final DiskSize disk; + final int cores; + final Price price; +} From fe820ef5bed4edea51fa896681cd0c61b991c9fc Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 15 Oct 2022 19:49:31 +0000 Subject: [PATCH 08/52] feat(initializing): Implement location selection step for initializing page --- .../server_installation_cubit.dart | 15 +++ .../server_installation_state.dart | 73 ++++++----- .../setup/initializing/initializing.dart | 18 ++- ...icker.dart => server_provider_picker.dart} | 8 +- .../initializing/server_type_picker.dart | 118 ++++++++++++++++++ 5 files changed, 194 insertions(+), 38 deletions(-) rename lib/ui/pages/setup/initializing/{provider_picker.dart => server_provider_picker.dart} (96%) create mode 100644 lib/ui/pages/setup/initializing/server_type_picker.dart diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 99dd1d82..84410468 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -16,6 +16,8 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart'; +import 'package:selfprivacy/logic/models/server_provider_location.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; export 'package:provider/provider.dart'; @@ -93,6 +95,19 @@ class ServerInstallationCubit extends Cubit { ) .isApiTokenValid(providerToken); + Future> fetchAvailableLocations() async { + if (repository.serverProviderApiFactory == null) { + return []; + } + + return repository.serverProviderApiFactory! + .getServerProvider( + settings: const ServerProviderApiSettings(region: 'fra1'), + ) + .getAvailableLocations(); + } + + void setServerProviderKey(final String serverProviderKey) async { await repository.saveServerProviderKey(serverProviderKey); diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 82eda971..1a130baf 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -3,6 +3,7 @@ part of '../server_installation/server_installation_cubit.dart'; abstract class ServerInstallationState extends Equatable { const ServerInstallationState({ required this.providerApiToken, + required this.serverType, required this.cloudFlareKey, required this.backblazeCredential, required this.serverDomain, @@ -31,11 +32,13 @@ abstract class ServerInstallationState extends Equatable { final ServerDomain? serverDomain; final User? rootUser; final ServerHostingDetails? serverDetails; + final ServerType? serverType; final bool isServerStarted; final bool isServerResetedFirstTime; final bool isServerResetedSecondTime; - bool get isServerProviderFilled => providerApiToken != null; + bool get isServerProviderApiKeyFilled => providerApiToken != null; + bool get isServerTypeFilled => serverType != null; bool get isDnsProviderFilled => cloudFlareKey != null; bool get isBackupsProviderFilled => backblazeCredential != null; bool get isDomainSelected => serverDomain != null; @@ -58,7 +61,8 @@ abstract class ServerInstallationState extends Equatable { List get _fulfilementList { final List res = [ - isServerProviderFilled, + isServerProviderApiKeyFilled, + isServerTypeFilled, isDnsProviderFilled, isBackupsProviderFilled, isDomainSelected, @@ -76,11 +80,12 @@ abstract class ServerInstallationState extends Equatable { class TimerState extends ServerInstallationNotFinished { TimerState({ required this.dataState, - required final super.isLoading, + required super.isLoading, this.timerStart, this.duration, }) : super( providerApiToken: dataState.providerApiToken, + serverType: dataState.serverType, cloudFlareKey: dataState.cloudFlareKey, backblazeCredential: dataState.backblazeCredential, serverDomain: dataState.serverDomain, @@ -119,17 +124,18 @@ enum ServerSetupProgress { class ServerInstallationNotFinished extends ServerInstallationState { const ServerInstallationNotFinished({ - required final super.isServerStarted, - required final super.isServerResetedFirstTime, - required final super.isServerResetedSecondTime, - required final this.isLoading, + required super.isServerStarted, + required super.isServerResetedFirstTime, + required super.isServerResetedSecondTime, + required this.isLoading, required this.dnsMatches, - final super.providerApiToken, - final super.cloudFlareKey, - final super.backblazeCredential, - final super.serverDomain, - final super.rootUser, - final super.serverDetails, + super.providerApiToken, + super.serverType, + super.cloudFlareKey, + super.backblazeCredential, + super.serverDomain, + super.rootUser, + super.serverDetails, }); final bool isLoading; final Map? dnsMatches; @@ -137,6 +143,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { @override List get props => [ providerApiToken, + serverType, cloudFlareKey, backblazeCredential, serverDomain, @@ -150,6 +157,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { ServerInstallationNotFinished copyWith({ final String? providerApiToken, + final ServerType? serverType, final String? cloudFlareKey, final BackblazeCredential? backblazeCredential, final ServerDomain? serverDomain, @@ -163,6 +171,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { }) => ServerInstallationNotFinished( providerApiToken: providerApiToken ?? this.providerApiToken, + serverType: serverType ?? this.serverType, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, @@ -179,6 +188,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, + serverType: serverType!, cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, @@ -209,20 +219,22 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished { class ServerInstallationFinished extends ServerInstallationState { const ServerInstallationFinished({ - required final String super.providerApiToken, - required final String super.cloudFlareKey, - required final BackblazeCredential super.backblazeCredential, - required final ServerDomain super.serverDomain, - required final User super.rootUser, - required final ServerHostingDetails super.serverDetails, - required final super.isServerStarted, - required final super.isServerResetedFirstTime, - required final super.isServerResetedSecondTime, + required String super.providerApiToken, + required ServerType super.serverType, + required String super.cloudFlareKey, + required BackblazeCredential super.backblazeCredential, + required ServerDomain super.serverDomain, + required User super.rootUser, + required ServerHostingDetails super.serverDetails, + required super.isServerStarted, + required super.isServerResetedFirstTime, + required super.isServerResetedSecondTime, }); @override List get props => [ providerApiToken, + serverType, cloudFlareKey, backblazeCredential, serverDomain, @@ -260,12 +272,13 @@ class ServerInstallationRecovery extends ServerInstallationState { const ServerInstallationRecovery({ required this.currentStep, required this.recoveryCapabilities, - final super.providerApiToken, - final super.cloudFlareKey, - final super.backblazeCredential, - final super.serverDomain, - final super.rootUser, - final super.serverDetails, + super.providerApiToken, + super.serverType, + super.cloudFlareKey, + super.backblazeCredential, + super.serverDomain, + super.rootUser, + super.serverDetails, }) : super( isServerStarted: true, isServerResetedFirstTime: true, @@ -277,6 +290,7 @@ class ServerInstallationRecovery extends ServerInstallationState { @override List get props => [ providerApiToken, + serverType, cloudFlareKey, backblazeCredential, serverDomain, @@ -289,6 +303,7 @@ class ServerInstallationRecovery extends ServerInstallationState { ServerInstallationRecovery copyWith({ final String? providerApiToken, + final ServerType? serverType, final String? cloudFlareKey, final BackblazeCredential? backblazeCredential, final ServerDomain? serverDomain, @@ -299,6 +314,7 @@ class ServerInstallationRecovery extends ServerInstallationState { }) => ServerInstallationRecovery( providerApiToken: providerApiToken ?? this.providerApiToken, + serverType: serverType ?? this.serverType, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, @@ -310,6 +326,7 @@ class ServerInstallationRecovery extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, + serverType: serverType!, cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 2904cfb8..a211588b 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -7,7 +7,6 @@ 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/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/provider_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_button/brand_button.dart'; @@ -17,13 +16,14 @@ 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/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; -import 'package:selfprivacy/ui/pages/setup/initializing/provider_picker.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/utils/route_transitions/basic.dart'; class InitializingPage extends StatelessWidget { const InitializingPage({ - final super.key, + super.key, }); @override @@ -36,7 +36,8 @@ class InitializingPage extends StatelessWidget { Widget? actualInitializingPage; if (cubit.state is! ServerInstallationFinished) { actualInitializingPage = [ - () => _stepHetzner(cubit), + () => _stepServerProviderToken(cubit), + () => _stepServerType(cubit), () => _stepCloudflare(cubit), () => _stepBackblaze(cubit), () => _stepDomain(cubit), @@ -139,11 +140,16 @@ class InitializingPage extends StatelessWidget { } } - Widget _stepHetzner(final ServerInstallationCubit serverInstallationCubit) => - ProviderPicker( + Widget _stepServerProviderToken(final ServerInstallationCubit serverInstallationCubit) => + ServerProviderPicker( serverInstallationCubit: serverInstallationCubit, ); + Widget _stepServerType(final ServerInstallationCubit serverInstallationCubit) => + ServerTypePicker( + serverInstallationCubit: serverInstallationCubit, + ); + void _showModal(final BuildContext context, final Widget widget) { showModalBottomSheet( context: context, diff --git a/lib/ui/pages/setup/initializing/provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart similarity index 96% rename from lib/ui/pages/setup/initializing/provider_picker.dart rename to lib/ui/pages/setup/initializing/server_provider_picker.dart index 6681a07b..ee0ab36a 100644 --- a/lib/ui/pages/setup/initializing/provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -9,8 +9,8 @@ import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet. import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; -class ProviderPicker extends StatefulWidget { - const ProviderPicker({ +class ServerProviderPicker extends StatefulWidget { + const ServerProviderPicker({ required this.serverInstallationCubit, super.key, }); @@ -18,10 +18,10 @@ class ProviderPicker extends StatefulWidget { final ServerInstallationCubit serverInstallationCubit; @override - State createState() => _ProviderPickerState(); + State createState() => _ServerProviderPickerState(); } -class _ProviderPickerState extends State { +class _ServerProviderPickerState extends State { ServerProvider selectedProvider = ServerProvider.unknown; void setProvider(final ServerProvider provider) { diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart new file mode 100644 index 00000000..3dd7d3c9 --- /dev/null +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -0,0 +1,118 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/server_provider_location.dart'; + +class ServerTypePicker extends StatefulWidget { + const ServerTypePicker({ + required this.serverInstallationCubit, + super.key, + }); + + final ServerInstallationCubit serverInstallationCubit; + + @override + State createState() => _ServerTypePickerState(); +} + +class _ServerTypePickerState extends State { + ServerProviderLocation? serverProviderLocation; + + void setServerProviderLocation(final ServerProviderLocation location) { + setState(() { + serverProviderLocation = location; + }); + } + + @override + Widget build(final BuildContext context) { + + } +} + +class SelectLocationPage extends StatelessWidget { + const SelectLocationPage({ + required this.serverInstallationCubit, + super.key, + }); + + final ServerInstallationCubit serverInstallationCubit; + + @override + Widget build(final BuildContext context) => FutureBuilder( + future: serverInstallationCubit.repository.serverProviderApiFactory, + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + return _KeyDisplay( + newDeviceKey: snapshot.data.toString(), + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), +} + +class ProviderSelectionPage extends StatelessWidget { + const ProviderSelectionPage({ + required this.callback, + super.key, + }); + + final Function callback; + + @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: () { + context + .read() + .setServerProviderType(ServerProvider.hetzner); + callback(ServerProvider.hetzner); + }, + child: Image.asset( + 'assets/images/logos/hetzner.png', + width: 150, + ), + ), + const SizedBox( + width: 20, + ), + InkWell( + onTap: () { + context + .read() + .setServerProviderType(ServerProvider.digitalOcean); + callback(ServerProvider.digitalOcean); + }, + child: Image.asset( + 'assets/images/logos/digital_ocean.png', + width: 150, + ), + ), + ], + ), + ), + ], + ); +} From 72760e7980142664b9bdde34eb95f44f7002d946 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 15 Oct 2022 21:51:37 +0000 Subject: [PATCH 09/52] feat(initializing): Implement server type selection for initialization page --- assets/translations/en.json | 2 + assets/translations/ru.json | 2 + lib/config/hive_config.dart | 3 + .../server_installation_cubit.dart | 39 +++- .../server_installation_repository.dart | 6 + .../server_installation_state.dart | 34 +-- lib/logic/get_it/api_config.dart | 7 + lib/logic/models/disk_size.dart | 2 +- .../setup/initializing/initializing.dart | 12 +- .../initializing/server_type_picker.dart | 197 +++++++++++------- 10 files changed, 203 insertions(+), 101 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index bf8e4e2c..d2c75572 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -268,6 +268,8 @@ "place_where_data": "A place where your data and SelfPrivacy services will reside:", "how": "How to obtain API token", "provider_bad_key_error": "Provider API key is invalid", + "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", "backblaze_bad_key_error": "Backblaze storage information is invalid", "connect_cloudflare": "Connect CloudFlare", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 604edf35..98f3ff23 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -267,6 +267,8 @@ "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", "how": "Как получить API Token", "provider_bad_key_error": "API ключ провайдера неверен", + "no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.", + "no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.", "cloudflare_bad_key_error": "Cloudflare API ключ неверен", "backblaze_bad_key_error": "Информация о Backblaze хранилище неверна", "connect_cloudflare": "Подключите CloudFlare", diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 29ab0519..b8cc287c 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -90,6 +90,9 @@ class BNames { /// A String field of [serverInstallationBox] box. static String cloudFlareKey = 'cloudFlareKey'; + /// A String field of [serverTypeIdentifier] box. + static String serverTypeIdentifier = 'serverTypeIdentifier'; + /// A [User] field of [serverInstallationBox] box. static String rootUser = 'rootUser'; diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 84410468..4f4da2e1 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -6,8 +6,6 @@ import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.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/provider_api_settings.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/server_details.dart'; @@ -82,7 +80,9 @@ class ServerInstallationCubit extends Cubit { repository.serverProviderApiFactory! .getServerProvider( settings: const ServerProviderApiSettings( - region: 'fra1', isWithToken: false), + region: 'fra1', + isWithToken: false, + ), ) .isApiTokenValid(providerToken); @@ -101,12 +101,25 @@ class ServerInstallationCubit extends Cubit { } return repository.serverProviderApiFactory! - .getServerProvider( - settings: const ServerProviderApiSettings(region: 'fra1'), - ) - .getAvailableLocations(); + .getServerProvider( + settings: const ServerProviderApiSettings(region: 'fra1'), + ) + .getAvailableLocations(); + } + + Future> fetchAvailableTypesByLocation( + final ServerProviderLocation location, + ) async { + if (repository.serverProviderApiFactory == null) { + return []; + } + + return repository.serverProviderApiFactory! + .getServerProvider( + settings: const ServerProviderApiSettings(region: 'fra1'), + ) + .getServerTypesByLocation(location: location); } - void setServerProviderKey(final String serverProviderKey) async { await repository.saveServerProviderKey(serverProviderKey); @@ -128,6 +141,16 @@ class ServerInstallationCubit extends Cubit { ); } + void setServerType(final String serverTypeId) async { + await repository.saveServerType(serverTypeId); + + emit( + (state as ServerInstallationNotFinished).copyWith( + serverTypeIdentificator: serverTypeId, + ), + ); + } + void setCloudflareKey(final String cloudFlareKey) async { if (state is ServerInstallationRecovery) { setAndValidateCloudflareToken(cloudFlareKey); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 4fe10b1f..404e60d1 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -50,6 +50,7 @@ class ServerInstallationRepository { Future load() async { final String? providerApiToken = getIt().serverProviderKey; final String? cloudflareToken = getIt().cloudFlareKey; + final String? serverTypeIdentificator = getIt().serverType; final ServerDomain? serverDomain = getIt().serverDomain; final BackblazeCredential? backblazeCredential = getIt().backblazeCredential; @@ -73,6 +74,7 @@ class ServerInstallationRepository { if (box.get(BNames.hasFinalChecked, defaultValue: false)) { return ServerInstallationFinished( providerApiToken: providerApiToken!, + serverTypeIdentificator: serverTypeIdentificator!, cloudFlareKey: cloudflareToken!, serverDomain: serverDomain!, backblazeCredential: backblazeCredential!, @@ -697,6 +699,10 @@ class ServerInstallationRepository { await getIt().storeServerProviderKey(key); } + Future saveServerType(final String serverType) async { + await getIt().storeServerTypeIdentifier(serverType); + } + Future deleteServerProviderKey() async { await box.delete(BNames.hetznerKey); getIt().init(); diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 1a130baf..40a21d5d 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -3,7 +3,7 @@ part of '../server_installation/server_installation_cubit.dart'; abstract class ServerInstallationState extends Equatable { const ServerInstallationState({ required this.providerApiToken, - required this.serverType, + required this.serverTypeIdentificator, required this.cloudFlareKey, required this.backblazeCredential, required this.serverDomain, @@ -17,6 +17,7 @@ abstract class ServerInstallationState extends Equatable { @override List get props => [ providerApiToken, + serverTypeIdentificator, cloudFlareKey, backblazeCredential, serverDomain, @@ -28,17 +29,17 @@ abstract class ServerInstallationState extends Equatable { final String? providerApiToken; final String? cloudFlareKey; + final String? serverTypeIdentificator; final BackblazeCredential? backblazeCredential; final ServerDomain? serverDomain; final User? rootUser; final ServerHostingDetails? serverDetails; - final ServerType? serverType; final bool isServerStarted; final bool isServerResetedFirstTime; final bool isServerResetedSecondTime; bool get isServerProviderApiKeyFilled => providerApiToken != null; - bool get isServerTypeFilled => serverType != null; + bool get isServerTypeFilled => serverTypeIdentificator != null; bool get isDnsProviderFilled => cloudFlareKey != null; bool get isBackupsProviderFilled => backblazeCredential != null; bool get isDomainSelected => serverDomain != null; @@ -85,7 +86,7 @@ class TimerState extends ServerInstallationNotFinished { this.duration, }) : super( providerApiToken: dataState.providerApiToken, - serverType: dataState.serverType, + serverTypeIdentificator: dataState.serverTypeIdentificator, cloudFlareKey: dataState.cloudFlareKey, backblazeCredential: dataState.backblazeCredential, serverDomain: dataState.serverDomain, @@ -130,7 +131,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { required this.isLoading, required this.dnsMatches, super.providerApiToken, - super.serverType, + super.serverTypeIdentificator, super.cloudFlareKey, super.backblazeCredential, super.serverDomain, @@ -143,7 +144,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { @override List get props => [ providerApiToken, - serverType, + serverTypeIdentificator, cloudFlareKey, backblazeCredential, serverDomain, @@ -157,7 +158,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { ServerInstallationNotFinished copyWith({ final String? providerApiToken, - final ServerType? serverType, + final String? serverTypeIdentificator, final String? cloudFlareKey, final BackblazeCredential? backblazeCredential, final ServerDomain? serverDomain, @@ -171,7 +172,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { }) => ServerInstallationNotFinished( providerApiToken: providerApiToken ?? this.providerApiToken, - serverType: serverType ?? this.serverType, + serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, @@ -188,7 +189,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, - serverType: serverType!, + serverTypeIdentificator: serverTypeIdentificator!, cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, @@ -204,6 +205,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished { const ServerInstallationEmpty() : super( providerApiToken: null, + serverTypeIdentificator: null, cloudFlareKey: null, backblazeCredential: null, serverDomain: null, @@ -220,7 +222,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished { class ServerInstallationFinished extends ServerInstallationState { const ServerInstallationFinished({ required String super.providerApiToken, - required ServerType super.serverType, + required String super.serverTypeIdentificator, required String super.cloudFlareKey, required BackblazeCredential super.backblazeCredential, required ServerDomain super.serverDomain, @@ -234,7 +236,7 @@ class ServerInstallationFinished extends ServerInstallationState { @override List get props => [ providerApiToken, - serverType, + serverTypeIdentificator, cloudFlareKey, backblazeCredential, serverDomain, @@ -273,7 +275,7 @@ class ServerInstallationRecovery extends ServerInstallationState { required this.currentStep, required this.recoveryCapabilities, super.providerApiToken, - super.serverType, + super.serverTypeIdentificator, super.cloudFlareKey, super.backblazeCredential, super.serverDomain, @@ -290,7 +292,7 @@ class ServerInstallationRecovery extends ServerInstallationState { @override List get props => [ providerApiToken, - serverType, + serverTypeIdentificator, cloudFlareKey, backblazeCredential, serverDomain, @@ -303,7 +305,7 @@ class ServerInstallationRecovery extends ServerInstallationState { ServerInstallationRecovery copyWith({ final String? providerApiToken, - final ServerType? serverType, + final String? serverTypeIdentificator, final String? cloudFlareKey, final BackblazeCredential? backblazeCredential, final ServerDomain? serverDomain, @@ -314,7 +316,7 @@ class ServerInstallationRecovery extends ServerInstallationState { }) => ServerInstallationRecovery( providerApiToken: providerApiToken ?? this.providerApiToken, - serverType: serverType ?? this.serverType, + serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, @@ -326,7 +328,7 @@ class ServerInstallationRecovery extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, - serverType: serverType!, + serverTypeIdentificator: serverTypeIdentificator!, cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 6a81141f..387d7e7f 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -10,6 +10,7 @@ class ApiConfigModel { ServerHostingDetails? get serverDetails => _serverDetails; String? get serverProviderKey => _serverProviderKey; + String? get serverType => _serverType; String? get cloudFlareKey => _cloudFlareKey; BackblazeCredential? get backblazeCredential => _backblazeCredential; ServerDomain? get serverDomain => _serverDomain; @@ -17,6 +18,7 @@ class ApiConfigModel { String? _serverProviderKey; String? _cloudFlareKey; + String? _serverType; ServerHostingDetails? _serverDetails; BackblazeCredential? _backblazeCredential; ServerDomain? _serverDomain; @@ -32,6 +34,11 @@ class ApiConfigModel { _cloudFlareKey = value; } + Future storeServerTypeIdentifier(final String typeIdentifier) async { + await _box.put(BNames.serverTypeIdentifier, typeIdentifier); + _serverType = typeIdentifier; + } + Future storeBackblazeCredential(final BackblazeCredential value) async { await _box.put(BNames.backblazeCredential, value); _backblazeCredential = value; diff --git a/lib/logic/models/disk_size.dart b/lib/logic/models/disk_size.dart index 44c6bc35..f7283689 100644 --- a/lib/logic/models/disk_size.dart +++ b/lib/logic/models/disk_size.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; class DiskSize { - const DiskSize({final this.byte = 0}); + const DiskSize({this.byte = 0}); DiskSize.fromKibibyte(final double kibibyte) : this(byte: (kibibyte * 1024).round()); diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index a211588b..029a30e4 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -140,15 +140,17 @@ class InitializingPage extends StatelessWidget { } } - Widget _stepServerProviderToken(final ServerInstallationCubit serverInstallationCubit) => + Widget _stepServerProviderToken( + final ServerInstallationCubit serverInstallationCubit) => ServerProviderPicker( serverInstallationCubit: serverInstallationCubit, ); - Widget _stepServerType(final ServerInstallationCubit serverInstallationCubit) => - ServerTypePicker( - serverInstallationCubit: serverInstallationCubit, - ); + Widget _stepServerType( + final ServerInstallationCubit serverInstallationCubit) => + ServerTypePicker( + serverInstallationCubit: serverInstallationCubit, + ); void _showModal(final BuildContext context, final Widget widget) { showModalBottomSheet( diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index 3dd7d3c9..2536aab9 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -1,8 +1,9 @@ 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/hive/server_details.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; class ServerTypePicker extends StatefulWidget { const ServerTypePicker({ @@ -18,6 +19,7 @@ class ServerTypePicker extends StatefulWidget { class _ServerTypePickerState extends State { ServerProviderLocation? serverProviderLocation; + ServerType? serverType; void setServerProviderLocation(final ServerProviderLocation location) { setState(() { @@ -27,92 +29,145 @@ class _ServerTypePickerState extends State { @override Widget build(final BuildContext context) { - + if (serverProviderLocation == null) { + return SelectLocationPage( + serverInstallationCubit: widget.serverInstallationCubit, + callback: setServerProviderLocation, + ); + } + + return SelectTypePage( + location: serverProviderLocation!, + serverInstallationCubit: widget.serverInstallationCubit, + ); } } class SelectLocationPage extends StatelessWidget { const SelectLocationPage({ required this.serverInstallationCubit, - super.key, - }); - - final ServerInstallationCubit serverInstallationCubit; - - @override - Widget build(final BuildContext context) => FutureBuilder( - future: serverInstallationCubit.repository.serverProviderApiFactory, - builder: ( - final BuildContext context, - final AsyncSnapshot snapshot, - ) { - if (snapshot.hasData) { - return _KeyDisplay( - newDeviceKey: snapshot.data.toString(), - ); - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ), -} - -class ProviderSelectionPage extends StatelessWidget { - const ProviderSelectionPage({ required this.callback, 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( + Widget build(final BuildContext context) => FutureBuilder( + future: serverInstallationCubit.fetchAvailableLocations(), + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + if ((snapshot.data as List).isEmpty) { + return Text('initializing.no_locations_found'.tr()); + } + return ListView( + padding: paddingH15V0, children: [ - InkWell( - onTap: () { - context - .read() - .setServerProviderType(ServerProvider.hetzner); - callback(ServerProvider.hetzner); - }, - child: Image.asset( - 'assets/images/logos/hetzner.png', - width: 150, - ), - ), - const SizedBox( - width: 20, - ), - InkWell( - onTap: () { - context - .read() - .setServerProviderType(ServerProvider.digitalOcean); - callback(ServerProvider.digitalOcean); - }, - child: Image.asset( - 'assets/images/logos/digital_ocean.png', - width: 150, + ...(snapshot.data! as List).map( + (final location) => InkWell( + onTap: () { + callback(location); + }, + child: Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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.location, + required this.serverInstallationCubit, + super.key, + }); + + final ServerProviderLocation location; + final ServerInstallationCubit serverInstallationCubit; + + @override + Widget build(final BuildContext context) => FutureBuilder( + future: serverInstallationCubit.fetchAvailableTypesByLocation(location), + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + if ((snapshot.data as List).isEmpty) { + return Text('initializing.no_server_types_found'.tr()); + } + return ListView( + padding: paddingH15V0, + children: [ + ...(snapshot.data! as List).map( + (final type) => InkWell( + onTap: () { + serverInstallationCubit.setServerType(type.identifier); + }, + child: Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(type.title), + const SizedBox(height: 8), + Text('cores: $type.cores'), + const SizedBox(height: 8), + Text('ram: $type.ram'), + const SizedBox(height: 8), + Text('disk: $type.disk.gibibyte'), + const SizedBox(height: 8), + Text('price: $type.price.value $type.price.currency'), + ], + ), + ], + ), + ), + ), + ), + ), + const SizedBox(height: 24), + ], + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, ); } From 8a93af2b06ab5d0d8584e4cb962cbe2bb0e63ff1 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 16 Oct 2022 00:51:10 +0000 Subject: [PATCH 10/52] fix(region): Move region settings for provider api to factory --- lib/config/hive_config.dart | 3 + .../rest_maps/api_factory_creator.dart | 13 ++-- .../rest_maps/api_factory_settings.dart | 20 ++++++ .../digital_ocean/digital_ocean.dart | 3 +- .../digital_ocean/digital_ocean_factory.dart | 6 +- .../server_providers/hetzner/hetzner.dart | 33 ++++----- .../hetzner/hetzner_factory.dart | 14 ++-- .../server_provider_api_settings.dart | 4 +- .../server_provider_factory.dart | 4 +- .../cubit/dns_records/dns_records_cubit.dart | 6 +- .../hetzner_metrics_repository.dart | 4 +- .../provider_volume_cubit.dart | 60 +++++------------ .../server_detailed_info_repository.dart | 6 +- .../server_installation_cubit.dart | 34 +++++----- .../server_installation_repository.dart | 67 ++++++++----------- .../server_installation_state.dart | 6 +- lib/logic/get_it/api_config.dart | 11 +++ lib/logic/models/server_type.dart | 3 + .../initializing/server_type_picker.dart | 5 +- 19 files changed, 161 insertions(+), 141 deletions(-) create mode 100644 lib/logic/api_maps/rest_maps/api_factory_settings.dart diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index b8cc287c..7d8e59aa 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -87,6 +87,9 @@ class BNames { /// A String field of [serverInstallationBox] box. static String hetznerKey = 'hetznerKey'; + /// A String field of [serverLocation] box. + static String serverLocation = 'serverLocation'; + /// A String field of [serverInstallationBox] box. static String cloudFlareKey = 'cloudFlareKey'; diff --git a/lib/logic/api_maps/rest_maps/api_factory_creator.dart b/lib/logic/api_maps/rest_maps/api_factory_creator.dart index a144c647..0dbe1e90 100644 --- a/lib/logic/api_maps/rest_maps/api_factory_creator.dart +++ b/lib/logic/api_maps/rest_maps/api_factory_creator.dart @@ -1,3 +1,4 @@ +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/dns_provider_factory.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart'; @@ -13,9 +14,9 @@ class UnknownApiProviderException implements Exception { class ApiFactoryCreator { static ServerProviderApiFactory createServerProviderApiFactory( - final ServerProvider provider, + final ServerProviderApiFactorySettings settings, ) { - switch (provider) { + switch (settings.provider) { case ServerProvider.hetzner: return HetznerApiFactory(); case ServerProvider.digitalOcean: @@ -26,9 +27,9 @@ class ApiFactoryCreator { } static DnsProviderApiFactory createDnsProviderApiFactory( - final DnsProvider provider, + final DnsProviderApiFactorySettings settings, ) { - switch (provider) { + switch (settings.provider) { case DnsProvider.cloudflare: return CloudflareApiFactory(); case DnsProvider.unknown: @@ -39,9 +40,9 @@ class ApiFactoryCreator { class VolumeApiFactoryCreator { static VolumeProviderApiFactory createVolumeProviderApiFactory( - final ServerProvider provider, + final ServerProviderApiFactorySettings settings, ) { - switch (provider) { + switch (settings.provider) { case ServerProvider.hetzner: return HetznerApiFactory(); case ServerProvider.digitalOcean: diff --git a/lib/logic/api_maps/rest_maps/api_factory_settings.dart b/lib/logic/api_maps/rest_maps/api_factory_settings.dart new file mode 100644 index 00000000..438b92d5 --- /dev/null +++ b/lib/logic/api_maps/rest_maps/api_factory_settings.dart @@ -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; +} diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 67811f26..fb44a4dd 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -27,7 +27,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @override bool isWithToken; - final String region; + final String? region; @override BaseOptions get options { @@ -631,6 +631,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { value: rawSize['price_monthly'], currency: 'USD', ), + location: location, ), ); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart index 7508e04e..6864c7ab 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart @@ -8,7 +8,8 @@ class DigitalOceanApiFactory extends ServerProviderApiFactory with VolumeProviderApiFactory { @override ServerProviderApi getServerProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings = + const ServerProviderApiSettings(), }) => DigitalOceanApi( region: settings.region, @@ -18,7 +19,8 @@ class DigitalOceanApiFactory extends ServerProviderApiFactory @override VolumeProviderApi getVolumeProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings = + const ServerProviderApiSettings(), }) => DigitalOceanApi( region: settings.region, diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 8e31521e..0d8e0ab0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -18,7 +18,7 @@ import 'package:selfprivacy/utils/password_generator.dart'; class HetznerApi extends ServerProviderApi with VolumeProviderApi { HetznerApi({ - required this.region, + this.region, this.hasLogger = false, this.isWithToken = true, }); @@ -27,7 +27,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { @override bool isWithToken; - final String region; + final String? region; @override BaseOptions get options { @@ -336,25 +336,25 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 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"; - final Map data = { - 'name': hostname, - 'server_type': 'cx11', - 'start_after_create': false, - 'image': 'ubuntu-20.04', - 'volumes': [dbId], - 'networks': [], - 'user_data': userdataString, - 'labels': {}, - 'automount': true, - 'location': region, - }; - print('Decoded data: $data'); - ServerHostingDetails? serverDetails; DioError? hetznerError; bool success = false; try { + final Map data = { + 'name': hostname, + 'server_type': 'cx11', + 'start_after_create': false, + 'image': 'ubuntu-20.04', + 'volumes': [dbId], + 'networks': [], + 'user_data': userdataString, + 'labels': {}, + 'automount': true, + 'location': region!, + }; + print('Decoded data: $data'); + final Response serverCreateResponse = await client.post( '/servers', data: data, @@ -616,6 +616,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { value: rawPrice['price_monthly']['gross'], currency: 'EUR', ), + location: location, ), ); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart index f303966b..5f8fcab5 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart @@ -6,22 +6,28 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_pro class HetznerApiFactory extends ServerProviderApiFactory with VolumeProviderApiFactory { + HetznerApiFactory({this.region}); + + final String? region; + @override ServerProviderApi getServerProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings = + const ServerProviderApiSettings(), }) => HetznerApi( - region: settings.region, + region: settings.region ?? region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); @override VolumeProviderApi getVolumeProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings = + const ServerProviderApiSettings(), }) => HetznerApi( - region: settings.region, + region: settings.region ?? region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart index b1513bf6..3931b45b 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart @@ -2,10 +2,10 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart' class ServerProviderApiSettings extends ProviderApiSettings { const ServerProviderApiSettings({ - required this.region, + this.region, super.hasLogger = false, super.isWithToken = true, }); - final String region; + final String? region; } diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart index 4fad8797..dbbb8035 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart @@ -4,12 +4,12 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_pro abstract class ServerProviderApiFactory { ServerProviderApi getServerProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings, }); } mixin VolumeProviderApiFactory { VolumeProviderApi getVolumeProvider({ - required final ServerProviderApiSettings settings, + final ServerProviderApiSettings settings, }); } diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index b6d503aa..f1c41351 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -1,5 +1,6 @@ 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_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_factory.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; @@ -20,8 +21,9 @@ class DnsRecordsCubit DnsProviderApiFactory? dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory( - DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!! - ); // TODO: Remove when provider selection is implemented. + DnsProviderApiFactorySettings(provider: DnsProvider.cloudflare), + ); // TODO: HARDCODE FOR NOW!!! + // TODO: Remove when provider selection is implemented. final ServerApi api = ServerApi(); diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart index 18137fdc..bc1d8e42 100644 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart +++ b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart @@ -1,3 +1,4 @@ +import 'package:selfprivacy/config/get_it_config.dart'; 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'; @@ -27,8 +28,9 @@ class HetznerMetricsRepository { } final HetznerApi api = HetznerApi( + /// TODO: Hetzner hardcode (???) hasLogger: false, - region: 'fra1', + region: getIt().serverLocation, ); final List> results = await Future.wait([ diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 95b0d362..66f21d7b 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; 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/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/common_enum/common_enum.dart'; @@ -25,22 +26,21 @@ class ApiProviderVolumeCubit Future load() async { if (serverInstallationCubit.state is ServerInstallationFinished) { final serverDetails = getIt().serverDetails; + final serverLocation = getIt().serverLocation; providerApi = serverDetails == null ? null : VolumeApiFactoryCreator.createVolumeProviderApiFactory( - getIt().serverDetails!.provider, + ServerProviderApiFactorySettings( + location: serverLocation, + provider: getIt().serverDetails!.provider, + ), ); _refetch(); } } - Future getPricePerGb() async => providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) - .getPricePerGb(); + Future getPricePerGb() async => + providerApi!.getVolumeProvider().getPricePerGb(); Future refresh() async { emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false)); @@ -52,13 +52,8 @@ class ApiProviderVolumeCubit return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); } - final List volumes = await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) - .getVolumes(); + final List volumes = + await providerApi!.getVolumeProvider().getVolumes(); if (volumes.isEmpty) { return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); @@ -70,22 +65,14 @@ class ApiProviderVolumeCubit Future attachVolume(final DiskVolume volume) async { final ServerHostingDetails server = getIt().serverDetails!; await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) + .getVolumeProvider() .attachVolume(volume.providerVolume!.id.toString(), server.id); refresh(); } Future detachVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) + .getVolumeProvider() .detachVolume(volume.providerVolume!.id.toString()); refresh(); } @@ -99,13 +86,7 @@ class ApiProviderVolumeCubit 'Starting resize', ); emit(state.copyWith(isResizing: true)); - final bool resized = await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) - .resizeVolume( + final bool resized = await providerApi!.getVolumeProvider().resizeVolume( volume.providerVolume!.id.toString(), newSizeGb, ); @@ -141,13 +122,8 @@ class ApiProviderVolumeCubit } Future createVolume() async { - final ServerVolume? volume = await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) - .createVolume(); + final ServerVolume? volume = + await providerApi!.getVolumeProvider().createVolume(); final diskVolume = DiskVolume(providerVolume: volume); await attachVolume(diskVolume); @@ -160,11 +136,7 @@ class ApiProviderVolumeCubit Future deleteVolume(final DiskVolume volume) async { await providerApi! - .getVolumeProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) + .getVolumeProvider() .deleteVolume(volume.providerVolume!.id.toString()); refresh(); } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index d4149f61..bfa3991f 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -1,3 +1,4 @@ +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; @@ -5,7 +6,10 @@ import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; class ServerDetailsRepository { - HetznerApi hetzner = HetznerApi(region: 'fra1'); + HetznerApi hetzner = HetznerApi( + /// TODO: Hetzner hardcode (???) + region: getIt().serverLocation, + ); ServerApi server = ServerApi(); Future load() async { diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 4f4da2e1..360ec2e5 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; 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_api_settings.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'; @@ -57,17 +58,15 @@ class ServerInstallationCubit extends Cubit { void setServerProviderType(final ServerProvider providerType) { repository.serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( - providerType, + ServerProviderApiFactorySettings( + provider: providerType, + ), ); } RegExp getServerProviderApiTokenValidation() => repository.serverProviderApiFactory! - .getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ) + .getServerProvider() .getApiTokenValidation(); RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory! @@ -80,7 +79,6 @@ class ServerInstallationCubit extends Cubit { repository.serverProviderApiFactory! .getServerProvider( settings: const ServerProviderApiSettings( - region: 'fra1', isWithToken: false, ), ) @@ -101,9 +99,7 @@ class ServerInstallationCubit extends Cubit { } return repository.serverProviderApiFactory! - .getServerProvider( - settings: const ServerProviderApiSettings(region: 'fra1'), - ) + .getServerProvider() .getAvailableLocations(); } @@ -115,9 +111,7 @@ class ServerInstallationCubit extends Cubit { } return repository.serverProviderApiFactory! - .getServerProvider( - settings: const ServerProviderApiSettings(region: 'fra1'), - ) + .getServerProvider() .getServerTypesByLocation(location: location); } @@ -141,12 +135,20 @@ class ServerInstallationCubit extends Cubit { ); } - void setServerType(final String serverTypeId) async { - await repository.saveServerType(serverTypeId); + void setServerType(final ServerType serverType) async { + await repository.saveServerType(serverType); + + repository.serverProviderApiFactory = + ApiFactoryCreator.createServerProviderApiFactory( + ServerProviderApiFactorySettings( + provider: getIt().serverDetails!.provider, + location: serverType.location.identifier, + ), + ); emit( (state as ServerInstallationNotFinished).copyWith( - serverTypeIdentificator: serverTypeId, + serverTypeIdentificator: serverType.identifier, ), ); } diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 404e60d1..7296d3a3 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -10,6 +10,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/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.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'; @@ -25,6 +26,7 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart'; import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/ui/components/action_button/action_button.dart'; import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; @@ -44,11 +46,14 @@ class ServerInstallationRepository { ServerProviderApiFactory? serverProviderApiFactory; DnsProviderApiFactory? dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory( - DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!! + DnsProviderApiFactorySettings( + provider: DnsProvider.cloudflare, + ), // TODO: HARDCODE FOR NOW!!! ); Future load() async { final String? providerApiToken = getIt().serverProviderKey; + final String? location = getIt().serverLocation; final String? cloudflareToken = getIt().cloudFlareKey; final String? serverTypeIdentificator = getIt().serverType; final ServerDomain? serverDomain = getIt().serverDomain; @@ -61,13 +66,18 @@ class ServerInstallationRepository { serverDetails.provider != ServerProvider.unknown) { serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( - serverDetails.provider, + ServerProviderApiFactorySettings( + provider: serverDetails.provider, + location: location, + ), ); } if (serverDomain != null && serverDomain.provider != DnsProvider.unknown) { dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory( - serverDomain.provider, + DnsProviderApiFactorySettings( + provider: serverDomain.provider, + ), ); } @@ -155,11 +165,7 @@ class ServerInstallationRepository { ) async { ServerHostingDetails serverDetails; - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); serverDetails = await api.powerOn(); return serverDetails; @@ -235,11 +241,7 @@ class ServerInstallationRepository { required final Future Function(ServerHostingDetails serverDetails) onSuccess, }) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); try { final ServerHostingDetails? serverDetails = await api.createServer( dnsApiToken: cloudFlareKey, @@ -344,11 +346,7 @@ class ServerInstallationRepository { final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); final ServerProviderApi serverApi = - serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + serverProviderApiFactory!.getServerProvider(); await dnsProviderApi.removeSimilarRecords( ip4: serverDetails.ip4, @@ -420,20 +418,12 @@ class ServerInstallationRepository { } Future restart() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); return api.restart(); } Future powerOn() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); return api.powerOn(); } @@ -676,11 +666,7 @@ class ServerInstallationRepository { } Future> getServersOnProviderAccount() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); return api.getServers(); } @@ -699,8 +685,13 @@ class ServerInstallationRepository { await getIt().storeServerProviderKey(key); } - Future saveServerType(final String serverType) async { - await getIt().storeServerTypeIdentifier(serverType); + Future saveServerType(final ServerType serverType) async { + await getIt().storeServerTypeIdentifier( + serverType.identifier, + ); + await getIt().storeServerLocation( + serverType.location.identifier, + ); } Future deleteServerProviderKey() async { @@ -762,11 +753,7 @@ class ServerInstallationRepository { } Future deleteServer(final ServerDomain serverDomain) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider( - settings: const ServerProviderApiSettings( - region: 'fra1', - ), - ); + final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 40a21d5d..9e5684a6 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -172,7 +172,8 @@ class ServerInstallationNotFinished extends ServerInstallationState { }) => ServerInstallationNotFinished( providerApiToken: providerApiToken ?? this.providerApiToken, - serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator, + serverTypeIdentificator: + serverTypeIdentificator ?? this.serverTypeIdentificator, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, @@ -316,7 +317,8 @@ class ServerInstallationRecovery extends ServerInstallationState { }) => ServerInstallationRecovery( providerApiToken: providerApiToken ?? this.providerApiToken, - serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator, + serverTypeIdentificator: + serverTypeIdentificator ?? this.serverTypeIdentificator, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, backblazeCredential: backblazeCredential ?? this.backblazeCredential, serverDomain: serverDomain ?? this.serverDomain, diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 387d7e7f..302a37b1 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -10,6 +10,7 @@ class ApiConfigModel { ServerHostingDetails? get serverDetails => _serverDetails; String? get serverProviderKey => _serverProviderKey; + String? get serverLocation => _serverLocation; String? get serverType => _serverType; String? get cloudFlareKey => _cloudFlareKey; BackblazeCredential? get backblazeCredential => _backblazeCredential; @@ -17,6 +18,7 @@ class ApiConfigModel { BackblazeBucket? get backblazeBucket => _backblazeBucket; String? _serverProviderKey; + String? _serverLocation; String? _cloudFlareKey; String? _serverType; ServerHostingDetails? _serverDetails; @@ -39,6 +41,11 @@ class ApiConfigModel { _serverType = typeIdentifier; } + Future storeServerLocation(final String serverLocation) async { + await _box.put(BNames.serverLocation, serverLocation); + _serverLocation = serverLocation; + } + Future storeBackblazeCredential(final BackblazeCredential value) async { await _box.put(BNames.backblazeCredential, value); _backblazeCredential = value; @@ -61,19 +68,23 @@ class ApiConfigModel { void clear() { _serverProviderKey = null; + _serverLocation = null; _cloudFlareKey = null; _backblazeCredential = null; _serverDomain = null; _serverDetails = null; _backblazeBucket = null; + _serverType = null; } void init() { _serverProviderKey = _box.get(BNames.hetznerKey); + _serverLocation = _box.get(BNames.serverLocation); _cloudFlareKey = _box.get(BNames.cloudFlareKey); _backblazeCredential = _box.get(BNames.backblazeCredential); _serverDomain = _box.get(BNames.serverDomain); _serverDetails = _box.get(BNames.serverDetails); _backblazeBucket = _box.get(BNames.backblazeBucket); + _serverType = _box.get(BNames.serverTypeIdentifier); } } diff --git a/lib/logic/models/server_type.dart b/lib/logic/models/server_type.dart index 8fb5df98..4e7b8d92 100644 --- a/lib/logic/models/server_type.dart +++ b/lib/logic/models/server_type.dart @@ -1,5 +1,6 @@ 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({ @@ -9,6 +10,7 @@ class ServerType { required this.cores, required this.disk, required this.price, + required this.location, }); final String title; final String identifier; @@ -16,4 +18,5 @@ class ServerType { final DiskSize disk; final int cores; final Price price; + final ServerProviderLocation location; } diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index 2536aab9..a913f240 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -133,7 +133,7 @@ class SelectTypePage extends StatelessWidget { ...(snapshot.data! as List).map( (final type) => InkWell( onTap: () { - serverInstallationCubit.setServerType(type.identifier); + serverInstallationCubit.setServerType(type); }, child: Card( child: Padding( @@ -153,7 +153,8 @@ class SelectTypePage extends StatelessWidget { const SizedBox(height: 8), Text('disk: $type.disk.gibibyte'), const SizedBox(height: 8), - Text('price: $type.price.value $type.price.currency'), + Text( + 'price: $type.price.value $type.price.currency'), ], ), ], From f5a75e6eb5bef34e7b2ebb81d147123700669229 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 16 Oct 2022 01:15:48 +0000 Subject: [PATCH 11/52] feat(initializing): Implement additional server type field for server installation --- .../server_providers/digital_ocean/digital_ocean.dart | 5 ++++- .../api_maps/rest_maps/server_providers/hetzner/hetzner.dart | 5 ++++- .../api_maps/rest_maps/server_providers/server_provider.dart | 1 + .../server_installation/server_installation_repository.dart | 4 +++- lib/ui/pages/setup/initializing/server_type_picker.dart | 3 ++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index fb44a4dd..dafda8c4 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -294,6 +294,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { required final String dnsApiToken, required final User rootUser, required final String domainName, + required final String serverType, }) async { ServerHostingDetails? details; @@ -307,6 +308,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { rootUser: rootUser, domainName: domainName, dataBase: newVolume, + serverType: serverType, ); return details; @@ -317,6 +319,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { required final User rootUser, required final String domainName, required final ServerVolume dataBase, + required final String serverType, }) async { final Dio client = await getClient(); @@ -336,7 +339,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Map data = { 'name': hostname, - 'server_type': 'cx11', + 'server_type': serverType, 'start_after_create': false, 'image': 'ubuntu-20.04', 'volumes': dbUuid == null ? [dbId] : [dbUuid], diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 0d8e0ab0..5980f9cb 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -298,6 +298,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { required final String dnsApiToken, required final User rootUser, required final String domainName, + required final String serverType, }) async { ServerHostingDetails? details; @@ -311,6 +312,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { rootUser: rootUser, domainName: domainName, dataBase: newVolume, + serverType: serverType, ); return details; @@ -321,6 +323,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { required final User rootUser, required final String domainName, required final ServerVolume dataBase, + required final String serverType, }) async { final Dio client = await getClient(); @@ -343,7 +346,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { try { final Map data = { 'name': hostname, - 'server_type': 'cx11', + 'server_type': serverType, 'start_after_create': false, 'image': 'ubuntu-20.04', 'volumes': [dbId], diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index ffc1d2d9..cd28b46f 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -21,6 +21,7 @@ abstract class ServerProviderApi extends ApiMap { required final String dnsApiToken, required final User rootUser, required final String domainName, + required final String serverType, }); Future createReverseDns({ required final ServerHostingDetails serverDetails, diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 7296d3a3..5b33ef28 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -16,7 +16,6 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_ import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.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/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; @@ -247,6 +246,7 @@ class ServerInstallationRepository { dnsApiToken: cloudFlareKey, rootUser: rootUser, domainName: domainName, + serverType: getIt().serverType!, ); if (serverDetails == null) { @@ -277,6 +277,7 @@ class ServerInstallationRepository { dnsApiToken: cloudFlareKey, rootUser: rootUser, domainName: domainName, + serverType: getIt().serverType!, ); } catch (e) { print(e); @@ -314,6 +315,7 @@ class ServerInstallationRepository { dnsApiToken: cloudFlareKey, rootUser: rootUser, domainName: domainName, + serverType: getIt().serverType!, ); } catch (e) { print(e); diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index a913f240..7a56ac2e 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -154,7 +154,8 @@ class SelectTypePage extends StatelessWidget { Text('disk: $type.disk.gibibyte'), const SizedBox(height: 8), Text( - 'price: $type.price.value $type.price.currency'), + 'price: $type.price.value $type.price.currency', + ), ], ), ], From d19531232c24beeb35b9898704633cf81b7160c0 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 17 Oct 2022 17:42:23 +0000 Subject: [PATCH 12/52] feat(digital-ocean): Implement endpoints for server creation and deletion --- .../digital_ocean/digital_ocean.dart | 136 +++++++----------- .../server_providers/hetzner/hetzner.dart | 1 - .../server_installation_cubit.dart | 2 +- lib/logic/models/server_basic_info.dart | 4 - 4 files changed, 51 insertions(+), 92 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index dafda8c4..125fe3bd 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -95,6 +95,9 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Response dbCreateResponse; final Dio client = await getClient(); try { + final List volumes = await getVolumes(); + await Future.delayed(const Duration(seconds: 6)); + dbCreateResponse = await client.post( '/volumes', data: { @@ -109,11 +112,12 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final dbSize = dbCreateResponse.data['volume']['size_gigabytes']; final dbName = dbCreateResponse.data['volume']['name']; volume = ServerVolume( - id: dbId, + id: volumes.length, name: dbName, sizeByte: dbSize, serverId: null, linuxDevice: null, + uuid: dbId, ); } catch (e) { print(e); @@ -296,36 +300,9 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { required final String domainName, required final String serverType, }) async { - ServerHostingDetails? details; - - final ServerVolume? newVolume = await createVolume(); - if (newVolume == null) { - return details; - } - - details = await createServerWithVolume( - dnsApiToken: dnsApiToken, - rootUser: rootUser, - domainName: domainName, - dataBase: newVolume, - serverType: serverType, - ); - - return details; - } - - Future createServerWithVolume({ - required final String dnsApiToken, - required final User rootUser, - required final String domainName, - required final ServerVolume dataBase, - required final String serverType, - }) async { - final Dio client = await getClient(); + ServerHostingDetails? serverDetails; final String dbPassword = StringGenerators.dbPassword(); - final int dbId = dataBase.id; - final String? dbUuid = dataBase.uuid; final String apiToken = StringGenerators.apiToken(); final String hostname = getHostnameFromDomain(domainName); @@ -334,58 +311,44 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { base64.encode(utf8.encode(rootUser.password ?? 'PASS')); 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/master/nixos-infect | PROVIDER=digital-ocean 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 data = { - 'name': hostname, - 'server_type': serverType, - 'start_after_create': false, - 'image': 'ubuntu-20.04', - 'volumes': dbUuid == null ? [dbId] : [dbUuid], - 'networks': [], - 'user_data': userdataString, - 'labels': {}, - 'automount': true, - 'location': 'fsn1' - }; - print('Decoded data: $data'); - - ServerHostingDetails? serverDetails; - DioError? hetznerError; - bool success = false; - + final Dio client = await getClient(); try { + final Map data = { + 'name': hostname, + 'size': serverType, + 'image': 'ubuntu-20-04-x64', + 'user_data': userdataString, + 'region': region!, + }; + print('Decoded data: $data'); + final Response serverCreateResponse = await client.post( - '/servers', + '/droplets', data: data, ); - print(serverCreateResponse.data); - serverDetails = ServerHostingDetails( - id: serverCreateResponse.data['server']['id'], - ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'], - createTime: DateTime.now(), - volume: dataBase, - apiToken: apiToken, - provider: ServerProvider.hetzner, - ); - success = true; - } on DioError catch (e) { - print(e); - hetznerError = e; + + final int serverId = serverCreateResponse.data['server']['id']; + final ServerVolume? newVolume = await createVolume(); + final bool attachedVolume = + await attachVolume(newVolume!.uuid!, serverId); + + if (attachedVolume) { + serverDetails = ServerHostingDetails( + id: serverId, + ip4: serverCreateResponse.data['droplet']['networks']['v4'][0], + createTime: DateTime.now(), + volume: newVolume, + apiToken: apiToken, + provider: ServerProvider.digitalOcean, + ); + } } catch (e) { print(e); } finally { - client.close(); - } - - if (!success) { - await Future.delayed(const Duration(seconds: 10)); - await deleteVolume(dbUuid ?? dbId.toString()); - } - - if (hetznerError != null) { - throw hetznerError; + close(client); } return serverDetails; @@ -416,24 +379,26 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String hostname = getHostnameFromDomain(domainName); - final Response serversReponse = await client.get('/servers'); - final List servers = serversReponse.data['servers']; - final Map server = servers.firstWhere((final el) => el['name'] == hostname); - final List volumes = server['volumes']; + final ServerBasicInfo serverToRemove = (await getServers()).firstWhere( + (final el) => el.name == hostname, + ); + final ServerVolume volumeToRemove = (await getVolumes()).firstWhere( + (final el) => el.serverId == serverToRemove.id, + ); final List laterFutures = []; - for (final volumeId in volumes) { - await client.post('/volumes/$volumeId/actions/detach'); - } + await detachVolume(volumeToRemove.uuid!); await Future.delayed(const Duration(seconds: 10)); - for (final volumeId in volumes) { - laterFutures.add(client.delete('/volumes/$volumeId')); + try { + laterFutures.add(deleteVolume(volumeToRemove.uuid!)); + laterFutures.add(client.delete('/droplets/$serverToRemove.id')); + await Future.wait(laterFutures); + } catch (e) { + print(e); + } finally { + close(client); } - laterFutures.add(client.delete('/servers/${server['id']}')); - - await Future.wait(laterFutures); - close(client); } @override @@ -530,7 +495,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ip: server.publicNet.ipv4.ip, reverseDns: server.publicNet.ipv4.reverseDns, created: server.created, - volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0, ), ) .toList(); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 5980f9cb..ba94dc53 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -532,7 +532,6 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ip: server.publicNet.ipv4.ip, reverseDns: server.publicNet.ipv4.reverseDns, created: server.created, - volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0, ), ) .toList(); diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 360ec2e5..499ff49a 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -588,7 +588,7 @@ class ServerInstallationCubit extends Cubit { id: server.id, createTime: server.created, volume: ServerVolume( - id: server.volumeId, + id: 0, name: 'recovered_volume', sizeByte: 0, serverId: server.id, diff --git a/lib/logic/models/server_basic_info.dart b/lib/logic/models/server_basic_info.dart index 8670dc8c..b20803b0 100644 --- a/lib/logic/models/server_basic_info.dart +++ b/lib/logic/models/server_basic_info.dart @@ -5,14 +5,12 @@ class ServerBasicInfo { required this.reverseDns, required this.ip, required this.created, - required this.volumeId, }); final int id; final String name; final String reverseDns; final String ip; final DateTime created; - final int volumeId; } class ServerBasicInfoWithValidators extends ServerBasicInfo { @@ -26,7 +24,6 @@ class ServerBasicInfoWithValidators extends ServerBasicInfo { reverseDns: serverBasicInfo.reverseDns, ip: serverBasicInfo.ip, created: serverBasicInfo.created, - volumeId: serverBasicInfo.volumeId, isIpValid: isIpValid, isReverseDnsValid: isReverseDnsValid, ); @@ -37,7 +34,6 @@ class ServerBasicInfoWithValidators extends ServerBasicInfo { required final super.reverseDns, required final super.ip, required final super.created, - required final super.volumeId, required this.isIpValid, required this.isReverseDnsValid, }); From e4ed69d1510473c462c9af55705e6ebf8e4f76fd Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 17 Oct 2022 23:58:29 +0000 Subject: [PATCH 13/52] refactor(volume): Make volume interfaces work through volume entities, not IDs --- .../digital_ocean/digital_ocean.dart | 82 ++++++------------- .../server_providers/hetzner/hetzner.dart | 18 ++-- .../server_providers/volume_provider.dart | 9 +- .../provider_volume_cubit.dart | 8 +- 4 files changed, 40 insertions(+), 77 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 125fe3bd..fb3b71e2 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -167,16 +167,13 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return volumes; } - @override - - /// volumeId is storage's UUID for Digital Ocean - Future getVolume(final String volumeId) async { + Future getVolume(final String volumeUuid) async { ServerVolume? neededVolume; final List volumes = await getVolumes(); for (final volume in volumes) { - if (volume.uuid == volumeId) { + if (volume.uuid == volumeUuid) { neededVolume = volume; } } @@ -185,12 +182,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - - /// volumeId is storage's UUID for Digital Ocean - Future deleteVolume(final String volumeId) async { + Future deleteVolume(final ServerVolume volume) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$volumeId'); + await client.delete('/volumes/$volume.uuid'); } catch (e) { print(e); } finally { @@ -199,12 +194,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - - /// volumeId is storage's UUID for Digital Ocean - Future attachVolume(final String volumeId, final int serverId) async { + Future attachVolume(final ServerVolume volume, final int serverId) async { bool success = false; - final ServerVolume? volumeToAttach = await getVolume(volumeId); + final ServerVolume? volumeToAttach = await getVolume(volume.uuid!); if (volumeToAttach == null) { return success; } @@ -233,12 +226,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - - /// volumeId is storage's UUID for Digital Ocean - Future detachVolume(final String volumeId) async { + Future detachVolume(final ServerVolume volume) async { bool success = false; - final ServerVolume? volumeToAttach = await getVolume(volumeId); + final ServerVolume? volumeToAttach = await getVolume(volume.uuid!); if (volumeToAttach == null) { return success; } @@ -266,9 +257,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - - /// volumeId is storage's UUID for Digital Ocean - Future resizeVolume(final String volumeId, final int sizeGb) async { + Future resizeVolume(final ServerVolume volume, final int sizeGb) async { bool success = false; final Response dbPostResponse; @@ -303,21 +292,19 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ServerHostingDetails? serverDetails; final String dbPassword = StringGenerators.dbPassword(); - final String apiToken = StringGenerators.apiToken(); - final String hostname = getHostnameFromDomain(domainName); final String base64Password = base64.encode(utf8.encode(rootUser.password ?? 'PASS')); final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=digital-ocean 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/master/nixos-infect | PROVIDER=digital-ocean NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$domainName bash 2>&1 | tee /tmp/infect.log"; print(userdataString); final Dio client = await getClient(); try { final Map data = { - 'name': hostname, + 'name': domainName, 'size': serverType, 'image': 'ubuntu-20-04-x64', 'user_data': userdataString, @@ -333,7 +320,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final int serverId = serverCreateResponse.data['server']['id']; final ServerVolume? newVolume = await createVolume(); final bool attachedVolume = - await attachVolume(newVolume!.uuid!, serverId); + await attachVolume(newVolume!, serverId); if (attachedVolume) { serverDetails = ServerHostingDetails( @@ -354,44 +341,25 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return serverDetails; } - 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 deleteServer({ required final String domainName, }) async { final Dio client = await getClient(); - final String hostname = getHostnameFromDomain(domainName); - final ServerBasicInfo serverToRemove = (await getServers()).firstWhere( - (final el) => el.name == hostname, + (final el) => el.name == domainName, ); final ServerVolume volumeToRemove = (await getVolumes()).firstWhere( (final el) => el.serverId == serverToRemove.id, ); final List laterFutures = []; - await detachVolume(volumeToRemove.uuid!); + await detachVolume(volumeToRemove); await Future.delayed(const Duration(seconds: 10)); try { - laterFutures.add(deleteVolume(volumeToRemove.uuid!)); + laterFutures.add(deleteVolume(volumeToRemove)); laterFutures.add(client.delete('/droplets/$serverToRemove.id')); await Future.wait(laterFutures); } catch (e) { @@ -479,22 +447,18 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - final Response response = await client.get('/servers'); - servers = response.data!['servers'] - .map( - (final e) => HetznerServerInfo.fromJson(e), - ) - .toList() + final Response response = await client.get('/droplets'); + servers = response.data!['droplets'] .where( - (final server) => server.publicNet.ipv4 != null, + (final server) => server['networks']['v4'].isNotEmpty, ) .map( (final server) => ServerBasicInfo( - id: server.id, - name: server.name, - ip: server.publicNet.ipv4.ip, - reverseDns: server.publicNet.ipv4.reverseDns, - created: server.created, + id: server['id'], + reverseDns: server['name'], + created: DateTime.now(), + ip: server['networks']['v4'][0], + name: server['name'], ), ) .toList(); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index ba94dc53..8373adb7 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -217,10 +217,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future deleteVolume(final String volumeId) async { + Future deleteVolume(final ServerVolume volume) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$volumeId'); + await client.delete('/volumes/$volume.id'); } catch (e) { print(e); } finally { @@ -229,14 +229,14 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future attachVolume(final String volumeId, final int serverId) async { + Future attachVolume(final ServerVolume volume, final int serverId) async { bool success = false; final Response dbPostResponse; final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volumeId/actions/attach', + '/volumes/$volume.id/actions/attach', data: { 'automount': true, 'server': serverId, @@ -253,13 +253,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future detachVolume(final String volumeId) async { + Future detachVolume(final ServerVolume volume) async { bool success = false; final Response dbPostResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post('/volumes/$volumeId/actions/detach'); + dbPostResponse = await client.post('/volumes/$volume.id/actions/detach'); success = dbPostResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); @@ -271,14 +271,14 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future resizeVolume(final String volumeId, final int sizeGb) async { + Future resizeVolume(final ServerVolume volume, final int sizeGb) async { bool success = false; final Response dbPostResponse; final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volumeId/actions/resize', + '/volumes/$volume.id/actions/resize', data: { 'size': sizeGb, }, @@ -383,7 +383,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { if (!success) { await Future.delayed(const Duration(seconds: 10)); - await deleteVolume(dbId.toString()); + await deleteVolume(dataBase); } if (hetznerError != null) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart index 52458217..da196406 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart @@ -5,10 +5,9 @@ import 'package:selfprivacy/logic/models/price.dart'; mixin VolumeProviderApi on ApiMap { Future createVolume(); Future> getVolumes({final String? status}); - Future getVolume(final String volumeId); - Future attachVolume(final String volumeId, final int serverId); - Future detachVolume(final String volumeId); - Future resizeVolume(final String volumeId, final int sizeGb); - Future deleteVolume(final String volumeId); + Future attachVolume(final ServerVolume volume, final int serverId); + Future detachVolume(final ServerVolume volume); + Future resizeVolume(final ServerVolume volume, final int sizeGb); + Future deleteVolume(final ServerVolume volume); Future getPricePerGb(); } diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 66f21d7b..bf8595ea 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -66,14 +66,14 @@ class ApiProviderVolumeCubit final ServerHostingDetails server = getIt().serverDetails!; await providerApi! .getVolumeProvider() - .attachVolume(volume.providerVolume!.id.toString(), server.id); + .attachVolume(volume.providerVolume!, server.id); refresh(); } Future detachVolume(final DiskVolume volume) async { await providerApi! .getVolumeProvider() - .detachVolume(volume.providerVolume!.id.toString()); + .detachVolume(volume.providerVolume!); refresh(); } @@ -87,7 +87,7 @@ class ApiProviderVolumeCubit ); emit(state.copyWith(isResizing: true)); final bool resized = await providerApi!.getVolumeProvider().resizeVolume( - volume.providerVolume!.id.toString(), + volume.providerVolume!, newSizeGb, ); @@ -137,7 +137,7 @@ class ApiProviderVolumeCubit Future deleteVolume(final DiskVolume volume) async { await providerApi! .getVolumeProvider() - .deleteVolume(volume.providerVolume!.id.toString()); + .deleteVolume(volume.providerVolume!); refresh(); } From bb846b08c14ab9f77a3a098dea5c07470ba54c56 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Tue, 18 Oct 2022 00:48:41 +0000 Subject: [PATCH 14/52] feat(digital-ocean): Implement system endpoints for digital ocean --- .../digital_ocean/digital_ocean.dart | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index fb3b71e2..f3df6e54 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -197,11 +197,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { Future attachVolume(final ServerVolume volume, final int serverId) async { bool success = false; - final ServerVolume? volumeToAttach = await getVolume(volume.uuid!); - if (volumeToAttach == null) { - return success; - } - final Response dbPostResponse; final Dio client = await getClient(); try { @@ -209,7 +204,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { '/volumes/actions', data: { 'type': 'attach', - 'volume_name': volumeToAttach.name, + 'volume_name': volume.name, 'region': region, 'droplet_id': serverId, }, @@ -229,11 +224,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { Future detachVolume(final ServerVolume volume) async { bool success = false; - final ServerVolume? volumeToAttach = await getVolume(volume.uuid!); - if (volumeToAttach == null) { - return success; - } - final Response dbPostResponse; final Dio client = await getClient(); try { @@ -241,7 +231,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { '/volumes/actions', data: { 'type': 'detach', - 'droplet_id': volumeToAttach.serverId, + 'volume_name': volume.name, + 'droplet_id': volume.serverId, 'region': region, }, ); @@ -267,6 +258,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { '/volumes/actions', data: { 'type': 'resize', + 'volume_name': volume.name, 'size_gigabytes': sizeGb, 'region': region, }, @@ -375,7 +367,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - await client.post('/servers/${server.id}/actions/reset'); + await client.post('/droplets/${server.id}/actions', + data: { + 'type': 'reboot', + },); } catch (e) { print(e); } finally { @@ -391,7 +386,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - await client.post('/servers/${server.id}/actions/poweron'); + await client.post('/droplets/${server.id}/actions', + data: { + 'type': 'power_on', + }, + ); } catch (e) { print(e); } finally { @@ -582,19 +581,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { required final ServerHostingDetails serverDetails, required final ServerDomain domain, }) async { - final Dio client = await getClient(); - try { - await client.post( - '/servers/${serverDetails.id}/actions/change_dns_ptr', - data: { - 'ip': serverDetails.ip4, - 'dns_ptr': domain.domainName, - }, - ); - } catch (e) { - print(e); - } finally { - close(client); - } + /// TODO remove from provider interface } } From ea85ce606434d9842f6e1c02101548f055275916 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Tue, 18 Oct 2022 05:38:26 +0000 Subject: [PATCH 15/52] refactor: Apply formatting --- .../digital_ocean/digital_ocean.dart | 25 +++++++++++-------- .../server_providers/hetzner/hetzner.dart | 3 ++- .../initializing/provider_form_cubit.dart | 6 ----- .../provider_volume_cubit.dart | 8 ++---- .../setup/initializing/initializing.dart | 10 ++++---- .../initializing/server_provider_picker.dart | 9 +------ 6 files changed, 24 insertions(+), 37 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index f3df6e54..4bc13ae7 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -194,7 +194,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future attachVolume(final ServerVolume volume, final int serverId) async { + Future attachVolume( + final ServerVolume volume, final int serverId) async { bool success = false; final Response dbPostResponse; @@ -311,8 +312,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final int serverId = serverCreateResponse.data['server']['id']; final ServerVolume? newVolume = await createVolume(); - final bool attachedVolume = - await attachVolume(newVolume!, serverId); + final bool attachedVolume = await attachVolume(newVolume!, serverId); if (attachedVolume) { serverDetails = ServerHostingDetails( @@ -367,10 +367,12 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - await client.post('/droplets/${server.id}/actions', - data: { - 'type': 'reboot', - },); + await client.post( + '/droplets/${server.id}/actions', + data: { + 'type': 'reboot', + }, + ); } catch (e) { print(e); } finally { @@ -386,10 +388,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { - await client.post('/droplets/${server.id}/actions', - data: { - 'type': 'power_on', - }, + await client.post( + '/droplets/${server.id}/actions', + data: { + 'type': 'power_on', + }, ); } catch (e) { print(e); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 8373adb7..7edb4cf1 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -229,7 +229,8 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future attachVolume(final ServerVolume volume, final int serverId) async { + Future attachVolume( + final ServerVolume volume, final int serverId) async { bool success = false; final Response dbPostResponse; diff --git a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart index 727daea8..7405073e 100644 --- a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart @@ -7,16 +7,10 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class ProviderFormCubit extends FormCubit { ProviderFormCubit(this.serverInstallationCubit) { - final RegExp regExp = - serverInstallationCubit.getServerProviderApiTokenValidation(); apiKey = FieldCubit( initalValue: '', validations: [ RequiredStringValidation('validations.required'.tr()), - ValidationModel( - regExp.hasMatch, - 'validations.invalid_format'.tr(), - ), LengthStringNotEqualValidation(64) ], ); diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index bf8595ea..b92a51c5 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -71,9 +71,7 @@ class ApiProviderVolumeCubit } Future detachVolume(final DiskVolume volume) async { - await providerApi! - .getVolumeProvider() - .detachVolume(volume.providerVolume!); + await providerApi!.getVolumeProvider().detachVolume(volume.providerVolume!); refresh(); } @@ -135,9 +133,7 @@ class ApiProviderVolumeCubit } Future deleteVolume(final DiskVolume volume) async { - await providerApi! - .getVolumeProvider() - .deleteVolume(volume.providerVolume!); + await providerApi!.getVolumeProvider().deleteVolume(volume.providerVolume!); refresh(); } diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 029a30e4..eb6f05a5 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -141,13 +141,13 @@ class InitializingPage extends StatelessWidget { } Widget _stepServerProviderToken( - final ServerInstallationCubit serverInstallationCubit) => - ServerProviderPicker( - serverInstallationCubit: serverInstallationCubit, - ); + final ServerInstallationCubit serverInstallationCubit, + ) => + const ServerProviderPicker(); Widget _stepServerType( - final ServerInstallationCubit serverInstallationCubit) => + final ServerInstallationCubit serverInstallationCubit, + ) => ServerTypePicker( serverInstallationCubit: serverInstallationCubit, ); diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index ee0ab36a..c8b8e828 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -11,12 +11,9 @@ import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; class ServerProviderPicker extends StatefulWidget { const ServerProviderPicker({ - required this.serverInstallationCubit, super.key, }); - final ServerInstallationCubit serverInstallationCubit; - @override State createState() => _ServerProviderPickerState(); } @@ -40,7 +37,6 @@ class _ServerProviderPickerState extends State { case ServerProvider.hetzner: return ProviderInputDataPage( - serverInstallationCubit: widget.serverInstallationCubit, providerInfo: ProviderPageInfo( providerType: ServerProvider.hetzner, pathToHow: 'hetzner_how', @@ -53,7 +49,6 @@ class _ServerProviderPickerState extends State { case ServerProvider.digitalOcean: return ProviderInputDataPage( - serverInstallationCubit: widget.serverInstallationCubit, providerInfo: ProviderPageInfo( providerType: ServerProvider.digitalOcean, pathToHow: 'hetzner_how', @@ -82,17 +77,15 @@ class ProviderPageInfo { class ProviderInputDataPage extends StatelessWidget { const ProviderInputDataPage({ required this.providerInfo, - required this.serverInstallationCubit, super.key, }); final ProviderPageInfo providerInfo; - final ServerInstallationCubit serverInstallationCubit; @override Widget build(final BuildContext context) => BlocProvider( create: (final context) => ProviderFormCubit( - serverInstallationCubit, + context.watch(), ), child: Builder( builder: (final context) { From b40ab171979ee594f6e48ce5766c8453e1beb114 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 19 Oct 2022 18:43:01 +0400 Subject: [PATCH 16/52] fix(initializing): Move bloc provider from provider picker to initialization page --- .../setup/initializing/initializing.dart | 23 +++- .../initializing/server_provider_picker.dart | 120 +++++++++--------- 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index eb6f05a5..a4a96435 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -2,6 +2,7 @@ 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/forms/setup/initializing/provider_form_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/setup/initializing/backblaze_form_cubit.dart'; @@ -143,13 +144,29 @@ class InitializingPage extends StatelessWidget { Widget _stepServerProviderToken( final ServerInstallationCubit serverInstallationCubit, ) => - const ServerProviderPicker(); + BlocProvider( + create: (final context) => ProviderFormCubit(serverInstallationCubit), + child: Builder( + builder: (final context) { + final providerCubit = context.watch(); + return ServerProviderPicker( + formCubit: providerCubit, + serverInstallationCubit: serverInstallationCubit, + ); + }, + ), + ); Widget _stepServerType( final ServerInstallationCubit serverInstallationCubit, ) => - ServerTypePicker( - serverInstallationCubit: serverInstallationCubit, + BlocProvider( + create: (final context) => ProviderFormCubit(serverInstallationCubit), + child: Builder( + builder: (final context) => ServerTypePicker( + serverInstallationCubit: serverInstallationCubit, + ), + ), ); void _showModal(final BuildContext context, final Widget widget) { diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index c8b8e828..3984b856 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -11,9 +11,14 @@ 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 createState() => _ServerProviderPickerState(); } @@ -32,11 +37,13 @@ class _ServerProviderPickerState extends State { 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', @@ -49,6 +56,7 @@ class _ServerProviderPickerState extends State { case ServerProvider.digitalOcean: return ProviderInputDataPage( + providerCubit: widget.formCubit, providerInfo: ProviderPageInfo( providerType: ServerProvider.digitalOcean, pathToHow: 'hetzner_how', @@ -77,81 +85,75 @@ class ProviderPageInfo { 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) => BlocProvider( - create: (final context) => ProviderFormCubit( - context.watch(), - ), - child: Builder( - builder: (final context) { - final formCubitState = context.watch().state; - return 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: context.read().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: () => formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - ), - const SizedBox(height: 10), - OutlinedButton( - child: Text('initializing.how'.tr()), - onPressed: () => showModalBottomSheet( - 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, - ), - ], - ), + 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.state.isSubmitting + ? null + : () => providerCubit.trySubmit(), + ), + const SizedBox(height: 10), + OutlinedButton( + child: Text('initializing.how'.tr()), + onPressed: () => showModalBottomSheet( + 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( @@ -173,8 +175,7 @@ class ProviderSelectionPage extends StatelessWidget { children: [ InkWell( onTap: () { - context - .read() + serverInstallationCubit .setServerProviderType(ServerProvider.hetzner); callback(ServerProvider.hetzner); }, @@ -188,8 +189,7 @@ class ProviderSelectionPage extends StatelessWidget { ), InkWell( onTap: () { - context - .read() + serverInstallationCubit .setServerProviderType(ServerProvider.digitalOcean); callback(ServerProvider.digitalOcean); }, From cb1fe6eafde0945b6d618f2ababb54e26f44a93c Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 20 Oct 2022 18:36:15 +0400 Subject: [PATCH 17/52] fix(initializing): Make provider and server type picking work --- lib/config/hive_config.dart | 3 + .../rest_maps/api_factory_creator.dart | 4 +- .../digital_ocean/digital_ocean_factory.dart | 8 +- .../server_providers/hetzner/hetzner.dart | 79 ++++++++++--------- .../server_installation_cubit.dart | 6 +- .../server_installation_repository.dart | 4 + lib/logic/get_it/api_config.dart | 7 ++ .../setup/initializing/initializing.dart | 3 +- .../initializing/server_provider_picker.dart | 4 +- .../initializing/server_type_picker.dart | 8 +- 10 files changed, 74 insertions(+), 52 deletions(-) diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 7d8e59aa..93bce6ee 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -87,6 +87,9 @@ class BNames { /// A String field of [serverInstallationBox] box. static String hetznerKey = 'hetznerKey'; + /// A String field of [serverInstallationBox] box. + static String serverProvider = 'serverProvider'; + /// A String field of [serverLocation] box. static String serverLocation = 'serverLocation'; diff --git a/lib/logic/api_maps/rest_maps/api_factory_creator.dart b/lib/logic/api_maps/rest_maps/api_factory_creator.dart index 0dbe1e90..25518f3c 100644 --- a/lib/logic/api_maps/rest_maps/api_factory_creator.dart +++ b/lib/logic/api_maps/rest_maps/api_factory_creator.dart @@ -18,9 +18,9 @@ class ApiFactoryCreator { ) { switch (settings.provider) { case ServerProvider.hetzner: - return HetznerApiFactory(); + return HetznerApiFactory(region: settings.location); case ServerProvider.digitalOcean: - return DigitalOceanApiFactory(); + return DigitalOceanApiFactory(region: settings.location); case ServerProvider.unknown: throw UnknownApiProviderException('Unknown server provider'); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart index 6864c7ab..73a1e647 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart @@ -6,13 +6,17 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_pro 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: settings.region ?? region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); @@ -23,7 +27,7 @@ class DigitalOceanApiFactory extends ServerProviderApiFactory const ServerProviderApiSettings(), }) => DigitalOceanApi( - region: settings.region, + region: settings.region ?? region, hasLogger: settings.hasLogger, isWithToken: settings.isWithToken, ); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 7edb4cf1..c5360a2d 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -187,8 +187,9 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return volumes; } - @override - Future getVolume(final String volumeId) async { + Future getVolume( + final String volumeId, + ) async { ServerVolume? volume; final Response dbGetResponse; @@ -230,7 +231,9 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { @override Future attachVolume( - final ServerVolume volume, final int serverId) async { + final ServerVolume volume, + final int serverId, + ) async { bool success = false; final Response dbPostResponse; @@ -576,14 +579,16 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { '/locations', ); - locations = response.data!['locations'].map( - (final location) => ServerProviderLocation( - title: location['city'], - description: location['description'], - flag: getEmojiFlag(location['country']), - identifier: location['name'], - ), - ); + locations = response.data!['locations'] + .map( + (final location) => ServerProviderLocation( + title: location['city'], + description: location['description'], + flag: getEmojiFlag(location['country']), + identifier: location['name'], + ), + ) + .toList(); } catch (e) { print(e); } finally { @@ -600,36 +605,36 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final List 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: rawPrice['price_monthly']['gross'], - currency: 'EUR', - ), - location: location, + //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); } + //} catch (e) { + //print(e); + //} finally { + close(client); + //} return types; } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 499ff49a..e298d7ad 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -55,7 +55,8 @@ class ServerInstallationCubit extends Cubit { } } - void setServerProviderType(final ServerProvider providerType) { + void setServerProviderType(final ServerProvider providerType) async { + await repository.saveServerProviderType(providerType); repository.serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( ServerProviderApiFactorySettings( @@ -117,7 +118,6 @@ class ServerInstallationCubit extends Cubit { void setServerProviderKey(final String serverProviderKey) async { await repository.saveServerProviderKey(serverProviderKey); - if (state is ServerInstallationRecovery) { emit( (state as ServerInstallationRecovery).copyWith( @@ -141,7 +141,7 @@ class ServerInstallationCubit extends Cubit { repository.serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( ServerProviderApiFactorySettings( - provider: getIt().serverDetails!.provider, + provider: getIt().serverProvider!, location: serverType.location.identifier, ), ); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 5b33ef28..f6120cbb 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -683,6 +683,10 @@ class ServerInstallationRepository { getIt().init(); } + Future saveServerProviderType(final ServerProvider type) async { + await getIt().storeServerProviderType(type); + } + Future saveServerProviderKey(final String key) async { await getIt().storeServerProviderKey(key); } diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 302a37b1..4f64af8f 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -13,6 +13,7 @@ class ApiConfigModel { String? get serverLocation => _serverLocation; String? get serverType => _serverType; String? get cloudFlareKey => _cloudFlareKey; + ServerProvider? get serverProvider => _serverProvider; BackblazeCredential? get backblazeCredential => _backblazeCredential; ServerDomain? get serverDomain => _serverDomain; BackblazeBucket? get backblazeBucket => _backblazeBucket; @@ -21,11 +22,17 @@ class ApiConfigModel { String? _serverLocation; String? _cloudFlareKey; String? _serverType; + ServerProvider? _serverProvider; ServerHostingDetails? _serverDetails; BackblazeCredential? _backblazeCredential; ServerDomain? _serverDomain; BackblazeBucket? _backblazeBucket; + Future storeServerProviderType(final ServerProvider value) async { + await _box.put(BNames.serverProvider, value); + _serverProvider = value; + } + Future storeServerProviderKey(final String value) async { await _box.put(BNames.hetznerKey, value); _serverProviderKey = value; diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index b5c7851c..217bfc86 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -72,7 +72,8 @@ class InitializingPage extends StatelessWidget { ) : ProgressBar( steps: const [ - 'Hetzner', + 'Hosting', + 'Server Type', 'CloudFlare', 'Backblaze', 'Domain', diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index 3984b856..4934d1e3 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -114,9 +114,7 @@ class ProviderInputDataPage extends StatelessWidget { const Spacer(), FilledButton( title: 'basis.connect'.tr(), - onPressed: () => providerCubit.state.isSubmitting - ? null - : () => providerCubit.trySubmit(), + onPressed: () => providerCubit.trySubmit(), ), const SizedBox(height: 10), OutlinedButton( diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index 7a56ac2e..ce21b5aa 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -147,14 +147,14 @@ class SelectTypePage extends StatelessWidget { children: [ Text(type.title), const SizedBox(height: 8), - Text('cores: $type.cores'), + Text('cores: $type.cores.toString()'), const SizedBox(height: 8), - Text('ram: $type.ram'), + Text('ram: $type.ram.toString()'), const SizedBox(height: 8), - Text('disk: $type.disk.gibibyte'), + Text('disk: $type.disk.gibibyte.toString()'), const SizedBox(height: 8), Text( - 'price: $type.price.value $type.price.currency', + 'price: $type.price.value.toString() $type.price.currency', ), ], ), From 7223b0e6144cce76440353169690c4eecbcffe51 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 21 Oct 2022 06:38:49 +0400 Subject: [PATCH 18/52] fix(initialization): Add missing setup progress step --- .../cubit/server_installation/server_installation_state.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 9e5684a6..18742303 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -112,7 +112,8 @@ class TimerState extends ServerInstallationNotFinished { enum ServerSetupProgress { nothingYet, - hetznerFilled, + serverProviderFilled, + servertTypeFilled, cloudFlareFilled, backblazeFilled, domainFilled, From df9ec28d027908efe7b4560f5b5f1da52ed49ff0 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 21 Oct 2022 08:33:47 +0400 Subject: [PATCH 19/52] fix(initializing): Adjust server location and type list cards --- .../initializing/server_type_picker.dart | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index ce21b5aa..a6757571 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -78,18 +78,12 @@ class SelectLocationPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - 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!), - ], - ), + 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!), ], ), ), @@ -141,22 +135,29 @@ class SelectTypePage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(type.title), - const SizedBox(height: 8), - Text('cores: $type.cores.toString()'), - const SizedBox(height: 8), - Text('ram: $type.ram.toString()'), - const SizedBox(height: 8), - Text('disk: $type.disk.gibibyte.toString()'), - const SizedBox(height: 8), - Text( - 'price: $type.price.value.toString() $type.price.currency', - ), - ], + 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, ), ], ), From b574659dc35ea7ffd8ff6d4c64955e20628dfac7 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 21 Oct 2022 08:34:36 +0400 Subject: [PATCH 20/52] fix(initializing): Implement store and load of server provider from BNames --- .../server_installation/server_installation_cubit.dart | 1 + .../server_installation_repository.dart | 9 ++++++--- lib/logic/get_it/api_config.dart | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index e298d7ad..824713ef 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -118,6 +118,7 @@ class ServerInstallationCubit extends Cubit { void setServerProviderKey(final String serverProviderKey) async { await repository.saveServerProviderKey(serverProviderKey); + if (state is ServerInstallationRecovery) { emit( (state as ServerInstallationRecovery).copyWith( diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index f6120cbb..8de56f93 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -56,17 +56,20 @@ class ServerInstallationRepository { final String? cloudflareToken = getIt().cloudFlareKey; final String? serverTypeIdentificator = getIt().serverType; final ServerDomain? serverDomain = getIt().serverDomain; + final ServerProvider? serverProvider = + getIt().serverProvider; final BackblazeCredential? backblazeCredential = getIt().backblazeCredential; final ServerHostingDetails? serverDetails = getIt().serverDetails; - if (serverDetails != null && - serverDetails.provider != ServerProvider.unknown) { + if (serverProvider != null || + (serverDetails != null && + serverDetails.provider != ServerProvider.unknown)) { serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( ServerProviderApiFactorySettings( - provider: serverDetails.provider, + provider: serverProvider ?? serverDetails!.provider, location: location, ), ); diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 4f64af8f..434c9b32 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -82,6 +82,7 @@ class ApiConfigModel { _serverDetails = null; _backblazeBucket = null; _serverType = null; + _serverProvider = null; } void init() { @@ -93,5 +94,6 @@ class ApiConfigModel { _serverDetails = _box.get(BNames.serverDetails); _backblazeBucket = _box.get(BNames.backblazeBucket); _serverType = _box.get(BNames.serverTypeIdentifier); + _serverProvider = _box.get(BNames.serverProvider); } } From a69b096d6f9a2037c052e295afd04630255d7d03 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 26 Oct 2022 20:07:35 +0400 Subject: [PATCH 21/52] fix(digital-ocean): Change /locations to /regions and fix tokens validation --- .../digital_ocean/digital_ocean.dart | 15 ++++++++++++--- .../server_providers/hetzner/hetzner.dart | 7 +++++-- .../server_providers/server_provider.dart | 13 ++++++++++--- .../setup/initializing/provider_form_cubit.dart | 4 +++- .../server_installation_cubit.dart | 3 ++- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 4bc13ae7..a878b958 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -195,7 +195,9 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { @override Future attachVolume( - final ServerVolume volume, final int serverId) async { + final ServerVolume volume, + final int serverId, + ) async { bool success = false; final Response dbPostResponse; @@ -518,10 +520,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { final Response response = await client.get( - '/locations', + '/regions', ); - locations = response.data!['locations'].map( + locations = response.data!['regions'].map( (final location) => ServerProviderLocation( title: location['slug'], description: location['name'], @@ -586,4 +588,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { }) async { /// TODO remove from provider interface } + + @override + ProviderApiTokenValidation getApiTokenValidation() => + ProviderApiTokenValidation( + regexp: RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'), + length: 71, + ); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index c5360a2d..70094eb6 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -81,8 +81,11 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - RegExp getApiTokenValidation() => - RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); + ProviderApiTokenValidation getApiTokenValidation() => + ProviderApiTokenValidation( + regexp: RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'), + length: 64, + ); @override Future getPricePerGb() async { diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index cd28b46f..7d42c0ab 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -6,6 +6,15 @@ import 'package:selfprivacy/logic/models/server_basic_info.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 { Future> getServers(); Future> getAvailableLocations(); @@ -29,7 +38,5 @@ abstract class ServerProviderApi extends ApiMap { }); Future isApiTokenValid(final String token); - RegExp getApiTokenValidation() => RegExp( - r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]', - ); + ProviderApiTokenValidation getApiTokenValidation(); } diff --git a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart index 7405073e..a99ebea9 100644 --- a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart @@ -7,11 +7,13 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class ProviderFormCubit extends FormCubit { ProviderFormCubit(this.serverInstallationCubit) { + //final int tokenLength = + // serverInstallationCubit.serverProviderApiTokenValidation().length; apiKey = FieldCubit( initalValue: '', validations: [ RequiredStringValidation('validations.required'.tr()), - LengthStringNotEqualValidation(64) + //LengthStringNotEqualValidation(tokenLength), ], ); diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 824713ef..6ff73893 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/config/get_it_config.dart'; 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_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/server_details.dart'; @@ -65,7 +66,7 @@ class ServerInstallationCubit extends Cubit { ); } - RegExp getServerProviderApiTokenValidation() => + ProviderApiTokenValidation serverProviderApiTokenValidation() => repository.serverProviderApiFactory! .getServerProvider() .getApiTokenValidation(); From cb94248df050d16883470ef10c760d9cc06dc847 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 27 Oct 2022 17:08:59 +0400 Subject: [PATCH 22/52] fix: Generate hive build runner model for server details --- lib/logic/models/hive/server_details.g.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/logic/models/hive/server_details.g.dart b/lib/logic/models/hive/server_details.g.dart index 29716607..6fad2e92 100644 --- a/lib/logic/models/hive/server_details.g.dart +++ b/lib/logic/models/hive/server_details.g.dart @@ -76,13 +76,14 @@ class ServerVolumeAdapter extends TypeAdapter { sizeByte: fields[3] == null ? 10737418240 : fields[3] as int, serverId: fields[4] as int?, linuxDevice: fields[5] as String?, + uuid: fields[6] as String?, ); } @override void write(BinaryWriter writer, ServerVolume obj) { writer - ..writeByte(5) + ..writeByte(6) ..writeByte(1) ..write(obj.id) ..writeByte(2) @@ -92,7 +93,9 @@ class ServerVolumeAdapter extends TypeAdapter { ..writeByte(4) ..write(obj.serverId) ..writeByte(5) - ..write(obj.linuxDevice); + ..write(obj.linuxDevice) + ..writeByte(6) + ..write(obj.uuid); } @override @@ -117,6 +120,8 @@ class ServerProviderAdapter extends TypeAdapter { return ServerProvider.unknown; case 1: return ServerProvider.hetzner; + case 2: + return ServerProvider.digitalOcean; default: return ServerProvider.unknown; } @@ -131,6 +136,9 @@ class ServerProviderAdapter extends TypeAdapter { case ServerProvider.hetzner: writer.writeByte(1); break; + case ServerProvider.digitalOcean: + writer.writeByte(2); + break; } } From 0dc0ba215a10263931606ba20c8fe68f4aa94100 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 27 Oct 2022 20:01:22 +0400 Subject: [PATCH 23/52] fix(digital-ocean): Adjust droplet data preparations according to API notation --- .../digital_ocean/digital_ocean.dart | 104 ++++++++++++------ .../server_providers/hetzner/hetzner.dart | 52 ++++----- lib/utils/password_generator.dart | 2 +- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index a878b958..ee574988 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -102,7 +102,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { '/volumes', data: { 'size_gigabytes': 10, - 'name': StringGenerators.dbStorageName(), + 'name': 'volume${StringGenerators.dbStorageName()}', 'labels': {'labelkey': 'value'}, 'region': region, 'filesystem_type': 'ext4', @@ -212,12 +212,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'droplet_id': serverId, }, ); - success = - dbPostResponse.data['action']['status'].toString() == 'completed'; + success = dbPostResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { - client.close(); + close(client); } return success; @@ -277,6 +276,23 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 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 createServer({ required final String dnsApiToken, @@ -292,14 +308,16 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String base64Password = base64.encode(utf8.encode(rootUser.password ?? 'PASS')); + final String formattedHostname = getHostnameFromDomain(domainName); + final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=digital-ocean NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$domainName bash 2>&1 | tee /tmp/infect.log"; + "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=digital-ocean NIX_CHANNEL=nixos-21.05 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 data = { - 'name': domainName, + 'name': formattedHostname, 'size': serverType, 'image': 'ubuntu-20-04-x64', 'user_data': userdataString, @@ -312,14 +330,28 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { data: data, ); - final int serverId = serverCreateResponse.data['server']['id']; + final int serverId = serverCreateResponse.data['droplet']['id']; final ServerVolume? newVolume = await createVolume(); final bool attachedVolume = await attachVolume(newVolume!, serverId); - if (attachedVolume) { + String? ipv4; + int attempts = 0; + while (attempts < 5 && ipv4 == null) { + await Future.delayed(const Duration(seconds: 20)); + final List 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: serverCreateResponse.data['droplet']['networks']['v4'][0], + ip4: ipv4, createTime: DateTime.now(), volume: newVolume, apiToken: apiToken, @@ -452,20 +484,26 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { final Response response = await client.get('/droplets'); - servers = response.data!['droplets'] - .where( - (final server) => server['networks']['v4'].isNotEmpty, - ) - .map( - (final server) => ServerBasicInfo( - id: server['id'], - reverseDns: server['name'], - created: DateTime.now(), - ip: server['networks']['v4'][0], - name: server['name'], - ), - ) - .toList(); + servers = response.data!['droplets'].map( + (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 { @@ -523,14 +561,16 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { '/regions', ); - locations = response.data!['regions'].map( - (final location) => ServerProviderLocation( - title: location['slug'], - description: location['name'], - flag: getEmojiFlag(location['slug']), - identifier: location['slug'], - ), - ); + locations = response.data!['regions'] + .map( + (final location) => ServerProviderLocation( + title: location['slug'], + description: location['name'], + flag: getEmojiFlag(location['slug']), + identifier: location['slug'], + ), + ) + .toList(); } catch (e) { print(e); } finally { @@ -559,7 +599,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ServerType( title: rawSize['description'], identifier: rawSize['slug'], - ram: rawSize['memory'], + ram: rawSize['memory'].toDouble(), cores: rawSize['vcpus'], disk: DiskSize(byte: rawSize['disk'] * 1024 * 1024 * 1024), price: Price( diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 70094eb6..7a26de87 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -608,36 +608,36 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final List 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', + 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, ), - location: location, - ), - ); + ); + } } } + } catch (e) { + print(e); + } finally { + close(client); } - //} catch (e) { - //print(e); - //} finally { - close(client); - //} return types; } diff --git a/lib/utils/password_generator.dart b/lib/utils/password_generator.dart index 5acf3888..c8a6cdf0 100644 --- a/lib/utils/password_generator.dart +++ b/lib/utils/password_generator.dart @@ -104,7 +104,7 @@ class StringGenerators { static StringGeneratorFunction dbStorageName = () => getRandomString( 6, hasLowercaseLetters: true, - hasUppercaseLetters: true, + hasUppercaseLetters: false, hasNumbers: true, ); From 57d82d0f7abf08232c5466e92a7347f22758d2b9 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 29 Oct 2022 12:03:43 +0400 Subject: [PATCH 24/52] refactor(server-api): Move provider name from hardcode string to a polymorphic variable - Rename 'digital-ocean' to 'digitalocean' --- .../server_providers/digital_ocean/digital_ocean.dart | 7 +++++-- .../rest_maps/server_providers/hetzner/hetzner.dart | 7 +++++-- .../rest_maps/server_providers/server_provider.dart | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index ee574988..37965890 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -46,7 +46,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - String rootAddress = 'https://api.digitalocean.com/v2'; + final String rootAddress = 'https://api.digitalocean.com/v2'; + + @override + final String infectProviderName = 'digitalocean'; @override Future isApiTokenValid(final String token) async { @@ -311,7 +314,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String formattedHostname = getHostnameFromDomain(domainName); final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=digital-ocean NIX_CHANNEL=nixos-21.05 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"; + "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | 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=$formattedHostname bash 2>&1 | tee /tmp/infect.log"; print(userdataString); final Dio client = await getClient(); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 7a26de87..6c67ff3e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -46,7 +46,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - String rootAddress = 'https://api.hetzner.cloud/v1'; + final String rootAddress = 'https://api.hetzner.cloud/v1'; + + @override + final String infectProviderName = 'hetzner'; @override Future isApiTokenValid(final String token) async { @@ -344,7 +347,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { base64.encode(utf8.encode(rootUser.password ?? 'PASS')); 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/master/nixos-infect | 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"; ServerHostingDetails? serverDetails; DioError? hetznerError; diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 7d42c0ab..b04ae1e0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -39,4 +39,6 @@ abstract class ServerProviderApi extends ApiMap { Future isApiTokenValid(final String token); ProviderApiTokenValidation getApiTokenValidation(); + + abstract final String infectProviderName; } From dc4ba7bce57e7e39a7ec7ddba06333cd340d12e7 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 2 Nov 2022 18:59:41 +0400 Subject: [PATCH 25/52] fix(digital-ocean): Modify cloud-init, add write_files sections Provide host.nix to infect with 0644 permissions --- .../server_providers/digital_ocean/digital_ocean.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 37965890..1ab8a132 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -313,8 +313,11 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String formattedHostname = getHostnameFromDomain(domainName); + // TODO: change to 'master' change to 'master' change to 'master' + const String infectBranch = 'digital-ocean'; + final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | 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=$formattedHostname bash 2>&1 | tee /tmp/infect.log"; + "#cloud-config\nwrite_files:\n- path: /etc/nixos/host.nix\n permissions: '0644'\n content: |\n {pkgs, ...}:\n {\n environment.systemPackages = with pkgs; [ vim ];\n }\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | PROVIDER=$infectProviderName NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-21.05 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(); From edc171efd7e2f80b806c5e4ab07ef4e596047327 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 4 Nov 2022 15:59:38 +0400 Subject: [PATCH 26/52] fix(digital-ocean): Fix emoji picking for Digital ocean locations --- .../rest_maps/server_providers/digital_ocean/digital_ocean.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 1ab8a132..f607da96 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -523,7 +523,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { String? getEmojiFlag(final String query) { String? emoji; - switch (query.toLowerCase().substring(0, 2)) { + switch (query.toLowerCase().substring(0, 3)) { case 'fra': emoji = '🇩🇪'; break; From aa1c04fdb8235cee00e2993c1efc73b6f7bc159b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 4 Nov 2022 16:05:40 +0400 Subject: [PATCH 27/52] fix(digital-ocean): Fix response code checking on volume actions We can't compare with 'compelted' because Digital Ocean responses with 'in progress' right away or something, so it's better to check if it's just not 'error' --- .../server_providers/digital_ocean/digital_ocean.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index f607da96..989085b2 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -241,8 +241,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'region': region, }, ); - success = - dbPostResponse.data['action']['status'].toString() == 'completed'; + success = dbPostResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -268,8 +267,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'region': region, }, ); - success = - dbPostResponse.data['action']['status'].toString() == 'completed'; + success = dbPostResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { From 59d186a8afab0a6438f1e9599a59a5ca0b03234d Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 4 Nov 2022 16:18:35 +0400 Subject: [PATCH 28/52] fix(volume): Change raw int to DiskSize object in volume endpoints We already have an abstraction for size, there is no need to pass size value as raw numeric variables --- .../server_providers/digital_ocean/digital_ocean.dart | 7 +++++-- .../rest_maps/server_providers/hetzner/hetzner.dart | 7 +++++-- .../rest_maps/server_providers/volume_provider.dart | 3 ++- .../cubit/provider_volumes/provider_volume_cubit.dart | 5 +++-- lib/ui/pages/server_storage/extending_volume.dart | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 989085b2..6bc66b35 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -252,7 +252,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - Future resizeVolume(final ServerVolume volume, final int sizeGb) async { + Future resizeVolume( + final ServerVolume volume, + final DiskSize size, + ) async { bool success = false; final Response dbPostResponse; @@ -263,7 +266,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { data: { 'type': 'resize', 'volume_name': volume.name, - 'size_gigabytes': sizeGb, + 'size_gigabytes': size.gibibyte, 'region': region, }, ); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 6c67ff3e..13fb2cde 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -281,7 +281,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - Future resizeVolume(final ServerVolume volume, final int sizeGb) async { + Future resizeVolume( + final ServerVolume volume, + final DiskSize size, + ) async { bool success = false; final Response dbPostResponse; @@ -290,7 +293,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { dbPostResponse = await client.post( '/volumes/$volume.id/actions/resize', data: { - 'size': sizeGb, + 'size': size.gibibyte, }, ); success = dbPostResponse.data['action']['status'].toString() != 'error'; diff --git a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart index da196406..d3ae6f2a 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/volume_provider.dart @@ -1,4 +1,5 @@ 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/price.dart'; @@ -7,7 +8,7 @@ mixin VolumeProviderApi on ApiMap { Future> getVolumes({final String? status}); Future attachVolume(final ServerVolume volume, final int serverId); Future detachVolume(final ServerVolume volume); - Future resizeVolume(final ServerVolume volume, final int sizeGb); + Future resizeVolume(final ServerVolume volume, final DiskSize size); Future deleteVolume(final ServerVolume volume); Future getPricePerGb(); } diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 1c9ef2b1..1a8f1c15 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -6,6 +6,7 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.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/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/disk_status.dart'; import 'package:selfprivacy/logic/models/price.dart'; @@ -76,7 +77,7 @@ class ApiProviderVolumeCubit Future resizeVolume( final DiskVolume volume, - final int newSizeGb, + final DiskSize newSize, final Function() callback, ) async { getIt().showSnackBar( @@ -85,7 +86,7 @@ class ApiProviderVolumeCubit emit(state.copyWith(isResizing: true)); final bool resized = await providerApi!.getVolumeProvider().resizeVolume( volume.providerVolume!, - newSizeGb, + newSize, ); if (!resized) { diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index b2293e11..e16544b5 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -153,7 +153,7 @@ class _ExtendingVolumePageState extends State { : () { context.read().resizeVolume( widget.diskVolumeToResize, - _currentSliderGbValue.round(), + DiskSize.fromGibibyte(_currentSliderGbValue), context.read().reload, ); Navigator.of(context).pushAndRemoveUntil( From 6eb49fa8f1a389c1326e88109e89eabef3e5f942 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 8 Nov 2022 04:25:04 +0300 Subject: [PATCH 29/52] fix(digital-ocean): Change the cloud-config payload Now the server builds! --- .../server_providers/digital_ocean/digital_ocean.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 6bc66b35..db9bcd93 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -315,10 +315,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final String formattedHostname = getHostnameFromDomain(domainName); // TODO: change to 'master' change to 'master' change to 'master' - const String infectBranch = 'digital-ocean'; + const String infectBranch = 'providers/digital-ocean'; final String userdataString = - "#cloud-config\nwrite_files:\n- path: /etc/nixos/host.nix\n permissions: '0644'\n content: |\n {pkgs, ...}:\n {\n environment.systemPackages = with pkgs; [ vim ];\n }\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | PROVIDER=$infectProviderName NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-21.05 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"; + "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | PROVIDER=$infectProviderName 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(); From cdc47ecdb3624769b64ddb5f294f208d3ee39923 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 9 Nov 2022 19:49:37 +0400 Subject: [PATCH 30/52] refactor(ui): Move all pop up dialogs into general utils function To not import get_it everywhere and encapsulate all the related dirt into utils. --- assets/translations/en.json | 3 +- assets/translations/ru.json | 3 +- .../server_installation_repository.dart | 165 +++++++----------- .../components/jobs_content/jobs_content.dart | 28 +-- lib/ui/helpers/modals.dart | 31 ++++ .../pages/backup_details/backup_details.dart | 35 ++-- 6 files changed, 119 insertions(+), 146 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 2958663e..dd6e2ca3 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -31,7 +31,8 @@ "remove": "Remove", "apply": "Apply", "done": "Done", - "continue": "Continue" + "continue": "Continue", + "alert": "Alert" }, "more_page": { "configuration_wizard": "Setup wizard", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 0fe5001b..71f5c0ef 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -31,7 +31,8 @@ "remove": "Удалить", "apply": "Применить", "done": "Готово", - "continue": "Продолжить" + "continue": "Продолжить", + "alert": "Уведомление" }, "more_page": { "configuration_wizard": "Мастер настройки", diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 386bc08c..f736310c 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -27,8 +27,7 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_type.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/helpers/modals.dart'; import 'package:selfprivacy/utils/network_utils.dart'; class IpNotFoundException implements Exception { @@ -262,84 +261,62 @@ class ServerInstallationRepository { onSuccess(serverDetails); } on DioError catch (e) { if (e.response!.data['error']['code'] == 'uniqueness_error') { - final NavigationService nav = getIt.get(); - nav.showPopUpDialog( - BrandAlert( - title: 'modals.already_exists'.tr(), - contentText: 'modals.destroy_server'.tr(), - actions: [ - ActionButton( - text: 'basis.delete'.tr(), - isRed: true, - onPressed: () async { - await api.deleteServer( - domainName: domainName, - ); + showPopUpAlert( + alertTitle: 'modals.already_exists'.tr(), + description: 'modals.destroy_server'.tr(), + actionButtonTitle: 'modals.yes'.tr(), + actionButtonOnPressed: () async { + await api.deleteServer( + domainName: domainName, + ); - ServerHostingDetails? serverDetails; - try { - serverDetails = await api.createServer( - dnsApiToken: cloudFlareKey, - rootUser: rootUser, - domainName: domainName, - serverType: getIt().serverType!, - ); - } catch (e) { - print(e); - } + ServerHostingDetails? serverDetails; + try { + serverDetails = await api.createServer( + dnsApiToken: cloudFlareKey, + rootUser: rootUser, + domainName: domainName, + serverType: getIt().serverType!, + ); + } catch (e) { + print(e); + } - if (serverDetails == null) { - print('Server is not initialized!'); - return; - } - await saveServerDetails(serverDetails); - onSuccess(serverDetails); - }, - ), - ActionButton( - text: 'basis.cancel'.tr(), - onPressed: onCancel, - ), - ], - ), + if (serverDetails == null) { + print('Server is not initialized!'); + return; + } + await saveServerDetails(serverDetails); + onSuccess(serverDetails); + }, + cancelButtonOnPressed: onCancel, ); } else { - final NavigationService nav = getIt.get(); - nav.showPopUpDialog( - BrandAlert( - title: 'modals.unexpected_error'.tr(), - contentText: 'modals.try_again'.tr(), - actions: [ - ActionButton( - text: 'modals.yes'.tr(), - isRed: true, - onPressed: () async { - ServerHostingDetails? serverDetails; - try { - serverDetails = await api.createServer( - dnsApiToken: cloudFlareKey, - rootUser: rootUser, - domainName: domainName, - serverType: getIt().serverType!, - ); - } catch (e) { - print(e); - } + showPopUpAlert( + alertTitle: 'modals.unexpected_error'.tr(), + description: 'modals.try_again'.tr(), + actionButtonTitle: 'modals.yes'.tr(), + actionButtonOnPressed: () async { + ServerHostingDetails? serverDetails; + try { + serverDetails = await api.createServer( + dnsApiToken: cloudFlareKey, + rootUser: rootUser, + domainName: domainName, + serverType: getIt().serverType!, + ); + } catch (e) { + print(e); + } - if (serverDetails == null) { - print('Server is not initialized!'); - return; - } - await saveServerDetails(serverDetails); - onSuccess(serverDetails); - }, - ), - ActionButton( - text: 'basis.cancel'.tr(), - onPressed: onCancel, - ), - ], - ), + if (serverDetails == null) { + print('Server is not initialized!'); + return; + } + await saveServerDetails(serverDetails); + onSuccess(serverDetails); + }, + cancelButtonOnPressed: onCancel, ); } } @@ -366,31 +343,19 @@ class ServerInstallationRepository { domain: domain, ); } on DioError catch (e) { - final NavigationService nav = getIt.get(); - nav.showPopUpDialog( - BrandAlert( - title: e.response!.data['errors'][0]['code'] == 1038 - ? 'modals.you_cant_use_this_api'.tr() - : 'domain.error'.tr(), - contentText: 'modals.delete_server_volume'.tr(), - actions: [ - ActionButton( - text: 'basis.delete'.tr(), - isRed: true, - onPressed: () async { - await serverApi.deleteServer( - domainName: domain.domainName, - ); - - onCancel(); - }, - ), - ActionButton( - text: 'basis.cancel'.tr(), - onPressed: onCancel, - ), - ], - ), + showPopUpAlert( + alertTitle: e.response!.data['errors'][0]['code'] == 1038 + ? 'modals.you_cant_use_this_api'.tr() + : 'domain.error'.tr(), + description: 'modals.delete_server_volume'.tr(), + cancelButtonOnPressed: onCancel, + actionButtonTitle: 'basis.delete'.tr(), + actionButtonOnPressed: () async { + await serverApi.deleteServer( + domainName: domain.domainName, + ); + onCancel(); + }, ); return false; } diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index 25fb3612..bafe94a7 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -2,18 +2,16 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_bloc/flutter_bloc.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/server_installation/server_installation_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/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_cards/brand_cards.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/jobs_content/server_job_card.dart'; +import 'package:selfprivacy/ui/helpers/modals.dart'; class JobsContent extends StatelessWidget { const JobsContent({super.key}); @@ -47,26 +45,16 @@ class JobsContent extends StatelessWidget { ), const SizedBox(height: 10), BrandButton.text( + title: 'jobs.reboot_server'.tr(), onPressed: () { - final NavigationService nav = getIt(); - nav.showPopUpDialog( - BrandAlert( - title: 'jobs.reboot_server'.tr(), - contentText: 'modals.are_you_sure'.tr(), - actions: [ - ActionButton( - text: 'basis.cancel'.tr(), - ), - ActionButton( - onPressed: () => - {context.read().rebootServer()}, - text: 'modals.reboot'.tr(), - ) - ], - ), + showPopUpAlert( + alertTitle: 'jobs.reboot_server'.tr(), + description: 'modals.are_you_sure'.tr(), + actionButtonTitle: 'modals.reboot'.tr(), + actionButtonOnPressed: () => + {context.read().rebootServer()}, ); }, - title: 'jobs.reboot_server'.tr(), ), ]; } diff --git a/lib/ui/helpers/modals.dart b/lib/ui/helpers/modals.dart index 8867885f..1750c2aa 100644 --- a/lib/ui/helpers/modals.dart +++ b/lib/ui/helpers/modals.dart @@ -1,5 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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 showBrandBottomSheet({ required final BuildContext context, @@ -12,3 +16,30 @@ Future showBrandBottomSheet({ shadow: const BoxShadow(color: 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().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, + ), + ], + ), + ); +} diff --git a/lib/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backup_details/backup_details.dart index e982a3d4..268b0870 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backup_details/backup_details.dart @@ -1,17 +1,15 @@ import 'package:easy_localization/easy_localization.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/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/models/json/backup.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_cards/outlined_card.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_text/brand_text.dart'; +import 'package:selfprivacy/ui/helpers/modals.dart'; GlobalKey navigatorKey = GlobalKey(); @@ -157,28 +155,17 @@ class _BackupDetailsState extends State onTap: preventActions ? null : () { - final NavigationService nav = - getIt(); - nav.showPopUpDialog( - BrandAlert( - title: 'backup.restoring'.tr(), - contentText: 'backup.restore_alert'.tr( - args: [backup.time.toString()], - ), - actions: [ - ActionButton( - text: 'basis.cancel'.tr(), - ), - ActionButton( - onPressed: () => { - context - .read() - .restoreBackup(backup.id) - }, - text: 'modals.yes'.tr(), - ) - ], + showPopUpAlert( + alertTitle: 'backup.restoring'.tr(), + description: 'backup.restore_alert'.tr( + args: [backup.time.toString()], ), + actionButtonTitle: 'modals.yes'.tr(), + actionButtonOnPressed: () => { + context + .read() + .restoreBackup(backup.id) + }, ); }, title: Text( From b3395915da3672a5639c27e879ba5835f56bf9d2 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 10 Nov 2022 21:03:16 +0400 Subject: [PATCH 31/52] fix(digital-ocean): Add correct linuxDevice path to volume objects linuxDevice consists of supposedly hardcoded 'scsi-0DO_Volume_' plus given volume name --- .../server_providers/digital_ocean/digital_ocean.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index db9bcd93..a951daa0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -119,7 +119,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { name: dbName, sizeByte: dbSize, serverId: null, - linuxDevice: null, + linuxDevice: 'scsi-0DO_Volume_$dbName', uuid: dbId, ); } catch (e) { @@ -156,7 +156,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { name: dbName, sizeByte: dbSize, serverId: dbDropletIds.isNotEmpty ? dbDropletIds[0] : null, - linuxDevice: null, + linuxDevice: 'scsi-0DO_Volume_$dbName', uuid: dbId, ); volumes.add(volume); From 10bdd4c80058348af75b4a89addfd32b12325549 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 11 Nov 2022 07:32:01 +0400 Subject: [PATCH 32/52] refactor(server-api): Make general server info polymorphic Removing Hetzner type hardcode from server page and replacing it with generic String-based metadata container --- assets/translations/en.json | 1 + assets/translations/ru.json | 1 + .../digital_ocean/digital_ocean.dart | 65 ++++++++++++++++--- .../server_providers/hetzner/hetzner.dart | 62 ++++++++++++++++-- .../server_providers/server_provider.dart | 2 + .../server_detailed_info_cubit.dart | 4 +- .../server_detailed_info_repository.dart | 40 ++++++++---- .../server_detailed_info_state.dart | 8 +-- lib/logic/models/server_metadata.dart | 21 ++++++ .../server_details/server_details_screen.dart | 1 + lib/ui/pages/server_details/text_details.dart | 51 ++++++--------- 11 files changed, 189 insertions(+), 67 deletions(-) create mode 100644 lib/logic/models/server_metadata.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index dd6e2ca3..4cc240b9 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -110,6 +110,7 @@ "disk": "Disk local", "monthly_cost": "Monthly cost", "location": "Location", + "provider": "Provider", "core_count": { "one": "{} core", "two": "{} cores", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 71f5c0ef..49c08079 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -110,6 +110,7 @@ "disk": "Диск", "monthly_cost": "Ежемесячная стоимость", "location": "Размещение", + "provider": "Провайдер", "core_count": { "one": "{} ядро", "two": "{} ядра", diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index a951daa0..c216d4c5 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -2,18 +2,20 @@ 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/models/disk_size.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/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.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 { @@ -313,8 +315,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { base64.encode(utf8.encode(rootUser.password ?? 'PASS')); final String formattedHostname = getHostnameFromDomain(domainName); - - // TODO: change to 'master' change to 'master' change to 'master' const String infectBranch = 'providers/digital-ocean'; final String userdataString = @@ -474,14 +474,59 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return metrics; } - Future getInfo() async { - final ServerHostingDetails? hetznerServer = - getIt().serverDetails; - final Dio client = await getClient(); - final Response response = await client.get('/servers/${hetznerServer!.id}'); - close(client); + @override + Future> getMetadata(final int serverId) async { + List metadata = []; - return HetznerServerInfo.fromJson(response.data!['server']); + final Dio client = await getClient(); + try { + final Response response = await client.get('/droplets/$serverId'); + final droplet = response.data!['droplet']; + metadata = [ + 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: 'Digital Ocean', + ), + ]; + } catch (e) { + print(e); + } finally { + close(client); + } + + return metadata; } @override diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 13fb2cde..922c309c 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -2,6 +2,7 @@ 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'; @@ -12,8 +13,10 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.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 HetznerApi extends ServerProviderApi with VolumeProviderApi { @@ -513,14 +516,59 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return metrics; } - Future getInfo() async { - final ServerHostingDetails? hetznerServer = - getIt().serverDetails; - final Dio client = await getClient(); - final Response response = await client.get('/servers/${hetznerServer!.id}'); - close(client); + @override + Future> getMetadata(final int serverId) async { + List metadata = []; - return HetznerServerInfo.fromJson(response.data!['server']); + 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: 'Hetzner', + ), + ]; + } catch (e) { + print(e); + } finally { + close(client); + } + + return metadata; } @override diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index b04ae1e0..9e0b57bf 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -3,6 +3,7 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; +import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_type.dart'; @@ -39,6 +40,7 @@ abstract class ServerProviderApi extends ApiMap { Future isApiTokenValid(final String token); ProviderApiTokenValidation getApiTokenValidation(); + Future> getMetadata(final int serverId); abstract final String infectProviderName; } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart index a3af96e8..b6a39733 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.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/server_detailed_info/server_detailed_info_repository.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'; part 'server_detailed_info_state.dart'; @@ -22,7 +22,7 @@ class ServerDetailsCubit final ServerDetailsRepositoryDto data = await repository.load(); emit( Loaded( - serverInfo: data.hetznerServerInfo, + metadata: data.metadata, autoUpgradeSettings: data.autoUpgradeSettings, serverTimezone: data.serverTimezone, checkTime: DateTime.now(), diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index bfa3991f..fcc0c5d8 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -1,22 +1,42 @@ import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; +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/server_providers/server_provider_factory.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/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; class ServerDetailsRepository { - HetznerApi hetzner = HetznerApi( - /// TODO: Hetzner hardcode (???) - region: getIt().serverLocation, - ); ServerApi server = ServerApi(); + ServerProviderApiFactory? serverProviderApiFactory; + + void _buildServerProviderFactory() { + final ServerProvider? providerType = getIt().serverProvider; + final String? location = getIt().serverLocation; + serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( + ServerProviderApiFactorySettings( + provider: providerType ?? ServerProvider.unknown, + location: location, + ), + ); + } Future load() async { + if (serverProviderApiFactory == null) { + _buildServerProviderFactory(); + } + final settings = await server.getSystemSettings(); + final serverId = getIt().serverDetails!.id; + final metadata = await serverProviderApiFactory! + .getServerProvider() + .getMetadata(serverId); + return ServerDetailsRepositoryDto( autoUpgradeSettings: settings.autoUpgradeSettings, - hetznerServerInfo: await hetzner.getInfo(), + metadata: metadata, serverTimezone: TimeZoneSettings.fromString( settings.timezone, ), @@ -40,13 +60,11 @@ class ServerDetailsRepository { class ServerDetailsRepositoryDto { ServerDetailsRepositoryDto({ - required this.hetznerServerInfo, + required this.metadata, required this.serverTimezone, required this.autoUpgradeSettings, }); - final HetznerServerInfo hetznerServerInfo; - + final List metadata; final TimeZoneSettings serverTimezone; - final AutoUpgradeSettings autoUpgradeSettings; } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart index ea5f4864..64f4d91d 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart @@ -17,21 +17,19 @@ class Loading extends ServerDetailsState {} class Loaded extends ServerDetailsState { const Loaded({ - required this.serverInfo, + required this.metadata, required this.serverTimezone, required this.autoUpgradeSettings, required this.checkTime, }); - final HetznerServerInfo serverInfo; - + final List metadata; final TimeZoneSettings serverTimezone; - final AutoUpgradeSettings autoUpgradeSettings; final DateTime checkTime; @override List get props => [ - serverInfo, + metadata, serverTimezone, autoUpgradeSettings, checkTime, diff --git a/lib/logic/models/server_metadata.dart b/lib/logic/models/server_metadata.dart new file mode 100644 index 00000000..1a08abc0 --- /dev/null +++ b/lib/logic/models/server_metadata.dart @@ -0,0 +1,21 @@ +enum MetadataType { + id, + status, + cpu, + ram, + cost, + location, + + other, +} + +class ServerMetadataEntity { + ServerMetadataEntity({ + required this.name, + required this.value, + this.type = MetadataType.other, + }); + final MetadataType type; + final String name; + final String value; +} diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index e0d82b6d..acf19583 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -10,6 +10,7 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; diff --git a/lib/ui/pages/server_details/text_details.dart b/lib/ui/pages/server_details/text_details.dart index e9b3c0f7..2c06375f 100644 --- a/lib/ui/pages/server_details/text_details.dart +++ b/lib/ui/pages/server_details/text_details.dart @@ -1,6 +1,16 @@ part of 'server_details_screen.dart'; class _TextDetails extends StatelessWidget { + final Map metadataToIcon = const { + MetadataType.id: Icons.numbers_outlined, + MetadataType.status: Icons.mode_standby_outlined, + MetadataType.cpu: Icons.memory_outlined, + MetadataType.ram: Icons.memory_outlined, + MetadataType.cost: Icons.euro_outlined, + MetadataType.location: Icons.location_on_outlined, + MetadataType.other: Icons.info_outlined, + }; + @override Widget build(final BuildContext context) { final details = context.watch().state; @@ -10,7 +20,6 @@ class _TextDetails extends StatelessWidget { } else if (details is ServerDetailsNotReady) { return _TempMessage(message: 'basis.no_data'.tr()); } else if (details is Loaded) { - final data = details.serverInfo; return FilledCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -24,37 +33,15 @@ class _TextDetails extends StatelessWidget { ), ), ), - ListTileOnSurfaceVariant( - leadingIcon: Icons.numbers_outlined, - title: data.id.toString(), - subtitle: 'server.server_id'.tr(), - ), - ListTileOnSurfaceVariant( - leadingIcon: Icons.mode_standby_outlined, - title: data.status.toString().split('.')[1].capitalize(), - subtitle: 'server.status'.tr(), - ), - 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(), - ), + ...details.metadata + .map( + (final metadata) => ListTileOnSurfaceVariant( + leadingIcon: metadataToIcon[metadata.type], + title: metadata.name, + subtitle: metadata.value, + ), + ) + .toList(), ], ), ); From e66b24d8696b3a99f025b218c12b86eabab7a816 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 11 Nov 2022 15:29:17 +0400 Subject: [PATCH 33/52] refactor: Remove obsolete initializing steps enum type --- lib/logic/common_enum/common_enum.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/logic/common_enum/common_enum.dart b/lib/logic/common_enum/common_enum.dart index 69686978..557448b1 100644 --- a/lib/logic/common_enum/common_enum.dart +++ b/lib/logic/common_enum/common_enum.dart @@ -5,17 +5,6 @@ enum LoadingStatus { error, } -enum InitializingSteps { - setHetznerKey, - setCloudFlareKey, - setDomainName, - setRootUser, - createServer, - checkCloudFlareDns, - startServer, - checkSystemDnsAndDkimSet, -} - enum Period { hour, day, From a7cbde663e1aab3913222b72bbbd15ccfbdc4725 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 12 Nov 2022 21:29:06 +0400 Subject: [PATCH 34/52] refactor(server-api): Generalize and encapsulate server metrics endpoints --- .../digital_ocean/digital_ocean.dart | 31 ++------ .../server_providers/hetzner/hetzner.dart | 75 +++++++++++++++++-- .../server_providers/server_provider.dart | 6 ++ .../hetzner_metrics_repository.dart | 74 ------------------ .../hetzner_metrics_state.dart | 45 ----------- .../metrics_cubit.dart} | 20 ++--- .../cubit/metrics/metrics_repository.dart | 67 +++++++++++++++++ lib/logic/cubit/metrics/metrics_state.dart | 31 ++++++++ .../provider_volume_cubit.dart | 4 +- .../server_installation_cubit.dart | 17 ++--- lib/logic/models/hetzner_metrics.dart | 11 --- lib/logic/models/metrics.dart | 34 +++++++++ .../server_details/charts/bottom_title.dart | 2 +- lib/ui/pages/server_details/charts/chart.dart | 28 +++---- .../server_details/charts/cpu_chart.dart | 2 +- .../server_details/charts/network_charts.dart | 2 +- .../server_details/server_details_screen.dart | 5 +- .../recovering/recovery_confirm_server.dart | 5 +- 18 files changed, 253 insertions(+), 206 deletions(-) delete mode 100644 lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart delete mode 100644 lib/logic/cubit/hetzner_metrics/hetzner_metrics_state.dart rename lib/logic/cubit/{hetzner_metrics/hetzner_metrics_cubit.dart => metrics/metrics_cubit.dart} (57%) create mode 100644 lib/logic/cubit/metrics/metrics_repository.dart create mode 100644 lib/logic/cubit/metrics/metrics_state.dart delete mode 100644 lib/logic/models/hetzner_metrics.dart create mode 100644 lib/logic/models/metrics.dart diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index c216d4c5..527a8ed2 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -10,6 +10,7 @@ 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'; @@ -444,34 +445,14 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return server.copyWith(startTime: DateTime.now()); } - Future> getMetrics( + @override + Future getMetrics( + final int serverId, final DateTime start, final DateTime end, - final String type, ) async { - final ServerHostingDetails? hetznerServer = - getIt().serverDetails; - - Map metrics = {}; - final Dio client = await getClient(); - try { - final Map queryParameters = { - 'start': start.toUtc().toIso8601String(), - 'end': end.toUtc().toIso8601String(), - 'type': type - }; - final Response res = await client.get( - '/servers/${hetznerServer!.id}/metrics', - queryParameters: queryParameters, - ); - metrics = res.data; - } catch (e) { - print(e); - } finally { - close(client); - } - - return metrics; + ServerMetrics? metrics; + return metrics!; } @override diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 922c309c..09bf4f82 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -11,6 +11,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/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'; @@ -486,14 +487,12 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return server.copyWith(startTime: DateTime.now()); } - Future> getMetrics( + Future> requestRawMetrics( + final int serverId, final DateTime start, final DateTime end, final String type, ) async { - final ServerHostingDetails? hetznerServer = - getIt().serverDetails; - Map metrics = {}; final Dio client = await getClient(); try { @@ -503,10 +502,10 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 'type': type }; final Response res = await client.get( - '/servers/${hetznerServer!.id}/metrics', + '/servers/$serverId/metrics', queryParameters: queryParameters, ); - metrics = res.data; + metrics = res.data['metrics']; } catch (e) { print(e); } finally { @@ -516,6 +515,70 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return metrics; } + List timeSeriesSerializer( + final Map 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(); + } + + @override + Future getMetrics( + final int serverId, + final DateTime start, + final DateTime end, + ) async { + ServerMetrics? metrics; + + final Map rawCpuMetrics = await requestRawMetrics( + serverId, + start, + end, + 'cpu', + ); + final Map rawNetworkMetrics = await requestRawMetrics( + serverId, + start, + end, + 'network', + ); + + if (rawNetworkMetrics.isEmpty || rawCpuMetrics.isEmpty) { + return metrics; + } + + metrics = ServerMetrics( + cpu: timeSeriesSerializer( + rawCpuMetrics, + 'cpu', + ), + ppsIn: timeSeriesSerializer( + rawNetworkMetrics, + 'network.0.pps.in', + ), + ppsOut: timeSeriesSerializer( + rawNetworkMetrics, + 'network.0.pps.out', + ), + bandwidthIn: timeSeriesSerializer( + rawNetworkMetrics, + 'network.0.bandwidth.in', + ), + bandwidthOut: timeSeriesSerializer( + rawNetworkMetrics, + 'network.0.bandwidth.out', + ), + end: end, + start: start, + stepsInSecond: rawCpuMetrics['step'], + ); + + return metrics; + } + @override Future> getMetadata(final int serverId) async { List metadata = []; diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 9e0b57bf..42004e70 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -2,6 +2,7 @@ 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_domain.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_metadata.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; @@ -41,6 +42,11 @@ abstract class ServerProviderApi extends ApiMap { Future isApiTokenValid(final String token); ProviderApiTokenValidation getApiTokenValidation(); Future> getMetadata(final int serverId); + Future getMetrics( + final int serverId, + final DateTime start, + final DateTime end, + ); abstract final String infectProviderName; } diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart deleted file mode 100644 index bc1d8e42..00000000 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:selfprivacy/config/get_it_config.dart'; -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 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( - /// TODO: Hetzner hardcode (???) - hasLogger: false, - region: getIt().serverLocation, - ); - - final List> 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 timeSeriesSerializer( - final Map 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(); -} diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_state.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_state.dart deleted file mode 100644 index b6204db9..00000000 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_state.dart +++ /dev/null @@ -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 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 cpu; - final List ppsIn; - final List ppsOut; - final List bandwidthIn; - final List bandwidthOut; - - @override - List get props => [period, start, end]; -} diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart b/lib/logic/cubit/metrics/metrics_cubit.dart similarity index 57% rename from lib/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart rename to lib/logic/cubit/metrics/metrics_cubit.dart index 1cfdc23a..2a2dec28 100644 --- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart +++ b/lib/logic/cubit/metrics/metrics_cubit.dart @@ -3,16 +3,16 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/models/hetzner_metrics.dart'; +import '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 { - HetznerMetricsCubit() : super(const HetznerMetricsLoading(Period.day)); +class MetricsCubit extends Cubit { + MetricsCubit() : super(const MetricsLoading(Period.day)); - final HetznerMetricsRepository repository = HetznerMetricsRepository(); + final MetricsRepository repository = MetricsRepository(); Timer? timer; @@ -30,7 +30,7 @@ class HetznerMetricsCubit extends Cubit { void changePeriod(final Period period) async { closeTimer(); - emit(HetznerMetricsLoading(period)); + emit(MetricsLoading(period)); load(period); } @@ -40,14 +40,14 @@ class HetznerMetricsCubit extends Cubit { void load(final Period period) async { try { - final HetznerMetricsLoaded newState = await repository.getMetrics(period); + final MetricsLoaded newState = await repository.getMetrics(period); timer = Timer( - Duration(seconds: newState.stepInSeconds.toInt()), + Duration(seconds: newState.metrics.stepsInSecond.toInt()), () => load(newState.period), ); emit(newState); } on StateError { - print('Tried to emit Hetzner metrics when cubit is closed'); + print('Tried to emit metrics when cubit is closed'); } on MetricsLoadException { timer = Timer( Duration(seconds: state.period.stepPeriodInSeconds), diff --git a/lib/logic/cubit/metrics/metrics_repository.dart b/lib/logic/cubit/metrics/metrics_repository.dart new file mode 100644 index 00000000..15da1b0c --- /dev/null +++ b/lib/logic/cubit/metrics/metrics_repository.dart @@ -0,0 +1,67 @@ +import 'package:selfprivacy/config/get_it_config.dart'; +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/server_providers/server_provider_factory.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; + +import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/metrics.dart'; + +class MetricsLoadException implements Exception { + MetricsLoadException(this.message); + final String message; +} + +class MetricsRepository { + ServerProviderApiFactory? serverProviderApiFactory; + + void _buildServerProviderFactory() { + final ServerProvider? providerType = getIt().serverProvider; + final String? location = getIt().serverLocation; + serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( + ServerProviderApiFactorySettings( + provider: providerType ?? ServerProvider.unknown, + location: location, + ), + ); + } + + Future getMetrics(final Period period) async { + if (serverProviderApiFactory == null) { + _buildServerProviderFactory(); + } + + 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().serverDetails!.id; + final ServerMetrics? metrics = + await serverProviderApiFactory!.getServerProvider().getMetrics( + serverId, + start, + end, + ); + + if (metrics == null) { + throw MetricsLoadException('Metrics data is null'); + } + + return MetricsLoaded( + period: period, + metrics: metrics, + ); + } +} diff --git a/lib/logic/cubit/metrics/metrics_state.dart b/lib/logic/cubit/metrics/metrics_state.dart new file mode 100644 index 00000000..b27546ce --- /dev/null +++ b/lib/logic/cubit/metrics/metrics_state.dart @@ -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 get props => [period]; +} + +class MetricsLoaded extends MetricsState { + const MetricsLoaded({ + required this.period, + required this.metrics, + }); + + @override + final Period period; + + final ServerMetrics metrics; + + @override + List get props => [period, metrics]; +} diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 1a8f1c15..b3a3dd48 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -98,13 +98,13 @@ class ApiProviderVolumeCubit } getIt().showSnackBar( - 'Hetzner resized, waiting 10 seconds', + 'Provider volume resized, waiting 10 seconds', ); await Future.delayed(const Duration(seconds: 10)); await ServerApi().resizeVolume(volume.name); getIt().showSnackBar( - 'Server api resized, waiting 20 seconds', + 'Server volume resized, waiting 20 seconds', ); await Future.delayed(const Duration(seconds: 20)); diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 6ff73893..9e82a780 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -305,14 +305,13 @@ class ServerInstallationCubit extends Cubit { ), ); timer = Timer(pauseDuration, () async { - final ServerHostingDetails hetznerServerDetails = - await repository.restart(); + final ServerHostingDetails serverDetails = await repository.restart(); await repository.saveIsServerResetedFirstTime(true); - await repository.saveServerDetails(hetznerServerDetails); + await repository.saveServerDetails(serverDetails); final ServerInstallationNotFinished newState = dataState.copyWith( isServerResetedFirstTime: true, - serverDetails: hetznerServerDetails, + serverDetails: serverDetails, isLoading: false, ); @@ -347,14 +346,13 @@ class ServerInstallationCubit extends Cubit { ), ); timer = Timer(pauseDuration, () async { - final ServerHostingDetails hetznerServerDetails = - await repository.restart(); + final ServerHostingDetails serverDetails = await repository.restart(); await repository.saveIsServerResetedSecondTime(true); - await repository.saveServerDetails(hetznerServerDetails); + await repository.saveServerDetails(serverDetails); final ServerInstallationNotFinished newState = dataState.copyWith( isServerResetedSecondTime: true, - serverDetails: hetznerServerDetails, + serverDetails: serverDetails, isLoading: false, ); @@ -560,8 +558,7 @@ class ServerInstallationCubit extends Cubit { } } - Future> - getServersOnHetznerAccount() async { + Future> getAvailableServers() async { final ServerInstallationRecovery dataState = state as ServerInstallationRecovery; final List servers = diff --git a/lib/logic/models/hetzner_metrics.dart b/lib/logic/models/hetzner_metrics.dart deleted file mode 100644 index 2f41a4b2..00000000 --- a/lib/logic/models/hetzner_metrics.dart +++ /dev/null @@ -1,11 +0,0 @@ -class TimeSeriesData { - TimeSeriesData( - this.secondsSinceEpoch, - this.value, - ); - - final int secondsSinceEpoch; - DateTime get time => - DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000); - final double value; -} diff --git a/lib/logic/models/metrics.dart b/lib/logic/models/metrics.dart new file mode 100644 index 00000000..786ee369 --- /dev/null +++ b/lib/logic/models/metrics.dart @@ -0,0 +1,34 @@ +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.ppsIn, + required this.ppsOut, + required this.bandwidthIn, + required this.bandwidthOut, + required this.start, + required this.end, + }); + + final num stepsInSecond; + final List cpu; + final List ppsIn; + final List ppsOut; + final List bandwidthIn; + final List bandwidthOut; + + final DateTime start; + final DateTime end; +} diff --git a/lib/ui/pages/server_details/charts/bottom_title.dart b/lib/ui/pages/server_details/charts/bottom_title.dart index 3db976ad..797fdebd 100644 --- a/lib/ui/pages/server_details/charts/bottom_title.dart +++ b/lib/ui/pages/server_details/charts/bottom_title.dart @@ -1,5 +1,5 @@ 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'; String bottomTitle( diff --git a/lib/ui/pages/server_details/charts/chart.dart b/lib/ui/pages/server_details/charts/chart.dart index fb46eb71..774dcf75 100644 --- a/lib/ui/pages/server_details/charts/chart.dart +++ b/lib/ui/pages/server_details/charts/chart.dart @@ -3,11 +3,11 @@ part of '../server_details_screen.dart'; class _Chart extends StatelessWidget { @override Widget build(final BuildContext context) { - final HetznerMetricsCubit cubit = context.watch(); + final MetricsCubit cubit = context.watch(); final Period period = cubit.state.period; - final HetznerMetricsState state = cubit.state; + final MetricsState state = cubit.state; List charts; - if (state is HetznerMetricsLoaded || state is HetznerMetricsLoading) { + if (state is MetricsLoaded || state is MetricsLoading) { charts = [ FilledCard( clipped: false, @@ -26,10 +26,10 @@ class _Chart extends StatelessWidget { Stack( alignment: Alignment.center, children: [ - if (state is HetznerMetricsLoaded) getCpuChart(state), + if (state is MetricsLoaded) getCpuChart(state), AnimatedOpacity( duration: const Duration(milliseconds: 200), - opacity: state is HetznerMetricsLoading ? 1 : 0, + opacity: state is MetricsLoading ? 1 : 0, child: const _GraphLoadingCardContent(), ), ], @@ -72,10 +72,10 @@ class _Chart extends StatelessWidget { Stack( alignment: Alignment.center, children: [ - if (state is HetznerMetricsLoaded) getBandwidthChart(state), + if (state is MetricsLoaded) getBandwidthChart(state), AnimatedOpacity( duration: const Duration(milliseconds: 200), - opacity: state is HetznerMetricsLoading ? 1 : 0, + opacity: state is MetricsLoading ? 1 : 0, child: const _GraphLoadingCardContent(), ), ], @@ -122,29 +122,29 @@ class _Chart extends StatelessWidget { ); } - Widget getCpuChart(final HetznerMetricsLoaded state) { - final data = state.cpu; + Widget getCpuChart(final MetricsLoaded state) { + final data = state.metrics.cpu; return SizedBox( height: 200, child: CpuChart( data: data, period: state.period, - start: state.start, + start: state.metrics.start, ), ); } - Widget getBandwidthChart(final HetznerMetricsLoaded state) { - final ppsIn = state.bandwidthIn; - final ppsOut = state.bandwidthOut; + Widget getBandwidthChart(final MetricsLoaded state) { + final ppsIn = state.metrics.bandwidthIn; + final ppsOut = state.metrics.bandwidthOut; return SizedBox( height: 200, child: NetworkChart( listData: [ppsIn, ppsOut], period: state.period, - start: state.start, + start: state.metrics.start, ), ); } diff --git a/lib/ui/pages/server_details/charts/cpu_chart.dart b/lib/ui/pages/server_details/charts/cpu_chart.dart index 9b437b16..89ead8fe 100644 --- a/lib/ui/pages/server_details/charts/cpu_chart.dart +++ b/lib/ui/pages/server_details/charts/cpu_chart.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.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:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; diff --git a/lib/ui/pages/server_details/charts/network_charts.dart b/lib/ui/pages/server_details/charts/network_charts.dart index c71ae7b1..d444ac84 100644 --- a/lib/ui/pages/server_details/charts/network_charts.dart +++ b/lib/ui/pages/server_details/charts/network_charts.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.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'; class NetworkChart extends StatelessWidget { diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index acf19583..245df021 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.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/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_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; @@ -22,7 +22,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_storage/storage_card.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/route_transitions/basic.dart'; import 'package:timezone/timezone.dart'; @@ -93,7 +92,7 @@ class _ServerDetailsScreenState extends State ), const SizedBox(height: 8), BlocProvider( - create: (final context) => HetznerMetricsCubit()..restart(), + create: (final context) => MetricsCubit()..restart(), child: _Chart(), ), const SizedBox(height: 8), diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart index 430e0894..969e3d39 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart @@ -45,9 +45,8 @@ class _RecoveryConfirmServerState extends State { hasFlashButton: false, children: [ FutureBuilder>( - future: context - .read() - .getServersOnHetznerAccount(), + future: + context.read().getAvailableServers(), builder: (final context, final snapshot) { if (snapshot.hasData) { final servers = snapshot.data; From e20063a9ad2e569104f6909f52f9b71f6bfc1843 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 12 Nov 2022 22:44:15 +0400 Subject: [PATCH 35/52] chore: Remove unneeded metrics fields PPS metrics aren't used in our charts --- .../rest_maps/server_providers/hetzner/hetzner.dart | 8 -------- lib/logic/models/metrics.dart | 4 ---- 2 files changed, 12 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 09bf4f82..f2bd0a9e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -555,14 +555,6 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { rawCpuMetrics, 'cpu', ), - ppsIn: timeSeriesSerializer( - rawNetworkMetrics, - 'network.0.pps.in', - ), - ppsOut: timeSeriesSerializer( - rawNetworkMetrics, - 'network.0.pps.out', - ), bandwidthIn: timeSeriesSerializer( rawNetworkMetrics, 'network.0.bandwidth.in', diff --git a/lib/logic/models/metrics.dart b/lib/logic/models/metrics.dart index 786ee369..4f5d3efc 100644 --- a/lib/logic/models/metrics.dart +++ b/lib/logic/models/metrics.dart @@ -14,8 +14,6 @@ class ServerMetrics { ServerMetrics({ required this.stepsInSecond, required this.cpu, - required this.ppsIn, - required this.ppsOut, required this.bandwidthIn, required this.bandwidthOut, required this.start, @@ -24,8 +22,6 @@ class ServerMetrics { final num stepsInSecond; final List cpu; - final List ppsIn; - final List ppsOut; final List bandwidthIn; final List bandwidthOut; From 7fdc5467146f3243e073d565956dbf1322408bb2 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 14 Nov 2022 09:48:36 +0400 Subject: [PATCH 36/52] refactor(server-api): Make appearance provider name polymorphic and required for metadata --- lib/logic/api_maps/rest_maps/api_map.dart | 3 ++- .../digital_ocean/digital_ocean.dart | 11 +++++++---- .../rest_maps/server_providers/hetzner/hetzner.dart | 12 ++++++++---- .../rest_maps/server_providers/server_provider.dart | 8 +++++++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/api_map.dart b/lib/logic/api_maps/rest_maps/api_map.dart index 007bfd98..4c22c461 100644 --- a/lib/logic/api_maps/rest_maps/api_map.dart +++ b/lib/logic/api_maps/rest_maps/api_map.dart @@ -41,7 +41,8 @@ abstract class ApiMap { FutureOr get options; - abstract final String rootAddress; + String get rootAddress; + abstract final bool hasLogger; abstract final bool isWithToken; diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 527a8ed2..1c1f342c 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -49,10 +49,13 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } @override - final String rootAddress = 'https://api.digitalocean.com/v2'; + String get rootAddress => 'https://api.digitalocean.com/v2'; @override - final String infectProviderName = 'digitalocean'; + String get infectProviderName => 'digitalocean'; + + @override + String get appearanceProviderName => 'Digital Ocean'; @override Future isApiTokenValid(final String token) async { @@ -452,7 +455,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final DateTime end, ) async { ServerMetrics? metrics; - return metrics!; + return metrics; } @override @@ -498,7 +501,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ServerMetadataEntity( type: MetadataType.other, name: 'server.provider'.tr(), - value: 'Digital Ocean', + value: appearanceProviderName, ), ]; } catch (e) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index f2bd0a9e..7e623c69 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -50,10 +50,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } @override - final String rootAddress = 'https://api.hetzner.cloud/v1'; + String get rootAddress => 'https://api.hetzner.cloud/v1'; @override - final String infectProviderName = 'hetzner'; + String get infectProviderName => 'hetzner'; + + @override + String get appearanceProviderName => 'Hetzner'; @override Future isApiTokenValid(final String token) async { @@ -349,12 +352,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final String apiToken = StringGenerators.apiToken(); final String hostname = getHostnameFromDomain(domainName); + const String infectBranch = 'providers/hetzner'; final String base64Password = base64.encode(utf8.encode(rootUser.password ?? 'PASS')); final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | 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"; + "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | 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"; ServerHostingDetails? serverDetails; DioError? hetznerError; @@ -614,7 +618,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ServerMetadataEntity( type: MetadataType.other, name: 'server.provider'.tr(), - value: 'Hetzner', + value: appearanceProviderName, ), ]; } catch (e) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 42004e70..21c6ddd0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -48,5 +48,11 @@ abstract class ServerProviderApi extends ApiMap { final DateTime end, ); - abstract final String infectProviderName; + /// 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 appearanceProviderName; } From 92b417a10317e81b891747933b1d84d84811b397 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 14 Nov 2022 20:45:05 +0400 Subject: [PATCH 37/52] feat(digital-ocean): Implement metrics for Digital Ocean --- lib/logic/api_maps/rest_maps/api_map.dart | 4 +- .../digital_ocean/digital_ocean.dart | 101 ++++++++++++++++++ .../server_providers/hetzner/hetzner.dart | 8 +- .../server_details/charts/bottom_title.dart | 2 +- 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/api_map.dart b/lib/logic/api_maps/rest_maps/api_map.dart index 4c22c461..6fd0bdda 100644 --- a/lib/logic/api_maps/rest_maps/api_map.dart +++ b/lib/logic/api_maps/rest_maps/api_map.dart @@ -9,8 +9,8 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/models/message.dart'; abstract class ApiMap { - Future getClient() async { - final Dio dio = Dio(await options); + Future getClient({final BaseOptions? customOptions}) async { + final Dio dio = Dio(customOptions ?? (await options)); if (hasLogger) { dio.interceptors.add(PrettyDioLogger()); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 1c1f342c..50c043e6 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -448,6 +448,46 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return server.copyWith(startTime: DateTime.now()); } + /// Digital Ocean returns a map of lists of /proc/state 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 calculateCpuLoadMetrics(final List rawProcStatMetrics) { + final List 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 getMetrics( final int serverId, @@ -455,6 +495,67 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { final DateTime end, ) async { ServerMetrics? metrics; + + 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; } diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index 7e623c69..ccbeeef0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -519,7 +519,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { return metrics; } - List timeSeriesSerializer( + List serializeTimeSeries( final Map json, final String type, ) { @@ -555,15 +555,15 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { } metrics = ServerMetrics( - cpu: timeSeriesSerializer( + cpu: serializeTimeSeries( rawCpuMetrics, 'cpu', ), - bandwidthIn: timeSeriesSerializer( + bandwidthIn: serializeTimeSeries( rawNetworkMetrics, 'network.0.bandwidth.in', ), - bandwidthOut: timeSeriesSerializer( + bandwidthOut: serializeTimeSeries( rawNetworkMetrics, 'network.0.bandwidth.out', ), diff --git a/lib/ui/pages/server_details/charts/bottom_title.dart b/lib/ui/pages/server_details/charts/bottom_title.dart index 797fdebd..8d215d7e 100644 --- a/lib/ui/pages/server_details/charts/bottom_title.dart +++ b/lib/ui/pages/server_details/charts/bottom_title.dart @@ -11,7 +11,7 @@ String bottomTitle( final day = DateFormat('MMMd'); String res; - if (value <= 0) { + if (value <= 0 || value >= data.length) { return ''; } From 2a5fceae9110fb70a37099f38cdb7b97907bf3e4 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 16 Nov 2022 00:49:41 +0400 Subject: [PATCH 38/52] fix(hetzner): Fix endpoints urls Incorrect dereferencing in strings --- .../rest_maps/server_providers/hetzner/hetzner.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index ccbeeef0..ce9aeeee 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -234,7 +234,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { Future deleteVolume(final ServerVolume volume) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$volume.id'); + await client.delete('/volumes/${volume.id}'); } catch (e) { print(e); } finally { @@ -253,7 +253,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volume.id/actions/attach', + '/volumes/${volume.id}/actions/attach', data: { 'automount': true, 'server': serverId, @@ -276,7 +276,9 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final Response dbPostResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post('/volumes/$volume.id/actions/detach'); + dbPostResponse = await client.post( + '/volumes/${volume.id}/actions/detach', + ); success = dbPostResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); @@ -298,7 +300,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final Dio client = await getClient(); try { dbPostResponse = await client.post( - '/volumes/$volume.id/actions/resize', + '/volumes/${volume.id}/actions/resize', data: { 'size': size.gibibyte, }, From bde364dde1dd0ee27fc60f7f1cca867b52ce2aba Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 16 Nov 2022 04:12:49 +0400 Subject: [PATCH 39/52] fix(digital-ocean): Adjust charts rendering for digital ocean values --- lib/ui/pages/server_details/charts/cpu_chart.dart | 2 +- lib/ui/pages/server_details/charts/network_charts.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/pages/server_details/charts/cpu_chart.dart b/lib/ui/pages/server_details/charts/cpu_chart.dart index 89ead8fe..8c3ae9c7 100644 --- a/lib/ui/pages/server_details/charts/cpu_chart.dart +++ b/lib/ui/pages/server_details/charts/cpu_chart.dart @@ -83,7 +83,7 @@ class CpuChart extends StatelessWidget { ], minY: 0, maxY: 100, - minX: data.length - 200, + minX: 0, titlesData: FlTitlesData( topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( diff --git a/lib/ui/pages/server_details/charts/network_charts.dart b/lib/ui/pages/server_details/charts/network_charts.dart index d444ac84..946d0247 100644 --- a/lib/ui/pages/server_details/charts/network_charts.dart +++ b/lib/ui/pages/server_details/charts/network_charts.dart @@ -116,7 +116,7 @@ class NetworkChart extends StatelessWidget { ...listData[1].map((final e) => e.value) ].reduce(max) * 1.2, - minX: listData[0].length - 200, + minX: 0, titlesData: FlTitlesData( topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( From 0234278c2c3f0e5f8dd458615c1fec3e8c66bf62 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 16 Nov 2022 04:24:40 +0400 Subject: [PATCH 40/52] refactor: Rename server.dart to server_api.dart Because the class is called ServerApi, not just Server, it's totally not consistent with all other apis --- lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart | 2 +- .../api_maps/graphql_maps/server_api/server_actions_api.dart | 2 +- .../graphql_maps/server_api/{server.dart => server_api.dart} | 0 lib/logic/api_maps/graphql_maps/server_api/services_api.dart | 2 +- lib/logic/api_maps/graphql_maps/server_api/users_api.dart | 2 +- lib/logic/api_maps/graphql_maps/server_api/volume_api.dart | 2 +- lib/logic/cubit/backups/backups_cubit.dart | 2 +- lib/logic/cubit/client_jobs/client_jobs_cubit.dart | 2 +- lib/logic/cubit/devices/devices_cubit.dart | 2 +- lib/logic/cubit/dns_records/dns_records_cubit.dart | 2 +- .../forms/setup/recovering/recovery_domain_form_cubit.dart | 2 +- lib/logic/cubit/provider_volumes/provider_volume_cubit.dart | 2 +- lib/logic/cubit/recovery_key/recovery_key_cubit.dart | 2 +- .../server_detailed_info/server_detailed_info_repository.dart | 2 +- .../server_installation/server_installation_repository.dart | 2 +- lib/logic/cubit/server_jobs/server_jobs_cubit.dart | 2 +- lib/logic/cubit/server_volumes/server_volume_cubit.dart | 2 +- lib/logic/cubit/services/services_cubit.dart | 2 +- lib/logic/cubit/users/users_cubit.dart | 2 +- lib/ui/pages/more/about_application.dart | 2 +- 20 files changed, 19 insertions(+), 19 deletions(-) rename lib/logic/api_maps/graphql_maps/server_api/{server.dart => server_api.dart} (100%) diff --git a/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart b/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart index 67e8bcb3..84acff43 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart @@ -1,4 +1,4 @@ -part of 'server.dart'; +part of 'server_api.dart'; mixin JobsApi on ApiMap { Future> getServerJobs() async { diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart index 780f1d37..65e77b98 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart @@ -1,4 +1,4 @@ -part of 'server.dart'; +part of 'server_api.dart'; mixin ServerActionsApi on ApiMap { Future _commonBoolRequest(final Function graphQLMethod) async { diff --git a/lib/logic/api_maps/graphql_maps/server_api/server.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart similarity index 100% rename from lib/logic/api_maps/graphql_maps/server_api/server.dart rename to lib/logic/api_maps/graphql_maps/server_api/server_api.dart diff --git a/lib/logic/api_maps/graphql_maps/server_api/services_api.dart b/lib/logic/api_maps/graphql_maps/server_api/services_api.dart index 2ef0e2ca..a2e85914 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/services_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/services_api.dart @@ -1,4 +1,4 @@ -part of 'server.dart'; +part of 'server_api.dart'; mixin ServicesApi on ApiMap { Future> getAllServices() async { diff --git a/lib/logic/api_maps/graphql_maps/server_api/users_api.dart b/lib/logic/api_maps/graphql_maps/server_api/users_api.dart index bb46bfef..c11f6a0e 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/users_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/users_api.dart @@ -1,4 +1,4 @@ -part of 'server.dart'; +part of 'server_api.dart'; mixin UsersApi on ApiMap { Future> getAllUsers() async { diff --git a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart index 70119f28..360dd491 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart @@ -1,4 +1,4 @@ -part of 'server.dart'; +part of 'server_api.dart'; mixin VolumeApi on ApiMap { Future> getServerDiskVolumes() async { diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart index 5f72f2e2..b0fbc020 100644 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ b/lib/logic/cubit/backups/backups_cubit.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.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/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/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/json/backup.dart'; diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index e9378bed..8b6a66b4 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/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/users/users_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart index 065113f1..10ad943d 100644 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ b/lib/logic/cubit/devices/devices_cubit.dart @@ -1,5 +1,5 @@ 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/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index d0d1cd12..f3b91bff 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_depe import 'package:selfprivacy/logic/models/hive/server_domain.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'; part 'dns_records_state.dart'; diff --git a/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart b/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart index dbfc7259..7f840d0a 100644 --- a/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:cubit_form/cubit_form.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/forms/factories/field_cubit_factory.dart'; diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index b3a3dd48..fb087cf9 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -1,6 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.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_factory_settings.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart'; diff --git a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart index 875d419a..76a572d1 100644 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart @@ -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/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index fcc0c5d8..5e0f78ea 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -1,5 +1,5 @@ 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_factory_settings.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart'; diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index f736310c..7e631388 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -14,7 +14,7 @@ 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_api_settings.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.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_factory.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/logic/cubit/server_jobs/server_jobs_cubit.dart b/lib/logic/cubit/server_jobs/server_jobs_cubit.dart index fc102115..4cc0cf97 100644 --- a/lib/logic/cubit/server_jobs/server_jobs_cubit.dart +++ b/lib/logic/cubit/server_jobs/server_jobs_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart index e48a809e..c10bc377 100644 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -1,6 +1,6 @@ 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/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart index f76c15a7..54e22b3d 100644 --- a/lib/logic/cubit/services/services_cubit.dart +++ b/lib/logic/cubit/services/services_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/service.dart'; diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index f31580fb..070fce2c 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hive/hive.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index 697e7811..ceefd0c7 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.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_text/brand_text.dart'; import 'package:package_info/package_info.dart'; From 51dc4c67b2b7a60a489fe27f0cce56ed26a9fb44 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 17 Nov 2022 11:14:34 +0400 Subject: [PATCH 41/52] feat(recovery): Implement access recovery routing for server providers --- assets/translations/en.json | 7 +- assets/translations/ru.json | 1 + .../schema/disk_volumes.graphql.dart | 2 +- .../graphql_maps/schema/schema.graphql | 1 + .../graphql_maps/schema/schema.graphql.dart | 2 + .../graphql_maps/schema/server_api.graphql | 8 + .../schema/server_api.graphql.dart | 420 +++++++++++++++++- .../schema/server_api.graphql.g.dart | 52 +++ .../schema/server_settings.graphql.dart | 2 +- .../graphql_maps/schema/services.graphql.dart | 1 + .../graphql_maps/schema/users.graphql.dart | 2 +- .../graphql_maps/server_api/server_api.dart | 26 ++ .../server_installation_cubit.dart | 11 + .../setup/recovering/recovery_routing.dart | 4 +- ...> recovery_server_provider_connected.dart} | 11 +- 15 files changed, 536 insertions(+), 14 deletions(-) rename lib/ui/pages/setup/recovering/{recovery_hentzner_connected.dart => recovery_server_provider_connected.dart} (89%) diff --git a/assets/translations/en.json b/assets/translations/en.json index 4cc240b9..908ef5e1 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -300,6 +300,7 @@ "checks": "Checks have been completed \n{} out of {}" }, "recovering": { + "generic_error": "Operation failed, please try again.", "recovery_main_header": "Connect to an existing server", "domain_recovery_description": "Enter a server domain you want to get access for:", "domain_recover_placeholder": "Your domain", @@ -319,9 +320,9 @@ "fallback_select_provider_console": "Access to the server console of my prodiver.", "authorization_failed": "Couldn't log in with this key", "fallback_select_provider_console_hint": "For example: Hetzner.", - "hetzner_connected": "Connect to Hetzner", - "hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:", - "hetzner_connected_placeholder": "Hetzner token", + "server_provider_connected": "Connect to your Server Provider", + "server_provider_connected_description": "Communication established. Enter you token with access to {}:", + "server_provider_connected_placeholder": "Server Provider token", "confirm_server": "Confirm server", "confirm_server_description": "Found your server! Confirm it is the right one:", "confirm_server_accept": "Yes! That's it", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 49c08079..ee056a3d 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -299,6 +299,7 @@ "checks": "Проверок выполнено: \n{} / {}" }, "recovering": { + "generic_error": "Ошибка проведения операции, попробуйте ещё раз.", "recovery_main_header": "Подключиться к существующему серверу", "domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:", "domain_recover_placeholder": "Домен", diff --git a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart index 2464c561..359a7a27 100644 --- a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart @@ -4,7 +4,7 @@ import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; +import 'server_api.graphql.dart'; part 'disk_volumes.graphql.g.dart'; @JsonSerializable(explicitToJson: true) diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index 5da67b2c..ed167742 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -173,6 +173,7 @@ input RecoveryKeyLimitsInput { enum ServerProvider { HETZNER + DIGITALOCEAN } type Service { diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart index 7187e0e2..11d49a43 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart @@ -693,6 +693,8 @@ enum Enum$DnsProvider { enum Enum$ServerProvider { @JsonValue('HETZNER') HETZNER, + @JsonValue('DIGITALOCEAN') + DIGITALOCEAN, $unknown } diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql index 96374fad..d4339094 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -64,6 +64,14 @@ mutation RebootSystem { } } +query SystemServerProvider { + system { + provider { + provider + } + } +} + query GetApiTokens { api { devices { diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart index c31a3487..325ee89d 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart @@ -4,7 +4,6 @@ import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; part 'server_api.graphql.g.dart'; @JsonSerializable(explicitToJson: true) @@ -3178,6 +3177,425 @@ class _CopyWithStubImpl$Mutation$RebootSystem$rebootSystem _res; } +@JsonSerializable(explicitToJson: true) +class Query$SystemServerProvider { + Query$SystemServerProvider({required this.system, required this.$__typename}); + + @override + factory Query$SystemServerProvider.fromJson(Map json) => + _$Query$SystemServerProviderFromJson(json); + + final Query$SystemServerProvider$system system; + + @JsonKey(name: '__typename') + final String $__typename; + + Map 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 + get copyWith => CopyWith$Query$SystemServerProvider(this, (i) => i); +} + +abstract class CopyWith$Query$SystemServerProvider { + 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 get system; +} + +class _CopyWithImpl$Query$SystemServerProvider + implements CopyWith$Query$SystemServerProvider { + _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 get system { + final local$system = _instance.system; + return CopyWith$Query$SystemServerProvider$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Query$SystemServerProvider + implements CopyWith$Query$SystemServerProvider { + _CopyWithStubImpl$Query$SystemServerProvider(this._res); + + TRes _res; + + call({Query$SystemServerProvider$system? system, String? $__typename}) => + _res; + CopyWith$Query$SystemServerProvider$system 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 data) => + Query$SystemServerProvider.fromJson(data); + +class Options$Query$SystemServerProvider + extends graphql.QueryOptions { + 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 { + 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> + query$SystemServerProvider( + [Options$Query$SystemServerProvider? options]) async => + await this.query(options ?? Options$Query$SystemServerProvider()); + graphql.ObservableQuery + 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 json) => + _$Query$SystemServerProvider$systemFromJson(json); + + final Query$SystemServerProvider$system$provider provider; + + @JsonKey(name: '__typename') + final String $__typename; + + Map 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 + get copyWith => + CopyWith$Query$SystemServerProvider$system(this, (i) => i); +} + +abstract class CopyWith$Query$SystemServerProvider$system { + 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 get provider; +} + +class _CopyWithImpl$Query$SystemServerProvider$system + implements CopyWith$Query$SystemServerProvider$system { + _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 get provider { + final local$provider = _instance.provider; + return CopyWith$Query$SystemServerProvider$system$provider( + local$provider, (e) => call(provider: e)); + } +} + +class _CopyWithStubImpl$Query$SystemServerProvider$system + implements CopyWith$Query$SystemServerProvider$system { + _CopyWithStubImpl$Query$SystemServerProvider$system(this._res); + + TRes _res; + + call( + {Query$SystemServerProvider$system$provider? provider, + String? $__typename}) => + _res; + CopyWith$Query$SystemServerProvider$system$provider 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 json) => + _$Query$SystemServerProvider$system$providerFromJson(json); + + @JsonKey(unknownEnumValue: Enum$ServerProvider.$unknown) + final Enum$ServerProvider provider; + + @JsonKey(name: '__typename') + final String $__typename; + + Map 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 { + 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 + implements CopyWith$Query$SystemServerProvider$system$provider { + _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 + implements CopyWith$Query$SystemServerProvider$system$provider { + _CopyWithStubImpl$Query$SystemServerProvider$system$provider(this._res); + + TRes _res; + + call({Enum$ServerProvider? provider, String? $__typename}) => _res; +} + @JsonSerializable(explicitToJson: true) class Query$GetApiTokens { Query$GetApiTokens({required this.api, required this.$__typename}); diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.g.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.g.dart index 525f8d64..f0ec390c 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.g.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.g.dart @@ -330,6 +330,58 @@ Map _$Mutation$RebootSystem$rebootSystemToJson( '__typename': instance.$__typename, }; +Query$SystemServerProvider _$Query$SystemServerProviderFromJson( + Map json) => + Query$SystemServerProvider( + system: Query$SystemServerProvider$system.fromJson( + json['system'] as Map), + $__typename: json['__typename'] as String, + ); + +Map _$Query$SystemServerProviderToJson( + Query$SystemServerProvider instance) => + { + 'system': instance.system.toJson(), + '__typename': instance.$__typename, + }; + +Query$SystemServerProvider$system _$Query$SystemServerProvider$systemFromJson( + Map json) => + Query$SystemServerProvider$system( + provider: Query$SystemServerProvider$system$provider.fromJson( + json['provider'] as Map), + $__typename: json['__typename'] as String, + ); + +Map _$Query$SystemServerProvider$systemToJson( + Query$SystemServerProvider$system instance) => + { + 'provider': instance.provider.toJson(), + '__typename': instance.$__typename, + }; + +Query$SystemServerProvider$system$provider + _$Query$SystemServerProvider$system$providerFromJson( + Map json) => + Query$SystemServerProvider$system$provider( + provider: $enumDecode(_$Enum$ServerProviderEnumMap, json['provider'], + unknownValue: Enum$ServerProvider.$unknown), + $__typename: json['__typename'] as String, + ); + +Map _$Query$SystemServerProvider$system$providerToJson( + Query$SystemServerProvider$system$provider instance) => + { + '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 json) => Query$GetApiTokens( api: Query$GetApiTokens$api.fromJson(json['api'] as Map), diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart index 5d036afa..a077cf7d 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart @@ -3,7 +3,7 @@ import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; +import 'server_api.graphql.dart'; part 'server_settings.graphql.g.dart'; @JsonSerializable(explicitToJson: true) diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart index a31058c4..92138d02 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart @@ -4,6 +4,7 @@ import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; +import 'server_api.graphql.dart'; part 'services.graphql.g.dart'; @JsonSerializable(explicitToJson: true) diff --git a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart index 18a15aa9..ce846b30 100644 --- a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart @@ -3,7 +3,7 @@ import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; +import 'server_api.graphql.dart'; part 'users.graphql.g.dart'; @JsonSerializable(explicitToJson: true) diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index c2cda13b..be5402ab 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -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/models/auto_upgrade_settings.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/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/backup.dart'; @@ -88,6 +89,31 @@ class ServerApi extends ApiMap return apiVersion; } + Future getServerProviderType() async { + QueryResult response; + ServerProvider providerType = ServerProvider.unknown; + + try { + final GraphQLClient client = await getClient(); + response = await client.query$SystemServerProvider(); + if (response.hasException) { + print(response.exception.toString()); + } + final rawProviderValue = response.data!['system']['provider']['provider']; + switch (rawProviderValue) { + case 'HETZNER': + providerType = ServerProvider.hetzner; + break; + case 'DIGITALOCEAN': + providerType = ServerProvider.digitalOcean; + break; + } + } catch (e) { + print(e); + } + return providerType; + } + Future isUsingBinds() async { QueryResult response; bool usesBinds = false; diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 9e82a780..3b972ac1 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/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_factory_settings.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart'; @@ -478,6 +479,16 @@ class ServerInstallationCubit extends Cubit { token, dataState.recoveryCapabilities, ); + final ServerProvider provider = await ServerApi( + customToken: token, + isWithToken: true, + ).getServerProviderType(); + if (provider == ServerProvider.unknown) { + getIt() + .showSnackBar('recovering.generic_error'.tr()); + return; + } + setServerProviderType(provider); await repository.saveServerDetails(serverDetails); emit( dataState.copyWith( diff --git a/lib/ui/pages/setup/recovering/recovery_routing.dart b/lib/ui/pages/setup/recovering/recovery_routing.dart index c6f56560..c2fb1d13 100644 --- a/lib/ui/pages/setup/recovering/recovery_routing.dart +++ b/lib/ui/pages/setup/recovering/recovery_routing.dart @@ -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_cloudflare.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/utils/route_transitions/basic.dart'; @@ -48,7 +48,7 @@ class RecoveryRouting extends StatelessWidget { currentPage = const RecoverByOldToken(); break; case RecoveryStep.serverProviderToken: - currentPage = const RecoveryHetznerConnected(); + currentPage = const RecoveryServerProviderConnected(); break; case RecoveryStep.serverSelection: currentPage = const RecoveryConfirmServer(); diff --git a/lib/ui/pages/setup/recovering/recovery_hentzner_connected.dart b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart similarity index 89% rename from lib/ui/pages/setup/recovering/recovery_hentzner_connected.dart rename to lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart index 76951395..152e4308 100644 --- a/lib/ui/pages/setup/recovering/recovery_hentzner_connected.dart +++ b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart @@ -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/ui/components/brand_md/brand_md.dart'; -class RecoveryHetznerConnected extends StatelessWidget { - const RecoveryHetznerConnected({super.key}); +class RecoveryServerProviderConnected extends StatelessWidget { + const RecoveryServerProviderConnected({super.key}); @override Widget build(final BuildContext context) { @@ -26,8 +26,8 @@ class RecoveryHetznerConnected extends StatelessWidget { context.watch().state; return BrandHeroScreen( - heroTitle: 'recovering.hetzner_connected'.tr(), - heroSubtitle: 'recovering.hetzner_connected_description'.tr( + heroTitle: 'recovering.server_provider_connected'.tr(), + heroSubtitle: 'recovering.server_provider_connected_description'.tr( args: [appConfig.state.serverDomain?.domainName ?? 'your domain'], ), hasBackButton: true, @@ -40,7 +40,8 @@ class RecoveryHetznerConnected extends StatelessWidget { formFieldCubit: context.read().apiKey, decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: 'recovering.hetzner_connected_placeholder'.tr(), + labelText: + 'recovering.server_provider_connected_placeholder'.tr(), ), ), const SizedBox(height: 16), From 611fe6bf453e73ce9f63e3b12c05b7b97d9a0f8a Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 17 Nov 2022 11:21:49 +0400 Subject: [PATCH 42/52] feat(server-api): Implement support for staging acme certificates Related to https://letsencrypt.org/docs/staging-environment/ to not get domain banned by constant renewal --- .../server_providers/digital_ocean/digital_ocean.dart | 2 +- .../api_maps/rest_maps/server_providers/hetzner/hetzner.dart | 2 +- .../api_maps/rest_maps/server_providers/server_provider.dart | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index 50c043e6..fedb397e 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -322,7 +322,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { const String infectBranch = 'providers/digital-ocean'; final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | PROVIDER=$infectProviderName 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"; + "#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(); diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index ce9aeeee..cbe45f46 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -360,7 +360,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { base64.encode(utf8.encode(rootUser.password ?? 'PASS')); final String userdataString = - "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | 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"; + "#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"; ServerHostingDetails? serverDetails; DioError? hetznerError; diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 21c6ddd0..0be28b85 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -55,4 +55,9 @@ abstract class ServerProviderApi extends ApiMap { /// Actual provider name to render on information page for user, /// for example 'Digital Ocean' for Digital Ocean String get appearanceProviderName; + + /// Whether we request for staging temprorary certificates. + /// Hardcode to 'true' in the middle of testing to not + /// get your domain banned but constant certificate renewal + String get stagingAcme => 'false'; } From 268816385f116ea143f417005b11f2cd4c1b1201 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 18 Nov 2022 05:19:54 +0400 Subject: [PATCH 43/52] fix(recovery): Fix custom api token on recovering provider type --- .../cubit/server_installation/server_installation_cubit.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 3b972ac1..d56c6e2e 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -480,7 +480,7 @@ class ServerInstallationCubit extends Cubit { dataState.recoveryCapabilities, ); final ServerProvider provider = await ServerApi( - customToken: token, + customToken: serverDetails.apiToken, isWithToken: true, ).getServerProviderType(); if (provider == ServerProvider.unknown) { From 0c4da8eb9f1d7c96606496ed623bf09aa94a91d3 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 18 Nov 2022 07:07:42 +0400 Subject: [PATCH 44/52] refactor: Move all API factories into encapsulated static class Because it's very hard to track all different provider factories created in different cubits, if users reset application config the previous factories stayed unchanged which would lead to unexpected behavior --- .../api_maps/rest_maps/api_controller.dart | 44 ++++++++++ .../server_providers/server_provider.dart | 2 +- .../cubit/dns_records/dns_records_cubit.dart | 15 +--- .../initializing/dns_provider_form_cubit.dart | 8 +- .../initializing/domain_setup_cubit.dart | 7 +- .../cubit/metrics/metrics_repository.dart | 25 ++---- .../provider_volume_cubit.dart | 49 ++++++----- .../server_detailed_info_repository.dart | 27 +----- .../server_installation_cubit.dart | 62 +++++++++----- .../server_installation_repository.dart | 82 ++++++++++--------- .../setup/initializing/initializing.dart | 5 +- 11 files changed, 173 insertions(+), 153 deletions(-) create mode 100644 lib/logic/api_maps/rest_maps/api_controller.dart diff --git a/lib/logic/api_maps/rest_maps/api_controller.dart b/lib/logic/api_maps/rest_maps/api_controller.dart new file mode 100644 index 00000000..440d25af --- /dev/null +++ b/lib/logic/api_maps/rest_maps/api_controller.dart @@ -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; +} diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 0be28b85..f846a7a8 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -59,5 +59,5 @@ abstract class ServerProviderApi extends ApiMap { /// Whether we request for staging temprorary certificates. /// Hardcode to 'true' in the middle of testing to not /// get your domain banned but constant certificate renewal - String get stagingAcme => 'false'; + String get stagingAcme => 'true'; } diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index f3b91bff..3403dc68 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -1,8 +1,6 @@ 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_factory_settings.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_factory.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/json/dns_records.dart'; @@ -20,12 +18,6 @@ class DnsRecordsCubit const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing), ); - DnsProviderApiFactory? dnsProviderApiFactory = - ApiFactoryCreator.createDnsProviderApiFactory( - DnsProviderApiFactorySettings(provider: DnsProvider.cloudflare), - ); // TODO: HARDCODE FOR NOW!!! - // TODO: Remove when provider selection is implemented. - final ServerApi api = ServerApi(); @override @@ -46,7 +38,8 @@ class DnsRecordsCubit final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4; if (domain != null && ipAddress != null) { - final List records = await dnsProviderApiFactory! + final List records = await ApiController + .currentDnsProviderApiFactory! .getDnsProvider() .getDnsRecords(domain: domain); final String? dkimPublicKey = @@ -126,7 +119,7 @@ class DnsRecordsCubit final ServerDomain? domain = serverInstallationCubit.state.serverDomain; final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4; final DnsProviderApi dnsProviderApi = - dnsProviderApiFactory!.getDnsProvider(); + ApiController.currentDnsProviderApiFactory!.getDnsProvider(); await dnsProviderApi.removeSimilarRecords(domain: domain!); await dnsProviderApi.createMultipleDnsRecords( domain: domain, diff --git a/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart index c2348a69..5ba72483 100644 --- a/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart @@ -7,16 +7,16 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class DnsProviderFormCubit extends FormCubit { DnsProviderFormCubit(this.initializingCubit) { - final RegExp regExp = initializingCubit.getDnsProviderApiTokenValidation(); + //final RegExp regExp = initializingCubit.getDnsProviderApiTokenValidation(); apiKey = FieldCubit( initalValue: '', validations: [ RequiredStringValidation('validations.required'.tr()), - ValidationModel( + /*ValidationModel( regExp.hasMatch, 'validations.invalid_format'.tr(), - ), - LengthStringNotEqualValidation(40) + ),*/ + //LengthStringNotEqualValidation(40) ], ); diff --git a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart index a1c458fb..62fc1050 100644 --- a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart @@ -1,4 +1,5 @@ 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/models/hive/server_domain.dart'; @@ -9,8 +10,7 @@ class DomainSetupCubit extends Cubit { Future load() async { emit(Loading(LoadingTypes.loadingDomain)); - final List list = await serverInstallationCubit - .repository.dnsProviderApiFactory! + final List list = await ApiController.currentDnsProviderApiFactory! .getDnsProvider() .domainList(); if (list.isEmpty) { @@ -31,8 +31,7 @@ class DomainSetupCubit extends Cubit { emit(Loading(LoadingTypes.saving)); - final String? zoneId = await serverInstallationCubit - .repository.dnsProviderApiFactory! + final String? zoneId = await ApiController.currentDnsProviderApiFactory! .getDnsProvider() .getZoneId(domainName); diff --git a/lib/logic/cubit/metrics/metrics_repository.dart b/lib/logic/cubit/metrics/metrics_repository.dart index 15da1b0c..71c298bf 100644 --- a/lib/logic/cubit/metrics/metrics_repository.dart +++ b/lib/logic/cubit/metrics/metrics_repository.dart @@ -1,11 +1,8 @@ import 'package:selfprivacy/config/get_it_config.dart'; -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/server_providers/server_provider_factory.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/hive/server_details.dart'; import 'package:selfprivacy/logic/models/metrics.dart'; class MetricsLoadException implements Exception { @@ -14,22 +11,10 @@ class MetricsLoadException implements Exception { } class MetricsRepository { - ServerProviderApiFactory? serverProviderApiFactory; - - void _buildServerProviderFactory() { - final ServerProvider? providerType = getIt().serverProvider; - final String? location = getIt().serverLocation; - serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( - ServerProviderApiFactorySettings( - provider: providerType ?? ServerProvider.unknown, - location: location, - ), - ); - } - Future getMetrics(final Period period) async { - if (serverProviderApiFactory == null) { - _buildServerProviderFactory(); + final providerApiFactory = ApiController.currentServerProviderApiFactory; + if (providerApiFactory == null) { + throw MetricsLoadException('Server Provider data is null'); } final DateTime end = DateTime.now(); @@ -49,7 +34,7 @@ class MetricsRepository { final serverId = getIt().serverDetails!.id; final ServerMetrics? metrics = - await serverProviderApiFactory!.getServerProvider().getMetrics( + await providerApiFactory.getServerProvider().getMetrics( serverId, start, end, diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index fb087cf9..11e180d0 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -1,9 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.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/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; @@ -17,30 +15,19 @@ class ApiProviderVolumeCubit extends ServerInstallationDependendCubit { ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit) : super(serverInstallationCubit, const ApiProviderVolumeState.initial()); - - VolumeProviderApiFactory? providerApi; - final ServerApi serverApi = ServerApi(); @override Future load() async { if (serverInstallationCubit.state is ServerInstallationFinished) { - final serverDetails = getIt().serverDetails; - final serverLocation = getIt().serverLocation; - providerApi = serverDetails == null - ? null - : VolumeApiFactoryCreator.createVolumeProviderApiFactory( - ServerProviderApiFactorySettings( - location: serverLocation, - provider: getIt().serverDetails!.provider, - ), - ); _refetch(); } } Future getPricePerGb() async => - providerApi!.getVolumeProvider().getPricePerGb(); + ApiController.currentVolumeProviderApiFactory! + .getVolumeProvider() + .getPricePerGb(); Future refresh() async { emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false)); @@ -48,12 +35,14 @@ class ApiProviderVolumeCubit } Future _refetch() async { - if (providerApi == null) { + if (ApiController.currentVolumeProviderApiFactory == null) { return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); } - final List volumes = - await providerApi!.getVolumeProvider().getVolumes(); + final List volumes = await ApiController + .currentVolumeProviderApiFactory! + .getVolumeProvider() + .getVolumes(); if (volumes.isEmpty) { return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); @@ -64,14 +53,16 @@ class ApiProviderVolumeCubit Future attachVolume(final DiskVolume volume) async { final ServerHostingDetails server = getIt().serverDetails!; - await providerApi! + await ApiController.currentVolumeProviderApiFactory! .getVolumeProvider() .attachVolume(volume.providerVolume!, server.id); refresh(); } Future detachVolume(final DiskVolume volume) async { - await providerApi!.getVolumeProvider().detachVolume(volume.providerVolume!); + await ApiController.currentVolumeProviderApiFactory! + .getVolumeProvider() + .detachVolume(volume.providerVolume!); refresh(); } @@ -84,7 +75,9 @@ class ApiProviderVolumeCubit 'Starting resize', ); emit(state.copyWith(isResizing: true)); - final bool resized = await providerApi!.getVolumeProvider().resizeVolume( + final bool resized = await ApiController.currentVolumeProviderApiFactory! + .getVolumeProvider() + .resizeVolume( volume.providerVolume!, newSize, ); @@ -120,8 +113,10 @@ class ApiProviderVolumeCubit } Future createVolume() async { - final ServerVolume? volume = - await providerApi!.getVolumeProvider().createVolume(); + final ServerVolume? volume = await ApiController + .currentVolumeProviderApiFactory! + .getVolumeProvider() + .createVolume(); final diskVolume = DiskVolume(providerVolume: volume); await attachVolume(diskVolume); @@ -133,7 +128,9 @@ class ApiProviderVolumeCubit } Future deleteVolume(final DiskVolume volume) async { - await providerApi!.getVolumeProvider().deleteVolume(volume.providerVolume!); + await ApiController.currentVolumeProviderApiFactory! + .getVolumeProvider() + .deleteVolume(volume.providerVolume!); refresh(); } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index 5e0f78ea..ca6848bc 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -1,38 +1,19 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.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/hive/server_details.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; class ServerDetailsRepository { ServerApi server = ServerApi(); - ServerProviderApiFactory? serverProviderApiFactory; - - void _buildServerProviderFactory() { - final ServerProvider? providerType = getIt().serverProvider; - final String? location = getIt().serverLocation; - serverProviderApiFactory = ApiFactoryCreator.createServerProviderApiFactory( - ServerProviderApiFactorySettings( - provider: providerType ?? ServerProvider.unknown, - location: location, - ), - ); - } Future load() async { - if (serverProviderApiFactory == null) { - _buildServerProviderFactory(); - } - + final serverProviderApi = ApiController.currentServerProviderApiFactory; final settings = await server.getSystemSettings(); final serverId = getIt().serverDetails!.id; - final metadata = await serverProviderApiFactory! - .getServerProvider() - .getMetadata(serverId); + final metadata = + await serverProviderApi!.getServerProvider().getMetadata(serverId); return ServerDetailsRepositoryDto( autoUpgradeSettings: settings.autoUpgradeSettings, diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index d56c6e2e..fa18cda8 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -5,7 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/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_api_settings.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart'; @@ -59,8 +59,7 @@ class ServerInstallationCubit extends Cubit { void setServerProviderType(final ServerProvider providerType) async { await repository.saveServerProviderType(providerType); - repository.serverProviderApiFactory = - ApiFactoryCreator.createServerProviderApiFactory( + ApiController.initServerProviderApiFactory( ServerProviderApiFactorySettings( provider: providerType, ), @@ -68,18 +67,19 @@ class ServerInstallationCubit extends Cubit { } ProviderApiTokenValidation serverProviderApiTokenValidation() => - repository.serverProviderApiFactory! + ApiController.currentServerProviderApiFactory! .getServerProvider() .getApiTokenValidation(); - RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory! - .getDnsProvider() - .getApiTokenValidation(); + RegExp getDnsProviderApiTokenValidation() => + ApiController.currentDnsProviderApiFactory! + .getDnsProvider() + .getApiTokenValidation(); Future isServerProviderApiTokenValid( final String providerToken, ) async => - repository.serverProviderApiFactory! + ApiController.currentServerProviderApiFactory! .getServerProvider( settings: const ServerProviderApiSettings( isWithToken: false, @@ -89,19 +89,30 @@ class ServerInstallationCubit extends Cubit { Future isDnsProviderApiTokenValid( final String providerToken, - ) async => - repository.dnsProviderApiFactory! - .getDnsProvider( - settings: const DnsProviderApiSettings(isWithToken: false), - ) - .isApiTokenValid(providerToken); + ) async { + if (ApiController.currentDnsProviderApiFactory == null) { + // No other DNS provider is supported for now, + // so it's safe to hardcode Cloudflare + ApiController.initDnsProviderApiFactory( + DnsProviderApiFactorySettings( + provider: DnsProvider.cloudflare, + ), + ); + } + + return ApiController.currentDnsProviderApiFactory! + .getDnsProvider( + settings: const DnsProviderApiSettings(isWithToken: false), + ) + .isApiTokenValid(providerToken); + } Future> fetchAvailableLocations() async { - if (repository.serverProviderApiFactory == null) { + if (ApiController.currentServerProviderApiFactory == null) { return []; } - return repository.serverProviderApiFactory! + return ApiController.currentServerProviderApiFactory! .getServerProvider() .getAvailableLocations(); } @@ -109,11 +120,11 @@ class ServerInstallationCubit extends Cubit { Future> fetchAvailableTypesByLocation( final ServerProviderLocation location, ) async { - if (repository.serverProviderApiFactory == null) { + if (ApiController.currentServerProviderApiFactory == null) { return []; } - return repository.serverProviderApiFactory! + return ApiController.currentServerProviderApiFactory! .getServerProvider() .getServerTypesByLocation(location: location); } @@ -141,8 +152,16 @@ class ServerInstallationCubit extends Cubit { void setServerType(final ServerType serverType) async { await repository.saveServerType(serverType); - repository.serverProviderApiFactory = - ApiFactoryCreator.createServerProviderApiFactory( + ApiController.initServerProviderApiFactory( + ServerProviderApiFactorySettings( + provider: getIt().serverProvider!, + location: serverType.location.identifier, + ), + ); + + // All server providers support volumes for now, + // so it's safe to initialize. + ApiController.initVolumeProviderApiFactory( ServerProviderApiFactorySettings( provider: getIt().serverProvider!, location: serverType.location.identifier, @@ -162,6 +181,7 @@ class ServerInstallationCubit extends Cubit { return; } await repository.saveCloudFlareKey(cloudFlareKey); + emit( (state as ServerInstallationNotFinished) .copyWith(cloudFlareKey: cloudFlareKey), @@ -677,7 +697,7 @@ class ServerInstallationCubit extends Cubit { void clearAppConfig() { closeTimer(); - + ApiController.clearProviderApiFactories(); repository.clearAppConfig(); emit(const ServerInstallationEmpty()); } diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 7e631388..59e4921c 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -9,14 +9,12 @@ import 'package:hive/hive.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; -import 'package:selfprivacy/logic/api_maps/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_api_settings.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/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/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; @@ -43,13 +41,6 @@ class ServerAuthorizationException implements Exception { class ServerInstallationRepository { Box box = Hive.box(BNames.serverInstallationBox); Box usersBox = Hive.box(BNames.usersBox); - ServerProviderApiFactory? serverProviderApiFactory; - DnsProviderApiFactory? dnsProviderApiFactory = - ApiFactoryCreator.createDnsProviderApiFactory( - DnsProviderApiFactorySettings( - provider: DnsProvider.cloudflare, - ), // TODO: HARDCODE FOR NOW!!! - ); Future load() async { final String? providerApiToken = getIt().serverProviderKey; @@ -67,8 +58,16 @@ class ServerInstallationRepository { if (serverProvider != null || (serverDetails != null && serverDetails.provider != ServerProvider.unknown)) { - serverProviderApiFactory = - ApiFactoryCreator.createServerProviderApiFactory( + ApiController.initServerProviderApiFactory( + 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, @@ -77,7 +76,7 @@ class ServerInstallationRepository { } if (serverDomain != null && serverDomain.provider != DnsProvider.unknown) { - dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory( + ApiController.initDnsProviderApiFactory( DnsProviderApiFactorySettings( provider: serverDomain.provider, ), @@ -168,14 +167,16 @@ class ServerInstallationRepository { ) async { ServerHostingDetails serverDetails; - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); - serverDetails = await api.powerOn(); + serverDetails = await ApiController.currentServerProviderApiFactory! + .getServerProvider() + .powerOn(); return serverDetails; } Future getDomainId(final String token, final String domain) async { - final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider( + final DnsProviderApi dnsProviderApi = + ApiController.currentDnsProviderApiFactory!.getDnsProvider( settings: DnsProviderApiSettings( isWithToken: false, customToken: token, @@ -244,7 +245,8 @@ class ServerInstallationRepository { required final Future Function(ServerHostingDetails serverDetails) onSuccess, }) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); + final ServerProviderApi api = + ApiController.currentServerProviderApiFactory!.getServerProvider(); try { final ServerHostingDetails? serverDetails = await api.createServer( dnsApiToken: cloudFlareKey, @@ -328,9 +330,9 @@ class ServerInstallationRepository { required final void Function() onCancel, }) async { final DnsProviderApi dnsProviderApi = - dnsProviderApiFactory!.getDnsProvider(); + ApiController.currentDnsProviderApiFactory!.getDnsProvider(); final ServerProviderApi serverApi = - serverProviderApiFactory!.getServerProvider(); + ApiController.currentServerProviderApiFactory!.getServerProvider(); await dnsProviderApi.removeSimilarRecords( ip4: serverDetails.ip4, @@ -370,7 +372,7 @@ class ServerInstallationRepository { Future createDkimRecord(final ServerDomain cloudFlareDomain) async { final DnsProviderApi dnsProviderApi = - dnsProviderApiFactory!.getDnsProvider(); + ApiController.currentDnsProviderApiFactory!.getDnsProvider(); final ServerApi api = ServerApi(); late DnsRecord record; @@ -389,15 +391,15 @@ class ServerInstallationRepository { return api.isHttpServerWorking(); } - Future restart() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); - return api.restart(); - } + Future restart() async => + ApiController.currentServerProviderApiFactory! + .getServerProvider() + .restart(); - Future powerOn() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); - return api.powerOn(); - } + Future powerOn() async => + ApiController.currentServerProviderApiFactory! + .getServerProvider() + .powerOn(); Future getRecoveryCapabilities( final ServerDomain serverDomain, @@ -632,10 +634,10 @@ class ServerInstallationRepository { } } - Future> getServersOnProviderAccount() async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); - return api.getServers(); - } + Future> getServersOnProviderAccount() async => + ApiController.currentServerProviderApiFactory! + .getServerProvider() + .getServers(); Future saveServerDetails( final ServerHostingDetails serverDetails, @@ -724,13 +726,11 @@ class ServerInstallationRepository { } Future deleteServer(final ServerDomain serverDomain) async { - final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(); - final DnsProviderApi dnsProviderApi = - dnsProviderApiFactory!.getDnsProvider(); - - await api.deleteServer( - domainName: serverDomain.domainName, - ); + await ApiController.currentServerProviderApiFactory! + .getServerProvider() + .deleteServer( + domainName: serverDomain.domainName, + ); await box.put(BNames.hasFinalChecked, false); await box.put(BNames.isServerStarted, false); @@ -739,7 +739,9 @@ class ServerInstallationRepository { await box.put(BNames.isLoading, false); await box.put(BNames.serverDetails, null); - await dnsProviderApi.removeSimilarRecords(domain: serverDomain); + await ApiController.currentDnsProviderApiFactory! + .getDnsProvider() + .removeSimilarRecords(domain: serverDomain); } Future deleteServerRelatedRecords() async { diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 48d95a6d..b81680bc 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -206,9 +206,8 @@ class InitializingPage extends StatelessWidget { ), const Spacer(), BrandButton.rised( - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), + onPressed: () => + context.read().trySubmit(), text: 'basis.connect'.tr(), ), const SizedBox(height: 10), From da394e22acc47b689b6935c79176b80a0f51df6b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 18 Nov 2022 10:59:47 +0400 Subject: [PATCH 45/52] feat(server-api): Implement bad certificates fallback for staging environment Without it client application won't accept staging certificates from server --- lib/logic/api_maps/graphql_maps/api_map.dart | 15 +++++++++ .../digital_ocean/digital_ocean.dart | 2 ++ .../server_providers/hetzner/hetzner.dart | 3 +- .../server_providers/server_provider.dart | 5 --- lib/logic/api_maps/staging_options.dart | 8 +++++ pubspec.lock | 32 +++++++++---------- pubspec.yaml | 1 + 7 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 lib/logic/api_maps/staging_options.dart diff --git a/lib/logic/api_maps/graphql_maps/api_map.dart b/lib/logic/api_maps/graphql_maps/api_map.dart index c01f1837..ed495fea 100644 --- a/lib/logic/api_maps/graphql_maps/api_map.dart +++ b/lib/logic/api_maps/graphql_maps/api_map.dart @@ -1,10 +1,25 @@ +import 'dart:io'; + import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:http/io_client.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/staging_options.dart'; abstract class ApiMap { Future getClient() async { + final HttpClient httpClient = HttpClient(); + if (StagingOptions.stagingAcme) { + httpClient.badCertificateCallback = ( + final cert, + final host, + final port, + ) => + true; + } + final httpLink = HttpLink( 'https://api.$rootAddress/graphql', + httpClient: IOClient(httpClient), ); final String token = _getApiToken(); diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index fedb397e..da5d975c 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -6,6 +6,7 @@ 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'; @@ -320,6 +321,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 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"; diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index cbe45f46..f8062286 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -6,6 +6,7 @@ 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/json/hetzner_server_info.dart'; @@ -355,7 +356,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { final String apiToken = StringGenerators.apiToken(); final String hostname = getHostnameFromDomain(domainName); const String infectBranch = 'providers/hetzner'; - + final String stagingAcme = StagingOptions.stagingAcme ? 'true' : 'false'; final String base64Password = base64.encode(utf8.encode(rootUser.password ?? 'PASS')); diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index f846a7a8..21c6ddd0 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -55,9 +55,4 @@ abstract class ServerProviderApi extends ApiMap { /// Actual provider name to render on information page for user, /// for example 'Digital Ocean' for Digital Ocean String get appearanceProviderName; - - /// Whether we request for staging temprorary certificates. - /// Hardcode to 'true' in the middle of testing to not - /// get your domain banned but constant certificate renewal - String get stagingAcme => 'true'; } diff --git a/lib/logic/api_maps/staging_options.dart b/lib/logic/api_maps/staging_options.dart new file mode 100644 index 00000000..3d04876e --- /dev/null +++ b/lib/logic/api_maps/staging_options.dart @@ -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 but constant certificate renewal + static bool get stagingAcme => true; +} diff --git a/pubspec.lock b/pubspec.lock index 8eae4c89..c5901997 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" auto_size_text: dependency: "direct main" description: @@ -126,7 +126,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -147,7 +147,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -350,7 +350,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -631,12 +631,12 @@ packages: source: hosted version: "1.1.3" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.4" + version: "0.13.5" http_multi_server: dependency: transitive description: @@ -762,21 +762,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -846,7 +846,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1159,7 +1159,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -1187,7 +1187,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" system_theme: dependency: "direct main" description: @@ -1208,28 +1208,28 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" timezone: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 372bd4a2..af1521e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: gtk_theme_fl: ^0.0.1 hive: ^2.2.3 hive_flutter: ^1.1.0 + http: ^0.13.5 intl: ^0.17.0 ionicons: ^0.1.2 json_annotation: ^4.6.0 From b26e22cd4ea41c9af38269893577f3ab59ecba9d Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 18 Nov 2022 11:30:50 +0400 Subject: [PATCH 46/52] fix: Check on null for server type identifier It is not needed to finish installation so it's okat if it's empty --- .../cubit/server_installation/server_installation_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 18742303..ad88571e 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -191,7 +191,7 @@ class ServerInstallationNotFinished extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, - serverTypeIdentificator: serverTypeIdentificator!, + serverTypeIdentificator: serverTypeIdentificator ?? '', cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, From b53bb6d4ddf5ffdef724284e25507798d866ce3a Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 18 Nov 2022 11:31:56 +0400 Subject: [PATCH 47/52] refactor: Remove 'unused' warnings --- .../initializing/dns_provider_form_cubit.dart | 7 +- .../setup/initializing/initializing.dart | 74 +++++++++---------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart index 5ba72483..e50d7db3 100644 --- a/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart @@ -7,16 +7,11 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class DnsProviderFormCubit extends FormCubit { DnsProviderFormCubit(this.initializingCubit) { - //final RegExp regExp = initializingCubit.getDnsProviderApiTokenValidation(); apiKey = FieldCubit( initalValue: '', validations: [ RequiredStringValidation('validations.required'.tr()), - /*ValidationModel( - regExp.hasMatch, - 'validations.invalid_format'.tr(), - ),*/ - //LengthStringNotEqualValidation(40) + LengthStringNotEqualValidation(40) ], ); diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index b81680bc..c56928a9 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -181,48 +181,44 @@ class InitializingPage extends StatelessWidget { BlocProvider( create: (final context) => DnsProviderFormCubit(initializingCubit), child: Builder( - builder: (final context) { - final formCubitState = context.watch().state; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset( - 'assets/images/logos/cloudflare.png', - width: 150, + builder: (final context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + 'assets/images/logos/cloudflare.png', + width: 150, + ), + const SizedBox(height: 10), + BrandText.h2('initializing.connect_cloudflare'.tr()), + const SizedBox(height: 10), + BrandText.body2('initializing.manage_domain_dns'.tr()), + const Spacer(), + CubitFormTextField( + formFieldCubit: context.read().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 SizedBox(height: 10), - BrandText.body2('initializing.manage_domain_dns'.tr()), - const Spacer(), - CubitFormTextField( - formFieldCubit: context.read().apiKey, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: InputDecoration( - hintText: 'initializing.cloudflare_api_token'.tr(), + ), + const Spacer(), + BrandButton.rised( + onPressed: () => + context.read().trySubmit(), + text: 'basis.connect'.tr(), + ), + const SizedBox(height: 10), + BrandButton.text( + onPressed: () => _showModal( + context, + const _HowTo( + fileName: 'how_cloudflare', ), ), - const Spacer(), - BrandButton.rised( - onPressed: () => - context.read().trySubmit(), - text: 'basis.connect'.tr(), - ), - const SizedBox(height: 10), - BrandButton.text( - onPressed: () => _showModal( - context, - const _HowTo( - fileName: 'how_cloudflare', - ), - ), - title: 'initializing.how'.tr(), - ), - ], - ); - }, + title: 'initializing.how'.tr(), + ), + ], + ), ), ); From b2a5d57a1dfa8c87a46246a8ea4e722ffe92214b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 20 Nov 2022 14:48:08 +0400 Subject: [PATCH 48/52] feat(initializing): Add description and back button to server type step --- assets/translations/en.json | 2 ++ assets/translations/ru.json | 4 +++- .../setup/initializing/initializing.dart | 5 ++++ .../initializing/server_type_picker.dart | 23 +++++++++++++++++-- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 908ef5e1..18e70c5b 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -273,6 +273,8 @@ "place_where_data": "A place where your data and SelfPrivacy services will reside:", "how": "How to obtain API token", "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", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index ee056a3d..7352bf59 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -271,7 +271,9 @@ "connect_to_server": "Подключите сервер", "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", "how": "Как получить API Token", - "provider_bad_key_error": "API ключ провайдера неверен", + "provider_bad_key_error": "Provider API key is invalid", + "choose_location_type": "Выберите локацию и тип вашего сервера:", + "back_to_locations": "Назад к доступным локациям!", "no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.", "no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.", "cloudflare_bad_key_error": "Cloudflare API ключ неверен", diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index c56928a9..29fcd6f0 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -82,6 +82,11 @@ class InitializingPage extends StatelessWidget { activeIndex: cubit.state.porgressBar, ), ), + if (cubit.state.porgressBar == + ServerSetupProgress.serverProviderFilled.index) + BrandText.h2( + 'initializing.choose_location_type'.tr(), + ), _addCard( AnimatedSwitcher( duration: const Duration(milliseconds: 300), diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index a6757571..04b3bd5f 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -4,6 +4,7 @@ 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({ @@ -21,7 +22,7 @@ class _ServerTypePickerState extends State { ServerProviderLocation? serverProviderLocation; ServerType? serverType; - void setServerProviderLocation(final ServerProviderLocation location) { + void setServerProviderLocation(final ServerProviderLocation? location) { setState(() { serverProviderLocation = location; }); @@ -39,6 +40,9 @@ class _ServerTypePickerState extends State { return SelectTypePage( location: serverProviderLocation!, serverInstallationCubit: widget.serverInstallationCubit, + backToLocationPickingCallback: () { + setServerProviderLocation(null); + }, ); } } @@ -102,6 +106,7 @@ class SelectLocationPage extends StatelessWidget { class SelectTypePage extends StatelessWidget { const SelectTypePage({ + required this.backToLocationPickingCallback, required this.location, required this.serverInstallationCubit, super.key, @@ -109,6 +114,7 @@ class SelectTypePage extends StatelessWidget { final ServerProviderLocation location; final ServerInstallationCubit serverInstallationCubit; + final Function backToLocationPickingCallback; @override Widget build(final BuildContext context) => FutureBuilder( @@ -119,7 +125,20 @@ class SelectTypePage extends StatelessWidget { ) { if (snapshot.hasData) { if ((snapshot.data as List).isEmpty) { - return Text('initializing.no_server_types_found'.tr()); + 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, From 58c9e00ce0d8191366739226916ffac750894aba Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 20 Nov 2022 18:31:31 +0400 Subject: [PATCH 49/52] fix(recovery): Add reverse dns validation for digital ocean In Digital Ocean reverse dns is not domain name but just name, like mydomainname instead of mydomainname.xyz, so we need additional condition --- .../server_installation/server_installation_cubit.dart | 6 ++++-- .../server_installation/server_installation_repository.dart | 2 +- .../server_installation/server_installation_state.dart | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index fa18cda8..08852825 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -508,8 +508,8 @@ class ServerInstallationCubit extends Cubit { .showSnackBar('recovering.generic_error'.tr()); return; } - setServerProviderType(provider); await repository.saveServerDetails(serverDetails); + setServerProviderType(provider); emit( dataState.copyWith( serverDetails: serverDetails, @@ -600,7 +600,9 @@ class ServerInstallationCubit extends Cubit { serverBasicInfo: server, isIpValid: server.ip == dataState.serverDetails?.ip4, isReverseDnsValid: - server.reverseDns == dataState.serverDomain?.domainName, + server.reverseDns == dataState.serverDomain?.domainName || + server.reverseDns == + dataState.serverDomain?.domainName.split('.')[0], ), ); return validated.toList(); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 59e4921c..cc3860e3 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -86,7 +86,7 @@ class ServerInstallationRepository { if (box.get(BNames.hasFinalChecked, defaultValue: false)) { return ServerInstallationFinished( providerApiToken: providerApiToken!, - serverTypeIdentificator: serverTypeIdentificator!, + serverTypeIdentificator: serverTypeIdentificator ?? '', cloudFlareKey: cloudflareToken!, serverDomain: serverDomain!, backblazeCredential: backblazeCredential!, diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index ad88571e..331c3e2a 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -331,7 +331,7 @@ class ServerInstallationRecovery extends ServerInstallationState { ServerInstallationFinished finish() => ServerInstallationFinished( providerApiToken: providerApiToken!, - serverTypeIdentificator: serverTypeIdentificator!, + serverTypeIdentificator: serverTypeIdentificator ?? '', cloudFlareKey: cloudFlareKey!, backblazeCredential: backblazeCredential!, serverDomain: serverDomain!, From 93b28d981e6398387dc9338cbdf4e7aaf5e4c82b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 20 Nov 2022 18:34:20 +0400 Subject: [PATCH 50/52] chore: Remove testing flag before merging into master --- lib/logic/api_maps/staging_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/api_maps/staging_options.dart b/lib/logic/api_maps/staging_options.dart index 3d04876e..fd4e8099 100644 --- a/lib/logic/api_maps/staging_options.dart +++ b/lib/logic/api_maps/staging_options.dart @@ -4,5 +4,5 @@ class StagingOptions { /// Whether we request for staging temprorary certificates. /// Hardcode to 'true' in the middle of testing to not /// get your domain banned but constant certificate renewal - static bool get stagingAcme => true; + static bool get stagingAcme => false; } From 479efac6e9e7c2cda6e2ef988b5fdfe3d3bc37a9 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 20 Nov 2022 18:35:44 +0400 Subject: [PATCH 51/52] chore: Fix an awkward commentary typo... --- lib/logic/api_maps/staging_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/api_maps/staging_options.dart b/lib/logic/api_maps/staging_options.dart index fd4e8099..7d3084b7 100644 --- a/lib/logic/api_maps/staging_options.dart +++ b/lib/logic/api_maps/staging_options.dart @@ -3,6 +3,6 @@ class StagingOptions { /// Whether we request for staging temprorary certificates. /// Hardcode to 'true' in the middle of testing to not - /// get your domain banned but constant certificate renewal + /// get your domain banned by constant certificate renewal static bool get stagingAcme => false; } From 8c6b56f61dc52dccd81aa5b84fd7bae9c3d13f01 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 23 Nov 2022 11:55:28 +0400 Subject: [PATCH 52/52] fix: Make improvements by Code Review --- assets/translations/ru.json | 2 +- lib/logic/api_maps/graphql_maps/api_map.dart | 6 +- .../graphql_maps/server_api/server_api.dart | 14 +- .../digital_ocean/digital_ocean.dart | 177 +++++++++--------- .../server_providers/hetzner/hetzner.dart | 119 ++++++------ .../server_providers/server_provider.dart | 2 +- lib/logic/models/hive/server_details.dart | 14 +- lib/logic/models/server_metadata.dart | 24 ++- .../server_details/server_details_screen.dart | 1 - lib/ui/pages/server_details/text_details.dart | 12 +- lib/utils/password_generator.dart | 2 +- 11 files changed, 192 insertions(+), 181 deletions(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 7352bf59..3dd4e590 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -271,7 +271,7 @@ "connect_to_server": "Подключите сервер", "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", "how": "Как получить API Token", - "provider_bad_key_error": "Provider API key is invalid", + "provider_bad_key_error": "API ключ провайдера неверен", "choose_location_type": "Выберите локацию и тип вашего сервера:", "back_to_locations": "Назад к доступным локациям!", "no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.", diff --git a/lib/logic/api_maps/graphql_maps/api_map.dart b/lib/logic/api_maps/graphql_maps/api_map.dart index ed495fea..185a5e54 100644 --- a/lib/logic/api_maps/graphql_maps/api_map.dart +++ b/lib/logic/api_maps/graphql_maps/api_map.dart @@ -7,19 +7,21 @@ import 'package:selfprivacy/logic/api_maps/staging_options.dart'; abstract class ApiMap { Future getClient() async { - final HttpClient httpClient = HttpClient(); + IOClient? ioClient; if (StagingOptions.stagingAcme) { + final HttpClient httpClient = HttpClient(); httpClient.badCertificateCallback = ( final cert, final host, final port, ) => true; + ioClient = IOClient(httpClient); } final httpLink = HttpLink( 'https://api.$rootAddress/graphql', - httpClient: IOClient(httpClient), + httpClient: ioClient, ); final String token = _getApiToken(); diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index be5402ab..4370131f 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -90,7 +90,7 @@ class ServerApi extends ApiMap } Future getServerProviderType() async { - QueryResult response; + QueryResult response; ServerProvider providerType = ServerProvider.unknown; try { @@ -99,15 +99,9 @@ class ServerApi extends ApiMap if (response.hasException) { print(response.exception.toString()); } - final rawProviderValue = response.data!['system']['provider']['provider']; - switch (rawProviderValue) { - case 'HETZNER': - providerType = ServerProvider.hetzner; - break; - case 'DIGITALOCEAN': - providerType = ServerProvider.digitalOcean; - break; - } + providerType = ServerProvider.fromGraphQL( + response.parsedData!.system.provider.provider, + ); } catch (e) { print(e); } diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart index da5d975c..ba3f0d63 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart @@ -56,7 +56,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { String get infectProviderName => 'digitalocean'; @override - String get appearanceProviderName => 'Digital Ocean'; + String get displayProviderName => 'Digital Ocean'; @override Future isApiTokenValid(final String token) async { @@ -102,32 +102,32 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { Future createVolume() async { ServerVolume? volume; - final Response dbCreateResponse; + final Response createVolumeResponse; final Dio client = await getClient(); try { final List volumes = await getVolumes(); await Future.delayed(const Duration(seconds: 6)); - dbCreateResponse = await client.post( + createVolumeResponse = await client.post( '/volumes', data: { 'size_gigabytes': 10, - 'name': 'volume${StringGenerators.dbStorageName()}', + 'name': 'volume${StringGenerators.storageName()}', 'labels': {'labelkey': 'value'}, 'region': region, 'filesystem_type': 'ext4', }, ); - final dbId = dbCreateResponse.data['volume']['id']; - final dbSize = dbCreateResponse.data['volume']['size_gigabytes']; - final dbName = dbCreateResponse.data['volume']['name']; + 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: dbName, - sizeByte: dbSize, + name: volumeName, + sizeByte: volumeSize, serverId: null, - linuxDevice: 'scsi-0DO_Volume_$dbName', - uuid: dbId, + linuxDevice: '/dev/disk/by-id/scsi-0DO_Volume_$volumeName', + uuid: volumeId, ); } catch (e) { print(e); @@ -142,29 +142,29 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { Future> getVolumes({final String? status}) async { final List volumes = []; - final Response dbGetResponse; + final Response getVolumesResponse; final Dio client = await getClient(); try { - dbGetResponse = await client.get( + getVolumesResponse = await client.get( '/volumes', queryParameters: { 'status': status, }, ); - final List rawVolumes = dbGetResponse.data['volumes']; + final List rawVolumes = getVolumesResponse.data['volumes']; int id = 0; for (final rawVolume in rawVolumes) { - final dbId = rawVolume['id']; - final int dbSize = rawVolume['size_gigabytes'] * 1024 * 1024 * 1024; - final dbDropletIds = rawVolume['droplet_ids']; - final String dbName = rawVolume['name']; + 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: dbName, - sizeByte: dbSize, - serverId: dbDropletIds.isNotEmpty ? dbDropletIds[0] : null, - linuxDevice: 'scsi-0DO_Volume_$dbName', - uuid: dbId, + name: volumeName, + sizeByte: volumeSize, + serverId: volumeDropletIds.isNotEmpty ? volumeDropletIds[0] : null, + linuxDevice: 'scsi-0DO_Volume_$volumeName', + uuid: volumeId, ); volumes.add(volume); } @@ -178,24 +178,24 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { } Future getVolume(final String volumeUuid) async { - ServerVolume? neededVolume; + ServerVolume? requestedVolume; final List volumes = await getVolumes(); for (final volume in volumes) { if (volume.uuid == volumeUuid) { - neededVolume = volume; + requestedVolume = volume; } } - return neededVolume; + return requestedVolume; } @override Future deleteVolume(final ServerVolume volume) async { final Dio client = await getClient(); try { - await client.delete('/volumes/$volume.uuid'); + await client.delete('/volumes/${volume.uuid}'); } catch (e) { print(e); } finally { @@ -210,10 +210,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ) async { bool success = false; - final Response dbPostResponse; + final Response attachVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + attachVolumeResponse = await client.post( '/volumes/actions', data: { 'type': 'attach', @@ -222,7 +222,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'droplet_id': serverId, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + attachVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -236,10 +237,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { Future detachVolume(final ServerVolume volume) async { bool success = false; - final Response dbPostResponse; + final Response detachVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + detachVolumeResponse = await client.post( '/volumes/actions', data: { 'type': 'detach', @@ -248,7 +249,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'region': region, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + detachVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -265,10 +267,10 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ) async { bool success = false; - final Response dbPostResponse; + final Response resizeVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + resizeVolumeResponse = await client.post( '/volumes/actions', data: { 'type': 'resize', @@ -277,7 +279,8 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 'region': region, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + resizeVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -450,7 +453,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { return server.copyWith(startTime: DateTime.now()); } - /// Digital Ocean returns a map of lists of /proc/state values, + /// 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. /// @@ -500,63 +503,63 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { 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', - }, - ); + 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']; + 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', - }, - ); + 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']; + 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', - }, - ); + 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) { + 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; } @@ -604,7 +607,7 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi { ServerMetadataEntity( type: MetadataType.other, name: 'server.provider'.tr(), - value: appearanceProviderName, + value: displayProviderName, ), ]; } catch (e) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart index f8062286..57df7837 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart @@ -57,7 +57,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { String get infectProviderName => 'hetzner'; @override - String get appearanceProviderName => 'Hetzner'; + String get displayProviderName => 'Hetzner'; @override Future isApiTokenValid(final String token) async { @@ -102,12 +102,12 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { Future getPricePerGb() async { double? price; - final Response dbGetResponse; + final Response pricingResponse; final Dio client = await getClient(); 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']; price = double.parse(volumePrice); } catch (e) { @@ -128,31 +128,31 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { Future createVolume() async { ServerVolume? volume; - final Response dbCreateResponse; + final Response createVolumeResponse; final Dio client = await getClient(); try { - dbCreateResponse = await client.post( + createVolumeResponse = await client.post( '/volumes', data: { 'size': 10, - 'name': StringGenerators.dbStorageName(), + 'name': StringGenerators.storageName(), 'labels': {'labelkey': 'value'}, 'location': region, 'automount': false, 'format': 'ext4' }, ); - final dbId = dbCreateResponse.data['volume']['id']; - final dbSize = dbCreateResponse.data['volume']['size']; - final dbServer = dbCreateResponse.data['volume']['server']; - final dbName = dbCreateResponse.data['volume']['name']; - final dbDevice = dbCreateResponse.data['volume']['linux_device']; + final volumeId = createVolumeResponse.data['volume']['id']; + final volumeSize = createVolumeResponse.data['volume']['size']; + final volumeServer = createVolumeResponse.data['volume']['server']; + final volumeName = createVolumeResponse.data['volume']['name']; + final volumeDevice = createVolumeResponse.data['volume']['linux_device']; volume = ServerVolume( - id: dbId, - name: dbName, - sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, + id: volumeId, + name: volumeName, + sizeByte: volumeSize, + serverId: volumeServer, + linuxDevice: volumeDevice, ); } catch (e) { print(e); @@ -167,28 +167,28 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { Future> getVolumes({final String? status}) async { final List volumes = []; - final Response dbGetResponse; + final Response getVolumesResonse; final Dio client = await getClient(); try { - dbGetResponse = await client.get( + getVolumesResonse = await client.get( '/volumes', queryParameters: { 'status': status, }, ); - final List rawVolumes = dbGetResponse.data['volumes']; + final List rawVolumes = getVolumesResonse.data['volumes']; for (final rawVolume in rawVolumes) { - final int dbId = rawVolume['id']; - final int dbSize = rawVolume['size'] * 1024 * 1024 * 1024; - final dbServer = rawVolume['server']; - final String dbName = rawVolume['name']; - final dbDevice = rawVolume['linux_device']; + final int volumeId = rawVolume['id']; + final int volumeSize = rawVolume['size'] * 1024 * 1024 * 1024; + final volumeServer = rawVolume['server']; + final String volumeName = rawVolume['name']; + final volumeDevice = rawVolume['linux_device']; final volume = ServerVolume( - id: dbId, - name: dbName, - sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, + id: volumeId, + name: volumeName, + sizeByte: volumeSize, + serverId: volumeServer, + linuxDevice: volumeDevice, ); volumes.add(volume); } @@ -206,21 +206,21 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ) async { ServerVolume? volume; - final Response dbGetResponse; + final Response getVolumeResponse; final Dio client = await getClient(); try { - dbGetResponse = await client.get('/volumes/$volumeId'); - final int dbId = dbGetResponse.data['volume']['id']; - final int dbSize = dbGetResponse.data['volume']['size']; - final int dbServer = dbGetResponse.data['volume']['server']; - final String dbName = dbGetResponse.data['volume']['name']; - final dbDevice = dbGetResponse.data['volume']['linux_device']; + getVolumeResponse = await client.get('/volumes/$volumeId'); + final int responseVolumeId = getVolumeResponse.data['volume']['id']; + final int volumeSize = getVolumeResponse.data['volume']['size']; + final int volumeServer = getVolumeResponse.data['volume']['server']; + final String volumeName = getVolumeResponse.data['volume']['name']; + final volumeDevice = getVolumeResponse.data['volume']['linux_device']; volume = ServerVolume( - id: dbId, - name: dbName, - sizeByte: dbSize, - serverId: dbServer, - linuxDevice: dbDevice, + id: responseVolumeId, + name: volumeName, + sizeByte: volumeSize, + serverId: volumeServer, + linuxDevice: volumeDevice, ); } catch (e) { print(e); @@ -250,17 +250,18 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ) async { bool success = false; - final Response dbPostResponse; + final Response attachVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + attachVolumeResponse = await client.post( '/volumes/${volume.id}/actions/attach', data: { 'automount': true, 'server': serverId, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + attachVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -274,13 +275,14 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { Future detachVolume(final ServerVolume volume) async { bool success = false; - final Response dbPostResponse; + final Response detachVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + detachVolumeResponse = await client.post( '/volumes/${volume.id}/actions/detach', ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + detachVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -297,16 +299,17 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ) async { bool success = false; - final Response dbPostResponse; + final Response resizeVolumeResponse; final Dio client = await getClient(); try { - dbPostResponse = await client.post( + resizeVolumeResponse = await client.post( '/volumes/${volume.id}/actions/resize', data: { 'size': size.gibibyte, }, ); - success = dbPostResponse.data['action']['status'].toString() != 'error'; + success = + resizeVolumeResponse.data['action']['status'].toString() != 'error'; } catch (e) { print(e); } finally { @@ -334,7 +337,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { dnsApiToken: dnsApiToken, rootUser: rootUser, domainName: domainName, - dataBase: newVolume, + volume: newVolume, serverType: serverType, ); @@ -345,13 +348,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { required final String dnsApiToken, required final User rootUser, required final String domainName, - required final ServerVolume dataBase, + required final ServerVolume volume, required final String serverType, }) async { final Dio client = await getClient(); final String dbPassword = StringGenerators.dbPassword(); - final int dbId = dataBase.id; + final int volumeId = volume.id; final String apiToken = StringGenerators.apiToken(); final String hostname = getHostnameFromDomain(domainName); @@ -373,7 +376,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { 'server_type': serverType, 'start_after_create': false, 'image': 'ubuntu-20.04', - 'volumes': [dbId], + 'volumes': [volumeId], 'networks': [], 'user_data': userdataString, 'labels': {}, @@ -391,7 +394,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { id: serverCreateResponse.data['server']['id'], ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'], createTime: DateTime.now(), - volume: dataBase, + volume: volume, apiToken: apiToken, provider: ServerProvider.hetzner, ); @@ -407,7 +410,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { if (!success) { await Future.delayed(const Duration(seconds: 10)); - await deleteVolume(dataBase); + await deleteVolume(volume); } if (hetznerError != null) { @@ -621,7 +624,7 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi { ServerMetadataEntity( type: MetadataType.other, name: 'server.provider'.tr(), - value: appearanceProviderName, + value: displayProviderName, ), ]; } catch (e) { diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart index 21c6ddd0..a2eb71f3 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart @@ -54,5 +54,5 @@ abstract class ServerProviderApi extends ApiMap { /// Actual provider name to render on information page for user, /// for example 'Digital Ocean' for Digital Ocean - String get appearanceProviderName; + String get displayProviderName; } diff --git a/lib/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index faaf37b4..57a54762 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -1,4 +1,5 @@ import 'package:hive/hive.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; part 'server_details.g.dart'; @@ -82,5 +83,16 @@ enum ServerProvider { @HiveField(1) hetzner, @HiveField(2) - digitalOcean, + 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; + } + } } diff --git a/lib/logic/models/server_metadata.dart b/lib/logic/models/server_metadata.dart index 1a08abc0..0275a2ef 100644 --- a/lib/logic/models/server_metadata.dart +++ b/lib/logic/models/server_metadata.dart @@ -1,12 +1,20 @@ -enum MetadataType { - id, - status, - cpu, - ram, - cost, - location, +import 'package:flutter/material.dart'; - other, +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 { diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 245df021..487e1a25 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -10,7 +10,6 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; -import 'package:selfprivacy/logic/models/server_metadata.dart'; import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; diff --git a/lib/ui/pages/server_details/text_details.dart b/lib/ui/pages/server_details/text_details.dart index 2c06375f..5f447901 100644 --- a/lib/ui/pages/server_details/text_details.dart +++ b/lib/ui/pages/server_details/text_details.dart @@ -1,16 +1,6 @@ part of 'server_details_screen.dart'; class _TextDetails extends StatelessWidget { - final Map metadataToIcon = const { - MetadataType.id: Icons.numbers_outlined, - MetadataType.status: Icons.mode_standby_outlined, - MetadataType.cpu: Icons.memory_outlined, - MetadataType.ram: Icons.memory_outlined, - MetadataType.cost: Icons.euro_outlined, - MetadataType.location: Icons.location_on_outlined, - MetadataType.other: Icons.info_outlined, - }; - @override Widget build(final BuildContext context) { final details = context.watch().state; @@ -36,7 +26,7 @@ class _TextDetails extends StatelessWidget { ...details.metadata .map( (final metadata) => ListTileOnSurfaceVariant( - leadingIcon: metadataToIcon[metadata.type], + leadingIcon: metadata.type.icon, title: metadata.name, subtitle: metadata.value, ), diff --git a/lib/utils/password_generator.dart b/lib/utils/password_generator.dart index c8a6cdf0..a940bb19 100644 --- a/lib/utils/password_generator.dart +++ b/lib/utils/password_generator.dart @@ -101,7 +101,7 @@ class StringGenerators { hasSymbols: true, ); - static StringGeneratorFunction dbStorageName = () => getRandomString( + static StringGeneratorFunction storageName = () => getRandomString( 6, hasLowercaseLetters: true, hasUppercaseLetters: false,