Add user synchronization and SSH keys screen
parent
e4bdd47848
commit
d240e493b1
|
@ -301,7 +301,9 @@
|
||||||
"upgradeSuccess": "Server upgrade started",
|
"upgradeSuccess": "Server upgrade started",
|
||||||
"upgradeFailed": "Failed to upgrade server",
|
"upgradeFailed": "Failed to upgrade server",
|
||||||
"upgradeServer": "Upgrade server",
|
"upgradeServer": "Upgrade server",
|
||||||
"rebootServer": "Reboot server"
|
"rebootServer": "Reboot server",
|
||||||
|
"create_ssh_key": "Create SSH key for {}",
|
||||||
|
"delete_ssh_key": "Delete SSH key for {}"
|
||||||
},
|
},
|
||||||
"validations": {
|
"validations": {
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
|
|
|
@ -62,6 +62,11 @@
|
||||||
"6": "Действие приведет к удалению сервера. После этого он будет недоступен."
|
"6": "Действие приведет к удалению сервера. После этого он будет недоступен."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ssh": {
|
||||||
|
"title": "SSH ключи",
|
||||||
|
"create": "Добавить SSH ключ",
|
||||||
|
"delete": "Удалить SSH ключ"
|
||||||
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"_comment": "страницы онбординга",
|
"_comment": "страницы онбординга",
|
||||||
"page1_title": "Цифровая независимость доступна каждому",
|
"page1_title": "Цифровая независимость доступна каждому",
|
||||||
|
@ -302,7 +307,9 @@
|
||||||
"upgradeSuccess": "Запущено обновление сервера",
|
"upgradeSuccess": "Запущено обновление сервера",
|
||||||
"upgradeFailed": "Обновить сервер не вышло",
|
"upgradeFailed": "Обновить сервер не вышло",
|
||||||
"upgradeServer": "Обновить сервер",
|
"upgradeServer": "Обновить сервер",
|
||||||
"rebootServer": "Перезагрузить сервер"
|
"rebootServer": "Перезагрузить сервер",
|
||||||
|
"create_ssh_key": "Создать SSH ключ для {}",
|
||||||
|
"delete_ssh_key": "Удалить SSH ключ для {}"
|
||||||
},
|
},
|
||||||
"validations": {
|
"validations": {
|
||||||
"required": "Обязательное поле.",
|
"required": "Обязательное поле.",
|
||||||
|
|
|
@ -23,14 +23,14 @@ class HiveConfig {
|
||||||
await Hive.openBox<User>(BNames.users);
|
await Hive.openBox<User>(BNames.users);
|
||||||
await Hive.openBox(BNames.servicesState);
|
await Hive.openBox(BNames.servicesState);
|
||||||
|
|
||||||
var cipher = HiveAesCipher(await getEncriptedKey(BNames.key));
|
var cipher = HiveAesCipher(await getEncryptedKey(BNames.key));
|
||||||
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
||||||
|
|
||||||
var sshCipher = HiveAesCipher(await getEncriptedKey(BNames.sshEnckey));
|
var sshCipher = HiveAesCipher(await getEncryptedKey(BNames.sshEnckey));
|
||||||
await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher);
|
await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List> getEncriptedKey(String encKey) async {
|
static Future<Uint8List> getEncryptedKey(String encKey) async {
|
||||||
final secureStorage = FlutterSecureStorage();
|
final secureStorage = FlutterSecureStorage();
|
||||||
var hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
var hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||||
if (!hasEncryptionKey) {
|
if (!hasEncryptionKey) {
|
||||||
|
@ -48,6 +48,7 @@ class BNames {
|
||||||
static String isDarkModeOn = 'isDarkModeOn';
|
static String isDarkModeOn = 'isDarkModeOn';
|
||||||
static String isOnbordingShowing = 'isOnbordingShowing';
|
static String isOnbordingShowing = 'isOnbordingShowing';
|
||||||
static String users = 'users';
|
static String users = 'users';
|
||||||
|
static String rootKeys = 'rootKeys';
|
||||||
|
|
||||||
static String appSettings = 'appSettings';
|
static String appSettings = 'appSettings';
|
||||||
static String servicesState = 'servicesState';
|
static String servicesState = 'servicesState';
|
||||||
|
|
|
@ -135,7 +135,7 @@ class HetznerApi extends ApiMap {
|
||||||
/// check the branch name, it could be "development" or "master".
|
/// check the branch name, it could be "development" or "master".
|
||||||
///
|
///
|
||||||
final userdataString =
|
final userdataString =
|
||||||
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${escapeQuotes(rootUser.login)}' PASSWORD='${escapeQuotes(rootUser.password)}' CF_TOKEN=$cloudFlareKey DB_PASSWORD=${escapeQuotes(dbPassword)} API_TOKEN=$apiToken HOSTNAME=${escapeQuotes(hostname)} bash 2>&1 | tee /tmp/infect.log";
|
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${escapeQuotes(rootUser.login)}' PASSWORD='${escapeQuotes(rootUser.password ?? 'PASS')}' CF_TOKEN=$cloudFlareKey DB_PASSWORD=${escapeQuotes(dbPassword)} API_TOKEN=$apiToken HOSTNAME=${escapeQuotes(hostname)} bash 2>&1 | tee /tmp/infect.log";
|
||||||
print(userdataString);
|
print(userdataString);
|
||||||
|
|
||||||
final data = {
|
final data = {
|
||||||
|
|
|
@ -13,6 +13,20 @@ import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
import 'api_map.dart';
|
import 'api_map.dart';
|
||||||
|
|
||||||
|
class ApiResponse<D> {
|
||||||
|
final int statusCode;
|
||||||
|
final String? errorMessage;
|
||||||
|
final D data;
|
||||||
|
|
||||||
|
get isSuccess => statusCode >= 200 && statusCode < 300;
|
||||||
|
|
||||||
|
ApiResponse({
|
||||||
|
required this.statusCode,
|
||||||
|
this.errorMessage,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class ServerApi extends ApiMap {
|
class ServerApi extends ApiMap {
|
||||||
bool hasLogger;
|
bool hasLogger;
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
@ -50,31 +64,151 @@ class ServerApi extends ApiMap {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> createUser(User user) async {
|
Future<ApiResponse<User>> createUser(User user) async {
|
||||||
bool res;
|
|
||||||
Response response;
|
Response response;
|
||||||
|
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
// POST request with JSON body containing username and password
|
// POST request with JSON body containing username and password
|
||||||
try {
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
'/users',
|
'/users',
|
||||||
data: {
|
data: {
|
||||||
'username': user.login,
|
'username': user.login,
|
||||||
'password': user.password,
|
'password': user.password,
|
||||||
},
|
},
|
||||||
options: Options(
|
options: Options(
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.created) {
|
||||||
|
return ApiResponse(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: User(
|
||||||
|
login: user.login,
|
||||||
|
password: user.password,
|
||||||
|
isFoundOnServer: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
res = response.statusCode == HttpStatus.created;
|
} else {
|
||||||
|
return ApiResponse(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: User(
|
||||||
|
login: user.login,
|
||||||
|
password: user.password,
|
||||||
|
isFoundOnServer: false,
|
||||||
|
note: response.data['message'] ?? null,
|
||||||
|
),
|
||||||
|
errorMessage: response.data?.containsKey('error') ?? false
|
||||||
|
? response.data['error']
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ApiResponse<List<String>>> getUsersList() async {
|
||||||
|
List<String> res;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
response = await client.get('/users');
|
||||||
|
try {
|
||||||
|
res = (json.decode(response.data) as List<dynamic>)
|
||||||
|
.map((e) => e as String)
|
||||||
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
res = [];
|
||||||
res = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close(client);
|
close(client);
|
||||||
return res;
|
return ApiResponse<List<String>>(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: res,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ApiResponse<void>> addUserSshKey(User user, String sshKey) async {
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
response = await client.post(
|
||||||
|
'/services/ssh/keys/${user.login}',
|
||||||
|
data: {
|
||||||
|
'public_key': sshKey,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
return ApiResponse<void>(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: null,
|
||||||
|
errorMessage: response.data?.containsKey('error') ?? false
|
||||||
|
? response.data['error']
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ApiResponse<void>> addRootSshKey(String ssh) async {
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
response = await client.put(
|
||||||
|
'/services/ssh/key/send',
|
||||||
|
data: {"public_key": ssh},
|
||||||
|
);
|
||||||
|
close(client);
|
||||||
|
|
||||||
|
return ApiResponse<void>(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: null,
|
||||||
|
errorMessage: response.data?.containsKey('error') ?? false
|
||||||
|
? response.data['error']
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ApiResponse<List<String>>> getUserSshKeys(User user) async {
|
||||||
|
List<String> res;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
response = await client.get('/services/ssh/keys/${user.login}');
|
||||||
|
try {
|
||||||
|
res = (response.data as List<dynamic>).map((e) => e as String).toList();
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
res = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
return ApiResponse<List<String>>(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: res,
|
||||||
|
errorMessage: response.data is List
|
||||||
|
? null
|
||||||
|
: response.data?.containsKey('error') ?? false
|
||||||
|
? response.data['error']
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ApiResponse<void>> deleteUserSshKey(User user, String sshKey) async {
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
response = await client.delete('/services/ssh/keys/${user.login}',
|
||||||
|
data: {"public_key": sshKey});
|
||||||
|
close(client);
|
||||||
|
|
||||||
|
return ApiResponse<void>(
|
||||||
|
statusCode: response.statusCode ?? HttpStatus.internalServerError,
|
||||||
|
data: null,
|
||||||
|
errorMessage: response.data?.containsKey('error') ?? false
|
||||||
|
? response.data['error']
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteUser(User user) async {
|
Future<bool> deleteUser(User user) async {
|
||||||
|
@ -89,7 +223,8 @@ class ServerApi extends ApiMap {
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
res = response.statusCode == HttpStatus.ok;
|
res = response.statusCode == HttpStatus.ok ||
|
||||||
|
response.statusCode == HttpStatus.notFound;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
res = false;
|
res = false;
|
||||||
|
@ -125,16 +260,7 @@ class ServerApi extends ApiMap {
|
||||||
Future<void> switchService(ServiceTypes type, bool needToTurnOn) async {
|
Future<void> switchService(ServiceTypes type, bool needToTurnOn) async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}');
|
client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}');
|
||||||
client.close();
|
close(client);
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendSsh(String ssh) async {
|
|
||||||
var client = await getClient();
|
|
||||||
client.put(
|
|
||||||
'/services/ssh/key/send',
|
|
||||||
data: {"public_key": ssh},
|
|
||||||
);
|
|
||||||
client.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<ServiceTypes, bool>> servicesPowerCheck() async {
|
Future<Map<ServiceTypes, bool>> servicesPowerCheck() async {
|
||||||
|
@ -161,13 +287,13 @@ class ServerApi extends ApiMap {
|
||||||
'bucket': bucket.bucketName,
|
'bucket': bucket.bucketName,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startBackup() async {
|
Future<void> startBackup() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
client.put('/services/restic/backup/create');
|
client.put('/services/restic/backup/create');
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Backup>> getBackups() async {
|
Future<List<Backup>> getBackups() async {
|
||||||
|
@ -210,13 +336,13 @@ class ServerApi extends ApiMap {
|
||||||
Future<void> forceBackupListReload() async {
|
Future<void> forceBackupListReload() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
client.get('/services/restic/backup/reload');
|
client.get('/services/restic/backup/reload');
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restoreBackup(String backupId) async {
|
Future<void> restoreBackup(String backupId) async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
client.put('/services/restic/backup/restore', data: {'backupId': backupId});
|
client.put('/services/restic/backup/restore', data: {'backupId': backupId});
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> pullConfigurationUpdate() async {
|
Future<bool> pullConfigurationUpdate() async {
|
||||||
|
@ -229,21 +355,21 @@ class ServerApi extends ApiMap {
|
||||||
Future<bool> reboot() async {
|
Future<bool> reboot() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
Response response = await client.get('/system/reboot');
|
Response response = await client.get('/system/reboot');
|
||||||
client.close();
|
close(client);
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> upgrade() async {
|
Future<bool> upgrade() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
Response response = await client.get('/system/configuration/upgrade');
|
Response response = await client.get('/system/configuration/upgrade');
|
||||||
client.close();
|
close(client);
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AutoUpgradeSettings> getAutoUpgradeSettings() async {
|
Future<AutoUpgradeSettings> getAutoUpgradeSettings() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
Response response = await client.get('/system/configuration/autoUpgrade');
|
Response response = await client.get('/system/configuration/autoUpgrade');
|
||||||
client.close();
|
close(client);
|
||||||
return AutoUpgradeSettings.fromJson(response.data);
|
return AutoUpgradeSettings.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,13 +379,13 @@ class ServerApi extends ApiMap {
|
||||||
'/system/configuration/autoUpgrade',
|
'/system/configuration/autoUpgrade',
|
||||||
data: settings.toJson(),
|
data: settings.toJson(),
|
||||||
);
|
);
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TimeZoneSettings> getServerTimezone() async {
|
Future<TimeZoneSettings> getServerTimezone() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
Response response = await client.get('/system/configuration/timezone');
|
Response response = await client.get('/system/configuration/timezone');
|
||||||
client.close();
|
close(client);
|
||||||
|
|
||||||
return TimeZoneSettings.fromString(response.data);
|
return TimeZoneSettings.fromString(response.data);
|
||||||
}
|
}
|
||||||
|
@ -270,13 +396,13 @@ class ServerApi extends ApiMap {
|
||||||
'/system/configuration/timezone',
|
'/system/configuration/timezone',
|
||||||
data: settings.toJson(),
|
data: settings.toJson(),
|
||||||
);
|
);
|
||||||
client.close();
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getDkim() async {
|
Future<String> getDkim() async {
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
Response response = await client.get('/services/mailserver/dkim');
|
Response response = await client.get('/services/mailserver/dkim');
|
||||||
client.close();
|
close(client);
|
||||||
|
|
||||||
// if got 404 raise exception
|
// if got 404 raise exception
|
||||||
if (response.statusCode == HttpStatus.notFound) {
|
if (response.statusCode == HttpStatus.notFound) {
|
||||||
|
|
|
@ -79,7 +79,7 @@ class DnsRecordsCubit extends AppConfigDependendCubit<DnsRecordsState> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onChange(Change<DnsRecordsState> change) {
|
void onChange(Change<DnsRecordsState> change) {
|
||||||
print(change);
|
// print(change);
|
||||||
super.onChange(change);
|
super.onChange(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ class UserFormCubit extends FormCubit {
|
||||||
);
|
);
|
||||||
|
|
||||||
password = FieldCubit(
|
password = FieldCubit(
|
||||||
initalValue: isEdit ? user!.password : StringGenerators.userPassword(),
|
initalValue:
|
||||||
|
isEdit ? (user?.password ?? '') : StringGenerators.userPassword(),
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
|
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||||
|
@ -5,10 +7,9 @@ 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/get_it/ssh.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
part 'jobs_state.dart';
|
part 'jobs_state.dart';
|
||||||
|
|
||||||
|
@ -103,12 +104,10 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
var hasServiceJobs = false;
|
var hasServiceJobs = false;
|
||||||
for (var job in jobs) {
|
for (var job in jobs) {
|
||||||
if (job is CreateUserJob) {
|
if (job is CreateUserJob) {
|
||||||
newUsers.add(job.user);
|
await usersCubit.createUser(job.user);
|
||||||
await api.createUser(job.user);
|
|
||||||
}
|
}
|
||||||
if (job is DeleteUserJob) {
|
if (job is DeleteUserJob) {
|
||||||
final deleted = await api.deleteUser(job.user);
|
await usersCubit.deleteUser(job.user);
|
||||||
if (deleted) usersCubit.remove(job.user);
|
|
||||||
}
|
}
|
||||||
if (job is ServiceToggleJob) {
|
if (job is ServiceToggleJob) {
|
||||||
hasServiceJobs = true;
|
hasServiceJobs = true;
|
||||||
|
@ -116,11 +115,10 @@ class JobsCubit extends Cubit<JobsState> {
|
||||||
}
|
}
|
||||||
if (job is CreateSSHKeyJob) {
|
if (job is CreateSSHKeyJob) {
|
||||||
await getIt<SSHModel>().generateKeys();
|
await getIt<SSHModel>().generateKeys();
|
||||||
api.sendSsh(getIt<SSHModel>().savedPubKey!);
|
api.addRootSshKey(getIt<SSHModel>().savedPubKey!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersCubit.addUsers(newUsers);
|
|
||||||
await api.pullConfigurationUpdate();
|
await api.pullConfigurationUpdate();
|
||||||
await api.apply();
|
await api.apply();
|
||||||
if (hasServiceJobs) {
|
if (hasServiceJobs) {
|
||||||
|
|
|
@ -3,35 +3,295 @@ import 'package:equatable/equatable.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
import '../../api_maps/server.dart';
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
part 'users_state.dart';
|
part 'users_state.dart';
|
||||||
|
|
||||||
class UsersCubit extends Cubit<UsersState> {
|
class UsersCubit extends Cubit<UsersState> {
|
||||||
UsersCubit() : super(UsersState(<User>[]));
|
UsersCubit()
|
||||||
|
: super(UsersState(
|
||||||
|
<User>[], User(login: 'root'), User(login: 'loading...')));
|
||||||
Box<User> box = Hive.box<User>(BNames.users);
|
Box<User> box = Hive.box<User>(BNames.users);
|
||||||
|
Box configBox = Hive.box(BNames.appConfig);
|
||||||
|
|
||||||
void load() async {
|
final api = ServerApi();
|
||||||
|
|
||||||
|
Future<void> load() async {
|
||||||
var loadedUsers = box.values.toList();
|
var loadedUsers = box.values.toList();
|
||||||
|
final primaryUser =
|
||||||
|
configBox.get(BNames.rootUser, defaultValue: User(login: 'loading...'));
|
||||||
|
List<String> rootKeys = [
|
||||||
|
...configBox.get(BNames.rootKeys, defaultValue: [])
|
||||||
|
];
|
||||||
if (loadedUsers.isNotEmpty) {
|
if (loadedUsers.isNotEmpty) {
|
||||||
emit(UsersState(loadedUsers));
|
emit(UsersState(
|
||||||
|
loadedUsers, User(login: 'root', sshKeys: rootKeys), primaryUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
final usersFromServer = await api.getUsersList();
|
||||||
|
if (usersFromServer.isSuccess) {
|
||||||
|
final updatedList =
|
||||||
|
mergeLocalAndServerUsers(loadedUsers, usersFromServer.data);
|
||||||
|
emit(UsersState(
|
||||||
|
updatedList, User(login: 'root', sshKeys: rootKeys), primaryUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
final usersWithSshKeys = await loadSshKeys(state.users);
|
||||||
|
// Update the users it the box
|
||||||
|
box.clear();
|
||||||
|
box.addAll(usersWithSshKeys);
|
||||||
|
|
||||||
|
final rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||||
|
configBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||||
|
final primaryUserWithSshKeys =
|
||||||
|
(await loadSshKeys([state.primaryUser])).first;
|
||||||
|
configBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||||
|
emit(UsersState(
|
||||||
|
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<User> mergeLocalAndServerUsers(
|
||||||
|
List<User> localUsers, List<String> serverUsers) {
|
||||||
|
// If local user not exists on server, add it with isFoundOnServer = false
|
||||||
|
// If server user not exists on local, add it
|
||||||
|
|
||||||
|
List<User> mergedUsers = [];
|
||||||
|
List<String> serverUsersCopy = List.from(serverUsers);
|
||||||
|
|
||||||
|
for (var localUser in localUsers) {
|
||||||
|
if (serverUsersCopy.contains(localUser.login)) {
|
||||||
|
mergedUsers.add(User(
|
||||||
|
login: localUser.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: localUser.password,
|
||||||
|
sshKeys: localUser.sshKeys,
|
||||||
|
));
|
||||||
|
serverUsersCopy.remove(localUser.login);
|
||||||
|
} else {
|
||||||
|
mergedUsers.add(User(
|
||||||
|
login: localUser.login,
|
||||||
|
isFoundOnServer: false,
|
||||||
|
password: localUser.password,
|
||||||
|
note: localUser.note,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var serverUser in serverUsersCopy) {
|
||||||
|
mergedUsers.add(User(
|
||||||
|
login: serverUser,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<User>> loadSshKeys(List<User> users) async {
|
||||||
|
List<User> updatedUsers = [];
|
||||||
|
|
||||||
|
for (var user in users) {
|
||||||
|
if (user.isFoundOnServer ||
|
||||||
|
user.login == 'root' ||
|
||||||
|
user.login == state.primaryUser.login) {
|
||||||
|
final sshKeys = await api.getUserSshKeys(user);
|
||||||
|
print('sshKeys for $user: ${sshKeys.data}');
|
||||||
|
if (sshKeys.isSuccess) {
|
||||||
|
updatedUsers.add(User(
|
||||||
|
login: user.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: user.password,
|
||||||
|
sshKeys: sshKeys.data,
|
||||||
|
note: user.note,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
updatedUsers.add(User(
|
||||||
|
login: user.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: user.password,
|
||||||
|
note: user.note,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedUsers.add(User(
|
||||||
|
login: user.login,
|
||||||
|
isFoundOnServer: false,
|
||||||
|
password: user.password,
|
||||||
|
note: user.note,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
List<User> updatedUsers = state.users;
|
||||||
|
final usersFromServer = await api.getUsersList();
|
||||||
|
if (usersFromServer.isSuccess) {
|
||||||
|
updatedUsers =
|
||||||
|
mergeLocalAndServerUsers(state.users, usersFromServer.data);
|
||||||
|
}
|
||||||
|
final usersWithSshKeys = await loadSshKeys(updatedUsers);
|
||||||
|
box.clear();
|
||||||
|
box.addAll(usersWithSshKeys);
|
||||||
|
final rootUserWithSshKeys = (await loadSshKeys([state.rootUser])).first;
|
||||||
|
configBox.put(BNames.rootKeys, rootUserWithSshKeys.sshKeys);
|
||||||
|
final primaryUserWithSshKeys =
|
||||||
|
(await loadSshKeys([state.primaryUser])).first;
|
||||||
|
configBox.put(BNames.rootUser, primaryUserWithSshKeys);
|
||||||
|
emit(UsersState(
|
||||||
|
usersWithSshKeys, rootUserWithSshKeys, primaryUserWithSshKeys));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createUser(User user) async {
|
||||||
|
// If user exists on server, do nothing
|
||||||
|
if (state.users.any((u) => u.login == user.login && u.isFoundOnServer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If user is root or primary user, do nothing
|
||||||
|
if (user.login == 'root' || user.login == state.primaryUser.login) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final result = await api.createUser(user);
|
||||||
|
await box.add(result.data);
|
||||||
|
emit(state.copyWith(users: box.values.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteUser(User user) async {
|
||||||
|
// If user is primary or root, don't delete
|
||||||
|
if (user.login == state.primaryUser.login || user.login == 'root') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final result = await api.deleteUser(user);
|
||||||
|
if (result) {
|
||||||
|
await box.deleteAt(box.values.toList().indexOf(user));
|
||||||
|
emit(state.copyWith(users: box.values.toList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addUsers(List<User> users) async {
|
Future<void> addSshKey(User user, String publicKey) async {
|
||||||
var newUserList = <User>[...state.users, ...users];
|
// If adding root key, use api.addRootSshKey
|
||||||
|
// Otherwise, use api.addUserSshKey
|
||||||
await box.addAll(users);
|
if (user.login == 'root') {
|
||||||
emit(UsersState(newUserList));
|
final result = await api.addRootSshKey(publicKey);
|
||||||
|
if (result.isSuccess) {
|
||||||
|
// Add ssh key to the array of root keys
|
||||||
|
final rootKeys =
|
||||||
|
configBox.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||||
|
rootKeys.add(publicKey);
|
||||||
|
configBox.put(BNames.rootKeys, rootKeys);
|
||||||
|
emit(state.copyWith(
|
||||||
|
rootUser: User(
|
||||||
|
login: state.rootUser.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: state.rootUser.password,
|
||||||
|
sshKeys: rootKeys,
|
||||||
|
note: state.rootUser.note,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final result = await api.addUserSshKey(user, publicKey);
|
||||||
|
if (result.isSuccess) {
|
||||||
|
// If it is primary user, update primary user
|
||||||
|
if (user.login == state.primaryUser.login) {
|
||||||
|
List<String> primaryUserKeys = state.primaryUser.sshKeys;
|
||||||
|
primaryUserKeys.add(publicKey);
|
||||||
|
final updatedUser = User(
|
||||||
|
login: state.primaryUser.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: state.primaryUser.password,
|
||||||
|
sshKeys: primaryUserKeys,
|
||||||
|
note: state.primaryUser.note,
|
||||||
|
);
|
||||||
|
configBox.put(BNames.rootUser, updatedUser);
|
||||||
|
emit(state.copyWith(
|
||||||
|
primaryUser: updatedUser,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// If it is not primary user, update user
|
||||||
|
List<String> userKeys = user.sshKeys;
|
||||||
|
userKeys.add(publicKey);
|
||||||
|
final updatedUser = User(
|
||||||
|
login: user.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: user.password,
|
||||||
|
sshKeys: userKeys,
|
||||||
|
note: user.note,
|
||||||
|
);
|
||||||
|
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||||
|
emit(state.copyWith(
|
||||||
|
users: box.values.toList(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(User user) async {
|
Future<void> deleteSshKey(User user, String publicKey) async {
|
||||||
var users = [...state.users];
|
// All keys are deleted via api.deleteUserSshKey
|
||||||
var index = users.indexOf(user);
|
|
||||||
users.remove(user);
|
|
||||||
await box.deleteAt(index);
|
|
||||||
|
|
||||||
emit(UsersState(users));
|
final result = await api.deleteUserSshKey(user, publicKey);
|
||||||
|
if (result.isSuccess) {
|
||||||
|
// If it is root user, delete key from root keys
|
||||||
|
// If it is primary user, update primary user
|
||||||
|
// If it is not primary user, update user
|
||||||
|
|
||||||
|
if (user.login == 'root') {
|
||||||
|
final rootKeys =
|
||||||
|
configBox.get(BNames.rootKeys, defaultValue: []) as List<String>;
|
||||||
|
rootKeys.remove(publicKey);
|
||||||
|
configBox.put(BNames.rootKeys, rootKeys);
|
||||||
|
emit(state.copyWith(
|
||||||
|
rootUser: User(
|
||||||
|
login: state.rootUser.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: state.rootUser.password,
|
||||||
|
sshKeys: rootKeys,
|
||||||
|
note: state.rootUser.note,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.login == state.primaryUser.login) {
|
||||||
|
List<String> primaryUserKeys = state.primaryUser.sshKeys;
|
||||||
|
primaryUserKeys.remove(publicKey);
|
||||||
|
final updatedUser = User(
|
||||||
|
login: state.primaryUser.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: state.primaryUser.password,
|
||||||
|
sshKeys: primaryUserKeys,
|
||||||
|
note: state.primaryUser.note,
|
||||||
|
);
|
||||||
|
configBox.put(BNames.rootUser, updatedUser);
|
||||||
|
emit(state.copyWith(
|
||||||
|
primaryUser: updatedUser,
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> userKeys = user.sshKeys;
|
||||||
|
userKeys.remove(publicKey);
|
||||||
|
final updatedUser = User(
|
||||||
|
login: user.login,
|
||||||
|
isFoundOnServer: true,
|
||||||
|
password: user.password,
|
||||||
|
sshKeys: userKeys,
|
||||||
|
note: user.note,
|
||||||
|
);
|
||||||
|
await box.putAt(box.values.toList().indexOf(user), updatedUser);
|
||||||
|
emit(state.copyWith(
|
||||||
|
users: box.values.toList(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(Change<UsersState> change) {
|
||||||
|
print(change);
|
||||||
|
super.onChange(change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
part of 'users_cubit.dart';
|
part of 'users_cubit.dart';
|
||||||
|
|
||||||
class UsersState extends Equatable {
|
class UsersState extends Equatable {
|
||||||
const UsersState(this.users);
|
const UsersState(this.users, this.rootUser, this.primaryUser);
|
||||||
|
|
||||||
final List<User> users;
|
final List<User> users;
|
||||||
|
final User rootUser;
|
||||||
|
final User primaryUser;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => users;
|
List<Object> get props => [users, rootUser, primaryUser];
|
||||||
|
|
||||||
|
UsersState copyWith({
|
||||||
|
List<User>? users,
|
||||||
|
User? rootUser,
|
||||||
|
User? primaryUser,
|
||||||
|
}) {
|
||||||
|
return UsersState(
|
||||||
|
users ?? this.users,
|
||||||
|
rootUser ?? this.rootUser,
|
||||||
|
primaryUser ?? this.primaryUser,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool get isEmpty => users.isEmpty;
|
bool get isEmpty => users.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,5 +20,5 @@ class DnsRecord {
|
||||||
final int priority;
|
final int priority;
|
||||||
final bool proxied;
|
final bool proxied;
|
||||||
|
|
||||||
toJson() => _$DnsRecordsToJson(this);
|
toJson() => _$DnsRecordToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@ part of 'dns_records.dart';
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Map<String, dynamic> _$DnsRecordsToJson(DnsRecord instance) =>
|
Map<String, dynamic> _$DnsRecordToJson(DnsRecord instance) => <String, dynamic>{
|
||||||
<String, dynamic>{
|
|
||||||
'type': instance.type,
|
'type': instance.type,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'content': instance.content,
|
'content': instance.content,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
import 'user.dart';
|
import 'user.dart';
|
||||||
|
|
||||||
|
@ -68,8 +68,27 @@ class ServiceToggleJob extends ToggleJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateSSHKeyJob extends Job {
|
class CreateSSHKeyJob extends Job {
|
||||||
CreateSSHKeyJob() : super(title: '${"more.create_ssh_key".tr()}');
|
CreateSSHKeyJob({
|
||||||
|
required this.user,
|
||||||
|
required this.publicKey,
|
||||||
|
}) : super(title: '${"jobs.create_ssh_key".tr(args: [user.login])}');
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
final String publicKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [id, title];
|
List<Object> get props => [id, title, user, publicKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteSSHKeyJob extends Job {
|
||||||
|
DeleteSSHKeyJob({
|
||||||
|
required this.user,
|
||||||
|
required this.publicKey,
|
||||||
|
}) : super(title: '${"jobs.delete_ssh_key".tr(args: [user.login])}');
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
final String publicKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [id, title, user, publicKey];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:crypt/crypt.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/utils/color_utils.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/color_utils.dart';
|
||||||
|
|
||||||
part 'user.g.dart';
|
part 'user.g.dart';
|
||||||
|
|
||||||
|
@ -12,26 +10,33 @@ part 'user.g.dart';
|
||||||
class User extends Equatable {
|
class User extends Equatable {
|
||||||
User({
|
User({
|
||||||
required this.login,
|
required this.login,
|
||||||
required this.password,
|
this.password,
|
||||||
|
this.sshKeys = const [],
|
||||||
|
this.isFoundOnServer = true,
|
||||||
|
this.note,
|
||||||
});
|
});
|
||||||
|
|
||||||
@HiveField(0)
|
@HiveField(0)
|
||||||
final String login;
|
final String login;
|
||||||
|
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
final String password;
|
final String? password;
|
||||||
|
|
||||||
|
@HiveField(2, defaultValue: const [])
|
||||||
|
final List<String> sshKeys;
|
||||||
|
|
||||||
|
@HiveField(3, defaultValue: true)
|
||||||
|
final bool isFoundOnServer;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
final String? note;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [login, password];
|
List<Object?> get props => [login, password, sshKeys, isFoundOnServer, note];
|
||||||
|
|
||||||
Color get color => stringToColor(login);
|
Color get color => stringToColor(login);
|
||||||
|
|
||||||
Crypt get hashPassword => Crypt.sha512(
|
|
||||||
password,
|
|
||||||
salt: StringGenerators.passwordSalt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return login;
|
return '$login, ${isFoundOnServer ? 'found' : 'not found'}, ${sshKeys.length} ssh keys, note: $note';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,27 @@ class UserAdapter extends TypeAdapter<User> {
|
||||||
};
|
};
|
||||||
return User(
|
return User(
|
||||||
login: fields[0] as String,
|
login: fields[0] as String,
|
||||||
password: fields[1] as String,
|
password: fields[1] as String?,
|
||||||
|
sshKeys: fields[2] == null ? [] : (fields[2] as List).cast<String>(),
|
||||||
|
isFoundOnServer: fields[3] == null ? true : fields[3] as bool,
|
||||||
|
note: fields[4] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, User obj) {
|
void write(BinaryWriter writer, User obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(2)
|
..writeByte(5)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.login)
|
..write(obj.login)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
..write(obj.password);
|
..write(obj.password)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.sshKeys)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.isFoundOnServer)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.note);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,30 +1,21 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:ionicons/ionicons.dart';
|
import 'package:ionicons/ionicons.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
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_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.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/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/ssh_keys/ssh_keys.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
|
|
||||||
|
import '../../../logic/cubit/users/users_cubit.dart';
|
||||||
import 'about/about.dart';
|
import 'about/about.dart';
|
||||||
import 'app_settings/app_setting.dart';
|
import 'app_settings/app_setting.dart';
|
||||||
import 'console/console.dart';
|
import 'console/console.dart';
|
||||||
|
@ -83,73 +74,12 @@ class MorePage extends StatelessWidget {
|
||||||
iconData: BrandIcons.terminal,
|
iconData: BrandIcons.terminal,
|
||||||
goTo: Console(),
|
goTo: Console(),
|
||||||
),
|
),
|
||||||
_MoreMenuTapItem(
|
_NavItem(
|
||||||
title: 'more.create_ssh_key'.tr(),
|
title: 'more.create_ssh_key'.tr(),
|
||||||
iconData: Ionicons.key_outline,
|
iconData: Ionicons.key_outline,
|
||||||
onTap: isReady
|
goTo: SshKeysPage(
|
||||||
? () {
|
user: context.read<UsersCubit>().state.rootUser,
|
||||||
if (getIt<SSHModel>().isSSHKeyGenerated) {
|
)),
|
||||||
showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return _SSHExitsDetails(
|
|
||||||
onShareTap: () {
|
|
||||||
Share.share(
|
|
||||||
getIt<SSHModel>().savedPrivateKey!);
|
|
||||||
},
|
|
||||||
onDeleteTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) {
|
|
||||||
return BrandAlert(
|
|
||||||
title: 'modals.3'.tr(),
|
|
||||||
contentText:
|
|
||||||
'more.delete_ssh_text'.tr(),
|
|
||||||
actions: [
|
|
||||||
ActionButton(
|
|
||||||
text: 'more.yes_delete'.tr(),
|
|
||||||
isRed: true,
|
|
||||||
onPressed: () {
|
|
||||||
getIt<SSHModel>().clear();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}),
|
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onCopyTap: () {
|
|
||||||
Clipboard.setData(ClipboardData(
|
|
||||||
text: getIt<SSHModel>()
|
|
||||||
.savedPrivateKey!));
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('more.copied_ssh'.tr());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return _MoreDetails(
|
|
||||||
title: 'more.create_ssh_key'.tr(),
|
|
||||||
icon: Ionicons.key_outline,
|
|
||||||
onTap: () {
|
|
||||||
jobsCubit.createShhJobIfNotExist(
|
|
||||||
CreateSSHKeyJob());
|
|
||||||
},
|
|
||||||
text: 'more.generate_key_text'.tr(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -159,150 +89,6 @@ class MorePage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SSHExitsDetails extends StatelessWidget {
|
|
||||||
const _SSHExitsDetails({
|
|
||||||
Key? key,
|
|
||||||
required this.onDeleteTap,
|
|
||||||
required this.onShareTap,
|
|
||||||
required this.onCopyTap,
|
|
||||||
}) : super(key: key);
|
|
||||||
final Function onDeleteTap;
|
|
||||||
final Function onShareTap;
|
|
||||||
final Function onCopyTap;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var textStyle = body1Style.copyWith(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white
|
|
||||||
: BrandColors.black);
|
|
||||||
return Dialog(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Container(
|
|
||||||
width: 350,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: paddingH15V30,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
'more.ssh_key_exist_text'.tr(),
|
|
||||||
style: textStyle,
|
|
||||||
),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Container(
|
|
||||||
child: BrandButton.text(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onShareTap();
|
|
||||||
},
|
|
||||||
title: 'more.share'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: BrandButton.text(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onDeleteTap();
|
|
||||||
},
|
|
||||||
title: 'basis.delete'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
child: BrandButton.text(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onCopyTap();
|
|
||||||
},
|
|
||||||
title: 'more.copy_buffer'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MoreDetails extends StatelessWidget {
|
|
||||||
const _MoreDetails({
|
|
||||||
Key? key,
|
|
||||||
required this.icon,
|
|
||||||
required this.title,
|
|
||||||
required this.onTap,
|
|
||||||
required this.text,
|
|
||||||
}) : super(key: key);
|
|
||||||
final String title;
|
|
||||||
final IconData icon;
|
|
||||||
final Function onTap;
|
|
||||||
final String text;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var textStyle = body1Style.copyWith(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white
|
|
||||||
: BrandColors.black);
|
|
||||||
return Dialog(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Container(
|
|
||||||
width: 350,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: paddingH15V30,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
IconStatusMask(
|
|
||||||
status: StateType.stable,
|
|
||||||
child: Icon(icon, size: 40, color: Colors.white),
|
|
||||||
),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
BrandText.h2(title),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
style: textStyle,
|
|
||||||
),
|
|
||||||
SizedBox(height: 40),
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
child: BrandButton.rised(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onTap();
|
|
||||||
},
|
|
||||||
text: 'more.generate_key'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavItem extends StatelessWidget {
|
class _NavItem extends StatelessWidget {
|
||||||
const _NavItem({
|
const _NavItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.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_icons/brand_icons.dart';
|
||||||
|
|
||||||
|
import '../../../logic/models/user.dart';
|
||||||
|
|
||||||
|
// Get user object as a parameter
|
||||||
|
class SshKeysPage extends StatefulWidget {
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
SshKeysPage({Key? key, required this.user}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SshKeysPageState createState() => _SshKeysPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SshKeysPageState extends State<SshKeysPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BrandHeroScreen(
|
||||||
|
heroTitle: 'ssh.title'.tr(),
|
||||||
|
heroSubtitle: widget.user.login,
|
||||||
|
heroIcon: BrandIcons.key,
|
||||||
|
children: <Widget>[
|
||||||
|
if (widget.user.login == 'root')
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// Show alert card if user is root
|
||||||
|
BrandCards.outlined(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.warning_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
title: Text('ssh.root.title'.tr()),
|
||||||
|
subtitle: Text('ssh.root.subtitle'.tr()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
BrandCards.outlined(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
'ssh.create'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
leading: Icon(Icons.add_circle_outline_rounded),
|
||||||
|
),
|
||||||
|
Divider(height: 0),
|
||||||
|
// show a list of ListTiles with ssh keys
|
||||||
|
// Clicking on one should delete it
|
||||||
|
Column(
|
||||||
|
children: widget.user.sshKeys.map((key) {
|
||||||
|
return ListTile(
|
||||||
|
title:
|
||||||
|
Text('${key.split(' ')[2]} (${key.split(' ')[0]})'),
|
||||||
|
// do not overflow text
|
||||||
|
subtitle: Text(key.split(' ')[1],
|
||||||
|
maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: delete ssh key
|
||||||
|
});
|
||||||
|
}).toList(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,11 @@ class _User extends StatelessWidget {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: isRootUser
|
child: isRootUser
|
||||||
? BrandText.h4Underlined(user.login)
|
? BrandText.h4Underlined(user.login)
|
||||||
: BrandText.h4(user.login),
|
// cross out text if user not found on server
|
||||||
|
: BrandText.h4(user.login,
|
||||||
|
style: user.isFoundOnServer
|
||||||
|
? null
|
||||||
|
: TextStyle(decoration: TextDecoration.lineThrough)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -141,13 +141,19 @@ class _UserDetails extends StatelessWidget {
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: BrandText.h4('${user.login}@$domainName'),
|
child: BrandText.h4('${user.login}@$domainName'),
|
||||||
),
|
),
|
||||||
SizedBox(height: 14),
|
if (user.password != null)
|
||||||
BrandText.small('basis.password'.tr()),
|
Column(
|
||||||
Container(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
height: 40,
|
children: [
|
||||||
alignment: Alignment.centerLeft,
|
SizedBox(height: 14),
|
||||||
child: BrandText.h4(user.password),
|
BrandText.small('basis.password'.tr()),
|
||||||
),
|
Container(
|
||||||
|
height: 40,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: BrandText.h4(user.password),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
|
@ -160,6 +166,19 @@ class _UserDetails extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
|
BrandDivider(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(materialRoute(SshKeysPage(user: user)));
|
||||||
|
},
|
||||||
|
title: Text('ssh.title'.tr()),
|
||||||
|
subtitle: user.sshKeys.length > 0
|
||||||
|
? Text('ssh.subtitle_with_keys'
|
||||||
|
.tr(args: [user.sshKeys.length.toString()]))
|
||||||
|
: Text('ssh.subtitle_without_keys'.tr()),
|
||||||
|
trailing: Icon(BrandIcons.key)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
|
||||||
|
@ -19,16 +18,18 @@ 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_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/ssh_keys/ssh_keys.dart';
|
||||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
import '../../../utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
part 'empty.dart';
|
||||||
part 'fab.dart';
|
part 'fab.dart';
|
||||||
part 'new_user.dart';
|
part 'new_user.dart';
|
||||||
part 'user_details.dart';
|
|
||||||
part 'user.dart';
|
part 'user.dart';
|
||||||
part 'empty.dart';
|
part 'user_details.dart';
|
||||||
|
|
||||||
class UsersPage extends StatelessWidget {
|
class UsersPage extends StatelessWidget {
|
||||||
const UsersPage({Key? key}) : super(key: key);
|
const UsersPage({Key? key}) : super(key: key);
|
||||||
|
@ -37,12 +38,8 @@ 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 primaryUser = usersCubitState.primaryUser;
|
||||||
//Todo: listen box events
|
final users = [primaryUser, ...usersCubitState.users];
|
||||||
User? user = Hive.box(BNames.appConfig).get(BNames.rootUser);
|
|
||||||
if (user != null) {
|
|
||||||
users.insert(0, user);
|
|
||||||
}
|
|
||||||
final isEmpty = users.isEmpty;
|
final isEmpty = users.isEmpty;
|
||||||
Widget child;
|
Widget child;
|
||||||
|
|
||||||
|
@ -56,14 +53,19 @@ class UsersPage extends StatelessWidget {
|
||||||
text: 'users.add_new_user'.tr(),
|
text: 'users.add_new_user'.tr(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: RefreshIndicator(
|
||||||
itemCount: users.length,
|
onRefresh: () async {
|
||||||
itemBuilder: (BuildContext context, int index) {
|
context.read<UsersCubit>().refresh();
|
||||||
return _User(
|
|
||||||
user: users[index],
|
|
||||||
isRootUser: index == 0,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: users.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return _User(
|
||||||
|
user: users[index],
|
||||||
|
isRootUser: index == 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
81
pubspec.lock
81
pubspec.lock
|
@ -21,7 +21,7 @@ packages:
|
||||||
name: archive
|
name: archive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.11"
|
version: "3.2.0"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -56,7 +56,7 @@ packages:
|
||||||
name: basic_utils
|
name: basic_utils
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.9.4"
|
version: "4.2.0"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -301,7 +301,7 @@ packages:
|
||||||
name: fl_chart
|
name: fl_chart
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.40.6"
|
version: "0.45.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -320,7 +320,7 @@ packages:
|
||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.0"
|
version: "0.9.2"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -346,7 +346,42 @@ packages:
|
||||||
name: flutter_secure_storage
|
name: flutter_secure_storage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "5.0.2"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -419,7 +454,7 @@ packages:
|
||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -482,7 +517,7 @@ packages:
|
||||||
name: local_auth
|
name: local_auth
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.10"
|
version: "1.1.11"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -587,7 +622,7 @@ packages:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.9"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -657,7 +692,7 @@ packages:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.5.0"
|
version: "3.5.1"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -720,7 +755,7 @@ packages:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "3.0.5"
|
||||||
share_plus_linux:
|
share_plus_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -769,28 +804,28 @@ packages:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.10"
|
version: "2.0.11"
|
||||||
shared_preferences_ios:
|
shared_preferences_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_ios
|
name: shared_preferences_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.9"
|
version: "2.1.0"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.1.0"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_macos
|
name: shared_preferences_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -811,7 +846,7 @@ packages:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.1.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -977,35 +1012,35 @@ packages:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.18"
|
version: "6.0.20"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.14"
|
version: "6.0.15"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.14"
|
version: "6.0.15"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "3.0.0"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "3.0.0"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1026,7 +1061,7 @@ packages:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "3.0.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1047,7 +1082,7 @@ packages:
|
||||||
name: wakelock
|
name: wakelock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.6"
|
version: "0.6.1+1"
|
||||||
wakelock_macos:
|
wakelock_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1103,7 +1138,7 @@ packages:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.11"
|
version: "2.4.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
62
pubspec.yaml
62
pubspec.yaml
|
@ -8,45 +8,45 @@ environment:
|
||||||
flutter: ">=2.10.0"
|
flutter: ">=2.10.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
auto_size_text: 3.0.0-nullsafety.0
|
auto_size_text: ^3.0.0
|
||||||
basic_utils: 3.4.0
|
basic_utils: ^4.2.0
|
||||||
crypt: 4.0.1
|
crypt: ^4.2.1
|
||||||
cubit_form: 2.0.1
|
cubit_form: ^2.0.1
|
||||||
cupertino_icons: 1.0.2
|
cupertino_icons: ^1.0.4
|
||||||
dio: 4.0.1
|
dio: ^4.0.4
|
||||||
easy_localization: 3.0.0
|
easy_localization: ^3.0.0
|
||||||
either_option: 2.0.1-dev.1
|
either_option: ^2.0.1-dev.1
|
||||||
equatable: 2.0.3
|
equatable: ^2.0.3
|
||||||
fl_chart: 0.40.0
|
fl_chart: ^0.45.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_bloc: 8.0.1
|
flutter_bloc: ^8.0.1
|
||||||
flutter_markdown: 0.6.9
|
flutter_markdown: ^0.6.9
|
||||||
flutter_secure_storage: 4.2.1
|
flutter_secure_storage: ^5.0.2
|
||||||
get_it: 7.2.0
|
get_it: ^7.2.0
|
||||||
hive: 2.0.5
|
hive: ^2.0.5
|
||||||
hive_flutter: 1.1.0
|
hive_flutter: ^1.1.0
|
||||||
ionicons: 0.1.2
|
ionicons: ^0.1.2
|
||||||
json_annotation: 4.3.0
|
json_annotation: ^4.4.0
|
||||||
local_auth: 1.1.7
|
local_auth: ^1.1.11
|
||||||
modal_bottom_sheet: 2.0.0
|
modal_bottom_sheet: ^2.0.0
|
||||||
nanoid: 1.0.0
|
nanoid: ^1.0.0
|
||||||
package_info: 2.0.0
|
package_info: ^2.0.2
|
||||||
pointycastle: 3.3.2
|
pointycastle: ^3.5.1
|
||||||
pretty_dio_logger: 1.2.0-beta-1
|
pretty_dio_logger: ^1.2.0-beta-1
|
||||||
provider: 6.0.0
|
provider: ^6.0.2
|
||||||
rsa_encrypt: 2.0.0
|
rsa_encrypt: ^2.0.0
|
||||||
share_plus: 2.1.4
|
share_plus: ^3.0.5
|
||||||
ssh_key: 0.7.0
|
ssh_key: ^0.7.1
|
||||||
timezone: ^0.8.0
|
timezone: ^0.8.0
|
||||||
url_launcher: 6.0.2
|
url_launcher: ^6.0.20
|
||||||
wakelock: 0.5.0+2
|
wakelock: ^0.6.1+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.1.1
|
build_runner: ^2.1.1
|
||||||
flutter_launcher_icons: ^0.9.0
|
flutter_launcher_icons: ^0.9.2
|
||||||
hive_generator: ^1.0.0
|
hive_generator: ^1.0.0
|
||||||
json_serializable: ^6.1.4
|
json_serializable: ^6.1.4
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue