Worked on admin page listing

This commit is contained in:
2024-10-07 17:39:05 +02:00
parent 6a781990e4
commit 075ca2286f
17 changed files with 195 additions and 21 deletions

View File

@@ -0,0 +1,6 @@
namespace HopFrame.Web.Admin.Attributes.Members;
[AttributeUsage(AttributeTargets.Property)]
public class AdminBoldAttribute(bool bold = true) : Attribute {
public bool Bold { get; set; } = bold;
}

View File

@@ -20,7 +20,7 @@ public interface IAdminPageGenerator<TModel> {
IAdminPageGenerator<TModel> DefaultSort<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression, ListSortDirection direction); IAdminPageGenerator<TModel> DefaultSort<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression, ListSortDirection direction);
IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : IModelRepository<TModel>; IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel>;
IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression); IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);

View File

@@ -8,6 +8,7 @@ public interface IAdminPropertyGenerator {
IAdminPropertyGenerator DisplayInListing(bool display = true); IAdminPropertyGenerator DisplayInListing(bool display = true);
IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator Ignore(bool ignore = true);
IAdminPropertyGenerator Generated(bool generated = true); IAdminPropertyGenerator Generated(bool generated = true);
IAdminPropertyGenerator Bold(bool bold = true);
IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator DisplayName(string displayName);
IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Description(string description);

View File

@@ -1,5 +1,6 @@
using HopFrame.Web.Admin.Models; using HopFrame.Web.Admin.Models;
using HopFrame.Web.Admin.Providers; using HopFrame.Web.Admin.Providers;
using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web.Admin.Generators.Implementation; namespace HopFrame.Web.Admin.Generators.Implementation;
@@ -58,7 +59,7 @@ internal class AdminContextGenerator : IAdminContextGenerator {
public static void RegisterPages(AdminPagesContext context, IAdminPagesProvider provider) { public static void RegisterPages(AdminPagesContext context, IAdminPagesProvider provider, IServiceCollection services) {
var properties = context.GetType().GetProperties(); var properties = context.GetType().GetProperties();
foreach (var property in properties) { foreach (var property in properties) {
@@ -66,6 +67,9 @@ internal class AdminContextGenerator : IAdminContextGenerator {
if (page is null) continue; if (page is null) continue;
provider.RegisterAdminPage(page.Title.ToLower(), page); provider.RegisterAdminPage(page.Title.ToLower(), page);
if (page.RepositoryProvider is not null)
services.AddScoped(page.RepositoryProvider);
} }
} }

View File

@@ -94,7 +94,7 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
return this; return this;
} }
public IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : IModelRepository<TModel> { public IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel> {
Page.RepositoryProvider = typeof(TRepository); Page.RepositoryProvider = typeof(TRepository);
return this; return this;
} }

View File

@@ -45,6 +45,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
return this; return this;
} }
public IAdminPropertyGenerator Bold(bool bold = true) {
_property.Bold = bold;
return this;
}
public IAdminPropertyGenerator DisplayName(string displayName) { public IAdminPropertyGenerator DisplayName(string displayName) {
_property.DisplayName = displayName; _property.DisplayName = displayName;
return this; return this;
@@ -69,6 +74,7 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
if (attributes.Any(a => a is KeyAttribute)) { if (attributes.Any(a => a is KeyAttribute)) {
pageGenerator.Page.DefaultSortPropertyName = property.Name; pageGenerator.Page.DefaultSortPropertyName = property.Name;
Editable(false); Editable(false);
Bold();
} }
if (attributes.Any(a => a is AdminUnsortableAttribute)) if (attributes.Any(a => a is AdminUnsortableAttribute))
@@ -99,6 +105,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute;
Description(attribute?.Description); Description(attribute?.Description);
} }
if (attributes.Any(a => a is AdminBoldAttribute)) {
var attribute = attributes.Single(a => a is AdminBoldAttribute) as AdminBoldAttribute;
Bold(attribute?.Bold == true);
}
if (attributes.Any(a => a is AdminPrefixAttribute)) { if (attributes.Any(a => a is AdminPrefixAttribute)) {
var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute;

View File

@@ -1,8 +0,0 @@
namespace HopFrame.Web.Admin;
public interface IModelRepository<TModel> {
Task<IEnumerable<TModel>> ReadAll();
Task<TModel> Create(TModel model);
Task<TModel> Update(TModel model);
Task Delete(TModel model);
}

View File

@@ -0,0 +1,33 @@
namespace HopFrame.Web.Admin;
public abstract class ModelRepository<TModel> : IModelRepository {
public abstract Task<IEnumerable<TModel>> ReadAll();
public abstract Task<TModel> Create(TModel model);
public abstract Task<TModel> Update(TModel model);
public abstract Task Delete(TModel model);
public async Task<IEnumerable<object>> ReadAllO() {
var models = await ReadAll();
return models.Select(m => (object)m);
}
public async Task<object> CreateO(object model) {
return await Create((TModel)model);
}
public async Task<object> UpdateO(object model) {
return await Update((TModel)model);
}
public Task DeleteO(object model) {
return Delete((TModel)model);
}
}
public interface IModelRepository {
Task<IEnumerable<object>> ReadAllO();
Task<object> CreateO(object model);
Task<object> UpdateO(object model);
Task DeleteO(object model);
}

View File

@@ -13,6 +13,7 @@ public sealed class AdminPageProperty {
public bool Editable { get; set; } = true; public bool Editable { get; set; } = true;
public bool EditDisplayValue { get; set; } = true; public bool EditDisplayValue { get; set; } = true;
public bool Generated { get; set; } public bool Generated { get; set; }
public bool Bold { get; set; } = false;
public bool Ignore { get; set; } public bool Ignore { get; set; }
[JsonIgnore] [JsonIgnore]
public Type Type { get; set; } public Type Type { get; set; }

View File

@@ -16,7 +16,7 @@ public static class ServiceCollectionExtensions {
var generator = new AdminContextGenerator(); var generator = new AdminContextGenerator();
var context = generator.CompileContext<TContext>(); var context = generator.CompileContext<TContext>();
AdminContextGenerator.RegisterPages(context, provider); AdminContextGenerator.RegisterPages(context, provider, services);
services.AddSingleton(context); services.AddSingleton(context);
return services; return services;

View File

@@ -3,6 +3,7 @@ using HopFrame.Security;
using HopFrame.Web.Admin; using HopFrame.Web.Admin;
using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Generators;
using HopFrame.Web.Admin.Models; using HopFrame.Web.Admin.Models;
using HopFrame.Web.Repositories;
namespace HopFrame.Web; namespace HopFrame.Web;
@@ -14,6 +15,7 @@ public class HopAdminContext : AdminPagesContext {
public override void OnModelCreating(IAdminContextGenerator generator) { public override void OnModelCreating(IAdminContextGenerator generator) {
generator.Page<User>() generator.Page<User>()
.Description("On this page you can manage all user accounts.") .Description("On this page you can manage all user accounts.")
.ConfigureRepository<UserProvider>()
.ViewPermission(AdminPermissions.ViewUsers) .ViewPermission(AdminPermissions.ViewUsers)
.CreatePermission(AdminPermissions.AddUser) .CreatePermission(AdminPermissions.AddUser)
.UpdatePermission(AdminPermissions.EditUser) .UpdatePermission(AdminPermissions.EditUser)
@@ -35,6 +37,7 @@ public class HopAdminContext : AdminPagesContext {
generator.Page<PermissionGroup>() generator.Page<PermissionGroup>()
.Description("On this page you can view, create, edit and delete permission groups.") .Description("On this page you can view, create, edit and delete permission groups.")
.ConfigureRepository<GroupProvider>()
.ViewPermission(AdminPermissions.ViewGroups) .ViewPermission(AdminPermissions.ViewGroups)
.CreatePermission(AdminPermissions.AddGroup) .CreatePermission(AdminPermissions.AddGroup)
.UpdatePermission(AdminPermissions.EditGroup) .UpdatePermission(AdminPermissions.EditGroup)

View File

@@ -11,6 +11,9 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using HopFrame.Web.Components.Administration @using HopFrame.Web.Components.Administration
@using BlazorStrap.V5 @using BlazorStrap.V5
@using HopFrame.Database.Repositories
@using HopFrame.Security.Claims
@using HopFrame.Web.Admin
@using HopFrame.Web.Components @using HopFrame.Web.Components
<PageTitle>@_pageData.Title</PageTitle> <PageTitle>@_pageData.Title</PageTitle>
@@ -48,25 +51,66 @@
} }
</BSTD> </BSTD>
} }
@if (_hasEditPermission || _hasDeletePermission) {
<BSTD>Actions</BSTD>
}
</BSTR> </BSTR>
</BSTHead> </BSTHead>
<BSTBody> <BSTBody>
@foreach (var entry in _displayedModels) {
<BSTR>
@foreach (var prop in GetListingProperties()) {
<BSTD Class="@GetClass(prop)">
@GetValue(entry, prop).GetAwaiter().GetResult()
</BSTD>
}
@if (_hasEditPermission || _hasDeletePermission) {
<BSTD>
<BSButtonGroup>
@if (_hasEditPermission) {
<BSButton Color="BSColor.Warning" OnClick="() => Edit(entry)">Edit</BSButton>
}
@if (_hasDeletePermission) {
<BSButton Color="BSColor.Danger" OnClick="() => Delete(entry)">Delete</BSButton>
}
</BSButtonGroup>
</BSTD>
}
</BSTR>
}
</BSTBody> </BSTBody>
</BSTable> </BSTable>
<style>
.bold {
font-weight: bold;
}
</style>
@inject IAdminPagesProvider Pages @inject IAdminPagesProvider Pages
@inject IServiceProvider Provider
@inject ITokenContext Auth
@inject IPermissionRepository Permissions
@code { @code {
[Parameter] [Parameter]
public string Url { get; set; } public string Url { get; set; }
private AdminPage _pageData; private AdminPage _pageData;
private IModelRepository _modelRepository;
private IEnumerable<object> _modelBuffer;
private bool _hasEditPermission;
private bool _hasDeletePermission;
private string _currentSortProperty; private string _currentSortProperty;
private ListSortDirection _currentSortDirection; private ListSortDirection _currentSortDirection;
private DateTime _lastSearch; private DateTime _lastSearch;
private IList<object> _displayedModels;
protected override void OnInitialized() { protected override void OnInitialized() {
_pageData = Pages.LoadAdminPage(Url); _pageData = Pages.LoadAdminPage(Url);
@@ -75,6 +119,17 @@
_currentSortDirection = _pageData.DefaultSortDirection; _currentSortDirection = _pageData.DefaultSortDirection;
} }
protected override async Task OnInitializedAsync() {
_modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository;
if (_modelRepository is null)
throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'");
_hasEditPermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update);
_hasDeletePermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete);
Reload();
}
private IList<AdminPageProperty> GetListingProperties() { private IList<AdminPageProperty> GetListingProperties() {
return _pageData.Properties return _pageData.Properties
.Where(p => p.Ignore == false) .Where(p => p.Ignore == false)
@@ -82,8 +137,11 @@
.ToList(); .ToList();
} }
private void Reload() { private async void Reload() {
_modelBuffer = await _modelRepository.ReadAllO();
_currentSortDirection = _pageData.DefaultSortDirection;
OrderBy(_pageData.DefaultSortPropertyName, false);
} }
private void OrderBy(string property, bool changeDir = true) { private void OrderBy(string property, bool changeDir = true) {
@@ -93,6 +151,7 @@
_currentSortDirection = ListSortDirection.Ascending; _currentSortDirection = ListSortDirection.Ascending;
//TODO: Handle ordering //TODO: Handle ordering
_displayedModels = _modelBuffer.ToList();
_currentSortProperty = property; _currentSortProperty = property;
} }
@@ -109,6 +168,26 @@
if (timeSinceLastKeyPress < TimeSpan.FromMilliseconds(500)) return; if (timeSinceLastKeyPress < TimeSpan.FromMilliseconds(500)) return;
//TODO: Handle searching //TODO: Handle searching
Console.WriteLine(search); OrderBy(_currentSortProperty, false);
}
private async Task<string> GetValue(object entry, AdminPageProperty property) {
object propValue = entry.GetType().GetProperty(property.Name)?.GetValue(entry);
return propValue?.ToString();
}
private string GetClass(AdminPageProperty property) {
if (property.Bold)
return "bold";
return "";
}
private async void Edit(object entry) {
}
private async void Delete(object entry) {
} }
} }

View File

@@ -20,7 +20,3 @@ h3 {
.reload, .sorter { .reload, .sorter {
cursor: pointer; cursor: pointer;
} }
.bold {
font-weight: bold;
}

View File

@@ -1,4 +1,4 @@
@page "/administration/groups" @page "/administration/group"
@rendermode InteractiveServer @rendermode InteractiveServer
@layout AdminLayout @layout AdminLayout

View File

@@ -1,4 +1,4 @@
@page "/administration/users" @page "/administration/user"
@rendermode InteractiveServer @rendermode InteractiveServer
@layout AdminLayout @layout AdminLayout

View File

@@ -0,0 +1,24 @@
using HopFrame.Database.Models;
using HopFrame.Database.Repositories;
using HopFrame.Web.Admin;
namespace HopFrame.Web.Repositories;
internal sealed class GroupProvider(IGroupRepository repo) : ModelRepository<PermissionGroup> {
public override async Task<IEnumerable<PermissionGroup>> ReadAll() {
return await repo.GetPermissionGroups();
}
public override async Task<PermissionGroup> Create(PermissionGroup model) {
return await repo.CreatePermissionGroup(model);
}
public override async Task<PermissionGroup> Update(PermissionGroup model) {
await repo.EditPermissionGroup(model);
return model;
}
public override Task Delete(PermissionGroup model) {
return repo.DeletePermissionGroup(model);
}
}

View File

@@ -0,0 +1,24 @@
using HopFrame.Database.Models;
using HopFrame.Database.Repositories;
using HopFrame.Web.Admin;
namespace HopFrame.Web.Repositories;
internal sealed class UserProvider(IUserRepository repo) : ModelRepository<User> {
public override async Task<IEnumerable<User>> ReadAll() {
return await repo.GetUsers();
}
public override Task<User> Create(User model) {
return repo.AddUser(model);
}
public override async Task<User> Update(User model) {
await repo.UpdateUser(model);
return model;
}
public override Task Delete(User model) {
return repo.DeleteUser(model);
}
}