Add Users And account administration panel #1
Merged
maxime.batista
merged 5 commits from users
into master
1 year ago
@ -1,32 +1,95 @@
|
|||||||
@page "/users"
|
@page "/users"
|
||||||
|
@using AdminPanel.Models
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using AdminPanel.Components;
|
|
||||||
@using AdminPanel.Models;
|
|
||||||
|
|
||||||
<PageTitle>User Panel</PageTitle>
|
<PageTitle>User Panel</PageTitle>
|
||||||
|
|
||||||
<h1>User Panel</h1>
|
<h1>User Panel</h1>
|
||||||
|
|
||||||
<div class="users-div">
|
<div id="users-div">
|
||||||
@if (Users == null)
|
|
||||||
{
|
<MudOverlay Visible="IsAddingUser" DarkBackground>
|
||||||
<p>Fetching Data...</p>
|
<MudForm id="add-account-form">
|
||||||
}
|
<MudTextField T="string"
|
||||||
else
|
Label="Name"
|
||||||
foreach (User user in Users)
|
@bind-Value="FormAccountName"
|
||||||
{
|
Required
|
||||||
<div class="user-cell">
|
Validation="@(VerifyLength(6, 256))"/>
|
||||||
<UserComponent User=@user/>
|
<MudTextField T="string"
|
||||||
<Leaflet>
|
Label="Email"
|
||||||
<Header>
|
@bind-Value="FormAccountEmail"
|
||||||
see more
|
Required
|
||||||
</Header>
|
Validation="@(new EmailAddressAttribute() { ErrorMessage = "The email address is invalid" })"/>
|
||||||
|
<MudTextField T="string"
|
||||||
<Body>
|
InputType="InputType.Password"
|
||||||
<p>Ratio</p>
|
Label="Password"
|
||||||
</Body>
|
@bind-Value="FormAccountPassword"
|
||||||
</Leaflet>
|
Validation="@(VerifyLength(6, 256))"
|
||||||
|
Required/>
|
||||||
|
<MudCheckBox T="bool" Label="Is Administrator" @bind-Value="FormAccountIsAdmin"/>
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(() => IsAddingUser = false)">Cancel</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ConfirmAddAccount">Add Account</MudButton>
|
||||||
|
</MudForm>
|
||||||
|
</MudOverlay>
|
||||||
|
|
||||||
|
|
||||||
|
<MudDataGrid
|
||||||
|
T="User"
|
||||||
|
@ref="Grid"
|
||||||
|
ServerData="LoadUsersFromServer"
|
||||||
|
MultiSelection="true"
|
||||||
|
ColumnResizeMode="ResizeMode.Column"
|
||||||
|
RowsPerPage="10"
|
||||||
|
ReadOnly="false"
|
||||||
|
EditMode="DataGridEditMode.Form"
|
||||||
|
@bind-SelectedItems="SelectedItems"
|
||||||
|
CommittedItemChanges="OnAccountUpdated">
|
||||||
|
|
||||||
|
<ToolBarContent>
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="@(() => IsAddingUser = true)" Color="@Color.Success" Class="add-item-btn"/>
|
||||||
|
<MudText Align="Align.Center">Add Account</MudText>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div style="display: flex; align-items: center">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Remove" OnClick="RemoveSelection" Color="@Color.Error" Class="remove-item-btn"/>
|
||||||
|
<MudText Align="Align.Center">Remove Selection</MudText>
|
||||||
|
</div>
|
||||||
|
<MudSpacer/>
|
||||||
|
<MudText>Accounts</MudText>
|
||||||
|
<MudSpacer/>
|
||||||
|
<MudTextField
|
||||||
|
T="string"
|
||||||
|
@bind-Value="SearchString"
|
||||||
|
OnKeyDown="@ValidateSearch"
|
||||||
|
Placeholder="Search an email or a name"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||||
|
Immediate
|
||||||
|
IconSize="Size.Medium"/>
|
||||||
|
</ToolBarContent>
|
||||||
|
|
||||||
|
<Columns>
|
||||||
|
<SelectColumn T="User"/>
|
||||||
|
<PropertyColumn Property="x => x.Name" Required/>
|
||||||
|
<PropertyColumn Property="x => x.Email" Title="Email Address" Required/>
|
||||||
|
<TemplateColumn Title="Is Administrator">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudCheckBox Value="@context.Item!.IsAdmin" ReadOnly/>
|
||||||
|
</CellTemplate>
|
||||||
|
<EditTemplate>
|
||||||
|
<MudText>Is Administrator :</MudText>
|
||||||
|
<MudCheckBox @bind-Value="context.Item.IsAdmin"/>
|
||||||
|
</EditTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.Id" IsEditable="false"/>
|
||||||
|
<TemplateColumn>
|
||||||
|
<CellTemplate>
|
||||||
|
<MudIconButton Size="Size.Small" Icon="@Icons.Material.Outlined.Edit" OnClick="@context.Actions.StartEditingItemAsync"/>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="User" PageSizeOptions="new[] { 1, 2, 4, 10, 25, 50, 100 }"/>
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#add-account-form {
|
||||||
|
background-color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
color: #ffba00;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,28 @@
|
|||||||
using AdminPanel;
|
using AdminPanel;
|
||||||
|
using AdminPanel.Pages;
|
||||||
|
using AdminPanel.Services;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
using MudBlazor.Services;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
builder.RootComponents.Add<App>("#app");
|
builder.RootComponents.Add<App>("#app");
|
||||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||||
|
|
||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services
|
||||||
|
.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
|
||||||
|
// builder.Logging.SetMinimumLevel(LogLevel.Debug);
|
||||||
|
|
||||||
|
var client = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri("http://localhost:8080")
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IUsersService>(sp => new HttpUsersService(client));
|
||||||
|
//builder.Services.AddScoped<IUsersService, UsersServiceStub>();
|
||||||
|
|
||||||
|
builder.Services.AddMudServices();
|
||||||
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
@ -0,0 +1,93 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using AdminPanel.Models;
|
||||||
|
|
||||||
|
namespace AdminPanel.Services;
|
||||||
|
|
||||||
|
public class HttpUsersService : IUsersService
|
||||||
|
{
|
||||||
|
private HttpClient Client { get; }
|
||||||
|
|
||||||
|
public HttpUsersService(HttpClient client)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ServerErrorMessageDto(string? Field, string? Error, string Message);
|
||||||
|
|
||||||
|
private async Task EnsureResponseIsOk(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = await response.Content.ReadFromJsonAsync<ServerErrorMessageDto[]>();
|
||||||
|
var messages = content!
|
||||||
|
.GroupBy(e => e.Field ?? e.Error!)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.ToList().ConvertAll(e => e.Message)
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new ServiceException("Server refused request", messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ListUsersResponseDto(List<User> Users, uint TotalCount);
|
||||||
|
|
||||||
|
public async Task<(uint, List<User>)> ListUsers(uint from, uint len, string? searchString = null)
|
||||||
|
{
|
||||||
|
var httpResponse =
|
||||||
|
await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}");
|
||||||
|
await EnsureResponseIsOk(httpResponse);
|
||||||
|
|
||||||
|
var response = await httpResponse.Content.ReadFromJsonAsync<ListUsersResponseDto>()
|
||||||
|
?? throw new Exception("Received non-parseable json from server");
|
||||||
|
|
||||||
|
return (response.TotalCount, response.Users);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record AddUserRequestDto(string Username, string Email, string Password, bool IsAdmin);
|
||||||
|
|
||||||
|
private record AddUserResponseDto(uint Id);
|
||||||
|
|
||||||
|
public async Task<User> AddUser(string username, string email, string password, bool isAdmin)
|
||||||
|
{
|
||||||
|
var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add",
|
||||||
|
new AddUserRequestDto(username, email, password, isAdmin));
|
||||||
|
|
||||||
|
await EnsureResponseIsOk(httpResponse);
|
||||||
|
|
||||||
|
var response = await httpResponse.Content.ReadFromJsonAsync<AddUserResponseDto>()
|
||||||
|
?? throw new Exception("Received non-parseable json from server");
|
||||||
|
|
||||||
|
return new User
|
||||||
|
{
|
||||||
|
Name = username,
|
||||||
|
Email = email,
|
||||||
|
IsAdmin = isAdmin,
|
||||||
|
Id = response.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RemoveUsersRequestDto(uint[] Identifiers);
|
||||||
|
|
||||||
|
public async Task RemoveUsers(IEnumerable<uint> userIds)
|
||||||
|
{
|
||||||
|
var httpResponse =
|
||||||
|
await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequestDto(userIds.ToArray()));
|
||||||
|
|
||||||
|
await EnsureResponseIsOk(httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UpdateUserRequestDto(string Email, string Username, bool IsAdmin);
|
||||||
|
|
||||||
|
public async Task UpdateUser(User user)
|
||||||
|
{
|
||||||
|
var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update",
|
||||||
|
new UpdateUserRequestDto(user.Email, user.Name, user.IsAdmin));
|
||||||
|
await EnsureResponseIsOk(httpResponse);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
namespace AdminPanel.Services;
|
||||||
|
|
||||||
|
class ServiceException : Exception
|
||||||
|
{
|
||||||
|
public Dictionary<string, List<string>> ArgumentMessages { get; init; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceException(string? message, Dictionary<string, List<string>> arguments) : base(message)
|
||||||
|
{
|
||||||
|
ArgumentMessages = arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(string? message, Dictionary<string, List<string>> arguments, Exception? innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
ArgumentMessages = arguments;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue