mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-09 01:21:14 +00:00
Added on-demand server removal option
Reviewed-on: https://git.selfprivacy.org/kherel/selfprivacy.org.app/pulls/21
This commit is contained in:
commit
739ff1aadf
|
@ -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"
|
||||||
|
|
|
@ -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": "{} сек"
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue