Add SSH key adding and deleting
parent
d240e493b1
commit
85235a2e7c
|
@ -62,6 +62,20 @@
|
||||||
"6": "This removes the Server. It will be no longer accessible"
|
"6": "This removes the Server. It will be no longer accessible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ssh": {
|
||||||
|
"title": "SSH keys",
|
||||||
|
"create": "Create SSH key",
|
||||||
|
"delete": "Delete SSH key",
|
||||||
|
"delete_confirm_question": "Are you sure you want to delete SSH key?",
|
||||||
|
"subtitle_with_keys": "{} keys",
|
||||||
|
"subtitle_without_keys": "No keys",
|
||||||
|
"no_key_name": "Unnamed key",
|
||||||
|
"root": {
|
||||||
|
"title": "These are superuser keys",
|
||||||
|
"subtitle": "Owners of these keys get full access to the server and can do anything on it. Only add your own keys to the server."
|
||||||
|
},
|
||||||
|
"input_label": "Public ED25519 or RSA key"
|
||||||
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"_comment": "Onboarding pages",
|
"_comment": "Onboarding pages",
|
||||||
"page1_title": "Digital independence, available to all of us",
|
"page1_title": "Digital independence, available to all of us",
|
||||||
|
@ -311,6 +325,7 @@
|
||||||
"root_name": "User name cannot be 'root'",
|
"root_name": "User name cannot be 'root'",
|
||||||
"key_format": "Invalid key format",
|
"key_format": "Invalid key format",
|
||||||
"length": "Length is [] should be {}",
|
"length": "Length is [] should be {}",
|
||||||
"user_already_exist": "Already exists"
|
"user_already_exist": "Already exists",
|
||||||
|
"key_already_exists": "This key already exists"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,16 @@
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH ключи",
|
"title": "SSH ключи",
|
||||||
"create": "Добавить SSH ключ",
|
"create": "Добавить SSH ключ",
|
||||||
"delete": "Удалить SSH ключ"
|
"delete": "Удалить SSH ключ",
|
||||||
|
"delete_confirm_question": "Вы уверены что хотите удалить следующий ключ?",
|
||||||
|
"subtitle_with_keys": "Ключей: {}",
|
||||||
|
"subtitle_without_keys": "Ключей нет",
|
||||||
|
"no_key_name": "Безымянный ключ",
|
||||||
|
"root": {
|
||||||
|
"title": "Это ключи суперпользователя",
|
||||||
|
"subtitle": "Владельцы указанных здесь ключей получают полный доступ к данным и настройкам сервера. Добавляйте исключительно свои ключи."
|
||||||
|
},
|
||||||
|
"input_label": "Публичный ED25519 или RSA ключ"
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"_comment": "страницы онбординга",
|
"_comment": "страницы онбординга",
|
||||||
|
@ -317,6 +326,7 @@
|
||||||
"root_name": "Имя пользователя не может быть'root'.",
|
"root_name": "Имя пользователя не может быть'root'.",
|
||||||
"key_format": "Неверный формат.",
|
"key_format": "Неверный формат.",
|
||||||
"length": "Длина строки [] должна быть {}.",
|
"length": "Длина строки [] должна быть {}.",
|
||||||
"user_already_exist": "Имя уже используется."
|
"user_already_exist": "Имя уже используется.",
|
||||||
|
"key_already_exists": "Этот ключ уже добавлен."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,16 +109,17 @@ class ServerApi extends ApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ApiResponse<List<String>>> getUsersList() async {
|
Future<ApiResponse<List<String>>> getUsersList() async {
|
||||||
List<String> res;
|
List<String> res = [];
|
||||||
Response response;
|
Response response;
|
||||||
|
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
response = await client.get('/users');
|
response = await client.get('/users');
|
||||||
try {
|
try {
|
||||||
res = (json.decode(response.data) as List<dynamic>)
|
for (var user in response.data) {
|
||||||
.map((e) => e as String)
|
res.add(user.toString());
|
||||||
.toList();
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
res = [];
|
res = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
class SshFormCubit extends FormCubit {
|
||||||
|
SshFormCubit({
|
||||||
|
required this.jobsCubit,
|
||||||
|
required this.user,
|
||||||
|
}) {
|
||||||
|
var keyRegExp = RegExp(
|
||||||
|
r"^(ssh-rsa AAAAB3NzaC1yc2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5)[0-9A-Za-z+/]+[=]{0,3}( .*)?$");
|
||||||
|
|
||||||
|
key = FieldCubit(
|
||||||
|
initalValue: '',
|
||||||
|
validations: [
|
||||||
|
ValidationModel(
|
||||||
|
(newKey) => user.sshKeys.any((key) => key == newKey),
|
||||||
|
'validations.key_already_exists'.tr(),
|
||||||
|
),
|
||||||
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
|
ValidationModel<String>((s) {
|
||||||
|
print(s);
|
||||||
|
print(keyRegExp.hasMatch(s));
|
||||||
|
return !keyRegExp.hasMatch(s);
|
||||||
|
}, 'validations.invalid_format'.tr()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
super.addFields([key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> onSubmit() {
|
||||||
|
print(key.state.isValid);
|
||||||
|
jobsCubit.addJob(CreateSSHKeyJob(user: user, publicKey: key.state.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
late FieldCubit<String> key;
|
||||||
|
|
||||||
|
final JobsCubit jobsCubit;
|
||||||
|
final User user;
|
||||||
|
}
|
|
@ -5,9 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -100,7 +98,6 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
if (state is JobsStateWithJobs) {
|
if (state is JobsStateWithJobs) {
|
||||||
var jobs = (state as JobsStateWithJobs).jobList;
|
var jobs = (state as JobsStateWithJobs).jobList;
|
||||||
emit(JobsStateLoading());
|
emit(JobsStateLoading());
|
||||||
var newUsers = <User>[];
|
|
||||||
var hasServiceJobs = false;
|
var hasServiceJobs = false;
|
||||||
for (var job in jobs) {
|
for (var job in jobs) {
|
||||||
if (job is CreateUserJob) {
|
if (job is CreateUserJob) {
|
||||||
|
@ -114,8 +111,10 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
await api.switchService(job.type, job.needToTurnOn);
|
await api.switchService(job.type, job.needToTurnOn);
|
||||||
}
|
}
|
||||||
if (job is CreateSSHKeyJob) {
|
if (job is CreateSSHKeyJob) {
|
||||||
await getIt<SSHModel>().generateKeys();
|
await usersCubit.addSshKey(job.user, job.publicKey);
|
||||||
api.addRootSshKey(getIt<SSHModel>().savedPubKey!);
|
}
|
||||||
|
if (job is DeleteSSHKeyJob) {
|
||||||
|
await usersCubit.deleteSshKey(job.user, job.publicKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +125,6 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(JobsStateEmpty());
|
emit(JobsStateEmpty());
|
||||||
|
|
||||||
getIt<NavigationService>().navigator!.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,11 +128,11 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
List<User> updatedUsers = state.users;
|
List<User> updatedUsers = List<User>.from(state.users);
|
||||||
final usersFromServer = await api.getUsersList();
|
final usersFromServer = await api.getUsersList();
|
||||||
if (usersFromServer.isSuccess) {
|
if (usersFromServer.isSuccess) {
|
||||||
updatedUsers =
|
updatedUsers =
|
||||||
mergeLocalAndServerUsers(state.users, usersFromServer.data);
|
mergeLocalAndServerUsers(updatedUsers, usersFromServer.data);
|
||||||
}
|
}
|
||||||
final usersWithSshKeys = await loadSshKeys(updatedUsers);
|
final usersWithSshKeys = await loadSshKeys(updatedUsers);
|
||||||
box.clear();
|
box.clear();
|
||||||
|
@ -157,8 +157,11 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final result = await api.createUser(user);
|
final result = await api.createUser(user);
|
||||||
await box.add(result.data);
|
var loadedUsers = List<User>.from(state.users);
|
||||||
emit(state.copyWith(users: box.values.toList()));
|
loadedUsers.add(result.data);
|
||||||
|
await box.clear();
|
||||||
|
await box.addAll(loadedUsers);
|
||||||
|
emit(state.copyWith(users: loadedUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteUser(User user) async {
|
Future<void> deleteUser(User user) async {
|
||||||
|
@ -166,10 +169,13 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
if (user.login == state.primaryUser.login || user.login == 'root') {
|
if (user.login == state.primaryUser.login || user.login == 'root') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var loadedUsers = List<User>.from(state.users);
|
||||||
final result = await api.deleteUser(user);
|
final result = await api.deleteUser(user);
|
||||||
if (result) {
|
if (result) {
|
||||||
await box.deleteAt(box.values.toList().indexOf(user));
|
loadedUsers.removeWhere((u) => u.login == user.login);
|
||||||
emit(state.copyWith(users: box.values.toList()));
|
await box.clear();
|
||||||
|
await box.addAll(loadedUsers);
|
||||||
|
emit(state.copyWith(users: loadedUsers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +205,8 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
// If it is primary user, update primary user
|
// If it is primary user, update primary user
|
||||||
if (user.login == state.primaryUser.login) {
|
if (user.login == state.primaryUser.login) {
|
||||||
List<String> primaryUserKeys = state.primaryUser.sshKeys;
|
List<String> primaryUserKeys =
|
||||||
|
List<String>.from(state.primaryUser.sshKeys);
|
||||||
primaryUserKeys.add(publicKey);
|
primaryUserKeys.add(publicKey);
|
||||||
final updatedUser = User(
|
final updatedUser = User(
|
||||||
login: state.primaryUser.login,
|
login: state.primaryUser.login,
|
||||||
|
@ -214,7 +221,7 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// If it is not primary user, update user
|
// If it is not primary user, update user
|
||||||
List<String> userKeys = user.sshKeys;
|
List<String> userKeys = List<String>.from(user.sshKeys);
|
||||||
userKeys.add(publicKey);
|
userKeys.add(publicKey);
|
||||||
final updatedUser = User(
|
final updatedUser = User(
|
||||||
login: user.login,
|
login: user.login,
|
||||||
|
@ -258,7 +265,8 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (user.login == state.primaryUser.login) {
|
if (user.login == state.primaryUser.login) {
|
||||||
List<String> primaryUserKeys = state.primaryUser.sshKeys;
|
List<String> primaryUserKeys =
|
||||||
|
List<String>.from(state.primaryUser.sshKeys);
|
||||||
primaryUserKeys.remove(publicKey);
|
primaryUserKeys.remove(publicKey);
|
||||||
final updatedUser = User(
|
final updatedUser = User(
|
||||||
login: state.primaryUser.login,
|
login: state.primaryUser.login,
|
||||||
|
@ -273,7 +281,7 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> userKeys = user.sshKeys;
|
List<String> userKeys = List<String>.from(user.sshKeys);
|
||||||
userKeys.remove(publicKey);
|
userKeys.remove(publicKey);
|
||||||
final updatedUser = User(
|
final updatedUser = User(
|
||||||
login: user.login,
|
login: user.login,
|
||||||
|
@ -291,7 +299,9 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onChange(Change<UsersState> change) {
|
void onChange(Change<UsersState> change) {
|
||||||
print(change);
|
|
||||||
super.onChange(change);
|
super.onChange(change);
|
||||||
|
|
||||||
|
print('UsersState changed');
|
||||||
|
print(change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,12 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Localization(
|
return Localization(
|
||||||
child: BlocAndProviderConfig(
|
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
child: Builder(builder: (context) {
|
|
||||||
var appSettings = context.watch<AppSettingsCubit>().state;
|
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
||||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||||
child: MaterialApp(
|
child: BlocAndProviderConfig(
|
||||||
|
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||||
|
builder: (context, appSettings) {
|
||||||
|
return MaterialApp(
|
||||||
scaffoldMessengerKey:
|
scaffoldMessengerKey:
|
||||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
||||||
|
@ -62,9 +61,10 @@ class MyApp extends StatelessWidget {
|
||||||
(FlutterErrorDetails errorDetails) => error;
|
(FlutterErrorDetails errorDetails) => error;
|
||||||
return widget!;
|
return widget!;
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,6 @@ class MorePage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var jobsCubit = context.watch<JobsCubit>();
|
|
||||||
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: BrandHeader(
|
child: BrandHeader(
|
||||||
|
@ -114,30 +111,6 @@ class _NavItem extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MoreMenuTapItem extends StatelessWidget {
|
|
||||||
const _MoreMenuTapItem({
|
|
||||||
Key? key,
|
|
||||||
required this.iconData,
|
|
||||||
required this.onTap,
|
|
||||||
required this.title,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final IconData iconData;
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
final String title;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: _MoreMenuItem(
|
|
||||||
isActive: onTap != null,
|
|
||||||
iconData: iconData,
|
|
||||||
title: title,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MoreMenuItem extends StatelessWidget {
|
class _MoreMenuItem extends StatelessWidget {
|
||||||
const _MoreMenuItem({
|
const _MoreMenuItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
part of 'ssh_keys.dart';
|
||||||
|
|
||||||
|
class _NewSshKey extends StatelessWidget {
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
_NewSshKey(this.user);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BrandBottomSheet(
|
||||||
|
child: BlocProvider(
|
||||||
|
create: (context) {
|
||||||
|
var jobCubit = context.read<JobsCubit>();
|
||||||
|
var jobState = jobCubit.state;
|
||||||
|
if (jobState is JobsStateWithJobs) {
|
||||||
|
var jobs = jobState.jobList;
|
||||||
|
jobs.forEach((job) {
|
||||||
|
if (job is CreateSSHKeyJob && job.user.login == user.login) {
|
||||||
|
user.sshKeys.add(job.publicKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return SshFormCubit(
|
||||||
|
jobsCubit: jobCubit,
|
||||||
|
user: user,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
var formCubitState = context.watch<SshFormCubit>().state;
|
||||||
|
|
||||||
|
return BlocListener<SshFormCubit, FormCubitState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.isSubmitted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
BrandHeader(
|
||||||
|
title: user.login,
|
||||||
|
),
|
||||||
|
SizedBox(width: 14),
|
||||||
|
Padding(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IntrinsicHeight(
|
||||||
|
child: CubitFormTextField(
|
||||||
|
formFieldCubit: context.read<SshFormCubit>().key,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'ssh.input_label'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: formCubitState.isSubmitting
|
||||||
|
? null
|
||||||
|
: () => context.read<SshFormCubit>().trySubmit(),
|
||||||
|
text: 'ssh.create'.tr(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,21 @@
|
||||||
|
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/forms/user/ssh_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.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';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
|
|
||||||
|
import '../../../config/brand_colors.dart';
|
||||||
|
import '../../../config/brand_theme.dart';
|
||||||
|
import '../../../logic/cubit/jobs/jobs_cubit.dart';
|
||||||
import '../../../logic/models/user.dart';
|
import '../../../logic/models/user.dart';
|
||||||
|
import '../../components/brand_button/brand_button.dart';
|
||||||
|
import '../../components/brand_header/brand_header.dart';
|
||||||
|
|
||||||
|
part 'new_ssh_key.dart';
|
||||||
|
|
||||||
// Get user object as a parameter
|
// Get user object as a parameter
|
||||||
class SshKeysPage extends StatefulWidget {
|
class SshKeysPage extends StatefulWidget {
|
||||||
|
@ -49,20 +60,77 @@ class _SshKeysPageState extends State<SshKeysPage> {
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.headline6,
|
||||||
),
|
),
|
||||||
leading: Icon(Icons.add_circle_outline_rounded),
|
leading: Icon(Icons.add_circle_outline_rounded),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: _NewSshKey(widget.user));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Divider(height: 0),
|
Divider(height: 0),
|
||||||
// show a list of ListTiles with ssh keys
|
// show a list of ListTiles with ssh keys
|
||||||
// Clicking on one should delete it
|
// Clicking on one should delete it
|
||||||
Column(
|
Column(
|
||||||
children: widget.user.sshKeys.map((key) {
|
children: widget.user.sshKeys.map((key) {
|
||||||
|
final publicKey =
|
||||||
|
key.split(' ').length > 1 ? key.split(' ')[1] : key;
|
||||||
|
final keyType = key.split(' ')[0];
|
||||||
|
final keyName = key.split(' ').length > 2
|
||||||
|
? key.split(' ')[2]
|
||||||
|
: 'ssh.no_key_name'.tr();
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title:
|
title: Text('$keyName ($keyType)'),
|
||||||
Text('${key.split(' ')[2]} (${key.split(' ')[0]})'),
|
|
||||||
// do not overflow text
|
// do not overflow text
|
||||||
subtitle: Text(key.split(' ')[1],
|
subtitle: Text(publicKey,
|
||||||
maxLines: 1, overflow: TextOverflow.ellipsis),
|
maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: delete ssh key
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('ssh.delete'.tr()),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('ssh.delete_confirm_question'.tr()),
|
||||||
|
Text('$keyName ($keyType)'),
|
||||||
|
Text(publicKey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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<JobsCubit>().addJob(
|
||||||
|
DeleteSSHKeyJob(
|
||||||
|
user: widget.user, publicKey: key));
|
||||||
|
Navigator.of(context)
|
||||||
|
..pop()
|
||||||
|
..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}).toList(),
|
}).toList(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -157,17 +157,6 @@ class _UserDetails extends StatelessWidget {
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
BrandButton.emptyWithIconText(
|
|
||||||
title: 'users.send_registration_data'.tr(),
|
|
||||||
icon: Icon(BrandIcons.share),
|
|
||||||
onPressed: () {
|
|
||||||
Share.share(
|
|
||||||
'login: ${user.login}, password: ${user.password}');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
BrandDivider(),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
|
@ -179,6 +168,17 @@ class _UserDetails extends StatelessWidget {
|
||||||
.tr(args: [user.sshKeys.length.toString()]))
|
.tr(args: [user.sshKeys.length.toString()]))
|
||||||
: Text('ssh.subtitle_without_keys'.tr()),
|
: Text('ssh.subtitle_without_keys'.tr()),
|
||||||
trailing: Icon(BrandIcons.key)),
|
trailing: Icon(BrandIcons.key)),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Share.share(
|
||||||
|
'login: ${user.login}, password: ${user.password}');
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
'users.send_registration_data'.tr(),
|
||||||
|
),
|
||||||
|
trailing: Icon(BrandIcons.share),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,24 +36,23 @@ class UsersPage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
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 primaryUser = usersCubitState.primaryUser;
|
// final primaryUser = usersCubitState.primaryUser;
|
||||||
final users = [primaryUser, ...usersCubitState.users];
|
// final users = [primaryUser, ...usersCubitState.users];
|
||||||
final isEmpty = users.isEmpty;
|
// final isEmpty = users.isEmpty;
|
||||||
Widget child;
|
Widget child;
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
child = isNotReady();
|
child = isNotReady();
|
||||||
} else {
|
} else {
|
||||||
child = isEmpty
|
child = BlocBuilder<UsersCubit, UsersState>(
|
||||||
? Container(
|
builder: (context, state) {
|
||||||
alignment: Alignment.center,
|
print('Rebuild users page');
|
||||||
child: _NoUsers(
|
final primaryUser = state.primaryUser;
|
||||||
text: 'users.add_new_user'.tr(),
|
final users = [primaryUser, ...state.users];
|
||||||
),
|
|
||||||
)
|
return RefreshIndicator(
|
||||||
: RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<UsersCubit>().refresh();
|
context.read<UsersCubit>().refresh();
|
||||||
},
|
},
|
||||||
|
@ -67,6 +66,8 @@ class UsersPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: selfprivacy
|
name: selfprivacy
|
||||||
description: selfprivacy.org
|
description: selfprivacy.org
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.4.2+10
|
version: 0.5.0+11
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.13.4 <3.0.0'
|
sdk: '>=2.13.4 <3.0.0'
|
||||||
|
|
Loading…
Reference in New Issue