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.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/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/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/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/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloSetup/ServerSetupPageView.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/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/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/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/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/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/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RepositoryPage/IssueEditDialogView.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_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_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_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_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_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_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_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_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>
|
<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]
|
[Reactive, NonSerialized]
|
||||||
private string _repositoryPath = AppDefaultValues.DefaultRepositoryDirectory;
|
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 string _phoneNumber;
|
||||||
|
|
||||||
[Reactive] private DateTime _joinDate;
|
[Reactive] private DateTime _joinDate;
|
||||||
|
|
||||||
|
[Reactive] private bool _isAdmin;
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ public class Api : BaseService<Api>
|
|||||||
|
|
||||||
public IObservable<string?> ServerUrl => _serverUrl;
|
public IObservable<string?> ServerUrl => _serverUrl;
|
||||||
|
|
||||||
public IObservable<ServerStatusResponse?> Status => _status;
|
public IReactiveProperty<ServerStatusResponse?> Status => _status;
|
||||||
|
|
||||||
public IObservable<TokenInfo?> Token => _token;
|
public IObservable<TokenInfo?> Token => _token;
|
||||||
|
|
||||||
|
|||||||
@ -818,7 +818,7 @@ namespace Flawless.Client.Remote
|
|||||||
public string NickName { get; set; }
|
public string NickName { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("gender")]
|
[JsonPropertyName("gender")]
|
||||||
public UserSex Gender { get; set; }
|
public UserSex? Gender { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("bio")]
|
[JsonPropertyName("bio")]
|
||||||
public string Bio { get; set; }
|
public string Bio { get; set; }
|
||||||
|
|||||||
@ -15,7 +15,18 @@ public class SettingService : BaseService<SettingService>
|
|||||||
public static string SettingFilePath { get; } =
|
public static string SettingFilePath { get; } =
|
||||||
Path.Combine(AppDefaultValues.ProgramDataDirectory, "settings.json");
|
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()
|
public async ValueTask WriteToDiskAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
try
|
||||||
{
|
{
|
||||||
@ -146,4 +146,17 @@ public static class UIHelper
|
|||||||
var vm = new SimpleMessageDialogViewModel(content);
|
var vm = new SimpleMessageDialogViewModel(content);
|
||||||
return OverlayDialog.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
|
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 (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId, false))
|
||||||
{
|
{
|
||||||
if (quitIfFailed) await NavigateBackAsync();
|
if (quitIfFailed) await NavigateBackAsync();
|
||||||
UIHelper.NotifyError("Operation failed", "Can not load issue...");
|
UIHelper.NotifyError("Can not load issue...", "Operation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
|
|||||||
using var _ = UIHelper.MakeLoading("Update issue...");
|
using var _ = UIHelper.MakeLoading("Update issue...");
|
||||||
if (string.IsNullOrWhiteSpace(NewComment))
|
if (string.IsNullOrWhiteSpace(NewComment))
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("No input", "No comment has been written!");
|
UIHelper.NotifyError("No comment has been written!", "No input");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -17,9 +17,9 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
|
|
||||||
public IScreen HostScreen { get; }
|
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;
|
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)
|
if (!IsOwnerRole)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!");
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,7 +599,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
if (!IsOwnerRole)
|
if (!IsOwnerRole)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!");
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,13 +615,13 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Permission issue", "Invalid role level!");
|
UIHelper.NotifyError("Invalid role level!", "Permission issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.SafeRole == member.Role)
|
if (vm.SafeRole == member.Role)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Modification issue", "No modification yet.");
|
UIHelper.NotifyError("No modification yet.", "Modification issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,7 +636,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
if (!IsOwnerRole)
|
if (!IsOwnerRole)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Permission issue", "Only repository owner can edit this!");
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,14 +649,14 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
||||||
{
|
{
|
||||||
UIHelper.NotifyError("Permission issue", "Invalid role level!");
|
UIHelper.NotifyError("Invalid role level!", "Permission issue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Username = vm.Username.Trim();
|
vm.Username = vm.Username.Trim();
|
||||||
if (string.IsNullOrEmpty(vm.Username) || vm.Username.Length < 3)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,10 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
using var l = UIHelper.MakeLoading("Contacting to server...");
|
using var l = UIHelper.MakeLoading("Contacting to server...");
|
||||||
await Api.C.SetGatewayAsync(Host);
|
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)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,12 +1,372 @@
|
|||||||
using System;
|
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;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
using Refit;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
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)
|
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:u="https://irihi.tech/ursa"
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
xmlns:semi="https://irihi.tech/semi"
|
|
||||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
x:DataType="vm:SettingViewModel"
|
x:DataType="vm:SettingViewModel"
|
||||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||||
@ -19,18 +18,20 @@
|
|||||||
<TabItem Header="Account">
|
<TabItem Header="Account">
|
||||||
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
||||||
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch" LabelPosition="Top">
|
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch" LabelPosition="Top">
|
||||||
<Label Content="You are a server manager!"/>
|
<Label IsVisible="{Binding LoginUser.IsAdmin}" Content="You are a server manager!"/>
|
||||||
<TextBox u:FormItem.Label="Username" IsReadOnly="True"/>
|
<TextBox u:FormItem.Label="Username" IsReadOnly="True"
|
||||||
<TextBox u:FormItem.Label="Nickname" IsReadOnly="True"/>
|
Text="{Binding LoginUser.Username, Mode=OneWay}"/>
|
||||||
<TextBox u:FormItem.Label="Email" IsReadOnly="True"/>
|
<TextBox u:FormItem.Label="Nickname" Text="{Binding Nickname}"/>
|
||||||
<TextBox u:FormItem.Label="Phone Number" IsReadOnly="True"/>
|
<TextBox u:FormItem.Label="Email" Text="{Binding Email}"/>
|
||||||
<ComboBox u:FormItem.Label="Gender">
|
<TextBox u:FormItem.Label="Phone Number" Text="{Binding PhoneNumber}"/>
|
||||||
<ComboBoxItem Content="Unset"/>
|
<u:EnumSelector u:FormItem.Label="Gender" SelectedValue="{Binding Gender}"/>
|
||||||
<ComboBoxItem Content="Male"/>
|
<TextBox u:FormItem.Label="Bio" Classes="TextArea" Text="{Binding Bio}"/>
|
||||||
<ComboBoxItem Content="Female"/>
|
|
||||||
<ComboBoxItem Content="Walmart Plastic Bag"/>
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
</ComboBox>
|
<u:IconButton Content="Save" Command="{Binding SaveAccountChangesCommand}"/>
|
||||||
<TextBox u:FormItem.Label="Bio" Classes="TextArea"/>
|
<u:IconButton Content="Change Password" Command="{Binding ChangePasswordCommand}"/>
|
||||||
|
<u:IconButton Classes="Danger" Content="Delete" Command="{Binding DeleteSelfAccountCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
</u:Form>
|
</u:Form>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@ -42,41 +43,70 @@
|
|||||||
</u:FormItem>
|
</u:FormItem>
|
||||||
<u:FormGroup Header="Workspace Refresh">
|
<u:FormGroup Header="Workspace Refresh">
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace"/>
|
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace"
|
||||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed"/>
|
IsChecked="{Binding SettingModel.RefreshWorkspaceOnOpen}"/>
|
||||||
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Gain Focus"/>
|
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed"
|
||||||
|
IsChecked="{Binding SettingModel.RefreshWorkspaceOnFilesystemChanges}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</u:FormGroup>
|
</u:FormGroup>
|
||||||
<u:FormGroup Header="Extern Tools">
|
<u:FormGroup Header="Extern Tools">
|
||||||
<u:PathPicker u:FormItem.Label="Open Folder"/>
|
<u:PathPicker u:FormItem.Label="Open Folder"/>
|
||||||
<u:PathPicker u:FormItem.Label="Diff Tool"/>
|
<u:PathPicker u:FormItem.Label="Diff Tool"/>
|
||||||
</u:FormGroup>
|
</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>
|
</u:Form>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Server">
|
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Server">
|
||||||
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
|
||||||
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
<TextBox u:FormItem.Label="Server Public Name"/>
|
<!-- <TextBox u:FormItem.Label="Server Public Name"/> -->
|
||||||
<ToggleSwitch u:FormItem.Label="Allow Public Register"/>
|
<!-- <ToggleSwitch u:FormItem.Label="Allow Public Register"/> -->
|
||||||
<u:FormGroup>
|
<u:FormGroup>
|
||||||
<TextBox u:FormItem.Label="IP Whitelist" Classes="TextArea"/>
|
<TextBox u:FormItem.Label="IP Whitelist" Classes="TextArea" AcceptsReturn="True"
|
||||||
<TextBox u:FormItem.Label="IP Blacklist" Classes="TextArea"/>
|
Text="{Binding ServerWhitelist}"/>
|
||||||
|
<TextBox u:FormItem.Label="IP Blacklist" Classes="TextArea" AcceptsReturn="True"
|
||||||
|
Text="{Binding ServerBlacklist}"/>
|
||||||
</u:FormGroup>
|
</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>
|
</u:Form>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Users">
|
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Users">
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Server Logfile">
|
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Logfile">
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
<StackPanel Spacing="8" Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Refresh"/>
|
<DatePicker SelectedDate="{Binding LogSearchFrom}"/>
|
||||||
|
<DatePicker SelectedDate="{Binding LogSearchTo}"/>
|
||||||
</StackPanel>
|
</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>
|
</StackPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Server Statitics"></TabItem>
|
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -21,4 +21,6 @@ public record UserInfoResponse
|
|||||||
public bool? PublicEmail { get; set; }
|
public bool? PublicEmail { get; set; }
|
||||||
|
|
||||||
public DateTime? CreatedAt { 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);
|
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);
|
query = query.Where(l => l.LogLevel == level.Value);
|
||||||
|
|
||||||
// 分页处理
|
// 分页处理
|
||||||
|
|||||||
@ -128,6 +128,7 @@ public class UserController(
|
|||||||
PublicEmail = authorized ? queryUser.PublicEmail : null,
|
PublicEmail = authorized ? queryUser.PublicEmail : null,
|
||||||
Email = queryUser.PublicEmail || authorized ? queryUser.Email : null,
|
Email = queryUser.PublicEmail || authorized ? queryUser.Email : null,
|
||||||
Phone = authorized ? queryUser.PhoneNumber : null,
|
Phone = authorized ? queryUser.PhoneNumber : null,
|
||||||
|
IsAdmin = queryUser.Admin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user