Merge pull request 'no-hash' (#70) from no-hash into master

Reviewed-on: https://git.selfprivacy.org/kherel/selfprivacy.org.app/pulls/70
This commit is contained in:
kherel 2021-10-12 14:22:03 +03:00
commit bdf5a3b1cc
9 changed files with 186 additions and 141 deletions

View file

@ -68,13 +68,8 @@ class HetznerApi extends ApiMap {
return server == null; return server == null;
} }
Future<HetznerServerDetails> createServer({ Future<HetznerDataBase> createVolume() async {
required String cloudFlareKey,
required User rootUser,
required String domainName,
}) async {
var client = await getClient(); var client = await getClient();
Response dbCreateResponse = await client.post( Response dbCreateResponse = await client.post(
'/volumes', '/volumes',
data: { data: {
@ -86,9 +81,36 @@ class HetznerApi extends ApiMap {
"format": "ext4" "format": "ext4"
}, },
); );
var dbId = dbCreateResponse.data['volume']['id'];
return HetznerDataBase(
id: dbId,
name: dbCreateResponse.data['volume']['name'],
);
}
Future<HetznerServerDetails> createServer({
required String cloudFlareKey,
required User rootUser,
required String domainName,
required HetznerDataBase dataBase,
}) async {
var client = await getClient();
// Response dbCreateResponse = await client.post(
// '/volumes',
// data: {
// "size": 10,
// "name": StringGenerators.dbStorageName(),
// "labels": {"labelkey": "value"},
// "location": "fsn1",
// "automount": false,
// "format": "ext4"
// },
// );
var dbPassword = StringGenerators.dbPassword(); var dbPassword = StringGenerators.dbPassword();
var dbId = dbCreateResponse.data['volume']['id']; // var dbId = dbCreateResponse.data['volume']['id'];
var dbId = dataBase.id;
/// add ssh key when you need it: e.g. "ssh_keys":["kherel"] /// add ssh key when you need it: e.g. "ssh_keys":["kherel"]
/// check the branch name, it could be "development" or "master". /// check the branch name, it could be "development" or "master".
@ -106,10 +128,7 @@ class HetznerApi extends ApiMap {
id: serverCreateResponse.data['server']['id'], id: serverCreateResponse.data['server']['id'],
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'], ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
createTime: DateTime.now(), createTime: DateTime.now(),
dataBase: HetznerDataBase( dataBase: dataBase,
id: dbId,
name: dbCreateResponse.data['volume']['name'],
),
); );
} }
@ -120,28 +139,22 @@ class HetznerApi extends ApiMap {
Response serversReponse = await client.get('/servers'); Response serversReponse = await client.get('/servers');
List servers = serversReponse.data['servers']; List servers = serversReponse.data['servers'];
var server = servers.firstWhere((el) => el['name'] == domainName); Map server = servers.firstWhere((el) => el['name'] == domainName);
await client.delete('/servers/${server['id']}'); List volumes = server['volumes'];
Response volumesReponse = await client.get('/volumes');
List volumes = volumesReponse.data['volumes'];
var laterFutures = <Future>[]; var laterFutures = <Future>[];
for (var volume in volumes) {
if (volume['server'] == null) {
await client.delete('/volumes/${volume['id']}');
} else {
laterFutures.add(Future.delayed(Duration(seconds: 60)).then(
(_) => client.delete('/volumes/${volume['id']}'),
));
}
}
if (laterFutures.isEmpty) { for (var volumeId in volumes) {
close(client); await client.post('/volumes/$volumeId/actions/detach');
} else {
Future.wait(laterFutures).then((value) => close(client));
} }
await Future.delayed(Duration(seconds: 10));
for (var volumeId in volumes) {
laterFutures.add(client.delete('/volumes/$volumeId'));
}
laterFutures.add(client.delete('/servers/${server['id']}'));
await Future.wait(laterFutures);
close(client);
} }
Future<HetznerServerDetails> reset() async { Future<HetznerServerDetails> reset() async {

View file

@ -53,8 +53,8 @@ class ServerApi extends ApiMap {
options: Options( options: Options(
headers: { headers: {
"X-User": user.login, "X-User": user.login,
"X-Password": "X-Password": user.password,
'\$6\$${user.hashPassword.salt}\$${user.hashPassword.hash}', "X-Domain": getIt<ApiConfigModel>().cloudFlareDomain!.domainName
}, },
), ),
); );

View file

@ -275,7 +275,18 @@ class AppConfigCubit extends Cubit<AppConfigState> {
await getIt<SSHModel>().clear(); await getIt<SSHModel>().clear();
} }
await repository.deleteRecords(); await repository.deleteRecords();
emit(AppConfigEmpty()); emit(AppConfigNotFinished(
hetznerKey: state.hetznerKey,
cloudFlareDomain: state.cloudFlareDomain,
cloudFlareKey: state.cloudFlareKey,
backblazeCredential: state.backblazeCredential,
rootUser: state.rootUser,
hetznerServer: null,
isServerStarted: false,
isServerResetedFirstTime: false,
isServerResetedSecondTime: false,
isLoading: false,
));
} }
void setHetznerKey(String hetznerKey) async { void setHetznerKey(String hetznerKey) async {

View file

@ -116,12 +116,16 @@ class AppConfigRepository {
onSuccess, onSuccess,
}) async { }) async {
var hetznerApi = HetznerApi(); var hetznerApi = HetznerApi();
late HetznerDataBase dataBase;
try { try {
dataBase = await hetznerApi.createVolume();
var serverDetails = await hetznerApi.createServer( var serverDetails = await hetznerApi.createServer(
cloudFlareKey: cloudFlareKey, cloudFlareKey: cloudFlareKey,
rootUser: rootUser, rootUser: rootUser,
domainName: domainName, domainName: domainName,
dataBase: dataBase,
); );
saveServerDetails(serverDetails); saveServerDetails(serverDetails);
onSuccess(serverDetails); onSuccess(serverDetails);
@ -144,6 +148,7 @@ class AppConfigRepository {
cloudFlareKey: cloudFlareKey, cloudFlareKey: cloudFlareKey,
rootUser: rootUser, rootUser: rootUser,
domainName: domainName, domainName: domainName,
dataBase: dataBase,
); );
await saveServerDetails(serverDetails); await saveServerDetails(serverDetails);
@ -245,10 +250,11 @@ class AppConfigRepository {
var hetznerApi = HetznerApi(); var hetznerApi = HetznerApi();
var cloudFlare = CloudflareApi(); var cloudFlare = CloudflareApi();
hetznerApi.deleteSelfprivacyServerAndAllVolumes( await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
domainName: cloudFlareDomain.domainName, domainName: cloudFlareDomain.domainName,
); );
cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
await cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
} }
Future<void> deleteRecords() async { Future<void> deleteRecords() async {

View file

@ -1,6 +1,6 @@
part of 'app_config_cubit.dart'; part of 'app_config_cubit.dart';
class AppConfigState extends Equatable { abstract class AppConfigState extends Equatable {
const AppConfigState({ const AppConfigState({
required this.hetznerKey, required this.hetznerKey,
required this.cloudFlareKey, required this.cloudFlareKey,

View file

@ -119,7 +119,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
], ],
), ),
), ),
// deleteServer(context) deleteServer(context)
], ],
), ),
); );
@ -128,7 +128,8 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
} }
Widget deleteServer(BuildContext context) { Widget deleteServer(BuildContext context) {
// todo: need to check var isDisabled =
context.watch<AppConfigCubit>().state.hetznerServer == null;
return Container( return Container(
padding: EdgeInsets.only(top: 20, bottom: 5), padding: EdgeInsets.only(top: 20, bottom: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -157,29 +158,41 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
fontWeight: NamedFontWeight.demiBold, fontWeight: NamedFontWeight.demiBold,
), ),
), ),
onPressed: () { onPressed: isDisabled
showDialog( ? null
context: context, : () {
builder: (_) { showDialog(
return BrandAlert( context: context,
title: 'modals.3'.tr(), builder: (_) {
contentText: 'modals.6'.tr(), return BrandAlert(
acitons: [ title: 'modals.3'.tr(),
ActionButton( contentText: 'modals.6'.tr(),
text: 'modals.7'.tr(), acitons: [
isRed: true, ActionButton(
onPressed: () async { text: 'modals.7'.tr(),
await context.read<AppConfigCubit>().serverDelete(); isRed: true,
Navigator.of(context).pop(); onPressed: () async {
}), showDialog(
ActionButton( context: context,
text: 'basis.cancel'.tr(), builder: (context) {
), return Container(
], alignment: Alignment.center,
); child: CircularProgressIndicator(),
}, );
); });
}, await context
.read<AppConfigCubit>()
.serverDelete();
Navigator.of(context).pop();
}),
ActionButton(
text: 'basis.cancel'.tr(),
),
],
);
},
);
},
), ),
], ],
), ),

View file

@ -1,11 +1,11 @@
part of 'users.dart'; part of 'users.dart';
class _User extends StatelessWidget { class _User extends StatelessWidget {
const _User({Key? key, required this.user, required this.rootUser}) const _User({Key? key, required this.user, required this.isRootUser})
: super(key: key); : super(key: key);
final User user; final User user;
final bool rootUser; final bool isRootUser;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
@ -13,7 +13,7 @@ class _User extends StatelessWidget {
showBrandBottomSheet<void>( showBrandBottomSheet<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return _UserDetails(user: user); return _UserDetails(user: user, isRootUser: isRootUser);
}, },
); );
}, },
@ -32,7 +32,7 @@ class _User extends StatelessWidget {
), ),
SizedBox(width: 20), SizedBox(width: 20),
Flexible( Flexible(
child: rootUser child: isRootUser
? BrandText.h4Underlined(user.login) ? BrandText.h4Underlined(user.login)
: BrandText.h4(user.login), : BrandText.h4(user.login),
), ),

View file

@ -4,10 +4,11 @@ class _UserDetails extends StatelessWidget {
const _UserDetails({ const _UserDetails({
Key? key, Key? key,
required this.user, required this.user,
required this.isRootUser,
}) : super(key: key); }) : super(key: key);
final User user; final User user;
final bool isRootUser;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var config = context.watch<AppConfigCubit>().state; var config = context.watch<AppConfigCubit>().state;
@ -31,86 +32,87 @@ class _UserDetails extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Align( if (!isRootUser)
alignment: Alignment.centerRight, Align(
child: Padding( alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric( child: Padding(
vertical: 4, padding: EdgeInsets.symmetric(
horizontal: 2, vertical: 4,
), horizontal: 2,
child: PopupMenuButton<PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
), ),
onSelected: (PopupMenuItemType result) { child: PopupMenuButton<PopupMenuItemType>(
switch (result) { shape: RoundedRectangleBorder(
// case PopupMenuItemType.reset: borderRadius: BorderRadius.circular(10.0),
// break; ),
case PopupMenuItemType.delete: onSelected: (PopupMenuItemType result) {
showDialog( switch (result) {
context: context, case PopupMenuItemType.delete:
builder: (context) { showDialog(
return AlertDialog( context: context,
title: Text('basis.confirmation'.tr()), builder: (context) {
content: SingleChildScrollView( return AlertDialog(
child: ListBody( title: Text('basis.confirmation'.tr()),
children: <Widget>[ content: SingleChildScrollView(
Text('users.delete_confirm_question' child: ListBody(
.tr()), children: <Widget>[
], Text('users.delete_confirm_question'
), .tr()),
), ],
actions: <Widget>[
TextButton(
child: Text('basis.cancel'.tr()),
onPressed: () {
Navigator.of(context)..pop();
},
),
TextButton(
child: Text(
'basis.delete'.tr(),
style: TextStyle(
color: BrandColors.red1,
),
), ),
onPressed: () {
context.read<UsersCubit>().remove(user);
Navigator.of(context)
..pop()
..pop();
},
), ),
], actions: <Widget>[
); TextButton(
}, child: Text('basis.cancel'.tr()),
); onPressed: () {
break; Navigator.of(context)..pop();
} },
}, ),
icon: Icon(Icons.more_vert), TextButton(
itemBuilder: (BuildContext context) => [ child: Text(
// PopupMenuItem<PopupMenuItemType>( 'basis.delete'.tr(),
// value: PopupMenuItemType.reset, style: TextStyle(
// child: Container( color: BrandColors.red1,
// padding: EdgeInsets.only(left: 5), ),
// child: Text('users.reset_password'.tr()), ),
// ), onPressed: () {
// ), context
PopupMenuItem<PopupMenuItemType>( .read<UsersCubit>()
value: PopupMenuItemType.delete, .remove(user);
child: Container( Navigator.of(context)
padding: EdgeInsets.only(left: 5), ..pop()
child: Text( ..pop();
'basis.delete'.tr(), },
style: TextStyle(color: BrandColors.red1), ),
],
);
},
);
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
// PopupMenuItem<PopupMenuItemType>(
// value: PopupMenuItemType.reset,
// child: Container(
// padding: EdgeInsets.only(left: 5),
// child: Text('users.reset_password'.tr()),
// ),
// ),
PopupMenuItem<PopupMenuItemType>(
value: PopupMenuItemType.delete,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text(
'basis.delete'.tr(),
style: TextStyle(color: BrandColors.red1),
),
), ),
), ),
), ],
], ),
), ),
), ),
),
Spacer(), Spacer(),
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(

View file

@ -37,7 +37,7 @@ class UsersPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final usersCubitState = context.watch<UsersCubit>().state; final usersCubitState = context.watch<UsersCubit>().state;
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished; var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
final users = usersCubitState.users; final users = [...usersCubitState.users];
//Todo: listen box events //Todo: listen box events
User? user = Hive.box(BNames.appConfig).get(BNames.rootUser); User? user = Hive.box(BNames.appConfig).get(BNames.rootUser);
if (user != null) { if (user != null) {
@ -61,7 +61,7 @@ class UsersPage extends StatelessWidget {
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return _User( return _User(
user: users[index], user: users[index],
rootUser: index == 0, isRootUser: index == 0,
); );
}, },
); );