using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; 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 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 Type _genderType = typeof(UserModel.SexType); [Reactive] private string _bio; [Reactive] private DateTimeOffset _logSearchFrom = DateTime.Now.AddDays(-1), _logSearchTo = DateTime.Now; [Reactive] private int? _page = 1; [Reactive] private int? _pageSize = 50; [Reactive] private Microsoft.Extensions.Logging.LogLevel _loglevel = Microsoft.Extensions.Logging.LogLevel.Information; [Reactive] private Type _loglevelType = typeof(Microsoft.Extensions.Logging.LogLevel); [Reactive] private string _serverBlacklist, _serverWhitelist; public SettingViewModel(IScreen hostScreen) : base(hostScreen) { _ = LoadClientSettingsAsync(); } private async Task LoadServerDataAsync() { try { using (UIHelper.MakeLoading("Fetch server data...")) { await RefreshUsersAsync(); var sb = new StringBuilder(); ServerWhitelist = 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 LoadClientSettingsAsync() { 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 LoadServerDataAsync(); } [ReactiveCommand] private async Task SaveAccountChangesAsync() { try { await Api.C.Gateway.UpdateInfo(new UserInfoModifyResponse { NickName = this.Nickname == LoginUser.Nickname ? null! : this.Nickname, Gender = (int) this.Gender, Bio = this.Bio == LoginUser.Bio ? null! : this.Bio, PublicEmail = false }); 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, }); LoginUser.Bio = Bio; LoginUser.Sex = Gender; LoginUser.Email = Email; 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 RefreshUsersAsync() { try { var users = await Api.C.Gateway.List(); Users.Clear(); foreach (var user in users) { var m = new UserModel { Username = user.Username, Email = user.Email, IsActive = user.IsActive, IsAdmin = user.IsAdmin ?? false, CanEdit = user.Username != Api.C.Username.Value! }; if (m.CanEdit) Users.Insert(0, m); else Users.Add(m); } } catch (ApiException ex) { UIHelper.NotifyError(ex); } } [ReactiveCommand] private async Task CreateUserAsync() { var opt = UIHelper.DefaultOverlayDialogOptionsYesNo(); var result = new UserCreateDialogViewModel { Password = GenerateRandomPassword() }; 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.AddUser(new RegisterRequest { Username = result.Username, Password = result.Password, Email = result.Email }); await this.RefreshUsersAsync(); 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.ResetPassword(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 PromoteUserAsync(string username) { await UpdateUserSuperAsync(username, true); } [ReactiveCommand] private async Task DemoteUserAsync(string username) { await UpdateUserSuperAsync(username, false); } [ReactiveCommand] private async Task DownloadServerLogAsync() { try { Page ??= 1; PageSize ??= 50; var logs = await Api.C.Gateway.Logs( LogSearchFrom, LogSearchTo, (int)Loglevel, Page.Value, PageSize.Value); Logs.Clear(); 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); Users.First(x => x.Username == username).IsActive = active; 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 set to {(active ? "superuser" : "nornmal user")}."); } 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()); } }