1
0

feat: Adjust features and fix some issue

This commit is contained in:
Ca2didi 2025-05-21 00:44:48 +08:00
parent b21dae1192
commit 3afafd4e91
9 changed files with 101 additions and 94 deletions

View File

@ -18,6 +18,12 @@ namespace Flawless.Client.Remote
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.5.5.0")]
public partial interface IFlawlessServer
{
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
[Headers("Content-Type: application/json")]
[Post("/api/admin/add_user")]
Task AddUser([Body] RegisterRequest body, CancellationToken cancellationToken = default);
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
[Post("/api/admin/superuser/{username}")]

View File

@ -2,18 +2,14 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reactive.Linq;
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 LiveChartsCore;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using Refit;
@ -49,9 +45,9 @@ public partial class SettingViewModel : RoutableViewModelBase
[Reactive] private string _bio;
[Reactive] private DateTime?
_logSearchFrom = null,
_logSearchTo = null;
[Reactive] private DateTime
_logSearchFrom = DateTime.Now.AddDays(-1),
_logSearchTo = DateTime.Now;
[Reactive] private int _page = 1;
@ -72,10 +68,10 @@ public partial class SettingViewModel : RoutableViewModelBase
{
using (UIHelper.MakeLoading("Fetch server data..."))
{
await RefreshUsersCommand.Execute();
await RefreshUsersAsync();
var sb = new StringBuilder();
ServerBlacklist = sb.AppendJoin(",\n", await Api.C.Gateway.IpWhitelistGet()).ToString();
ServerWhitelist = sb.AppendJoin(",\n", await Api.C.Gateway.IpWhitelistGet()).ToString();
ServerBlacklist = sb.Clear().AppendJoin(",\n", await Api.C.Gateway.IpBlacklistGet()).ToString();
}
}
@ -244,7 +240,8 @@ public partial class SettingViewModel : RoutableViewModelBase
Username = user.Username,
Email = user.Email,
IsActive = user.IsActive,
IsAdmin = user.IsAdmin ?? false
IsAdmin = user.IsAdmin ?? false,
CanEdit = user.Username != Api.C.Username.Value!
});
}
}
@ -257,9 +254,12 @@ public partial class SettingViewModel : RoutableViewModelBase
[ReactiveCommand]
private async Task CreateUserAsync()
{
var result = new UserCreateDialogViewModel();
result.Password = GenerateRandomPassword();
var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
var result = new UserCreateDialogViewModel
{
Password = GenerateRandomPassword()
};
while (true)
{
var r = await OverlayDialog.ShowModal<UserCreateDialogView, UserCreateDialogViewModel>(result, AppDefaultValues.HostId, opt);
@ -271,14 +271,14 @@ public partial class SettingViewModel : RoutableViewModelBase
try
{
await Api.C.Gateway.Register(new RegisterRequest
await Api.C.Gateway.AddUser(new RegisterRequest
{
Username = result.Username,
Password = result.Password,
Email = result.Email
});
Users.Add(UserService.C.GetUserInfoAsync(result.Username)!);
await this.RefreshUsersAsync();
UIHelper.NotifySuccess($"User {result.Username} create successfully");
}
catch (ApiException ex)
@ -311,11 +311,12 @@ public partial class SettingViewModel : RoutableViewModelBase
var newPassword = GenerateRandomPassword();
try
{
await Api.C.Gateway.RenewPassword(new ResetPasswordRequest
await Api.C.Gateway.ResetPassword(new ResetPasswordRequest
{
Identity = username,
NewPassword = newPassword
});
await UIHelper.SimpleAlert($"Password has been reset to {newPassword}");
}
catch (Exception ex)
@ -386,7 +387,7 @@ public partial class SettingViewModel : RoutableViewModelBase
{
await Api.C.Gateway.SuperuserPost(username, active);
Users.First(x => x.Username == username).IsAdmin = active;
UIHelper.NotifySuccess($"{username} has already {(active ? "enabled" : "disabled")}.");
UIHelper.NotifySuccess($"{username} has already set to {(active ? "superuser" : "nornmal user")}.");
}
catch (Exception ex)
{

View File

@ -12,7 +12,7 @@
<DockPanel Margin="50">
<Grid RowDefinitions="Auto, 18, Auto" ColumnDefinitions="*, Auto" DockPanel.Dock="Top">
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<Label Content="{Binding ServerFriendlyName, StringFormat='Server {0}', FallbackValue='Server LocalTest'}" FontSize="18" FontWeight="400"></Label>
<Label Content="{Binding ServerFriendlyName, FallbackValue='Unknown Server'}" FontSize="18" FontWeight="400"></Label>
<Label Content="Repositories" FontSize="32" FontWeight="600"></Label>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">

View File

@ -6,7 +6,7 @@
xmlns:vm="clr-namespace:Flawless.Client.ViewModels.ModalBox"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:IssueEditDialogViewModel"
MinWidth="400"
MinWidth="500"
x:Class="Flawless.Client.Views.ModalBox.IssueDetailEditView">
<u:Form HorizontalAlignment="Stretch">

View File

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Flawless.Client.ViewModels.ModalBox"
x:Class="Flawless.Client.Views.ModalBox.UserCreateDialogView"
MinWidth="400"
x:DataType="vm:UserCreateDialogViewModel">
<Grid Margin="10" HorizontalAlignment="Stretch" RowDefinitions="Auto,Auto,Auto">

View File

@ -39,19 +39,10 @@
<TabItem Header="Preference">
<ScrollViewer Width="600" HorizontalAlignment="Left" Margin="6">
<u:Form HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<u:FormItem Label="Default Storage Location">
<u:PathPicker/>
</u:FormItem>
<!-- <u:FormGroup Header="Workspace Refresh"> -->
<!-- <StackPanel Spacing="4"> -->
<!-- <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:FormItem Label="Default Storage Location"> -->
<!-- <u:PathPicker/> -->
<!-- </u:FormItem> -->
<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">
@ -92,36 +83,28 @@
Command="{Binding CreateUserCommand}"/>
</StackPanel>
<DataGrid Grid.Row="1" ItemsSource="{Binding Users, Mode=TwoWay}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Username" Binding="{Binding Username}" Width="*"/>
<DataGridTextColumn Header="CreateAt" Binding="{Binding JoinDate}" Width="*"/>
<DataGridCheckBoxColumn Header="Active" Binding="{Binding IsActive}" Width="Auto"/>
<DataGridCheckBoxColumn Header="Admin" Binding="{Binding IsAdmin}" Width="Auto"/>
<DataGridTemplateColumn Header="Operations" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="4">
<ListBox Grid.Row="1" ItemsSource="{Binding Users, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Spacing="12" Margin="6">
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<PathIcon Data="{StaticResource SemiIconCrown}" Height="12" Width="12" IsVisible="{Binding IsAdmin}"/>
<PathIcon Data="{StaticResource SemiIconMinusCircle}" Height="12" Width="12" IsVisible="{Binding !IsActive}"/>
<Label Content="{Binding Username}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center" IsEnabled="{Binding CanEdit}">
<Button Content="Enable"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).InactivateUserCommand}"
CommandParameter="{Binding Username}"
IsEnabled="{Binding !CanEdit}"
IsVisible="{Binding IsActive}"/>
<Button Content="Disable"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).ActivateUserCommand}"
CommandParameter="{Binding Username}"
IsEnabled="{Binding !CanEdit}"
IsVisible="{Binding !IsActive}"/>
<Button Content="Disable"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).InactivateUserCommand}"
CommandParameter="{Binding Username}"
IsVisible="{Binding IsActive}"/>
<Button Content="Promote"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).PromoteUserCommand}"
CommandParameter="{Binding Username}"
IsEnabled="{Binding !CanEdit}"
IsVisible="{Binding !IsAdmin}"
Classes="Danger"/>
@ -129,39 +112,35 @@
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).DemoteUserCommand}"
CommandParameter="{Binding Username}"
IsVisible="{Binding IsAdmin}"
IsEnabled="{Binding !CanEdit}"
Classes="Danger"/>
<Button Content="Delete"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).DeleteUserCommand}"
CommandParameter="{Binding Username}"
IsEnabled="{Binding !CanEdit}"
Classes="Danger"/>
<Button Content="Reset Password"
Command="{Binding $parent[views:SettingView].((vm:SettingViewModel)DataContext).ForceUpdateUserPasswordCommand}"
CommandParameter="{Binding Username}"
IsEnabled="{Binding !CanEdit}"/>
CommandParameter="{Binding Username}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</TabItem>
<TabItem IsVisible="{Binding LoginUser.IsAdmin}" Header="Logfile">
<StackPanel Spacing="8" Orientation="Vertical" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Horizontal" Spacing="4">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="4">
<DatePicker SelectedDate="{Binding LogSearchFrom}"/>
<Label Content=" → "/>
<DatePicker SelectedDate="{Binding LogSearchTo}"/>
</StackPanel>
<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:EnumSelector SelectedValue="{Binding Loglevel, Mode=TwoWay}"/>
<NumericUpDown Value="{Binding PageSize, Mode=TwoWay}" Minimum="10" Maximum="100"/>
<NumericUpDown Value="{Binding Page, Mode=TwoWay}" Minimum="1"/>
<u:IconButton Icon="{StaticResource SemiIconSearch}"
Command="{Binding DownloadServerLogCommand}"/>
</StackPanel>

View File

@ -13,6 +13,7 @@ namespace Flawless.Server.Controllers;
public class AdminController(
UserManager<AppUser> userManager,
AccessControlService accessControlService,
ILogger<AdminController> logger,
AppDbContext dbContext) : ControllerBase
{
@ -23,6 +24,29 @@ public class AdminController(
return null;
}
[HttpPost("add_user")]
public async Task<IActionResult> AddUserAsync(RegisterRequest request)
{
var user = new AppUser
{
UserName = request.Username,
Email = request.Email,
EmailConfirmed = true,
CreatedOn = DateTime.UtcNow,
};
user.RenewSecurityStamp();
var result = await userManager.CreateAsync(user, request.Password);
if (result.Succeeded)
{
logger.LogInformation("User '{0}' created (SUPERUSER REGISTER)", user.UserName);
return Ok();
}
logger.LogInformation("User '{0}' NOT created (SUPERUSER REGISTER) : {1}", user.UserName, result.Errors);
return BadRequest(new FailedResponse(result.Errors));
}
[HttpPost("superuser/{username}")]
public async Task<IActionResult> SetSuperuserAsync(string username, bool toSuper)
{
@ -59,7 +83,7 @@ public class AdminController(
var t = await TestIfValid();
if (t != null) return t;
return await userManager.Users.Select(x => new UserInfoResponse
var r = await userManager.Users.Select(x => new UserInfoResponse
{
Authorized = true,
Username = x.UserName,
@ -71,8 +95,10 @@ public class AdminController(
Email = x.Email,
Phone = x.PhoneNumber,
IsAdmin = x.Admin,
IsActive = x.LockoutEnabled
IsActive = !x.LockoutEnabled
}).ToArrayAsync();
return r;
}
[HttpPost("user/delete/{username}")]
@ -93,6 +119,9 @@ public class AdminController(
[HttpPost("user/enable/{username}")]
public async Task<IActionResult> EnableUserAsync(string username)
{
var t = await TestIfValid();
if (t != null) return t;
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
@ -124,11 +153,10 @@ public class AdminController(
if (t != null) return t;
if (r.Identity == null) return BadRequest(new FailedResponse("Identity (User Id) is not set!"));
var user = await userManager.FindByIdAsync(r.Identity);
var user = await userManager.FindByNameAsync(r.Identity);
if (user == null) return BadRequest(new FailedResponse("Identity (User Id) does not exist!"));
var resetToken = await userManager.GeneratePasswordResetTokenAsync(user);
var result = await userManager.ResetPasswordAsync(user, resetToken, r.NewPassword);
var result = await userManager.ResetPasswordAsync(user, "", r.NewPassword);
if (!result.Succeeded) return BadRequest(new FailedResponse(result.Errors));
return Ok();
@ -174,26 +202,18 @@ public class AdminController(
[HttpGet("logs")]
public async Task<ActionResult<IEnumerable<LogEntryResponse>>> GetSystemLogsAsync(
[FromQuery] DateTime? startTime = null,
[FromQuery] DateTime? endTime = null,
[FromQuery] LogLevel? level = null,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
[FromQuery] DateTime startTime,
[FromQuery] DateTime endTime,
[FromQuery] LogLevel level,
[FromQuery] int page,
[FromQuery] int pageSize)
{
var t = await TestIfValid();
if (t != null) return t;
var query = dbContext.SystemLogs.AsQueryable();
// 时间过滤
if (startTime.HasValue)
query = query.Where(l => l.Timestamp >= startTime);
if (endTime.HasValue)
query = query.Where(l => l.Timestamp <= endTime);
// 日志级别过滤
if (level.HasValue && level.Value != LogLevel.None)
query = query.Where(l => l.LogLevel == level.Value);
var query = dbContext.SystemLogs.Where(x =>
x.Timestamp >= startTime && x.Timestamp <= endTime && x.LogLevel >= level
).AsQueryable();
// 分页处理
var totalCount = await query.CountAsync();

View File

@ -10,9 +10,9 @@
"CoreDb": "Server=localhost;Port=5432;User Id=postgres;Database=flawless"
},
"LocalStoragePath": "/Users/cardidi/flawless-data",
"User": {
"PublicRegister": true
},
"UseWebHook": true,
"AllowPublicRegistration": true,
"ServerName": "Cardidi Private Area",
"Jwt": {
"SecretKey": "your_256bit_security_key_at_here_otherwise_not_bootable",
"Issuer": "test",

View File

@ -10,9 +10,9 @@
"CoreDb": "Server=localhost;Port=5432;User Id=postgres;Database=flawless"
},
"LocalStoragePath": "/Users/cardidi/flawless-data",
"User": {
"PublicRegister": true
},
"UseWebHook": true,
"AllowPublicRegistration": true,
"ServerName": "Cardidi Private Area",
"Jwt": {
"SecretKey": "your_256bit_security_key_at_here_otherwise_not_bootable",
"Issuer": "test",