Added on-demand server removal option

Reviewed-on: kherel/selfprivacy.org.app#21
fdroid
ilchub 2021-04-22 21:22:38 +03:00
commit 739ff1aadf
9 changed files with 186 additions and 56 deletions

View File

@ -41,7 +41,9 @@
"1": "Dark Theme", "1": "Dark Theme",
"2": "Change your the app theme", "2": "Change your the app theme",
"3": "Reset app config", "3": "Reset app config",
"4": "Reset api keys and root user" "4": "Reset api keys and root user",
"5": "Delete Server",
"6": "This removes the Server. It will be no longer accessible"
} }
}, },
"onboarding": { "onboarding": {
@ -162,7 +164,7 @@
}, },
"initializing": { "initializing": {
"_comment": "initializing page", "_comment": "initializing page",
"1": "Connect Hetzner server", "1": "Connect a server",
"2": "Here, your data and SelfPrivacy services wiil reside", "2": "Here, your data and SelfPrivacy services wiil reside",
"how": "How to obtain API token", "how": "How to obtain API token",
"3": "Connect CloudFlare", "3": "Connect CloudFlare",
@ -184,7 +186,10 @@
"18": "How to obtain Hetzner API Token", "18": "How to obtain Hetzner API Token",
"19": "1 Go via this link ", "19": "1 Go via this link ",
"20": "\n", "20": "\n",
"21": "One more restart to apply your security certificates." "21": "One more restart to apply your security certificates.",
"22": "Create master account",
"23": "Enter a nickname and strong password"
}, },
"modals": { "modals": {
"_comment": "messages in modals", "_comment": "messages in modals",
@ -192,7 +197,9 @@
"2": "Destroy server and create a new one?", "2": "Destroy server and create a new one?",
"3": "Are you sure?", "3": "Are you sure?",
"4": "Purge all authentication keys?", "4": "Purge all authentication keys?",
"5": "Yes, purge all my tokens" "5": "Yes, purge all my tokens",
"6": "Delete the server and volume?",
"7": "Yes"
}, },
"timer": { "timer": {
"sec": "{} sec" "sec": "{} sec"

View File

@ -41,7 +41,9 @@
"1": "Темная тема", "1": "Темная тема",
"2": "Сменить цветовую тему", "2": "Сменить цветовую тему",
"3": "Сброс настроек", "3": "Сброс настроек",
"4": "Сбросить API ключи а так же root пользвателя" "4": "Сбросить API ключи а так же root пользвателя",
"5": "Удалить сервер",
"6": "Действие приведет к удалению сервера. После чего он не будет доступен."
} }
}, },
"onboarding": { "onboarding": {
@ -162,10 +164,10 @@
}, },
"initializing": { "initializing": {
"_comment": "initializing page", "_comment": "initializing page",
"1": "Подключите сервер Hetzner", "1": "Подключите сервер",
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы", "2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
"how": "Как получить API Token", "how": "Как получить API Token",
"3": "'Подключите CloudFlare'", "3": "Подключите CloudFlare",
"4": "Для управления DNS вашего домена", "4": "Для управления DNS вашего домена",
"5": "CloudFlare API Token", "5": "CloudFlare API Token",
"6": "Подключите облачное хранилище Backblaze", "6": "Подключите облачное хранилище Backblaze",
@ -184,7 +186,9 @@
"18": "Как получить Hetzner API Token'", "18": "Как получить Hetzner API Token'",
"19": "1 Переходим по ссылке ", "19": "1 Переходим по ссылке ",
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.", "20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности" "21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
"22": "Создайте главную учетную запись",
"23": "Введите никнейм и сложный пароль"
}, },
"modals": { "modals": {
"_comment": "messages in modals", "_comment": "messages in modals",
@ -192,7 +196,9 @@
"2": "Уничтожить сервер и создать новый?", "2": "Уничтожить сервер и создать новый?",
"3": "Вы уверенны", "3": "Вы уверенны",
"4": "Сбросить все ключи?", "4": "Сбросить все ключи?",
"5": "Да, сбросить" "5": "Да, сбросить",
"6": "Удалить сервер и диск?",
"7": "Да, удалить"
}, },
"timer": { "timer": {
"sec": "{} сек" "sec": "{} сек"

View File

@ -263,6 +263,28 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(InitialAppConfigState()); emit(InitialAppConfigState());
} }
Future<void> serverDelete() async {
closeTimer();
if (state.hetznerServer != null) {
await repository.deleteServer(state.cloudFlareDomain!);
}
await repository.deleteRecords();
emit(AppConfigState(
hetznerKey: state.hetznerKey,
cloudFlareKey: state.cloudFlareKey,
backblazeCredential: state.backblazeCredential,
cloudFlareDomain: state.cloudFlareDomain,
rootUser: state.rootUser,
hetznerServer: null,
isServerStarted: false,
isServerResetedFirstTime: false,
isServerResetedSecondTime: false,
hasFinalChecked: false,
isLoading: false,
error: null,
));
}
void setHetznerKey(String hetznerKey) async { void setHetznerKey(String hetznerKey) async {
await repository.saveHetznerKey(hetznerKey); await repository.saveHetznerKey(hetznerKey);
emit(state.copyWith(hetznerKey: hetznerKey)); emit(state.copyWith(hetznerKey: hetznerKey));

View File

@ -220,4 +220,26 @@ class AppConfigRepository {
Future<void> saveHasFinalChecked(bool value) async { Future<void> saveHasFinalChecked(bool value) async {
await box.put(BNames.hasFinalChecked, value); await box.put(BNames.hasFinalChecked, value);
} }
Future<void> deleteServer(CloudFlareDomain cloudFlareDomain) async {
var hetznerApi = HetznerApi();
var cloudFlare = CloudflareApi();
hetznerApi.deleteSelfprivacyServerAndAllVolumes(
domainName: cloudFlareDomain.domainName,
);
cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
}
Future<void> deleteRecords() async {
await box.deleteAll([
BNames.hetznerServer,
BNames.isServerStarted,
BNames.isServerResetedFirstTime,
BNames.isServerResetedSecondTime,
BNames.hasFinalChecked,
BNames.isLoading,
]);
getIt<ApiConfigModel>().init();
}
} }

View File

@ -43,7 +43,7 @@ class AppConfigState extends Equatable {
final bool hasFinalChecked; final bool hasFinalChecked;
final bool? isLoading; final bool isLoading;
final Exception? error; final Exception? error;
AppConfigState copyWith({ AppConfigState copyWith({

View File

@ -39,7 +39,9 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isDark = Theme.of(context).brightness == Brightness.dark; var isDark = Theme.of(context).brightness == Brightness.dark;
var markdown = MarkdownStyleSheet( var markdown = MarkdownStyleSheet(
p: defaultTextStyle, p: defaultTextStyle.copyWith(
color: isDark ? BrandColors.white : null,
),
h1: headline1Style.copyWith( h1: headline1Style.copyWith(
color: isDark ? BrandColors.white : null, color: isDark ? BrandColors.white : null,
), ),

View File

@ -33,9 +33,7 @@ class NotReadyCard extends StatelessWidget {
child: Text( child: Text(
'not_ready_card.2'.tr(), 'not_ready_card.2'.tr(),
style: body1Style.copyWith( style: body1Style.copyWith(
color: Theme.of(context).brightness == Brightness.dark color: Colors.white,
? Colors.black
: BrandColors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
// height: 1.1, // height: 1.1,

View File

@ -1,5 +1,6 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
@ -45,43 +46,56 @@ class InitializingPage extends StatelessWidget {
}, },
child: SafeArea( child: SafeArea(
child: Scaffold( child: Scaffold(
body: ListView( body: SingleChildScrollView(
children: [ child: Column(
Padding( children: [
padding: brandPagePadding2.copyWith(top: 10, bottom: 10), Padding(
child: ProgressBar( padding: brandPagePadding2.copyWith(top: 10, bottom: 10),
steps: [ child: ProgressBar(
'Hetzner', steps: [
'CloudFlare', 'Hetzner',
'Backblaze', 'CloudFlare',
'Domain', 'Backblaze',
'User', 'Domain',
'Server', 'User',
'', 'Server',
'', '',
'', '',
'', '',
], '',
activeIndex: cubit.state.progress, ],
activeIndex: cubit.state.progress,
),
), ),
), _addCard(
_addCard( AnimatedSwitcher(
AnimatedSwitcher( duration: Duration(milliseconds: 300),
duration: Duration(milliseconds: 300), child: actualPage,
child: actualPage, ),
), ),
), ConstrainedBox(
BrandButton.text( constraints: BoxConstraints(
title: cubit.state.isFullyInitilized minHeight: MediaQuery.of(context).size.height -
? 'basis.close'.tr() MediaQuery.of(context).padding.top -
: 'basis.later'.tr(), MediaQuery.of(context).padding.bottom -
onPressed: () { 566,
Navigator.of(context).pushAndRemoveUntil( ),
materialRoute(RootPage()), child: Container(
(predicate) => false, alignment: Alignment.center,
); child: BrandButton.text(
}), title: cubit.state.isFullyInitilized
], ? 'basis.close'.tr()
: 'basis.later'.tr(),
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
materialRoute(RootPage()),
(predicate) => false,
);
},
),
)),
],
),
), ),
), ),
), ),
@ -341,8 +355,10 @@ class InitializingPage extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Spacer(), BrandText.h2('initializing.22'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2('initializing.23'.tr()),
Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<RootUserFormCubit>().userName, formFieldCubit: context.read<RootUserFormCubit>().userName,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -409,7 +425,7 @@ class InitializingPage extends StatelessWidget {
BrandText.body2('initializing.11'.tr()), BrandText.body2('initializing.11'.tr()),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: isLoading! onPressed: isLoading
? null ? null
: () => appConfigCubit.createServerAndSetDnsRecords(), : () => appConfigCubit.createServerAndSetDnsRecords(),
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(), title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
@ -446,7 +462,7 @@ class InitializingPage extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2(text), BrandText.body2(text),
SizedBox(height: 10), SizedBox(height: 10),
if (!state.isLoading!) if (!state.isLoading)
Row( Row(
children: [ children: [
BrandText.body2('initializing.16'.tr()), BrandText.body2('initializing.16'.tr()),
@ -456,7 +472,7 @@ class InitializingPage extends StatelessWidget {
) )
], ],
), ),
if (state.isLoading!) BrandText.body2('initializing.17'.tr()), if (state.isLoading) BrandText.body2('initializing.17'.tr()),
Spacer( Spacer(
flex: 2, flex: 2,
), ),

View File

@ -27,8 +27,8 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: child: BrandHeader(
BrandHeader(title: 'more.settings.title'.tr(), hasBackButton: true), title: 'more.settings.title'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
@ -116,7 +116,64 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
}, },
); );
}, },
) ),
],
),
),
Container(
padding: EdgeInsets.only(top: 20, bottom: 5),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: _TextColumn(
title: 'more.settings.5'.tr(),
value: 'more.settings.6'.tr(),
),
),
SizedBox(width: 5),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: BrandColors.red1,
),
child: Text(
'basis.delete'.tr(),
style: TextStyle(
color: BrandColors.white,
fontWeight: NamedFontWeight.demiBold,
),
),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return BrandAlert(
title: 'modals.3'.tr(),
contentText: 'modals.6'.tr(),
acitons: [
ActionButton(
text: 'modals.7'.tr(),
isRed: true,
onPressed: () async {
await context
.read<AppConfigCubit>()
.serverDelete();
Navigator.of(context).pop();
}),
ActionButton(
text: 'basis.cancel'.tr(),
),
],
);
},
);
},
),
], ],
), ),
) )