diff --git a/assets/translations/en.json b/assets/translations/en.json index 73c3575e..7b184548 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -21,7 +21,8 @@ "saving": "Saving..", "nickname": "Nickname", "loading": "Loading...", - "later": "I will setup it later", + "later": "Skip to setup later", + "connect_to_existing": "Connect to existing server", "reset": "Reset", "details": "Details", "no_data": "No data", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index ee085ed4..7198a040 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -21,7 +21,8 @@ "saving": "Сохранение…", "nickname": "Никнейм", "loading": "Загрузка", - "later": "Настрою потом", + "later": "Пропустить и настроить потом", + "connect_to_existing": "Подключиться к существующему серверу", "reset": "Сбросить", "details": "Детальная информация", "no_data": "Нет данных", diff --git a/lib/logic/api_maps/server.dart b/lib/logic/api_maps/server.dart index 7554f46b..aac1add0 100644 --- a/lib/logic/api_maps/server.dart +++ b/lib/logic/api_maps/server.dart @@ -58,7 +58,17 @@ class ServerApi extends ApiMap { var client = await getClient(); try { - response = await client.get('/services/status'); + response = await client.get( + '/services/status', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); res = response.statusCode == HttpStatus.ok; } catch (e) { res = false; @@ -129,7 +139,17 @@ class ServerApi extends ApiMap { Response response; var client = await getClient(); - response = await client.get('/users'); + response = await client.get( + '/users', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); try { for (var user in response.data) { res.add(user.toString()); @@ -155,6 +175,14 @@ class ServerApi extends ApiMap { data: { 'public_key': sshKey, }, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); close(client); @@ -174,6 +202,14 @@ class ServerApi extends ApiMap { response = await client.put( '/services/ssh/key/send', data: {"public_key": ssh}, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); close(client); @@ -191,7 +227,17 @@ class ServerApi extends ApiMap { Response response; var client = await getClient(); - response = await client.get('/services/ssh/keys/${user.login}'); + response = await client.get( + '/services/ssh/keys/${user.login}', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); try { res = (response.data as List).map((e) => e as String).toList(); } catch (e) { @@ -215,8 +261,18 @@ class ServerApi extends ApiMap { Response response; var client = await getClient(); - response = await client.delete('/services/ssh/keys/${user.login}', - data: {"public_key": sshKey}); + response = await client.delete( + '/services/ssh/keys/${user.login}', + data: {"public_key": sshKey}, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return ApiResponse( @@ -237,8 +293,13 @@ class ServerApi extends ApiMap { response = await client.delete( '/users/${user.login}', options: Options( - contentType: 'application/json', - ), + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); res = response.statusCode == HttpStatus.ok || response.statusCode == HttpStatus.notFound; @@ -262,6 +323,14 @@ class ServerApi extends ApiMap { try { response = await client.get( '/system/configuration/apply', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); res = response.statusCode == HttpStatus.ok; @@ -276,13 +345,33 @@ class ServerApi extends ApiMap { Future switchService(ServiceTypes type, bool needToTurnOn) async { var client = await getClient(); - client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}'); + client.post( + '/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); } Future> servicesPowerCheck() async { var client = await getClient(); - Response response = await client.get('/services/status'); + Response response = await client.get( + '/services/status', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return { @@ -303,13 +392,31 @@ class ServerApi extends ApiMap { 'accountKey': bucket.applicationKey, 'bucket': bucket.bucketName, }, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); close(client); } Future startBackup() async { var client = await getClient(); - client.put('/services/restic/backup/create'); + client.put( + '/services/restic/backup/create', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); } @@ -320,6 +427,14 @@ class ServerApi extends ApiMap { try { response = await client.get( '/services/restic/backup/list', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); return response.data.map((e) => Backup.fromJson(e)).toList(); } catch (e) { @@ -336,6 +451,14 @@ class ServerApi extends ApiMap { try { response = await client.get( '/services/restic/backup/status', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); return BackupStatus.fromJson(response.data); } catch (e) { @@ -352,40 +475,101 @@ class ServerApi extends ApiMap { Future forceBackupListReload() async { var client = await getClient(); - client.get('/services/restic/backup/reload'); + client.get( + '/services/restic/backup/reload', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); } Future restoreBackup(String backupId) async { var client = await getClient(); - client.put('/services/restic/backup/restore', data: {'backupId': backupId}); + client.put( + '/services/restic/backup/restore', + data: {'backupId': backupId}, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); } Future pullConfigurationUpdate() async { var client = await getClient(); - Response response = await client.get('/system/configuration/pull'); + Response response = await client.get( + '/system/configuration/pull', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return response.statusCode == HttpStatus.ok; } Future reboot() async { var client = await getClient(); - Response response = await client.get('/system/reboot'); + Response response = await client.get( + '/system/reboot', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return response.statusCode == HttpStatus.ok; } Future upgrade() async { var client = await getClient(); - Response response = await client.get('/system/configuration/upgrade'); + Response response = await client.get( + '/system/configuration/upgrade', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return response.statusCode == HttpStatus.ok; } Future getAutoUpgradeSettings() async { var client = await getClient(); - Response response = await client.get('/system/configuration/autoUpgrade'); + Response response = await client.get( + '/system/configuration/autoUpgrade', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return AutoUpgradeSettings.fromJson(response.data); } @@ -395,13 +579,31 @@ class ServerApi extends ApiMap { await client.put( '/system/configuration/autoUpgrade', data: settings.toJson(), + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); close(client); } Future getServerTimezone() async { var client = await getClient(); - Response response = await client.get('/system/configuration/timezone'); + Response response = await client.get( + '/system/configuration/timezone', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); return TimeZoneSettings.fromString(response.data); @@ -412,20 +614,45 @@ class ServerApi extends ApiMap { await client.put( '/system/configuration/timezone', data: settings.toJson(), + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), ); close(client); } - Future getDkim() async { + Future getDkim() async { var client = await getClient(); - Response response = await client.get('/services/mailserver/dkim'); + Response response = await client.get( + '/services/mailserver/dkim', + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); close(client); - // if got 404 raise exception - if (response.statusCode == HttpStatus.notFound) { + if (response.statusCode == null) { + return null; + } + + if (response.statusCode == HttpStatus.notFound || response.data == null) { throw Exception('No DKIM key found'); } + if (response.statusCode != HttpStatus.ok) { + return ""; + } + final base64toString = utf8.fuse(base64); return base64toString diff --git a/lib/logic/cubit/app_config/app_config_repository.dart b/lib/logic/cubit/app_config/app_config_repository.dart index 76a63b25..78560f14 100644 --- a/lib/logic/cubit/app_config/app_config_repository.dart +++ b/lib/logic/cubit/app_config/app_config_repository.dart @@ -206,7 +206,7 @@ class AppConfigRepository { var dkimRecordString = await api.getDkim(); - await cloudflareApi.setDkim(dkimRecordString, cloudFlareDomain); + await cloudflareApi.setDkim(dkimRecordString ?? "", cloudFlareDomain); } Future isHttpServerWorking() async { diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 227ac227..de03e356 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -97,11 +97,11 @@ class DnsRecordsCubit extends AppConfigDependendCubit { emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing)); final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain; final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4; - final dkimPublicKey = await api.getDkim(); + final String? dkimPublicKey = await api.getDkim(); await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!); await cloudflare.createMultipleDnsRecords( cloudFlareDomain: domain, ip4: ipAddress); - await cloudflare.setDkim(dkimPublicKey, domain); + await cloudflare.setDkim(dkimPublicKey ?? "", domain); await load(); } diff --git a/lib/main.dart b/lib/main.dart index e5af3656..2b98b667 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/hive_config.dart'; -import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/rootRoute.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 7d1c6cc5..a2eac28c 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/initializing/initializing.dart'; +import 'package:selfprivacy/ui/pages/setup/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 d87438c1..c588c007 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.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/pages/initializing/initializing.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/ui/pages/ssh_keys/ssh_keys.dart'; diff --git a/lib/ui/pages/initializing/initializing.dart b/lib/ui/pages/setup/initializing.dart similarity index 91% rename from lib/ui/pages/initializing/initializing.dart rename to lib/ui/pages/setup/initializing.dart index d30569ca..e696e51e 100644 --- a/lib/ui/pages/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing.dart @@ -17,13 +17,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/rootRoute.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recovery_domain.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; class InitializingPage extends StatelessWidget { @override Widget build(BuildContext context) { var cubit = context.watch(); - var actualPage = [ + var actualInitializingPage = [ () => _stepHetzner(cubit), () => _stepCloudflare(cubit), () => _stepBackblaze(cubit), @@ -69,7 +70,7 @@ class InitializingPage extends StatelessWidget { _addCard( AnimatedSwitcher( duration: Duration(milliseconds: 300), - child: actualPage, + child: actualInitializingPage, ), ), ConstrainedBox( @@ -79,19 +80,38 @@ class InitializingPage extends StatelessWidget { MediaQuery.of(context).padding.bottom - 566, ), - child: Container( - alignment: Alignment.center, - child: BrandButton.text( - title: cubit.state is AppConfigFinished - ? 'basis.close'.tr() - : 'basis.later'.tr(), - onPressed: () { - Navigator.of(context).pushAndRemoveUntil( - materialRoute(RootPage()), - (predicate) => false, - ); - }, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: BrandButton.text( + title: cubit.state is AppConfigFinished + ? 'basis.close'.tr() + : 'basis.later'.tr(), + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + materialRoute(RootPage()), + (predicate) => false, + ); + }, + ), + ), + (cubit.state is AppConfigFinished) + ? Container() + : Container( + alignment: Alignment.center, + child: BrandButton.text( + title: 'basis.connect_to_existing'.tr(), + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + materialRoute(RecoveryDomain()), + (predicate) => false, + ); + }, + ), + ) + ], )), ], ), diff --git a/lib/ui/pages/setup/recovering/recovery_domain.dart b/lib/ui/pages/setup/recovering/recovery_domain.dart new file mode 100644 index 00000000..ffa38085 --- /dev/null +++ b/lib/ui/pages/setup/recovering/recovery_domain.dart @@ -0,0 +1,54 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; +import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; +import 'package:selfprivacy/ui/pages/rootRoute.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; + +class RecoveryDomain extends StatelessWidget { + @override + Widget build(BuildContext context) { + var cubit = context.watch(); + return BlocListener( + listener: (context, state) { + if (cubit.state is AppConfigFinished) { + Navigator.of(context).pushReplacement(materialRoute(RootPage())); + } + }, + child: SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height - + MediaQuery.of(context).padding.top - + MediaQuery.of(context).padding.bottom - + 566, + ), + child: Container( + alignment: Alignment.center, + child: BrandButton.text( + title: cubit.state is AppConfigFinished + ? 'basis.close'.tr() + : 'basis.later'.tr(), + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + materialRoute(RootPage()), + (predicate) => false, + ); + }, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +}