From e9a986058d67bafe510d470391b609b993f405b9 Mon Sep 17 00:00:00 2001 From: cardidi Date: Mon, 21 Apr 2025 02:47:18 +0800 Subject: [PATCH] feat: Add features created by server. --- .../.idea/avalonia.xml | 4 + Flawless-Version-Control.sln.DotSettings.user | 2 + Flawless.Client/Models/AppSettingModel.cs | 10 + Flawless.Client/Models/UserModel.cs | 2 + Flawless.Client/Service/Api.cs | 2 +- Flawless.Client/Service/Remote_Generated.cs | 2 +- Flawless.Client/Service/SettingService.cs | 13 +- Flawless.Client/UIHelper.cs | 15 +- .../ViewModels/FirstSetupViewModel.cs | 60 +++ .../ViewModels/IssueDetailViewModel.cs | 4 +- .../ViewModels/LoginPageViewModel.cs | 4 +- .../ModalBox/PasswordChangeDialogViewModel.cs | 20 + .../ModalBox/UserCreateDialogViewModel.cs | 19 + .../ViewModels/RepositoryViewModel.cs | 14 +- .../ViewModels/ServerSetupPageViewModel.cs | 5 +- .../ViewModels/SettingViewModel.cs | 364 +++++++++++++++++- .../Views/HelloSetup/FirstSetupView.axaml | 24 ++ .../Views/HelloSetup/FirstSetupView.axaml.cs | 15 + .../ModalBox/PasswordChangeDialogView.axaml | 20 + .../PasswordChangeDialogView.axaml.cs | 13 + .../Views/ModalBox/UserCreateDialogView.axaml | 13 + .../ModalBox/UserCreateDialogView.axaml.cs | 13 + Flawless.Client/Views/SettingView.axaml | 82 ++-- .../Response/UserInfoResponse.cs | 2 + .../Controllers/AdminController.cs | 2 +- Flawless.Server/Controllers/UserController.cs | 1 + 26 files changed, 680 insertions(+), 45 deletions(-) create mode 100644 Flawless.Client/ViewModels/FirstSetupViewModel.cs create mode 100644 Flawless.Client/ViewModels/ModalBox/PasswordChangeDialogViewModel.cs create mode 100644 Flawless.Client/ViewModels/ModalBox/UserCreateDialogViewModel.cs create mode 100644 Flawless.Client/Views/HelloSetup/FirstSetupView.axaml create mode 100644 Flawless.Client/Views/HelloSetup/FirstSetupView.axaml.cs create mode 100644 Flawless.Client/Views/ModalBox/PasswordChangeDialogView.axaml create mode 100644 Flawless.Client/Views/ModalBox/PasswordChangeDialogView.axaml.cs create mode 100644 Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml create mode 100644 Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml.cs diff --git a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml index f483985..f5c805f 100644 --- a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml +++ b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml @@ -6,6 +6,8 @@ + + @@ -24,7 +26,9 @@ + + diff --git a/Flawless-Version-Control.sln.DotSettings.user b/Flawless-Version-Control.sln.DotSettings.user index 79daafd..43be6a0 100644 --- a/Flawless-Version-Control.sln.DotSettings.user +++ b/Flawless-Version-Control.sln.DotSettings.user @@ -31,9 +31,11 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/Flawless.Client/Models/AppSettingModel.cs b/Flawless.Client/Models/AppSettingModel.cs index 95a7b4a..7b9ebfd 100644 --- a/Flawless.Client/Models/AppSettingModel.cs +++ b/Flawless.Client/Models/AppSettingModel.cs @@ -8,4 +8,14 @@ public partial class AppSettingModel : ReactiveModel { [Reactive, NonSerialized] private string _repositoryPath = AppDefaultValues.DefaultRepositoryDirectory; + + [Reactive, NonSerialized] private bool + _refreshWorkspaceOnOpen = true, + _refreshWorkspaceOnFilesystemChanges = true; + + + [Reactive, NonSerialized] private string _diffTool; + + + [Reactive, NonSerialized] private string _fileManagerTool; } \ No newline at end of file diff --git a/Flawless.Client/Models/UserModel.cs b/Flawless.Client/Models/UserModel.cs index b3c9644..13ba932 100644 --- a/Flawless.Client/Models/UserModel.cs +++ b/Flawless.Client/Models/UserModel.cs @@ -30,4 +30,6 @@ public partial class UserModel : ReactiveModel [Reactive] private string _phoneNumber; [Reactive] private DateTime _joinDate; + + [Reactive] private bool _isAdmin; } \ No newline at end of file diff --git a/Flawless.Client/Service/Api.cs b/Flawless.Client/Service/Api.cs index e096f92..88fa7b1 100644 --- a/Flawless.Client/Service/Api.cs +++ b/Flawless.Client/Service/Api.cs @@ -32,7 +32,7 @@ public class Api : BaseService public IObservable ServerUrl => _serverUrl; - public IObservable Status => _status; + public IReactiveProperty Status => _status; public IObservable Token => _token; diff --git a/Flawless.Client/Service/Remote_Generated.cs b/Flawless.Client/Service/Remote_Generated.cs index a9d5af0..a2c6faf 100644 --- a/Flawless.Client/Service/Remote_Generated.cs +++ b/Flawless.Client/Service/Remote_Generated.cs @@ -818,7 +818,7 @@ namespace Flawless.Client.Remote public string NickName { get; set; } [JsonPropertyName("gender")] - public UserSex Gender { get; set; } + public UserSex? Gender { get; set; } [JsonPropertyName("bio")] public string Bio { get; set; } diff --git a/Flawless.Client/Service/SettingService.cs b/Flawless.Client/Service/SettingService.cs index e5be8e6..5f31358 100644 --- a/Flawless.Client/Service/SettingService.cs +++ b/Flawless.Client/Service/SettingService.cs @@ -15,8 +15,19 @@ public class SettingService : BaseService public static string SettingFilePath { get; } = Path.Combine(AppDefaultValues.ProgramDataDirectory, "settings.json"); - public AppSettingModel AppSetting { get; } = new AppSettingModel(); + public AppSettingModel AppSetting { get; private set; } = new AppSettingModel(); + public ValueTask ResetAsync() + { + AppSetting.RepositoryPath = AppDefaultValues.DefaultRepositoryDirectory; + AppSetting.RefreshWorkspaceOnOpen = true; + AppSetting.RefreshWorkspaceOnFilesystemChanges = true; + AppSetting.DiffTool = ""; + AppSetting.FileManagerTool = ""; + + return WriteToDiskAsync(); + } + public async ValueTask WriteToDiskAsync() { var stream = File.Exists(SettingFilePath) ? File.OpenWrite(SettingFilePath) : File.Create(SettingFilePath); diff --git a/Flawless.Client/UIHelper.cs b/Flawless.Client/UIHelper.cs index 78c4150..9583e51 100644 --- a/Flawless.Client/UIHelper.cs +++ b/Flawless.Client/UIHelper.cs @@ -86,7 +86,7 @@ public static class UIHelper } } - public static void NotifyError(string title, string content) + public static void NotifyError(string content, string title = "Error") { try { @@ -146,4 +146,17 @@ public static class UIHelper var vm = new SimpleMessageDialogViewModel(content); return OverlayDialog.ShowModal(vm, AppDefaultValues.HostId, opt); } + + public static void NotifySuccess(string content, string title = "Success") + { + try + { + var nf = new Notification(title, content, NotificationType.Success); + Notify.Show(nf); + } + catch (Exception e) + { + Console.WriteLine($"Can not notify success to users: {title} - {content}, {e}"); + } + } } \ No newline at end of file diff --git a/Flawless.Client/ViewModels/FirstSetupViewModel.cs b/Flawless.Client/ViewModels/FirstSetupViewModel.cs new file mode 100644 index 0000000..c644fca --- /dev/null +++ b/Flawless.Client/ViewModels/FirstSetupViewModel.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Flawless.Client.Remote; +using Flawless.Client.Service; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using Refit; + +namespace Flawless.Client.ViewModels; + +public partial class FirstSetupViewModel : ViewModelBase, IRoutableViewModel +{ + + public string? UrlPathSegment { get; } = Guid.NewGuid().ToString(); + + public IScreen HostScreen { get; } + + [Reactive] private string _email; + + [Reactive] private string _username; + + [Reactive] private string _password; + + public FirstSetupViewModel(IScreen hostScreen) + { + HostScreen = hostScreen; + } + + + [ReactiveCommand] + private async Task SetupAsync() + { + try + { + using var l = UIHelper.MakeLoading("Setup..."); + await Api.C.Gateway.FirstSetup(new FirstSetupRequest() + { + AdminEmail = Email, + AdminUsername = Username, + AdminPassword = Password + }); + + + await Api.C.LoginAsync(Username, Password); + Api.C.Status.Value!.RequireInitialization = false; + } + catch (ApiException ex) + { + await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex.Content}"); + UIHelper.NotifyError(ex); + } + catch (Exception ex) + { + await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex}"); + UIHelper.NotifyError(ex); + } + + Console.WriteLine($"Register as '{Username}' success!"); + } +} \ No newline at end of file diff --git a/Flawless.Client/ViewModels/IssueDetailViewModel.cs b/Flawless.Client/ViewModels/IssueDetailViewModel.cs index 1d89c76..5233b78 100644 --- a/Flawless.Client/ViewModels/IssueDetailViewModel.cs +++ b/Flawless.Client/ViewModels/IssueDetailViewModel.cs @@ -37,7 +37,7 @@ public partial class IssueDetailViewModel : RoutableViewModelBase if (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId, false)) { if (quitIfFailed) await NavigateBackAsync(); - UIHelper.NotifyError("Operation failed", "Can not load issue..."); + UIHelper.NotifyError("Can not load issue...", "Operation failed"); return; } @@ -80,7 +80,7 @@ public partial class IssueDetailViewModel : RoutableViewModelBase using var _ = UIHelper.MakeLoading("Update issue..."); if (string.IsNullOrWhiteSpace(NewComment)) { - UIHelper.NotifyError("No input", "No comment has been written!"); + UIHelper.NotifyError("No comment has been written!", "No input"); } else { diff --git a/Flawless.Client/ViewModels/LoginPageViewModel.cs b/Flawless.Client/ViewModels/LoginPageViewModel.cs index c9ed6db..05aee0b 100644 --- a/Flawless.Client/ViewModels/LoginPageViewModel.cs +++ b/Flawless.Client/ViewModels/LoginPageViewModel.cs @@ -17,9 +17,9 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel public IScreen HostScreen { get; } - [Reactive] private string _username = "cardidi"; + [Reactive] private string _username; - [Reactive] private string _password = "4453A2b33"; + [Reactive] private string _password; public IObservable CanLogin; diff --git a/Flawless.Client/ViewModels/ModalBox/PasswordChangeDialogViewModel.cs b/Flawless.Client/ViewModels/ModalBox/PasswordChangeDialogViewModel.cs new file mode 100644 index 0000000..d2c0e60 --- /dev/null +++ b/Flawless.Client/ViewModels/ModalBox/PasswordChangeDialogViewModel.cs @@ -0,0 +1,20 @@ +using System; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace Flawless.Client.ViewModels.ModalBox; + +public partial class PasswordChangeDialogViewModel : ReactiveObject +{ + [Reactive] public string OldPassword { get; set; } = string.Empty; + [Reactive] public string NewPassword { get; set; } = string.Empty; + [Reactive] public string ConfirmPassword { get; set; } = string.Empty; + + + public bool Validate() + { + return !string.IsNullOrEmpty(NewPassword) + && NewPassword == ConfirmPassword + && !string.IsNullOrEmpty(OldPassword); + } +} \ No newline at end of file diff --git a/Flawless.Client/ViewModels/ModalBox/UserCreateDialogViewModel.cs b/Flawless.Client/ViewModels/ModalBox/UserCreateDialogViewModel.cs new file mode 100644 index 0000000..d1e535e --- /dev/null +++ b/Flawless.Client/ViewModels/ModalBox/UserCreateDialogViewModel.cs @@ -0,0 +1,19 @@ +using System; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace Flawless.Client.ViewModels.ModalBox; + +public partial class UserCreateDialogViewModel : ReactiveObject +{ + [Reactive] public string Username { get; set; } = string.Empty; + [Reactive] public string Password { get; set; } = string.Empty; + [Reactive] public string Email { get; set; } = string.Empty; + + public bool Validate() + { + return !string.IsNullOrWhiteSpace(Username) + && !string.IsNullOrWhiteSpace(Password) + && !string.IsNullOrWhiteSpace(Email); + } +} \ No newline at end of file diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs index 8bb4710..dd1cf67 100644 --- a/Flawless.Client/ViewModels/RepositoryViewModel.cs +++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs @@ -580,7 +580,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase { if (!IsOwnerRole) { - UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!"); + UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue"); return; } @@ -599,7 +599,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase { if (!IsOwnerRole) { - UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!"); + UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue"); return; } @@ -615,13 +615,13 @@ public partial class RepositoryViewModel : RoutableViewModelBase { if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner) { - UIHelper.NotifyError("Permission issue", "Invalid role level!"); + UIHelper.NotifyError("Invalid role level!", "Permission issue"); return; } if (vm.SafeRole == member.Role) { - UIHelper.NotifyError("Modification issue", "No modification yet."); + UIHelper.NotifyError("No modification yet.", "Modification issue"); return; } @@ -636,7 +636,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase { if (!IsOwnerRole) { - UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!"); + UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue"); return; } @@ -649,14 +649,14 @@ public partial class RepositoryViewModel : RoutableViewModelBase { if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner) { - UIHelper.NotifyError("Permission issue", "Invalid role level!"); + UIHelper.NotifyError("Invalid role level!", "Permission issue"); return; } vm.Username = vm.Username.Trim(); if (string.IsNullOrEmpty(vm.Username) || vm.Username.Length < 3) { - UIHelper.NotifyError("Parameter error", "Not a valid username!"); + UIHelper.NotifyError("Not a valid username!", "Parameter error"); return; } diff --git a/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs b/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs index 508f2da..4757f8a 100644 --- a/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs +++ b/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs @@ -33,7 +33,10 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase { using var l = UIHelper.MakeLoading("Contacting to server..."); await Api.C.SetGatewayAsync(Host); - HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen)); + + if (Api.C.Status.Value.RequireInitialization) + HostScreen.Router.Navigate.Execute(new FirstSetupViewModel(HostScreen)); + else HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen)); } catch (ApiException ex) { diff --git a/Flawless.Client/ViewModels/SettingViewModel.cs b/Flawless.Client/ViewModels/SettingViewModel.cs index fa3b287..07e9236 100644 --- a/Flawless.Client/ViewModels/SettingViewModel.cs +++ b/Flawless.Client/ViewModels/SettingViewModel.cs @@ -1,12 +1,372 @@ using System; -using System.Windows.Input; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Threading; +using DynamicData; +using Flawless.Client.Models; +using Flawless.Client.Remote; +using Flawless.Client.Service; +using Flawless.Client.ViewModels.ModalBox; +using Flawless.Client.Views.ModalBox; using ReactiveUI; +using ReactiveUI.SourceGenerators; +using Refit; +using Ursa.Controls; namespace Flawless.Client.ViewModels; -public class SettingViewModel : RoutableViewModelBase +public record Log(DateTime Time, Microsoft.Extensions.Logging.LogLevel Level, string Message) { + public static Log From(LogEntryResponse response) => new( + response.Timestamp.LocalDateTime, + Enum.Parse(response.Level), + response.Message); +} + +public partial class SettingViewModel : RoutableViewModelBase +{ + public ObservableCollection Users { get; } = new(); + + public ObservableCollection Logs { get; } = new(); + + public AppSettingModel SettingModel => SettingService.C.AppSetting; + + [Reactive] private UserModel _loginUser; + + [Reactive] private string _nickname; + + [Reactive] private string _email; + + [Reactive] private string _phoneNumber; + + [Reactive] private UserModel.SexType _gender; + + [Reactive] private string _bio; + + [Reactive] private DateTime? + _logSearchFrom = null, + _logSearchTo = null; + + [Reactive] private int _page = 1; + + [Reactive] private int _pageSize = 50; + + [Reactive] private Microsoft.Extensions.Logging.LogLevel _loglevel = Microsoft.Extensions.Logging.LogLevel.Information; + + [Reactive] private string _serverBlacklist, _serverWhitelist; + public SettingViewModel(IScreen hostScreen) : base(hostScreen) { + LoadClientSettings(); + } + + private async Task LoadServerData() + { + try + { + var sb = new StringBuilder(); + ServerBlacklist = sb.AppendJoin(",\n", await Api.C.Gateway.IpWhitelistGet()).ToString(); + ServerBlacklist = sb.Clear().AppendJoin(",\n", await Api.C.Gateway.IpBlacklistGet()).ToString(); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + private async Task LoadClientSettings() + { + LoginUser = (await UserService.C.GetOrDownloadUserInfoAsync(Api.C.Username.Value!))!; + + Nickname = LoginUser.Nickname; + Email = LoginUser.Email; + PhoneNumber = LoginUser.PhoneNumber; + Gender = LoginUser.Sex; + Bio = LoginUser.Bio; + + if (LoginUser.IsAdmin) await LoadServerData(); + } + + [ReactiveCommand] + private async Task SaveAccountChangesAsync() + { + try + { + await Api.C.Gateway.UpdateInfo(new UserInfoModifyResponse + { + NickName = this.Nickname == LoginUser.Nickname ? null! : this.Nickname, + Gender = this.Gender == LoginUser.Sex ? null : (UserSex) this.Gender, + Bio = this.Bio == LoginUser.Bio ? null! : this.Bio + }); + + if (Email != LoginUser.Email) + await Api.C.Gateway.UpdateEmail(new UserContactModifyResponse + { Email = Email, }); + + if (PhoneNumber != LoginUser.PhoneNumber) + await Api.C.Gateway.UpdatePhone(new UserContactModifyResponse + { Phone = PhoneNumber, }); + + UIHelper.NotifySuccess("Saved"); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + [ReactiveCommand] + private async Task ChangePasswordAsync() + { + var result = new PasswordChangeDialogViewModel(); + var opt = UIHelper.DefaultOverlayDialogOptionsYesNo(); + while (true) + { + var r = await OverlayDialog.ShowModal(result, AppDefaultValues.HostId, opt); + if (r == DialogResult.No) return; + if (result.Validate()) break; + + UIHelper.NotifyError("Please check your input"); + } + + try + { + await Api.C.Gateway.ResetPassword(new ResetPasswordRequest() + { + Identity = LoginUser.Username, + NewPassword = result.NewPassword, + OldPassword = result.OldPassword + }); + + UIHelper.NotifySuccess("Password create successfully"); + } + catch (ApiException ex) + { + UIHelper.NotifyError(ex); + } + } + + [ReactiveCommand] + private async Task DeleteSelfAccountAsync() + { + if (await UIHelper.SimpleAskAsync("This operation can not undo. Do you sure that?") == DialogResult.Yes) + { + try + { + await Api.C.Gateway.Delete(Api.C.Username.Value!); + Process.GetCurrentProcess().Kill(); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + } + + [ReactiveCommand] + private async Task SaveClientPreferenceAsync() + { + try + { + await SettingService.C.WriteToDiskAsync(); + UIHelper.NotifySuccess("Saved Success!"); + } + catch (Exception e) + { + UIHelper.NotifyError(e); + } + } + + [ReactiveCommand] + private async Task SaveServerPreferenceAsync() + { + try + { + await Api.C.Gateway.IpWhitelistPost(ServerWhitelist.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); + await Api.C.Gateway.IpBlacklistPost(ServerBlacklist.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); + UIHelper.NotifySuccess("Saved Success!"); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + [ReactiveCommand] + private async Task ResetClientPreferenceAsync() + { + if (await UIHelper.SimpleAskAsync("Do you sure reset settings to default?") == DialogResult.Yes) + { + await SettingService.C.ResetAsync(); + } + } + + [ReactiveCommand] + private async Task ResetServerPreferenceAsync() + { + if (await UIHelper.SimpleAskAsync("Do you sure reset settings to default?") == DialogResult.Yes) + { + try + { + ServerBlacklist = ServerWhitelist = string.Empty; + await Api.C.Gateway.IpBlacklistPost([]); + await Api.C.Gateway.IpWhitelistPost([]); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + } + + [ReactiveCommand] + private async Task CreateUserAsync() + { + var result = new UserCreateDialogViewModel(); + result.Password = GenerateRandomPassword(); + var opt = UIHelper.DefaultOverlayDialogOptionsYesNo(); + while (true) + { + var r = await OverlayDialog.ShowModal(result, AppDefaultValues.HostId, opt); + if (r == DialogResult.No) return; + if (result.Validate()) break; + + UIHelper.NotifyError("Please check your input"); + } + + try + { + await Api.C.Gateway.Register(new RegisterRequest() + { + Username = result.Username, + Password = result.Password, + Email = result.Email + }); + + Users.Add(UserService.C.GetUserInfoAsync(result.Username)!); + UIHelper.NotifySuccess($"User {result.Username} create successfully"); + } + catch (ApiException ex) + { + UIHelper.NotifyError(ex); + } + } + + [ReactiveCommand] + private async Task DeleteUserAsync(string username) + { + if (await UIHelper.SimpleAskAsync($"Do you sure that wanted to delete user '{username}' permanently?") == DialogResult.Yes) + { + try + { + await Api.C.Gateway.Delete(username); + Users.Remove(Users.First(u => u.Username == username)); + UIHelper.NotifySuccess("User deleted"); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + } + + [ReactiveCommand] + private async Task ForceUpdateUserPasswordAsync(string username) + { + var newPassword = GenerateRandomPassword(); + try + { + await Api.C.Gateway.RenewPassword(new ResetPasswordRequest + { + Identity = username, + NewPassword = newPassword + }); + await UIHelper.SimpleAlert($"Password has been reset to {newPassword}"); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + [ReactiveCommand] + private async Task ActivateUserAsync(string username) + { + await UpdateUserActivateAsync(username, true); + } + + [ReactiveCommand] + private async Task InactivateUserAsync(string username) + { + await UpdateUserActivateAsync(username, false); + } + + [ReactiveCommand] + private async Task ToSuperUserAsync(string username) + { + await UpdateUserSuperAsync(username, true); + } + + [ReactiveCommand] + private async Task ToNormalUserAsync(string username) + { + await UpdateUserSuperAsync(username, false); + } + + [ReactiveCommand] + private async Task DownloadServerLogAsync() + { + try + { + var logs = await Api.C.Gateway.Logs( + LogSearchFrom, LogSearchTo, (LogLevel?)Loglevel, Page, PageSize); + + Logs.AddRange(logs.Select(Log.From)); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + private async Task UpdateUserActivateAsync(string username, bool active) + { + try + { + if (active) await Api.C.Gateway.Enable(username); + else await Api.C.Gateway.Disable(username); + + UIHelper.NotifySuccess($"{username} has already {(active ? "enabled" : "disabled")}."); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + private async Task UpdateUserSuperAsync(string username, bool active) + { + try + { + await Api.C.Gateway.SuperuserPost(username, active); + Users.First(x => x.Username == username).IsAdmin = active; + UIHelper.NotifySuccess($"{username} has already {(active ? "enabled" : "disabled")}."); + } + catch (Exception ex) + { + UIHelper.NotifyError(ex); + } + } + + private static string GenerateRandomPassword(int length = 12) + { + const string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*"; + var random = new Random(); + return new string(Enumerable.Repeat(validChars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); } } \ No newline at end of file diff --git a/Flawless.Client/Views/HelloSetup/FirstSetupView.axaml b/Flawless.Client/Views/HelloSetup/FirstSetupView.axaml new file mode 100644 index 0000000..3b92530 --- /dev/null +++ b/Flawless.Client/Views/HelloSetup/FirstSetupView.axaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + +