From bba29caeba90639e4166b7cf2f58a611a76d4feb Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sat, 9 Mar 2024 19:30:43 +0300 Subject: [PATCH] feat: Add ssh settings --- assets/translations/en.json | 10 +- .../schema/server_settings.graphql | 10 + .../schema/server_settings.graphql.dart | 780 ++++++++++++++++++ .../graphql_maps/server_api/server_api.dart | 44 + .../server_detailed_info_cubit.dart | 2 + .../server_detailed_info_state.dart | 6 +- .../get_it/api_connection_repository.dart | 19 + lib/logic/models/job.dart | 44 + .../pages/server_details/server_settings.dart | 73 ++ .../server_settings_screen.dart | 14 +- lib/ui/pages/users/user_details.dart | 181 ++-- lib/ui/pages/users/users.dart | 1 + 12 files changed, 1094 insertions(+), 90 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 2c0c50a7..d78873c9 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -95,7 +95,8 @@ "no_key_name": "Unnamed key", "root_title": "These are superuser keys", "root_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, ECDSA or RSA key" + "input_label": "Public ED25519, ECDSA or RSA key", + "ssh_disabled_warning": "SSH is disabled. You can enable it in the server settings." }, "onboarding": { "page1_title": "Digital independence, available to all of us", @@ -130,6 +131,10 @@ "reboot_after_upgrade_hint": "Reboot without prompt after applying changes on server", "server_timezone": "Server timezone", "select_timezone": "Select timezone", + "enable_ssh": "Enable SSH", + "enable_ssh_hint": "Allow SSH access to the server", + "allow_password_authentication": "Allow password authentication for SSH", + "allow_password_authentication_hint": "Allow users to log into your server's shell with a password (does not apply to root user)", "timezone_search_bar": "Timezone name or time shift value", "server_id": "Server ID", "status": "Status", @@ -626,7 +631,8 @@ "rebuild_system": "Rebuild system", "start_server_upgrade": "Start the server upgrade", "change_auto_upgrade_settings": "Change auto-upgrade settings", - "change_server_timezone": "Change server timezone" + "change_server_timezone": "Change server timezone", + "change_ssh_settings": "Change SSH settings" }, "validations": { "required": "Required", diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql index ca6c5078..f7d93d92 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql @@ -62,3 +62,13 @@ mutation ChangeAutoUpgradeSettings($settings: AutoUpgradeSettingsInput!) { } } } + +mutation ChangeSshSettings($settings: SSHSettingsInput!) { + system { + changeSshSettings(settings: $settings) { + ...basicMutationReturnFields + enable + passwordAuthentication + } + } +} diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart index 6e461a41..1a70fc06 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart @@ -4123,3 +4123,783 @@ class _CopyWithStubImpl$Mutation$ChangeAutoUpgradeSettings$system$changeAutoUpgr }) => _res; } + +class Variables$Mutation$ChangeSshSettings { + factory Variables$Mutation$ChangeSshSettings( + {required Input$SSHSettingsInput settings}) => + Variables$Mutation$ChangeSshSettings._({ + r'settings': settings, + }); + + Variables$Mutation$ChangeSshSettings._(this._$data); + + factory Variables$Mutation$ChangeSshSettings.fromJson( + Map data) { + final result$data = {}; + final l$settings = data['settings']; + result$data['settings'] = + Input$SSHSettingsInput.fromJson((l$settings as Map)); + return Variables$Mutation$ChangeSshSettings._(result$data); + } + + Map _$data; + + Input$SSHSettingsInput get settings => + (_$data['settings'] as Input$SSHSettingsInput); + + Map toJson() { + final result$data = {}; + final l$settings = settings; + result$data['settings'] = l$settings.toJson(); + return result$data; + } + + CopyWith$Variables$Mutation$ChangeSshSettings< + Variables$Mutation$ChangeSshSettings> + get copyWith => CopyWith$Variables$Mutation$ChangeSshSettings( + this, + (i) => i, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Variables$Mutation$ChangeSshSettings) || + runtimeType != other.runtimeType) { + return false; + } + final l$settings = settings; + final lOther$settings = other.settings; + if (l$settings != lOther$settings) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$settings = settings; + return Object.hashAll([l$settings]); + } +} + +abstract class CopyWith$Variables$Mutation$ChangeSshSettings { + factory CopyWith$Variables$Mutation$ChangeSshSettings( + Variables$Mutation$ChangeSshSettings instance, + TRes Function(Variables$Mutation$ChangeSshSettings) then, + ) = _CopyWithImpl$Variables$Mutation$ChangeSshSettings; + + factory CopyWith$Variables$Mutation$ChangeSshSettings.stub(TRes res) = + _CopyWithStubImpl$Variables$Mutation$ChangeSshSettings; + + TRes call({Input$SSHSettingsInput? settings}); +} + +class _CopyWithImpl$Variables$Mutation$ChangeSshSettings + implements CopyWith$Variables$Mutation$ChangeSshSettings { + _CopyWithImpl$Variables$Mutation$ChangeSshSettings( + this._instance, + this._then, + ); + + final Variables$Mutation$ChangeSshSettings _instance; + + final TRes Function(Variables$Mutation$ChangeSshSettings) _then; + + static const _undefined = {}; + + TRes call({Object? settings = _undefined}) => + _then(Variables$Mutation$ChangeSshSettings._({ + ..._instance._$data, + if (settings != _undefined && settings != null) + 'settings': (settings as Input$SSHSettingsInput), + })); +} + +class _CopyWithStubImpl$Variables$Mutation$ChangeSshSettings + implements CopyWith$Variables$Mutation$ChangeSshSettings { + _CopyWithStubImpl$Variables$Mutation$ChangeSshSettings(this._res); + + TRes _res; + + call({Input$SSHSettingsInput? settings}) => _res; +} + +class Mutation$ChangeSshSettings { + Mutation$ChangeSshSettings({ + required this.system, + this.$__typename = 'Mutation', + }); + + factory Mutation$ChangeSshSettings.fromJson(Map json) { + final l$system = json['system']; + final l$$__typename = json['__typename']; + return Mutation$ChangeSshSettings( + system: Mutation$ChangeSshSettings$system.fromJson( + (l$system as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$ChangeSshSettings$system system; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$system = system; + _resultData['system'] = l$system.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$system = system; + final l$$__typename = $__typename; + return Object.hashAll([ + l$system, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$ChangeSshSettings) || + runtimeType != other.runtimeType) { + return false; + } + final l$system = system; + final lOther$system = other.system; + if (l$system != lOther$system) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$ChangeSshSettings + on Mutation$ChangeSshSettings { + CopyWith$Mutation$ChangeSshSettings + get copyWith => CopyWith$Mutation$ChangeSshSettings( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$ChangeSshSettings { + factory CopyWith$Mutation$ChangeSshSettings( + Mutation$ChangeSshSettings instance, + TRes Function(Mutation$ChangeSshSettings) then, + ) = _CopyWithImpl$Mutation$ChangeSshSettings; + + factory CopyWith$Mutation$ChangeSshSettings.stub(TRes res) = + _CopyWithStubImpl$Mutation$ChangeSshSettings; + + TRes call({ + Mutation$ChangeSshSettings$system? system, + String? $__typename, + }); + CopyWith$Mutation$ChangeSshSettings$system get system; +} + +class _CopyWithImpl$Mutation$ChangeSshSettings + implements CopyWith$Mutation$ChangeSshSettings { + _CopyWithImpl$Mutation$ChangeSshSettings( + this._instance, + this._then, + ); + + final Mutation$ChangeSshSettings _instance; + + final TRes Function(Mutation$ChangeSshSettings) _then; + + static const _undefined = {}; + + TRes call({ + Object? system = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$ChangeSshSettings( + system: system == _undefined || system == null + ? _instance.system + : (system as Mutation$ChangeSshSettings$system), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$ChangeSshSettings$system get system { + final local$system = _instance.system; + return CopyWith$Mutation$ChangeSshSettings$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Mutation$ChangeSshSettings + implements CopyWith$Mutation$ChangeSshSettings { + _CopyWithStubImpl$Mutation$ChangeSshSettings(this._res); + + TRes _res; + + call({ + Mutation$ChangeSshSettings$system? system, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$ChangeSshSettings$system get system => + CopyWith$Mutation$ChangeSshSettings$system.stub(_res); +} + +const documentNodeMutationChangeSshSettings = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'ChangeSshSettings'), + variableDefinitions: [ + VariableDefinitionNode( + variable: VariableNode(name: NameNode(value: 'settings')), + type: NamedTypeNode( + name: NameNode(value: 'SSHSettingsInput'), + isNonNull: true, + ), + defaultValue: DefaultValueNode(value: null), + directives: [], + ) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'system'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'changeSshSettings'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'settings'), + value: VariableNode(name: NameNode(value: 'settings')), + ) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicMutationReturnFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: 'enable'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'passwordAuthentication'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionbasicMutationReturnFields, +]); +Mutation$ChangeSshSettings _parserFn$Mutation$ChangeSshSettings( + Map data) => + Mutation$ChangeSshSettings.fromJson(data); +typedef OnMutationCompleted$Mutation$ChangeSshSettings = FutureOr + Function( + Map?, + Mutation$ChangeSshSettings?, +); + +class Options$Mutation$ChangeSshSettings + extends graphql.MutationOptions { + Options$Mutation$ChangeSshSettings({ + String? operationName, + required Variables$Mutation$ChangeSshSettings variables, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$ChangeSshSettings? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$ChangeSshSettings? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + variables: variables.toJson(), + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$ChangeSshSettings(data), + ), + update: update, + onError: onError, + document: documentNodeMutationChangeSshSettings, + parserFn: _parserFn$Mutation$ChangeSshSettings, + ); + + final OnMutationCompleted$Mutation$ChangeSshSettings? onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$ChangeSshSettings + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$ChangeSshSettings({ + String? operationName, + required Variables$Mutation$ChangeSshSettings variables, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$ChangeSshSettings? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + variables: variables.toJson(), + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationChangeSshSettings, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$ChangeSshSettings, + ); +} + +extension ClientExtension$Mutation$ChangeSshSettings on graphql.GraphQLClient { + Future> + mutate$ChangeSshSettings( + Options$Mutation$ChangeSshSettings options) async => + await this.mutate(options); + graphql.ObservableQuery + watchMutation$ChangeSshSettings( + WatchOptions$Mutation$ChangeSshSettings options) => + this.watchMutation(options); +} + +class Mutation$ChangeSshSettings$system { + Mutation$ChangeSshSettings$system({ + required this.changeSshSettings, + this.$__typename = 'SystemMutations', + }); + + factory Mutation$ChangeSshSettings$system.fromJson( + Map json) { + final l$changeSshSettings = json['changeSshSettings']; + final l$$__typename = json['__typename']; + return Mutation$ChangeSshSettings$system( + changeSshSettings: + Mutation$ChangeSshSettings$system$changeSshSettings.fromJson( + (l$changeSshSettings as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$ChangeSshSettings$system$changeSshSettings changeSshSettings; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$changeSshSettings = changeSshSettings; + _resultData['changeSshSettings'] = l$changeSshSettings.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$changeSshSettings = changeSshSettings; + final l$$__typename = $__typename; + return Object.hashAll([ + l$changeSshSettings, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$ChangeSshSettings$system) || + runtimeType != other.runtimeType) { + return false; + } + final l$changeSshSettings = changeSshSettings; + final lOther$changeSshSettings = other.changeSshSettings; + if (l$changeSshSettings != lOther$changeSshSettings) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$ChangeSshSettings$system + on Mutation$ChangeSshSettings$system { + CopyWith$Mutation$ChangeSshSettings$system + get copyWith => CopyWith$Mutation$ChangeSshSettings$system( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$ChangeSshSettings$system { + factory CopyWith$Mutation$ChangeSshSettings$system( + Mutation$ChangeSshSettings$system instance, + TRes Function(Mutation$ChangeSshSettings$system) then, + ) = _CopyWithImpl$Mutation$ChangeSshSettings$system; + + factory CopyWith$Mutation$ChangeSshSettings$system.stub(TRes res) = + _CopyWithStubImpl$Mutation$ChangeSshSettings$system; + + TRes call({ + Mutation$ChangeSshSettings$system$changeSshSettings? changeSshSettings, + String? $__typename, + }); + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings + get changeSshSettings; +} + +class _CopyWithImpl$Mutation$ChangeSshSettings$system + implements CopyWith$Mutation$ChangeSshSettings$system { + _CopyWithImpl$Mutation$ChangeSshSettings$system( + this._instance, + this._then, + ); + + final Mutation$ChangeSshSettings$system _instance; + + final TRes Function(Mutation$ChangeSshSettings$system) _then; + + static const _undefined = {}; + + TRes call({ + Object? changeSshSettings = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$ChangeSshSettings$system( + changeSshSettings: + changeSshSettings == _undefined || changeSshSettings == null + ? _instance.changeSshSettings + : (changeSshSettings + as Mutation$ChangeSshSettings$system$changeSshSettings), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings + get changeSshSettings { + final local$changeSshSettings = _instance.changeSshSettings; + return CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings( + local$changeSshSettings, (e) => call(changeSshSettings: e)); + } +} + +class _CopyWithStubImpl$Mutation$ChangeSshSettings$system + implements CopyWith$Mutation$ChangeSshSettings$system { + _CopyWithStubImpl$Mutation$ChangeSshSettings$system(this._res); + + TRes _res; + + call({ + Mutation$ChangeSshSettings$system$changeSshSettings? changeSshSettings, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings + get changeSshSettings => + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings.stub( + _res); +} + +class Mutation$ChangeSshSettings$system$changeSshSettings + implements Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn { + Mutation$ChangeSshSettings$system$changeSshSettings({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'SSHSettingsMutationReturn', + required this.enable, + required this.passwordAuthentication, + }); + + factory Mutation$ChangeSshSettings$system$changeSshSettings.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + final l$enable = json['enable']; + final l$passwordAuthentication = json['passwordAuthentication']; + return Mutation$ChangeSshSettings$system$changeSshSettings( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + enable: (l$enable as bool), + passwordAuthentication: (l$passwordAuthentication as bool), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + final bool enable; + + final bool passwordAuthentication; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + final l$enable = enable; + _resultData['enable'] = l$enable; + final l$passwordAuthentication = passwordAuthentication; + _resultData['passwordAuthentication'] = l$passwordAuthentication; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + final l$enable = enable; + final l$passwordAuthentication = passwordAuthentication; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + l$enable, + l$passwordAuthentication, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$ChangeSshSettings$system$changeSshSettings) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + final l$enable = enable; + final lOther$enable = other.enable; + if (l$enable != lOther$enable) { + return false; + } + final l$passwordAuthentication = passwordAuthentication; + final lOther$passwordAuthentication = other.passwordAuthentication; + if (l$passwordAuthentication != lOther$passwordAuthentication) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$ChangeSshSettings$system$changeSshSettings + on Mutation$ChangeSshSettings$system$changeSshSettings { + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings< + Mutation$ChangeSshSettings$system$changeSshSettings> + get copyWith => + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings< + TRes> { + factory CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings( + Mutation$ChangeSshSettings$system$changeSshSettings instance, + TRes Function(Mutation$ChangeSshSettings$system$changeSshSettings) then, + ) = _CopyWithImpl$Mutation$ChangeSshSettings$system$changeSshSettings; + + factory CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings.stub( + TRes res) = + _CopyWithStubImpl$Mutation$ChangeSshSettings$system$changeSshSettings; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + bool? enable, + bool? passwordAuthentication, + }); +} + +class _CopyWithImpl$Mutation$ChangeSshSettings$system$changeSshSettings + implements + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings { + _CopyWithImpl$Mutation$ChangeSshSettings$system$changeSshSettings( + this._instance, + this._then, + ); + + final Mutation$ChangeSshSettings$system$changeSshSettings _instance; + + final TRes Function(Mutation$ChangeSshSettings$system$changeSshSettings) + _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + Object? enable = _undefined, + Object? passwordAuthentication = _undefined, + }) => + _then(Mutation$ChangeSshSettings$system$changeSshSettings( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + enable: enable == _undefined || enable == null + ? _instance.enable + : (enable as bool), + passwordAuthentication: passwordAuthentication == _undefined || + passwordAuthentication == null + ? _instance.passwordAuthentication + : (passwordAuthentication as bool), + )); +} + +class _CopyWithStubImpl$Mutation$ChangeSshSettings$system$changeSshSettings< + TRes> + implements + CopyWith$Mutation$ChangeSshSettings$system$changeSshSettings { + _CopyWithStubImpl$Mutation$ChangeSshSettings$system$changeSshSettings( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + bool? enable, + bool? passwordAuthentication, + }) => + _res; +} diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index 31c09a74..e2efb41b 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -258,6 +258,50 @@ class ServerApi extends GraphQLApiMap } } + Future> setSshSettings( + final SshSettings settings, + ) async { + try { + final GraphQLClient client = await getClient(); + final input = Input$SSHSettingsInput( + enable: settings.enable, + passwordAuthentication: settings.passwordAuthentication, + ); + final variables = Variables$Mutation$ChangeSshSettings( + settings: input, + ); + final mutation = Options$Mutation$ChangeSshSettings( + variables: variables, + ); + final result = await client.mutate$ChangeSshSettings(mutation); + if (result.hasException) { + return GenericResult( + success: false, + message: result.exception.toString(), + data: null, + ); + } + return GenericResult( + success: result.parsedData?.system.changeSshSettings.success ?? false, + message: result.parsedData?.system.changeSshSettings.message, + data: result.parsedData == null + ? null + : SshSettings( + enable: result.parsedData!.system.changeSshSettings.enable, + passwordAuthentication: result.parsedData!.system + .changeSshSettings.passwordAuthentication, + ), + ); + } catch (e) { + print(e); + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); + } + } + Future getSystemSettings() async { QueryResult response; SystemSettings settings = SystemSettings( diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart index a2b53f59..d6e548f8 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart @@ -4,6 +4,7 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart'; +import 'package:selfprivacy/logic/models/ssh_settings.dart'; import 'package:selfprivacy/logic/models/system_settings.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; import 'package:selfprivacy/logic/providers/providers_controller.dart'; @@ -31,6 +32,7 @@ class ServerDetailsCubit metadata: state.metadata, serverTimezone: TimeZoneSettings.fromString(settings.timezone), autoUpgradeSettings: settings.autoUpgradeSettings, + sshSettings: settings.sshSettings, ), ); } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart index 8fb3a6c7..f8589f82 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart @@ -52,15 +52,18 @@ class Loaded extends ServerDetailsState { required super.metadata, required this.serverTimezone, required this.autoUpgradeSettings, + required this.sshSettings, }); final TimeZoneSettings serverTimezone; final AutoUpgradeSettings autoUpgradeSettings; + final SshSettings sshSettings; @override List get props => [ metadata, serverTimezone, autoUpgradeSettings, + sshSettings, ]; @override @@ -68,11 +71,12 @@ class Loaded extends ServerDetailsState { final List? metadata, final TimeZoneSettings? serverTimezone, final AutoUpgradeSettings? autoUpgradeSettings, - final DateTime? checkTime, + final SshSettings? sshSettings, }) => Loaded( metadata: metadata ?? this.metadata, serverTimezone: serverTimezone ?? this.serverTimezone, autoUpgradeSettings: autoUpgradeSettings ?? this.autoUpgradeSettings, + sshSettings: sshSettings ?? this.sshSettings, ); } diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 78968a45..b46c9e81 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -16,6 +16,7 @@ import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; +import 'package:selfprivacy/logic/models/ssh_settings.dart'; import 'package:selfprivacy/logic/models/system_settings.dart'; /// Repository for all API calls @@ -220,6 +221,24 @@ class ApiConnectionRepository { } } + Future<(bool, String)> setSshSettings( + final bool enable, + final bool passwordAuthentication, + ) async { + final GenericResult result = await api.setSshSettings( + SshSettings( + enable: enable, + passwordAuthentication: passwordAuthentication, + ), + ); + _apiData.settings.invalidate(); + if (result.data != null) { + return (true, result.message ?? 'basis.done'.tr()); + } else { + return (false, result.message ?? 'jobs.generic_error'.tr()); + } + } + void dispose() { _dataStream.close(); _connectionStatusStream.close(); diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index 0cda1407..896da72a 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -399,3 +399,47 @@ class ChangeServerTimezoneJob extends ReplaceableJob { id: id, ); } + +class ChangeSshSettingsJob extends ReplaceableJob { + ChangeSshSettingsJob({ + required this.enable, + required this.passwordAuthentication, + super.status, + super.message, + super.id, + }) : super(title: 'jobs.change_ssh_settings'.tr()); + + final bool enable; + final bool passwordAuthentication; + + @override + Future<(bool, String)> execute() async => getIt() + .setSshSettings(enable, passwordAuthentication); + + @override + bool shouldRemoveInsteadOfAdd(final List jobs) { + final currentSettings = + getIt().apiData.settings.data?.sshSettings; + if (currentSettings == null) { + return false; + } + return currentSettings.enable == enable && + currentSettings.passwordAuthentication == passwordAuthentication; + } + + @override + List get props => [...super.props, enable, passwordAuthentication]; + + @override + ChangeSshSettingsJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ChangeSshSettingsJob( + enable: enable, + passwordAuthentication: passwordAuthentication, + status: status, + message: message, + id: id, + ); +} diff --git a/lib/ui/pages/server_details/server_settings.dart b/lib/ui/pages/server_details/server_settings.dart index df343ca1..f3a78292 100644 --- a/lib/ui/pages/server_details/server_settings.dart +++ b/lib/ui/pages/server_details/server_settings.dart @@ -10,6 +10,8 @@ class _ServerSettings extends StatefulWidget { class _ServerSettingsState extends State<_ServerSettings> { bool? allowAutoUpgrade; bool? rebootAfterUpgrade; + bool? enableSsh; + bool? allowPasswordAuthentication; @override Widget build(final BuildContext context) { @@ -24,6 +26,12 @@ class _ServerSettingsState extends State<_ServerSettings> { rebootAfterUpgrade = serverDetailsState.autoUpgradeSettings.allowReboot; } + if (enableSsh == null || allowPasswordAuthentication == null) { + enableSsh = serverDetailsState.sshSettings.enable; + allowPasswordAuthentication = + serverDetailsState.sshSettings.passwordAuthentication; + } + return Column( children: [ SwitchListTile( @@ -105,6 +113,71 @@ class _ServerSettingsState extends State<_ServerSettings> { ); }, ), + SwitchListTile( + value: enableSsh ?? true, + onChanged: (final switched) { + context.read().addJob( + ChangeSshSettingsJob( + enable: switched, + passwordAuthentication: + allowPasswordAuthentication ?? false, + ), + ); + setState(() { + enableSsh = switched; + }); + }, + title: Text( + 'server.enable_ssh'.tr(), + style: TextStyle( + fontStyle: enableSsh != serverDetailsState.sshSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), + ), + subtitle: Text( + 'server.enable_ssh_hint'.tr(), + style: TextStyle( + fontStyle: enableSsh != serverDetailsState.sshSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), + ), + activeColor: Theme.of(context).colorScheme.primary, + ), + SwitchListTile( + value: allowPasswordAuthentication ?? false, + onChanged: (final switched) { + context.read().addJob( + ChangeSshSettingsJob( + enable: enableSsh ?? true, + passwordAuthentication: switched, + ), + ); + setState(() { + allowPasswordAuthentication = switched; + }); + }, + title: Text( + 'server.allow_password_authentication'.tr(), + style: TextStyle( + fontStyle: allowPasswordAuthentication != + serverDetailsState.sshSettings.passwordAuthentication + ? FontStyle.italic + : FontStyle.normal, + ), + ), + subtitle: Text( + 'server.allow_password_authentication_hint'.tr(), + style: TextStyle( + fontStyle: allowPasswordAuthentication != + serverDetailsState.sshSettings.passwordAuthentication + ? FontStyle.italic + : FontStyle.normal, + ), + ), + activeColor: Theme.of(context).colorScheme.primary, + ), ], ); } diff --git a/lib/ui/pages/server_details/server_settings_screen.dart b/lib/ui/pages/server_details/server_settings_screen.dart index ea98e100..87926db8 100644 --- a/lib/ui/pages/server_details/server_settings_screen.dart +++ b/lib/ui/pages/server_details/server_settings_screen.dart @@ -25,11 +25,11 @@ class ServerSettingsScreen extends StatefulWidget { class _ServerSettingsScreenState extends State { @override Widget build(final BuildContext context) => BrandHeroScreen( - hasFlashButton: true, - heroIcon: Icons.settings_outlined, - heroTitle: 'server.settings'.tr(), - children: const [ - _ServerSettings(), - ], - ); + hasFlashButton: true, + heroIcon: Icons.settings_outlined, + heroTitle: 'server.settings'.tr(), + children: const [ + _ServerSettings(), + ], + ); } diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index f09aac70..a117af3c 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -169,89 +169,110 @@ class _SshKeysCard extends StatelessWidget { final User user; @override - Widget build(final BuildContext context) => FilledCard( - child: Column( - children: [ - ListTileOnSurfaceVariant( - title: 'ssh.title'.tr(), - ), - const Divider(height: 0), - ListTileOnSurfaceVariant( - title: 'ssh.create'.tr(), - leadingIcon: Icons.add_circle_outline, - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - useRootNavigator: true, - builder: (final BuildContext context) => Padding( - padding: MediaQuery.of(context).viewInsets, - child: NewSshKey(user), - ), - ); - }, - ), - Column( - children: user.sshKeys.map((final String 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 ListTileOnSurfaceVariant( - title: '$keyName ($keyType)', - disableSubtitleOverflow: true, - // do not overflow text - subtitle: publicKey, - onTap: () { - showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - title: Text('ssh.delete'.tr()), - content: SingleChildScrollView( - child: ListBody( - children: [ - Text('ssh.delete_confirm_question'.tr()), - Text('$keyName ($keyType)'), - Text(publicKey), - ], - ), + Widget build(final BuildContext context) { + final serverDetailsState = context.watch().state; + final bool sshDisabled = + serverDetailsState is Loaded && !serverDetailsState.sshSettings.enable; + + return FilledCard( + child: Column( + children: [ + ListTileOnSurfaceVariant( + title: 'ssh.title'.tr(), + ), + const Divider(height: 0), + ListTileOnSurfaceVariant( + title: 'ssh.create'.tr(), + leadingIcon: Icons.add_circle_outline, + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + builder: (final BuildContext context) => Padding( + padding: MediaQuery.of(context).viewInsets, + child: NewSshKey(user), + ), + ); + }, + ), + Column( + children: user.sshKeys.map((final String 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 ListTileOnSurfaceVariant( + title: '$keyName ($keyType)', + disableSubtitleOverflow: true, + // do not overflow text + subtitle: publicKey, + onTap: () { + showDialog( + context: context, + builder: (final BuildContext context) => AlertDialog( + title: Text('ssh.delete'.tr()), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text('ssh.delete_confirm_question'.tr()), + Text('$keyName ($keyType)'), + Text(publicKey), + ], ), - actions: [ - TextButton( - child: Text('basis.cancel'.tr()), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text( - 'basis.delete'.tr(), - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), - ), - onPressed: () { - context.read().addJob( - DeleteSSHKeyJob( - user: user, - publicKey: key, - ), - ); - context.popRoute(); - }, - ), - ], ), - ); - }, - ); - }).toList(), + actions: [ + TextButton( + child: Text('basis.cancel'.tr()), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text( + 'basis.delete'.tr(), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + onPressed: () { + context.read().addJob( + DeleteSSHKeyJob( + user: user, + publicKey: key, + ), + ); + context.popRoute(); + }, + ), + ], + ), + ); + }, + ); + }).toList(), + ), + if (sshDisabled) + Column( + children: [ + const Divider(height: 0), + Padding( + padding: const EdgeInsets.all(8.0), + child: Expanded( + child: InfoBox( + text: 'ssh.ssh_disabled_warning'.tr(), + isWarning: true, + ), + ), + ), + ], ), - ], - ), - ); + ], + ), + ); + } } class NewSshKey extends StatelessWidget { diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index f7edce9e..b08b168e 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/job.dart';