feat: Add features created by server.
This commit is contained in:
parent
e88a483025
commit
e9a986058d
@ -6,6 +6,8 @@
|
||||
<entry key="Flawless.Client.Avanonia/Views/MainWindow.axaml" value="Flawless.Client.Avanonia/Flawless.Client.Avanonia.csproj" />
|
||||
<entry key="Flawless.Client/App.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/FirsetSetipViewModel.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/FirstSetupView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/LoginPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
@ -24,7 +26,9 @@
|
||||
<entry key="Flawless.Client/Views/ModalBox/EditRepositoryMemberDialogueView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/MergeDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/PasswordChangeDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/SimpleMessageDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/IssueEditDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
|
||||
@ -31,9 +31,11 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIReactiveObjectExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5a_003F1516290d_003FIReactiveObjectExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F61447741f88e235f7cd1a276ef5abe648b2dee4b210873893d178b861c9d0_003FJwtBearerHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALoggerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1b4c09e663de4a9ea6963d356da7095e13a00_003Ffa_003F8ad0b278_003FLoggerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogLevel_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd0319a0b2a6743ffb27031fdd18a267a1f000_003F81_003Fe3a61b51_003FLogLevel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003F28_003F6a41ec86_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5c_003F228dd86b_003FReactiveObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveUrsaView_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01a3316df1f14aff8404556f39e0d9e52200_003F3c_003F7b186404_003FReactiveUrsaView_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveUrsaView_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01a3316df1f14aff8404556f39e0d9e52200_003Fde_003F4660a705_003FReactiveUrsaView_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveUrsaWindow_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01a3316df1f14aff8404556f39e0d9e52200_003F1f_003F9c292772_003FReactiveUrsaWindow_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARefitSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa2b3a0b86cd053ed984c171eb448b551b9a2a0c89c5a68191996cffac5d85d2b_003FRefitSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AScopedLoggerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb0587797ea44bd6915ede69888c6766291038_003Fb3_003Fdcbe23f1_003FScopedLoggerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -30,4 +30,6 @@ public partial class UserModel : ReactiveModel
|
||||
[Reactive] private string _phoneNumber;
|
||||
|
||||
[Reactive] private DateTime _joinDate;
|
||||
|
||||
[Reactive] private bool _isAdmin;
|
||||
}
|
||||
@ -32,7 +32,7 @@ public class Api : BaseService<Api>
|
||||
|
||||
public IObservable<string?> ServerUrl => _serverUrl;
|
||||
|
||||
public IObservable<ServerStatusResponse?> Status => _status;
|
||||
public IReactiveProperty<ServerStatusResponse?> Status => _status;
|
||||
|
||||
public IObservable<TokenInfo?> Token => _token;
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -15,8 +15,19 @@ public class SettingService : BaseService<SettingService>
|
||||
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);
|
||||
|
||||
@ -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<SimpleMessageDialogView, SimpleMessageDialogViewModel>(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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Flawless.Client/ViewModels/FirstSetupViewModel.cs
Normal file
60
Flawless.Client/ViewModels/FirstSetupViewModel.cs
Normal file
@ -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!");
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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<bool> CanLogin;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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<Microsoft.Extensions.Logging.LogLevel>(response.Level),
|
||||
response.Message);
|
||||
}
|
||||
|
||||
public partial class SettingViewModel : RoutableViewModelBase
|
||||
{
|
||||
public ObservableCollection<UserModel> Users { get; } = new();
|
||||
|
||||
public ObservableCollection<Log> 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<PasswordChangeDialogView, PasswordChangeDialogViewModel>(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<UserCreateDialogView, UserCreateDialogViewModel>(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());
|
||||
}
|
||||
}
|
||||
24
Flawless.Client/Views/HelloSetup/FirstSetupView.axaml
Normal file
24
Flawless.Client/Views/HelloSetup/FirstSetupView.axaml
Normal file
@ -0,0 +1,24 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Flawless.Client.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="380" d:DesignHeight="540"
|
||||
x:Class="Flawless.Client.Views.HelloSetup.FirstSetupView"
|
||||
x:DataType="vm:FirstSetupViewModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:RegisterPageViewModel/>
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="10">
|
||||
<TextBox Watermark="Email" Text="{Binding Email, Mode=TwoWay}"/>
|
||||
<TextBox Watermark="Username" Text="{Binding Username, Mode=TwoWay}"/>
|
||||
<TextBox Watermark="Password" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="{DynamicResource Semi}">
|
||||
<Button Content="Register" Command="{Binding SetupCommand}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
15
Flawless.Client/Views/HelloSetup/FirstSetupView.axaml.cs
Normal file
15
Flawless.Client/Views/HelloSetup/FirstSetupView.axaml.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Flawless.Client.ViewModels;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views.HelloSetup;
|
||||
|
||||
public partial class FirstSetupView : ReactiveUrsaView<FirstSetupViewModel>
|
||||
{
|
||||
public FirstSetupView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels.ModalBox"
|
||||
x:Class="Flawless.Client.Views.ModalBox.PasswordChangeDialogView"
|
||||
x:DataType="vm:PasswordChangeDialogViewModel">
|
||||
|
||||
<Grid Margin="10" RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBox Watermark="Old Password"
|
||||
PasswordChar="*"
|
||||
Text="{Binding OldPassword}"/>
|
||||
|
||||
<TextBox Grid.Row="1" Watermark="New Password"
|
||||
PasswordChar="*"
|
||||
Text="{Binding NewPassword}"/>
|
||||
|
||||
<TextBox Grid.Row="2" Watermark="Confirm New Password"
|
||||
PasswordChar="*"
|
||||
Text="{Binding ConfirmPassword}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Flawless.Client.Views.ModalBox;
|
||||
|
||||
public partial class PasswordChangeDialogView : UserControl
|
||||
{
|
||||
public PasswordChangeDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
13
Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml
Normal file
13
Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml
Normal file
@ -0,0 +1,13 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels.ModalBox"
|
||||
x:Class="Flawless.Client.Views.ModalBox.UserCreateDialogView"
|
||||
x:DataType="vm:UserCreateDialogViewModel">
|
||||
|
||||
<Grid Margin="10" RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBox Grid.Row="0" Watermark="Username" Text="{Binding Username}"/>
|
||||
<TextBox Grid.Row="1" Watermark="Password"
|
||||
PasswordChar="*" Text="{Binding Password}"/>
|
||||
<TextBox Grid.Row="2" Watermark="Email" Text="{Binding Email}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml.cs
Normal file
13
Flawless.Client/Views/ModalBox/UserCreateDialogView.axaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Flawless.Client.Views.ModalBox;
|
||||
|
||||
public partial class UserCreateDialogView : UserControl
|
||||
{
|
||||
public UserCreateDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:semi="https://irihi.tech/semi"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||
x:DataType="vm:SettingViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||
@ -19,18 +18,20 @@
|
||||
<TabItem Header="Account">
|
||||
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
||||
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch" LabelPosition="Top">
|
||||
<Label Content="You are a server manager!"/>
|
||||
<TextBox u:FormItem.Label="Username" IsReadOnly="True"/>
|
||||
<TextBox u:FormItem.Label="Nickname" IsReadOnly="True"/>
|
||||
<TextBox u:FormItem.Label="Email" IsReadOnly="True"/>
|
||||
<TextBox u:FormItem.Label="Phone Number" IsReadOnly="True"/>
|
||||
<ComboBox u:FormItem.Label="Gender">
|
||||
<ComboBoxItem Content="Unset"/>
|
||||
<ComboBoxItem Content="Male"/>
|
||||
<ComboBoxItem Content="Female"/>
|
||||
<ComboBoxItem Content="Walmart Plastic Bag"/>
|
||||
</ComboBox>
|
||||
<TextBox u:FormItem.Label="Bio" Classes="TextArea"/>
|
||||
<Label IsVisible="{Binding LoginUser.IsAdmin}" Content="You are a server manager!"/>
|
||||
<TextBox u:FormItem.Label="Username" IsReadOnly="True"
|
||||
Text="{Binding LoginUser.Username, Mode=OneWay}"/>
|
||||
<TextBox u:FormItem.Label="Nickname" Text="{Binding Nickname}"/>
|
||||
<TextBox u:FormItem.Label="Email" Text="{Binding Email}"/>
|
||||
<TextBox u:FormItem.Label="Phone Number" Text="{Binding PhoneNumber}"/>
|
||||
<u:EnumSelector u:FormItem.Label="Gender" SelectedValue="{Binding Gender}"/>
|
||||
<TextBox u:FormItem.Label="Bio" Classes="TextArea" Text="{Binding Bio}"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<u:IconButton Content="Save" Command="{Binding SaveAccountChangesCommand}"/>
|
||||
<u:IconButton Content="Change Password" Command="{Binding ChangePasswordCommand}"/>
|
||||
<u:IconButton Classes="Danger" Content="Delete" Command="{Binding DeleteSelfAccountCommand}"/>
|
||||
</StackPanel>
|
||||
</u:Form>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@ -42,41 +43,70 @@
|
||||
</u:FormItem>
|
||||
<u:FormGroup Header="Workspace Refresh">
|
||||
<StackPanel Spacing="4">
|
||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace"/>
|
||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed"/>
|
||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Gain Focus"/>
|
||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace"
|
||||
IsChecked="{Binding SettingModel.RefreshWorkspaceOnOpen}"/>
|
||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed"
|
||||
IsChecked="{Binding SettingModel.RefreshWorkspaceOnFilesystemChanges}"/>
|
||||
</StackPanel>
|
||||
</u:FormGroup>
|
||||
<u:FormGroup Header="Extern Tools">
|
||||
<u:PathPicker u:FormItem.Label="Open Folder"/>
|
||||
<u:PathPicker u:FormItem.Label="Diff Tool"/>
|
||||
</u:FormGroup>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<u:IconButton Content="Save" Command="{Binding SaveClientPreferenceCommand}"/>
|
||||
<u:IconButton Classes="Danger" Content="Reset" Command="{Binding ResetClientPreferenceCommand}"/>
|
||||
</StackPanel>
|
||||
</u:Form>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="Server">
|
||||
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Server">
|
||||
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
||||
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<TextBox u:FormItem.Label="Server Public Name"/>
|
||||
<ToggleSwitch u:FormItem.Label="Allow Public Register"/>
|
||||
<!-- <TextBox u:FormItem.Label="Server Public Name"/> -->
|
||||
<!-- <ToggleSwitch u:FormItem.Label="Allow Public Register"/> -->
|
||||
<u:FormGroup>
|
||||
<TextBox u:FormItem.Label="IP Whitelist" Classes="TextArea"/>
|
||||
<TextBox u:FormItem.Label="IP Blacklist" Classes="TextArea"/>
|
||||
<TextBox u:FormItem.Label="IP Whitelist" Classes="TextArea" AcceptsReturn="True"
|
||||
Text="{Binding ServerWhitelist}"/>
|
||||
<TextBox u:FormItem.Label="IP Blacklist" Classes="TextArea" AcceptsReturn="True"
|
||||
Text="{Binding ServerBlacklist}"/>
|
||||
</u:FormGroup>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<u:IconButton Content="Save" Command="{Binding SaveServerPreferenceCommand}"/>
|
||||
<u:IconButton Classes="Danger" Content="Reset"
|
||||
Command="{Binding ResetServerPreferenceCommand}"/>
|
||||
</StackPanel>
|
||||
</u:Form>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="Users">
|
||||
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Users">
|
||||
</TabItem>
|
||||
<TabItem Header="Server Logfile">
|
||||
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Logfile">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Refresh"/>
|
||||
<DatePicker SelectedDate="{Binding LogSearchFrom}"/>
|
||||
<DatePicker SelectedDate="{Binding LogSearchTo}"/>
|
||||
</StackPanel>
|
||||
<TextBox IsReadOnly="True" Classes="TextArea Bordered" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<u:EnumSelector SelectedValue="{Binding Loglevel}"/>
|
||||
<NumericUpDown Value="{Binding PageSize}" Minimum="10" Maximum="100"/>
|
||||
<NumericUpDown Value="{Binding Page}" Minimum="1"/>
|
||||
<u:IconButton Icon="{StaticResource SemiIconSearch}"
|
||||
Command="{Binding DownloadServerLogCommand}"/>
|
||||
</StackPanel>
|
||||
<ListBox ItemsSource="{Binding Logs}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid RowDefinitions="Auto, *" ColumnDefinitions="Auto,*,Auto">
|
||||
<Label Grid.Row="0" Grid.Column="0" FontSize="16" Content="{Binding Level}"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" FontSize="16" Content="{Binding Time}"/>
|
||||
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding Message}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="Server Statitics"></TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
||||
@ -21,4 +21,6 @@ public record UserInfoResponse
|
||||
public bool? PublicEmail { get; set; }
|
||||
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
public bool? IsAdmin { get; set; }
|
||||
}
|
||||
@ -133,7 +133,7 @@ public class AdminController(
|
||||
query = query.Where(l => l.Timestamp <= endTime);
|
||||
|
||||
// 日志级别过滤
|
||||
if (level.HasValue)
|
||||
if (level.HasValue && level.Value != LogLevel.None)
|
||||
query = query.Where(l => l.LogLevel == level.Value);
|
||||
|
||||
// 分页处理
|
||||
|
||||
@ -128,6 +128,7 @@ public class UserController(
|
||||
PublicEmail = authorized ? queryUser.PublicEmail : null,
|
||||
Email = queryUser.PublicEmail || authorized ? queryUser.Email : null,
|
||||
Phone = authorized ? queryUser.PhoneNumber : null,
|
||||
IsAdmin = queryUser.Admin
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user