Implement server selection pages
Co-authored-by: Inex Code <inex.code@selfprivacy.org>pull/90/head
parent
eaa1ba143c
commit
eddeac57d6
|
@ -300,6 +300,7 @@
|
||||||
"fallback_select_token_copy": "Copy of auth token from other version of the application.",
|
"fallback_select_token_copy": "Copy of auth token from other version of the application.",
|
||||||
"fallback_select_root_ssh": "Root SSH access to the server.",
|
"fallback_select_root_ssh": "Root SSH access to the server.",
|
||||||
"fallback_select_provider_console": "Access to the server console of my prodiver.",
|
"fallback_select_provider_console": "Access to the server console of my prodiver.",
|
||||||
|
"authorization_failed": "Couldn't log in with this key",
|
||||||
"fallback_select_provider_console_hint": "For example: Hetzner.",
|
"fallback_select_provider_console_hint": "For example: Hetzner.",
|
||||||
"hetzner_connected": "Connect to Hetzner",
|
"hetzner_connected": "Connect to Hetzner",
|
||||||
"hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:",
|
"hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:",
|
||||||
|
@ -307,7 +308,10 @@
|
||||||
"confirm_server": "Confirm server",
|
"confirm_server": "Confirm server",
|
||||||
"confirm_server_description": "Found your server! Confirm it is correct.",
|
"confirm_server_description": "Found your server! Confirm it is correct.",
|
||||||
"confirm_server_accept": "Yes! That's it",
|
"confirm_server_accept": "Yes! That's it",
|
||||||
"confirm_server_decline": "Choose a different server"
|
"confirm_server_decline": "Choose a different server",
|
||||||
|
"choose_server": "Choose your server",
|
||||||
|
"choose_server_description": "We couldn't figure out which server your are trying to connect to.",
|
||||||
|
"no_servers": "There is no available servers on your account."
|
||||||
|
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
|
|
|
@ -302,7 +302,18 @@
|
||||||
"fallback_select_token_copy": "Копия токена авторизации из другой версии приложения.",
|
"fallback_select_token_copy": "Копия токена авторизации из другой версии приложения.",
|
||||||
"fallback_select_root_ssh": "Root доступ к серверу по SSH.",
|
"fallback_select_root_ssh": "Root доступ к серверу по SSH.",
|
||||||
"fallback_select_provider_console": "Доступ к консоли хостинга.",
|
"fallback_select_provider_console": "Доступ к консоли хостинга.",
|
||||||
"fallback_select_provider_console_hint": "Например, Hetzner."
|
"authorization_failed": "Не удалось войти с этим ключом",
|
||||||
|
"fallback_select_provider_console_hint": "Например, Hetzner.",
|
||||||
|
"hetzner_connected": "Подключение к Hetzner",
|
||||||
|
"hetzner_connected_description": "Связь с сервером установлена. Введите токен Hetzner с доступом к {}:",
|
||||||
|
"hetzner_connected_placeholder": "Hetzner токен",
|
||||||
|
"confirm_server": "Подтвердите сервер",
|
||||||
|
"confirm_server_description": "Нашли сервер! Подтвердите, что это он:",
|
||||||
|
"confirm_server_accept": "Да, это он",
|
||||||
|
"confirm_server_decline": "Выбрать другой сервер",
|
||||||
|
"choose_server": "Выберите сервер",
|
||||||
|
"choose_server_description": "Не удалось определить, с каким сервером вы устанавливаете связь.",
|
||||||
|
"no_servers": "На вашем аккаунте нет доступных серверов."
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"_comment": "messages in modals",
|
"_comment": "messages in modals",
|
||||||
|
|
|
@ -19,6 +19,9 @@ class HiveConfig {
|
||||||
Hive.registerAdapter(BackblazeBucketAdapter());
|
Hive.registerAdapter(BackblazeBucketAdapter());
|
||||||
Hive.registerAdapter(ServerVolumeAdapter());
|
Hive.registerAdapter(ServerVolumeAdapter());
|
||||||
|
|
||||||
|
Hive.registerAdapter(DnsProviderAdapter());
|
||||||
|
Hive.registerAdapter(ServerProviderAdapter());
|
||||||
|
|
||||||
await Hive.openBox(BNames.appSettingsBox);
|
await Hive.openBox(BNames.appSettingsBox);
|
||||||
|
|
||||||
var cipher = HiveAesCipher(
|
var cipher = HiveAesCipher(
|
||||||
|
|
|
@ -6,8 +6,24 @@ import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
|
|
||||||
|
class DomainNotFoundException implements Exception {
|
||||||
|
final String message;
|
||||||
|
DomainNotFoundException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
class CloudflareApi extends ApiMap {
|
class CloudflareApi extends ApiMap {
|
||||||
CloudflareApi({this.hasLogger = false, this.isWithToken = true});
|
@override
|
||||||
|
final bool hasLogger;
|
||||||
|
@override
|
||||||
|
final bool isWithToken;
|
||||||
|
|
||||||
|
final String? customToken;
|
||||||
|
|
||||||
|
CloudflareApi({
|
||||||
|
this.hasLogger = false,
|
||||||
|
this.isWithToken = true,
|
||||||
|
this.customToken,
|
||||||
|
});
|
||||||
|
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
var options = BaseOptions(baseUrl: rootAddress);
|
var options = BaseOptions(baseUrl: rootAddress);
|
||||||
|
@ -17,6 +33,10 @@ class CloudflareApi extends ApiMap {
|
||||||
options.headers = {'Authorization': 'Bearer $token'};
|
options.headers = {'Authorization': 'Bearer $token'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (customToken != null) {
|
||||||
|
options.headers = {'Authorization': 'Bearer $customToken'};
|
||||||
|
}
|
||||||
|
|
||||||
if (validateStatus != null) {
|
if (validateStatus != null) {
|
||||||
options.validateStatus = validateStatus!;
|
options.validateStatus = validateStatus!;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +78,11 @@ class CloudflareApi extends ApiMap {
|
||||||
|
|
||||||
close(client);
|
close(client);
|
||||||
|
|
||||||
return response.data['result'][0]['id'];
|
if (response.data['result'].isEmpty) {
|
||||||
|
throw DomainNotFoundException('No domains found');
|
||||||
|
} else {
|
||||||
|
return response.data['result'][0]['id'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeSimilarRecords({
|
Future<void> removeSimilarRecords({
|
||||||
|
@ -209,7 +233,7 @@ class CloudflareApi extends ApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> domainList() async {
|
Future<List<String>> domainList() async {
|
||||||
var url = '$rootAddress/zones?per_page=50';
|
var url = '$rootAddress/zones';
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
|
|
||||||
var response = await client.get(
|
var response = await client.get(
|
||||||
|
@ -222,10 +246,4 @@ class CloudflareApi extends ApiMap {
|
||||||
.map<String>((el) => el['name'] as String)
|
.map<String>((el) => el['name'] as String)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
final bool hasLogger;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final bool isWithToken;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,9 @@ class ApiResponse<D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerApi extends ApiMap {
|
class ServerApi extends ApiMap {
|
||||||
|
@override
|
||||||
bool hasLogger;
|
bool hasLogger;
|
||||||
|
@override
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
String? overrideDomain;
|
String? overrideDomain;
|
||||||
String? customToken;
|
String? customToken;
|
||||||
|
@ -734,7 +736,8 @@ class ServerApi extends ApiMap {
|
||||||
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
final int code = response.statusCode ?? HttpStatus.internalServerError;
|
||||||
|
|
||||||
return ApiResponse(
|
return ApiResponse(
|
||||||
statusCode: code, data: response.data != null ? response.data : '');
|
statusCode: code,
|
||||||
|
data: response.data["token"] != null ? response.data["token"] : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ApiResponse<String>> createDeviceToken() async {
|
Future<ApiResponse<String>> createDeviceToken() async {
|
||||||
|
|
|
@ -52,7 +52,7 @@ class FieldCubitFactory {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldCubit<String> createServerDomainField() {
|
FieldCubit<String> createRequiredStringField() {
|
||||||
return FieldCubit(
|
return FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
|
|
|
@ -5,21 +5,19 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
|
|
||||||
class RecoveryDeviceFormCubit extends FormCubit {
|
class RecoveryDeviceFormCubit extends FormCubit {
|
||||||
RecoveryDeviceFormCubit(
|
RecoveryDeviceFormCubit(this.installationCubit,
|
||||||
this.initializingCubit, final FieldCubitFactory fieldFactory) {
|
final FieldCubitFactory fieldFactory, this.recoveryMethod) {
|
||||||
tokenField = fieldFactory.createServerDomainField();
|
tokenField = fieldFactory.createRequiredStringField();
|
||||||
|
|
||||||
super.addFields([tokenField]);
|
super.addFields([tokenField]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> onSubmit() async {
|
FutureOr<void> onSubmit() async {
|
||||||
// initializingCubit.setDomain(ServerDomain(
|
installationCubit.tryToRecover(tokenField.state.value, recoveryMethod);
|
||||||
// domainName: serverDomainField.state.value,
|
|
||||||
// provider: DnsProvider.Unknown,
|
|
||||||
// zoneId: ""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final ServerInstallationCubit initializingCubit;
|
final ServerInstallationCubit installationCubit;
|
||||||
late final FieldCubit<String> tokenField;
|
late final FieldCubit<String> tokenField;
|
||||||
|
final ServerRecoveryMethods recoveryMethod;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,22 +5,19 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
|
||||||
|
|
||||||
class RecoveryDomainFormCubit extends FormCubit {
|
class RecoveryDomainFormCubit extends FormCubit {
|
||||||
RecoveryDomainFormCubit(
|
RecoveryDomainFormCubit(
|
||||||
this.initializingCubit, final FieldCubitFactory fieldFactory) {
|
this.initializingCubit, final FieldCubitFactory fieldFactory) {
|
||||||
serverDomainField = fieldFactory.createServerDomainField();
|
serverDomainField = fieldFactory.createRequiredStringField();
|
||||||
|
|
||||||
super.addFields([serverDomainField]);
|
super.addFields([serverDomainField]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> onSubmit() async {
|
FutureOr<void> onSubmit() async {
|
||||||
initializingCubit.setDomain(ServerDomain(
|
initializingCubit
|
||||||
domainName: serverDomainField.state.value,
|
.submitDomainForAccessRecovery(serverDomainField.state.value);
|
||||||
provider: DnsProvider.Unknown,
|
|
||||||
zoneId: ""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
|
||||||
import '../server_installation/server_installation_repository.dart';
|
import '../server_installation/server_installation_repository.dart';
|
||||||
|
|
||||||
|
@ -53,6 +56,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
hetznerKey: hetznerKey,
|
hetznerKey: hetznerKey,
|
||||||
currentStep: RecoveryStep.ServerSelection,
|
currentStep: RecoveryStep.ServerSelection,
|
||||||
));
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit((state as ServerInstallationNotFinished)
|
emit((state as ServerInstallationNotFinished)
|
||||||
|
@ -269,6 +273,8 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
final recoveryCapabilities =
|
final recoveryCapabilities =
|
||||||
await repository.getRecoveryCapabilities(serverDomain);
|
await repository.getRecoveryCapabilities(serverDomain);
|
||||||
|
|
||||||
|
await repository.saveDomain(serverDomain);
|
||||||
|
|
||||||
emit(ServerInstallationRecovery(
|
emit(ServerInstallationRecovery(
|
||||||
serverDomain: serverDomain,
|
serverDomain: serverDomain,
|
||||||
recoveryCapabilities: recoveryCapabilities,
|
recoveryCapabilities: recoveryCapabilities,
|
||||||
|
@ -302,13 +308,18 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
serverDomain,
|
serverDomain,
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
|
await repository.saveServerDetails(serverDetails);
|
||||||
emit(dataState.copyWith(
|
emit(dataState.copyWith(
|
||||||
serverDetails: serverDetails,
|
serverDetails: serverDetails,
|
||||||
currentStep: RecoveryStep.HetznerToken,
|
currentStep: RecoveryStep.HetznerToken,
|
||||||
));
|
));
|
||||||
} on ServerAuthorizationException {
|
} on ServerAuthorizationException {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('recovering.authorization_failed'.tr());
|
||||||
return;
|
return;
|
||||||
} on IpNotFoundException {
|
} on IpNotFoundException {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('recovering.domain_recover_error'.tr());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,6 +328,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
final dataState = this.state as ServerInstallationRecovery;
|
final dataState = this.state as ServerInstallationRecovery;
|
||||||
switch (dataState.currentStep) {
|
switch (dataState.currentStep) {
|
||||||
case RecoveryStep.Selecting:
|
case RecoveryStep.Selecting:
|
||||||
|
repository.deleteDomain();
|
||||||
emit(ServerInstallationEmpty());
|
emit(ServerInstallationEmpty());
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.RecoveryKey:
|
case RecoveryStep.RecoveryKey:
|
||||||
|
@ -327,6 +339,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.ServerSelection:
|
case RecoveryStep.ServerSelection:
|
||||||
|
repository.deleteHetznerKey();
|
||||||
emit(dataState.copyWith(
|
emit(dataState.copyWith(
|
||||||
currentStep: RecoveryStep.HetznerToken,
|
currentStep: RecoveryStep.HetznerToken,
|
||||||
));
|
));
|
||||||
|
@ -358,6 +371,72 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<ServerBasicInfoWithValidators>>
|
||||||
|
getServersOnHetznerAccount() async {
|
||||||
|
final dataState = this.state as ServerInstallationRecovery;
|
||||||
|
final servers = await repository.getServersOnHetznerAccount();
|
||||||
|
final validated = servers
|
||||||
|
.map((server) => ServerBasicInfoWithValidators.fromServerBasicInfo(
|
||||||
|
serverBasicInfo: server,
|
||||||
|
isIpValid: server.ip == dataState.serverDetails?.ip4,
|
||||||
|
isReverseDnsValid:
|
||||||
|
server.reverseDns == dataState.serverDomain?.domainName,
|
||||||
|
));
|
||||||
|
return validated.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setServerId(ServerBasicInfo server) async {
|
||||||
|
final dataState = this.state as ServerInstallationRecovery;
|
||||||
|
final serverDomain = dataState.serverDomain;
|
||||||
|
if (serverDomain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final serverDetails = ServerHostingDetails(
|
||||||
|
ip4: server.ip,
|
||||||
|
id: server.id,
|
||||||
|
createTime: server.created,
|
||||||
|
volume: ServerVolume(
|
||||||
|
id: server.volumeId,
|
||||||
|
name: "recovered_volume",
|
||||||
|
),
|
||||||
|
apiToken: dataState.serverDetails!.apiToken,
|
||||||
|
provider: ServerProvider.Hetzner,
|
||||||
|
);
|
||||||
|
await repository.saveDomain(serverDomain);
|
||||||
|
await repository.saveServerDetails(serverDetails);
|
||||||
|
emit(dataState.copyWith(
|
||||||
|
serverDetails: serverDetails,
|
||||||
|
currentStep: RecoveryStep.CloudflareToken,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<void> setAndValidateCloudflareToken(String token) async {
|
||||||
|
// final dataState = this.state as ServerInstallationRecovery;
|
||||||
|
// final serverDomain = dataState.serverDomain;
|
||||||
|
// if (serverDomain == null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// final domainId = await repository.getDomainId(serverDomain.domainName);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(Change<ServerInstallationState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
print('================================');
|
||||||
|
print('ServerInstallationState changed!');
|
||||||
|
print('Current type: ${change.nextState.runtimeType}');
|
||||||
|
print('Hetzner key: ${change.nextState.hetznerKey}');
|
||||||
|
print('Cloudflare key: ${change.nextState.cloudFlareKey}');
|
||||||
|
print('Domain: ${change.nextState.serverDomain}');
|
||||||
|
print('BackblazeCredential: ${change.nextState.backblazeCredential}');
|
||||||
|
if (change.nextState is ServerInstallationRecovery) {
|
||||||
|
print(
|
||||||
|
'Recovery Step: ${(change.nextState as ServerInstallationRecovery).currentStep}');
|
||||||
|
print(
|
||||||
|
'Recovery Capabilities: ${(change.nextState as ServerInstallationRecovery).recoveryCapabilities}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void clearAppConfig() {
|
void clearAppConfig() {
|
||||||
closeTimer();
|
closeTimer();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/device_token.dart';
|
import 'package:selfprivacy/logic/models/json/device_token.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/message.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.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_alert/brand_alert.dart';
|
||||||
|
|
||||||
|
@ -100,10 +101,13 @@ class ServerInstallationRepository {
|
||||||
) {
|
) {
|
||||||
if (serverDetails != null) {
|
if (serverDetails != null) {
|
||||||
if (hetznerToken != null) {
|
if (hetznerToken != null) {
|
||||||
if (cloudflareToken != null) {
|
if (serverDetails.provider != ServerProvider.Unknown) {
|
||||||
return RecoveryStep.BackblazeToken;
|
if (serverDomain.provider != DnsProvider.Unknown) {
|
||||||
|
return RecoveryStep.BackblazeToken;
|
||||||
|
}
|
||||||
|
return RecoveryStep.CloudflareToken;
|
||||||
}
|
}
|
||||||
return RecoveryStep.CloudflareToken;
|
return RecoveryStep.ServerSelection;
|
||||||
}
|
}
|
||||||
return RecoveryStep.HetznerToken;
|
return RecoveryStep.HetznerToken;
|
||||||
}
|
}
|
||||||
|
@ -123,6 +127,20 @@ class ServerInstallationRepository {
|
||||||
return serverDetails;
|
return serverDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getDomainId(String token, String domain) async {
|
||||||
|
var cloudflareApi = CloudflareApi(
|
||||||
|
isWithToken: false,
|
||||||
|
customToken: token,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final domainId = await cloudflareApi.getZoneId(domain);
|
||||||
|
return domainId;
|
||||||
|
} on DomainNotFoundException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, bool>> isDnsAddressesMatch(String? domainName, String? ip4,
|
Future<Map<String, bool>> isDnsAddressesMatch(String? domainName, String? ip4,
|
||||||
Map<String, bool>? skippedMatches) async {
|
Map<String, bool>? skippedMatches) async {
|
||||||
var addresses = <String>[
|
var addresses = <String>[
|
||||||
|
@ -467,6 +485,21 @@ class ServerInstallationRepository {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<ServerBasicInfo>> getServersOnHetznerAccount() async {
|
||||||
|
var hetznerApi = HetznerApi();
|
||||||
|
final servers = await hetznerApi.getServers();
|
||||||
|
return servers
|
||||||
|
.map((server) => ServerBasicInfo(
|
||||||
|
id: server.id,
|
||||||
|
name: server.name,
|
||||||
|
ip: server.publicNet.ipv4.ip,
|
||||||
|
reverseDns: server.publicNet.ipv4.reverseDns,
|
||||||
|
created: server.created,
|
||||||
|
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveServerDetails(ServerHostingDetails serverDetails) async {
|
Future<void> saveServerDetails(ServerHostingDetails serverDetails) async {
|
||||||
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
|
||||||
}
|
}
|
||||||
|
@ -476,6 +509,11 @@ class ServerInstallationRepository {
|
||||||
await getIt<ApiConfigModel>().storeHetznerKey(key);
|
await getIt<ApiConfigModel>().storeHetznerKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteHetznerKey() async {
|
||||||
|
await box.delete(BNames.hetznerKey);
|
||||||
|
getIt<ApiConfigModel>().init();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveBackblazeKey(BackblazeCredential backblazeCredential) async {
|
Future<void> saveBackblazeKey(BackblazeCredential backblazeCredential) async {
|
||||||
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
|
||||||
}
|
}
|
||||||
|
@ -488,6 +526,11 @@ class ServerInstallationRepository {
|
||||||
await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
|
await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteDomain() async {
|
||||||
|
await box.delete(BNames.serverDomain);
|
||||||
|
getIt<ApiConfigModel>().init();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveIsServerStarted(bool value) async {
|
Future<void> saveIsServerStarted(bool value) async {
|
||||||
await box.put(BNames.isServerStarted, value);
|
await box.put(BNames.isServerStarted, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ class HetznerServerInfo {
|
||||||
final String name;
|
final String name;
|
||||||
final ServerStatus status;
|
final ServerStatus status;
|
||||||
final DateTime created;
|
final DateTime created;
|
||||||
|
final List<int> volumes;
|
||||||
|
|
||||||
@JsonKey(name: 'server_type')
|
@JsonKey(name: 'server_type')
|
||||||
final HetznerServerTypeInfo serverType;
|
final HetznerServerTypeInfo serverType;
|
||||||
|
@ -32,17 +33,18 @@ class HetznerServerInfo {
|
||||||
this.serverType,
|
this.serverType,
|
||||||
this.location,
|
this.location,
|
||||||
this.publicNet,
|
this.publicNet,
|
||||||
|
this.volumes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class HetznerPublicNetInfo {
|
class HetznerPublicNetInfo {
|
||||||
final HetznerIp4 ip4;
|
final HetznerIp4 ipv4;
|
||||||
|
|
||||||
static HetznerPublicNetInfo fromJson(Map<String, dynamic> json) =>
|
static HetznerPublicNetInfo fromJson(Map<String, dynamic> json) =>
|
||||||
_$HetznerPublicNetInfoFromJson(json);
|
_$HetznerPublicNetInfoFromJson(json);
|
||||||
|
|
||||||
HetznerPublicNetInfo(this.ip4);
|
HetznerPublicNetInfo(this.ipv4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
|
@ -16,6 +16,7 @@ HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||||
json['server_type'] as Map<String, dynamic>),
|
json['server_type'] as Map<String, dynamic>),
|
||||||
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
||||||
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
||||||
|
(json['volumes'] as List<dynamic>).map((e) => e as int).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const _$ServerStatusEnumMap = {
|
const _$ServerStatusEnumMap = {
|
||||||
|
@ -33,7 +34,7 @@ const _$ServerStatusEnumMap = {
|
||||||
HetznerPublicNetInfo _$HetznerPublicNetInfoFromJson(
|
HetznerPublicNetInfo _$HetznerPublicNetInfoFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
HetznerPublicNetInfo(
|
HetznerPublicNetInfo(
|
||||||
HetznerIp4.fromJson(json['ip4'] as Map<String, dynamic>),
|
HetznerIp4.fromJson(json['ipv4'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
class ServerBasicInfo {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String reverseDns;
|
||||||
|
final String ip;
|
||||||
|
final DateTime created;
|
||||||
|
final int volumeId;
|
||||||
|
|
||||||
|
ServerBasicInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.reverseDns,
|
||||||
|
required this.ip,
|
||||||
|
required this.created,
|
||||||
|
required this.volumeId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerBasicInfoWithValidators extends ServerBasicInfo {
|
||||||
|
final bool isIpValid;
|
||||||
|
final bool isReverseDnsValid;
|
||||||
|
|
||||||
|
ServerBasicInfoWithValidators({
|
||||||
|
required int id,
|
||||||
|
required String name,
|
||||||
|
required String reverseDns,
|
||||||
|
required String ip,
|
||||||
|
required DateTime created,
|
||||||
|
required int volumeId,
|
||||||
|
required this.isIpValid,
|
||||||
|
required this.isReverseDnsValid,
|
||||||
|
}) : super(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
reverseDns: reverseDns,
|
||||||
|
ip: ip,
|
||||||
|
created: created,
|
||||||
|
volumeId: volumeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
ServerBasicInfoWithValidators.fromServerBasicInfo({
|
||||||
|
required ServerBasicInfo serverBasicInfo,
|
||||||
|
required isIpValid,
|
||||||
|
required isReverseDnsValid,
|
||||||
|
}) : this(
|
||||||
|
id: serverBasicInfo.id,
|
||||||
|
name: serverBasicInfo.name,
|
||||||
|
reverseDns: serverBasicInfo.reverseDns,
|
||||||
|
ip: serverBasicInfo.ip,
|
||||||
|
created: serverBasicInfo.created,
|
||||||
|
volumeId: serverBasicInfo.volumeId,
|
||||||
|
isIpValid: isIpValid,
|
||||||
|
isReverseDnsValid: isReverseDnsValid,
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
||||||
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
import 'package:selfprivacy/ui/pages/rootRoute.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_routing.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
@ -25,103 +27,105 @@ class InitializingPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var cubit = context.watch<ServerInstallationCubit>();
|
var cubit = context.watch<ServerInstallationCubit>();
|
||||||
var actualInitializingPage = [
|
|
||||||
() => _stepHetzner(cubit),
|
|
||||||
() => _stepCloudflare(cubit),
|
|
||||||
() => _stepBackblaze(cubit),
|
|
||||||
() => _stepDomain(cubit),
|
|
||||||
() => _stepUser(cubit),
|
|
||||||
() => _stepServer(cubit),
|
|
||||||
() => _stepCheck(cubit),
|
|
||||||
() => _stepCheck(cubit),
|
|
||||||
() => _stepCheck(cubit),
|
|
||||||
() => Container(child: Center(child: Text('initializing.finish'.tr())))
|
|
||||||
][cubit.state.progress.index]();
|
|
||||||
|
|
||||||
if (cubit is ServerInstallationRecovery) {
|
if (cubit.state is ServerInstallationRecovery) {
|
||||||
return RecoveryRouting();
|
return RecoveryRouting();
|
||||||
}
|
} else {
|
||||||
return BlocListener<ServerInstallationCubit, ServerInstallationState>(
|
var actualInitializingPage = [
|
||||||
listener: (context, state) {
|
() => _stepHetzner(cubit),
|
||||||
if (cubit.state is ServerInstallationFinished) {
|
() => _stepCloudflare(cubit),
|
||||||
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
() => _stepBackblaze(cubit),
|
||||||
}
|
() => _stepDomain(cubit),
|
||||||
},
|
() => _stepUser(cubit),
|
||||||
child: SafeArea(
|
() => _stepServer(cubit),
|
||||||
child: Scaffold(
|
() => _stepCheck(cubit),
|
||||||
body: SingleChildScrollView(
|
() => _stepCheck(cubit),
|
||||||
child: Column(
|
() => _stepCheck(cubit),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
() => Container(child: Center(child: Text('initializing.finish'.tr())))
|
||||||
children: [
|
][cubit.state.progress.index]();
|
||||||
Padding(
|
|
||||||
padding: paddingH15V0.copyWith(top: 10, bottom: 10),
|
return BlocListener<ServerInstallationCubit, ServerInstallationState>(
|
||||||
child: cubit.state.isFullyInitilized
|
listener: (context, state) {
|
||||||
? SizedBox(
|
if (cubit.state is ServerInstallationFinished) {
|
||||||
height: 80,
|
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
||||||
)
|
}
|
||||||
: ProgressBar(
|
},
|
||||||
steps: [
|
child: SafeArea(
|
||||||
'Hetzner',
|
child: Scaffold(
|
||||||
'CloudFlare',
|
body: SingleChildScrollView(
|
||||||
'Backblaze',
|
child: Column(
|
||||||
'Domain',
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
'User',
|
children: [
|
||||||
'Server',
|
Padding(
|
||||||
'✅ Check',
|
padding: paddingH15V0.copyWith(top: 10, bottom: 10),
|
||||||
],
|
child: cubit.state.isFullyInitilized
|
||||||
activeIndex: cubit.state.porgressBar,
|
? SizedBox(
|
||||||
),
|
height: 80,
|
||||||
),
|
)
|
||||||
_addCard(
|
: ProgressBar(
|
||||||
AnimatedSwitcher(
|
steps: [
|
||||||
duration: Duration(milliseconds: 300),
|
'Hetzner',
|
||||||
child: actualInitializingPage,
|
'CloudFlare',
|
||||||
),
|
'Backblaze',
|
||||||
),
|
'Domain',
|
||||||
ConstrainedBox(
|
'User',
|
||||||
constraints: BoxConstraints(
|
'Server',
|
||||||
minHeight: MediaQuery.of(context).size.height -
|
'✅ Check',
|
||||||
MediaQuery.of(context).padding.top -
|
],
|
||||||
MediaQuery.of(context).padding.bottom -
|
activeIndex: cubit.state.porgressBar,
|
||||||
566,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: BrandButton.text(
|
|
||||||
title: cubit.state is ServerInstallationFinished
|
|
||||||
? 'basis.close'.tr()
|
|
||||||
: 'basis.later'.tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
|
||||||
materialRoute(RootPage()),
|
|
||||||
(predicate) => false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(cubit.state is ServerInstallationFinished)
|
_addCard(
|
||||||
? Container()
|
AnimatedSwitcher(
|
||||||
: Container(
|
duration: Duration(milliseconds: 300),
|
||||||
alignment: Alignment.center,
|
child: actualInitializingPage,
|
||||||
child: BrandButton.text(
|
),
|
||||||
title: 'basis.connect_to_existing'.tr(),
|
),
|
||||||
onPressed: () {
|
ConstrainedBox(
|
||||||
Navigator.of(context).push(
|
constraints: BoxConstraints(
|
||||||
materialRoute(RecoveryMethodSelect()));
|
minHeight: MediaQuery.of(context).size.height -
|
||||||
},
|
MediaQuery.of(context).padding.top -
|
||||||
),
|
MediaQuery.of(context).padding.bottom -
|
||||||
)
|
566,
|
||||||
],
|
),
|
||||||
)),
|
child: Column(
|
||||||
],
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.text(
|
||||||
|
title: cubit.state is ServerInstallationFinished
|
||||||
|
? 'basis.close'.tr()
|
||||||
|
: 'basis.later'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
materialRoute(RootPage()),
|
||||||
|
(predicate) => false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(cubit.state is ServerInstallationFinished)
|
||||||
|
? Container()
|
||||||
|
: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.text(
|
||||||
|
title: 'basis.connect_to_existing'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
materialRoute(RecoveryRouting()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepHetzner(ServerInstallationCubit serverInstallationCubit) {
|
Widget _stepHetzner(ServerInstallationCubit serverInstallationCubit) {
|
||||||
|
|
|
@ -35,8 +35,11 @@ class RecoverByNewDeviceKeyInput extends StatelessWidget {
|
||||||
var appConfig = context.watch<ServerInstallationCubit>();
|
var appConfig = context.watch<ServerInstallationCubit>();
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => RecoveryDeviceFormCubit(
|
||||||
RecoveryDeviceFormCubit(appConfig, FieldCubitFactory(context)),
|
appConfig,
|
||||||
|
FieldCubitFactory(context),
|
||||||
|
ServerRecoveryMethods.newDeviceKey,
|
||||||
|
),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_f
|
||||||
import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart';
|
import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
|
@ -44,8 +43,11 @@ class RecoverByOldToken extends StatelessWidget {
|
||||||
var appConfig = context.watch<ServerInstallationCubit>();
|
var appConfig = context.watch<ServerInstallationCubit>();
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => RecoveryDeviceFormCubit(
|
||||||
RecoveryDeviceFormCubit(appConfig, FieldCubitFactory(context)),
|
appConfig,
|
||||||
|
FieldCubitFactory(context),
|
||||||
|
ServerRecoveryMethods.oldToken,
|
||||||
|
),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
||||||
|
|
|
@ -13,8 +13,11 @@ class RecoverByRecoveryKey extends StatelessWidget {
|
||||||
var appConfig = context.watch<ServerInstallationCubit>();
|
var appConfig = context.watch<ServerInstallationCubit>();
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => RecoveryDeviceFormCubit(
|
||||||
RecoveryDeviceFormCubit(appConfig, FieldCubitFactory(context)),
|
appConfig,
|
||||||
|
FieldCubitFactory(context),
|
||||||
|
ServerRecoveryMethods.recoveryKey,
|
||||||
|
),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
var formCubitState = context.watch<RecoveryDeviceFormCubit>().state;
|
||||||
|
|
|
@ -1,62 +1,188 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart';
|
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart';
|
import 'package:selfprivacy/ui/components/brand_button/FilledButton.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_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||||
|
|
||||||
class RecoveryConfirmServer extends StatelessWidget {
|
class RecoveryConfirmServer extends StatefulWidget {
|
||||||
|
const RecoveryConfirmServer({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RecoveryConfirmServerState createState() => _RecoveryConfirmServerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
||||||
|
bool _isExtended = false;
|
||||||
|
|
||||||
|
bool _isServerFound(List<ServerBasicInfoWithValidators> servers) {
|
||||||
|
return servers
|
||||||
|
.where((server) => server.isIpValid && server.isReverseDnsValid)
|
||||||
|
.length ==
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerBasicInfoWithValidators _firstValidServer(
|
||||||
|
List<ServerBasicInfoWithValidators> servers) {
|
||||||
|
return servers
|
||||||
|
.where((server) => server.isIpValid && server.isReverseDnsValid)
|
||||||
|
.first;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var serverInstallation = context.watch<ServerInstallationCubit>();
|
return BrandHeroScreen(
|
||||||
|
heroTitle: _isExtended
|
||||||
return Builder(
|
? "recovering.choose_server".tr()
|
||||||
builder: (context) {
|
: "recovering.confirm_server".tr(),
|
||||||
var formCubitState = context.watch<RecoveryDomainFormCubit>().state;
|
heroSubtitle: _isExtended
|
||||||
|
? "recovering.choose_server_description".tr()
|
||||||
return BlocListener<ServerInstallationCubit, ServerInstallationState>(
|
: "recovering.confirm_server_description".tr(),
|
||||||
listener: (context, state) {
|
hasBackButton: true,
|
||||||
if (state is ServerInstallationRecovery) {
|
hasFlashButton: false,
|
||||||
if (state.currentStep == RecoveryStep.Selecting) {
|
children: [
|
||||||
if (state.recoveryCapabilities ==
|
FutureBuilder<List<ServerBasicInfoWithValidators>>(
|
||||||
ServerRecoveryCapabilities.none) {
|
future: context
|
||||||
context
|
.read<ServerInstallationCubit>()
|
||||||
.read<RecoveryDomainFormCubit>()
|
.getServersOnHetznerAccount(),
|
||||||
.setCustomError("recovering.domain_recover_error".tr());
|
builder: (context, snapshot) {
|
||||||
}
|
if (snapshot.hasData) {
|
||||||
}
|
final servers = snapshot.data;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if (servers != null && servers.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
if (servers.length == 1 ||
|
||||||
|
(!_isExtended && _isServerFound(servers)))
|
||||||
|
_ConfirmServer(context, _firstValidServer(servers),
|
||||||
|
servers.length > 1),
|
||||||
|
if (servers.length > 1 &&
|
||||||
|
(_isExtended || !_isServerFound(servers)))
|
||||||
|
_ChooseServer(context, servers),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (servers?.isEmpty ?? true)
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"recovering.no_servers".tr(),
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BrandHeroScreen(
|
)
|
||||||
heroTitle: "recovering.recovery_main_header".tr(),
|
],
|
||||||
heroSubtitle: "recovering.domain_recovery_description".tr(),
|
|
||||||
hasBackButton: true,
|
|
||||||
hasFlashButton: false,
|
|
||||||
onBackButtonPressed:
|
|
||||||
serverInstallation is ServerInstallationRecovery
|
|
||||||
? () => serverInstallation.clearAppConfig()
|
|
||||||
: null,
|
|
||||||
children: [
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit:
|
|
||||||
context.read<RecoveryDomainFormCubit>().serverDomainField,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
labelText: "recovering.domain_recover_placeholder".tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
FilledButton(
|
|
||||||
title: "more.continue".tr(),
|
|
||||||
onPressed: formCubitState.isSubmitting
|
|
||||||
? null
|
|
||||||
: () => context.read<RecoveryDomainFormCubit>().trySubmit(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _ConfirmServer(
|
||||||
|
BuildContext context,
|
||||||
|
ServerBasicInfoWithValidators server,
|
||||||
|
bool showMoreServersButton,
|
||||||
|
) {
|
||||||
|
return Container(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_ServerCard(
|
||||||
|
context: context,
|
||||||
|
server: server,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
FilledButton(
|
||||||
|
title: "recovering.confirm_server_accept".tr(),
|
||||||
|
onPressed: () => _showConfirmationDialog(context, server),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
if (showMoreServersButton)
|
||||||
|
BrandButton.text(
|
||||||
|
title: 'recovering.confirm_server_decline'.tr(),
|
||||||
|
onPressed: () => setState(() => _isExtended = true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _ChooseServer(
|
||||||
|
BuildContext context, List<ServerBasicInfoWithValidators> servers) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
for (final server in servers)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: _ServerCard(
|
||||||
|
context: context,
|
||||||
|
server: server,
|
||||||
|
onTap: () => _showConfirmationDialog(context, server),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _ServerCard(
|
||||||
|
{required BuildContext context,
|
||||||
|
required ServerBasicInfoWithValidators server,
|
||||||
|
VoidCallback? onTap}) {
|
||||||
|
return BrandCards.filled(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: onTap,
|
||||||
|
title: Text(server.name),
|
||||||
|
leading: Icon(Icons.dns),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(server.isReverseDnsValid ? Icons.check : Icons.close),
|
||||||
|
Text('rDNS: ${server.reverseDns}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(server.isIpValid ? Icons.check : Icons.close),
|
||||||
|
Text('IP: ${server.ip}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showConfirmationDialog(
|
||||||
|
BuildContext context, ServerBasicInfoWithValidators server) =>
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('ssh.delete'.tr()),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Text("WOW DIALOGUE TEXT WOW :)"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text('basis.cancel'.tr()),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ class RecoveryHetznerConnected extends StatelessWidget {
|
||||||
|
|
||||||
return BrandHeroScreen(
|
return BrandHeroScreen(
|
||||||
heroTitle: "recovering.hetzner_connected".tr(),
|
heroTitle: "recovering.hetzner_connected".tr(),
|
||||||
heroSubtitle: "recovering.hetzner_connected_description".tr(),
|
heroSubtitle: "recovering.hetzner_connected_description".tr(args: [
|
||||||
|
appConfig.state.serverDomain?.domainName ?? "your domain"
|
||||||
|
]),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -9,20 +9,22 @@ import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.da
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_new_device_key.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_new_device_key.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_method_select.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
||||||
|
|
||||||
class RecoveryRouting extends StatelessWidget {
|
class RecoveryRouting extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var serverInstallation = context.watch<ServerInstallationCubit>();
|
var serverInstallation = context.watch<ServerInstallationCubit>().state;
|
||||||
|
|
||||||
StatelessWidget currentPage = SelectDomainToRecover();
|
Widget currentPage = SelectDomainToRecover();
|
||||||
|
|
||||||
if (serverInstallation is ServerInstallationRecovery) {
|
if (serverInstallation is ServerInstallationRecovery) {
|
||||||
final state = (serverInstallation as ServerInstallationRecovery);
|
switch (serverInstallation.currentStep) {
|
||||||
switch (state.currentStep) {
|
|
||||||
case RecoveryStep.Selecting:
|
case RecoveryStep.Selecting:
|
||||||
if (state.recoveryCapabilities != ServerRecoveryCapabilities.none)
|
if (serverInstallation.recoveryCapabilities !=
|
||||||
|
ServerRecoveryCapabilities.none)
|
||||||
currentPage = RecoveryMethodSelect();
|
currentPage = RecoveryMethodSelect();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.RecoveryKey:
|
case RecoveryStep.RecoveryKey:
|
||||||
|
@ -35,8 +37,10 @@ class RecoveryRouting extends StatelessWidget {
|
||||||
currentPage = RecoverByOldToken();
|
currentPage = RecoverByOldToken();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.HetznerToken:
|
case RecoveryStep.HetznerToken:
|
||||||
|
currentPage = RecoveryHetznerConnected();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.ServerSelection:
|
case RecoveryStep.ServerSelection:
|
||||||
|
currentPage = RecoveryConfirmServer();
|
||||||
break;
|
break;
|
||||||
case RecoveryStep.CloudflareToken:
|
case RecoveryStep.CloudflareToken:
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue