From 66ddc22012dc16ad9f09168e430c329f36faa583 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 30 Sep 2024 19:01:39 +0200 Subject: [PATCH 01/16] Created AdminContext handling --- HopFrame.sln | 6 + .../HopFrame.Database.csproj | 4 + .../Models/PermissionGroup.cs | 9 +- src/HopFrame.Database/Models/User.cs | 10 +- src/HopFrame.Web.Admin/AdminPagesContext.cs | 9 + .../Attributes/AdminDescriptionAttribute.cs | 6 + .../Attributes/AdminNameAttribute.cs | 6 + .../Classes/AdminButtonConfigAttribute.cs | 8 + .../Classes/AdminPermissionsAttribute.cs | 8 + .../Attributes/Members/AdminBoldAttribute.cs | 4 + .../Members/AdminHideValueAttribute.cs | 4 + .../Members/AdminIgnoreAttribute.cs | 6 + .../Members/AdminUneditableAttribute.cs | 4 + .../Members/AdminUnsortableAttribute.cs | 4 + .../Generators/IAdminContextGenerator.cs | 7 + .../Generators/IAdminPageGenerator.cs | 26 +++ .../Generators/IAdminPropertyGenerator.cs | 14 ++ .../Generators/IGenerator.cs | 7 + .../Implementation/AdminContextGenerator.cs | 91 +++++++++ .../Implementation/AdminPageGenerator.cs | 180 ++++++++++++++++++ .../Implementation/AdminPropertyGenerator.cs | 52 +++++ .../HopFrame.Web.Admin.csproj | 13 ++ src/HopFrame.Web.Admin/IModelRepository.cs | 8 + src/HopFrame.Web.Admin/Models/AdminPage.cs | 22 +++ .../Models/AdminPagePermissions.cs | 8 + .../Models/AdminPageProperty.cs | 17 ++ .../ServiceCollectionExtensions.cs | 17 ++ src/HopFrame.Web/HopFrame.Web.csproj | 1 + test/FrontendTest/FrontendTest.csproj | 2 +- test/FrontendTest/Program.cs | 1 + test/RestApiTest/AdminContext.cs | 26 +++ .../RestApiTest/Controllers/TestController.cs | 7 +- test/RestApiTest/Program.cs | 2 + 33 files changed, 582 insertions(+), 7 deletions(-) create mode 100644 src/HopFrame.Web.Admin/AdminPagesContext.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/IGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs create mode 100644 src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs create mode 100644 src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj create mode 100644 src/HopFrame.Web.Admin/IModelRepository.cs create mode 100644 src/HopFrame.Web.Admin/Models/AdminPage.cs create mode 100644 src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs create mode 100644 src/HopFrame.Web.Admin/Models/AdminPageProperty.cs create mode 100644 src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs create mode 100644 test/RestApiTest/AdminContext.cs diff --git a/HopFrame.sln b/HopFrame.sln index f9217e8..2e65007 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "src\HopFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "test\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web.Admin", "src\HopFrame.Web.Admin\HopFrame.Web.Admin.csproj", "{02D9F10A-664A-4EF7-BF19-310C26FF4DEB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.Build.0 = Release|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02D9F10A-664A-4EF7-BF19-310C26FF4DEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj index f2bcba7..1216958 100644 --- a/src/HopFrame.Database/HopFrame.Database.csproj +++ b/src/HopFrame.Database/HopFrame.Database.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index 7a70ebd..0a0c669 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,22 +1,27 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; +[AdminName("Groups")] +[AdminDescription("On this page you can view, create, edit and delete permission groups.")] public class PermissionGroup : IPermissionOwner { [Key, Required, MaxLength(50)] public string Name { get; init; } - [Required, DefaultValue(false)] + [Required, DefaultValue(false), AdminUnsortable] public bool IsDefaultGroup { get; set; } [MaxLength(500)] public string Description { get; set; } - [Required] + [Required, AdminUneditable] public DateTime CreatedAt { get; set; } + [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 971d899..ab944f0 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; +[AdminDescription("On this page you can manage all user accounts.")] public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] @@ -14,15 +17,16 @@ public class User : IPermissionOwner { [Required, MaxLength(50), EmailAddress] public string Email { get; set; } - [Required, MinLength(8), MaxLength(255), JsonIgnore] + [Required, MinLength(8), MaxLength(255), JsonIgnore, AdminIgnore(onlyForListing: true), AdminHideValue] public string Password { get; set; } - [Required] + [Required, AdminUneditable] public DateTime CreatedAt { get; set; } + [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } - [JsonIgnore] + [JsonIgnore, AdminIgnore] public virtual IList Tokens { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/AdminPagesContext.cs b/src/HopFrame.Web.Admin/AdminPagesContext.cs new file mode 100644 index 0000000..7be251e --- /dev/null +++ b/src/HopFrame.Web.Admin/AdminPagesContext.cs @@ -0,0 +1,9 @@ +using HopFrame.Web.Admin.Generators; + +namespace HopFrame.Web.Admin; + +public abstract class AdminPagesContext { + + public abstract void OnModelCreating(IAdminContextGenerator generator); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs new file mode 100644 index 0000000..843f250 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class AdminDescriptionAttribute(string description) : Attribute { + public string Description { get; set; } = description; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs new file mode 100644 index 0000000..aaa8bac --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class AdminNameAttribute(string name) : Attribute { + public string Name { get; set; } = name; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs new file mode 100644 index 0000000..2995164 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin.Attributes.Classes; + +[AttributeUsage(AttributeTargets.Class)] +public class AdminButtonConfigAttribute(bool showCreateButton = true, bool showDeleteButton = true, bool showUpdateButton = true) : Attribute { + public bool ShowCreateButton { get; set; } = showCreateButton; + public bool ShowDeleteButton { get; set; } = showDeleteButton; + public bool ShowUpdateButton { get; set; } = showUpdateButton; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs new file mode 100644 index 0000000..59b0747 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs @@ -0,0 +1,8 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Attributes.Classes; + +[AttributeUsage(AttributeTargets.Class)] +public class AdminPermissionsAttribute(AdminPagePermissions permissions) : Attribute { + public AdminPagePermissions Permissions { get; set; } = permissions; +} diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs new file mode 100644 index 0000000..70fa63e --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminBoldAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs new file mode 100644 index 0000000..c40288d --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminHideValueAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs new file mode 100644 index 0000000..6022cf3 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminIgnoreAttribute(bool onlyForListing = false) : Attribute { + public bool OnlyForListing { get; set; } = onlyForListing; +} diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs new file mode 100644 index 0000000..f29e575 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminUneditableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs new file mode 100644 index 0000000..6ddb865 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminUnsortableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs new file mode 100644 index 0000000..49cf115 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs @@ -0,0 +1,7 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminContextGenerator { + + IAdminPageGenerator Page(); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs new file mode 100644 index 0000000..c4f7c36 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.Linq.Expressions; + +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminPageGenerator { + + IAdminPageGenerator Title(string title); + IAdminPageGenerator Description(string description); + + IAdminPageGenerator ViewPermission(string permission); + IAdminPageGenerator CreatePermission(string permission); + IAdminPageGenerator UpdatePermission(string permission); + IAdminPageGenerator DeletePermission(string permission); + + IAdminPageGenerator ShowCreateButton(bool show); + IAdminPageGenerator ShowDeleteButton(bool show); + IAdminPageGenerator ShowUpdateButton(bool show); + + IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction); + + IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository; + + IAdminPropertyGenerator Property(Expression> propertyExpression); + +} diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs new file mode 100644 index 0000000..d6b644d --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -0,0 +1,14 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IAdminPropertyGenerator { + + IAdminPropertyGenerator Sortable(bool sortable); + IAdminPropertyGenerator Editable(bool editable); + IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + IAdminPropertyGenerator DisplayInListing(bool display = true); + IAdminPropertyGenerator Bold(bool isBold = true); + + IAdminPropertyGenerator DisplayName(string displayName); + IAdminPropertyGenerator Description(string description); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IGenerator.cs b/src/HopFrame.Web.Admin/Generators/IGenerator.cs new file mode 100644 index 0000000..5cf9d6f --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/IGenerator.cs @@ -0,0 +1,7 @@ +namespace HopFrame.Web.Admin.Generators; + +public interface IGenerator { + + TGeneratedType Compile(); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs new file mode 100644 index 0000000..314c490 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -0,0 +1,91 @@ +using System.Reflection; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Classes; +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal class AdminContextGenerator : IAdminContextGenerator { + + private readonly IDictionary _adminPages = new Dictionary(); + + public IAdminPageGenerator Page() { + if (_adminPages.TryGetValue(typeof(TModel), out var pageGenerator)) + return pageGenerator as IAdminPageGenerator; + + var generator = Activator.CreateInstance(typeof(IAdminPageGenerator)) as IAdminPageGenerator; + + ApplyConfigurationFromAttributes(generator, typeof(TModel).GetCustomAttributes(false)); + + _adminPages.Add(typeof(TModel), generator); + + return generator; + } + + public AdminPage CompilePage() { + var generator = _adminPages[typeof(TModel)]; + if (generator is null) return null; + + return (generator as AdminPageGenerator)?.Compile(); + } + + public TContext CompileContext() where TContext : AdminPagesContext { + var type = typeof(TContext); + var compileMethod = typeof(AdminContextGenerator).GetMethod(nameof(CompilePage)); + + var properties = type.GetProperties(); + + var context = Activator.CreateInstance(); + + foreach (var property in properties) { + var propertyType = property.PropertyType.GenericTypeArguments[0]; + var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); + var generatorInstance = Activator.CreateInstance(pageGeneratorType, [propertyType.Name]); + + var populatorMethod = typeof(AdminContextGenerator) + .GetMethod(nameof(ApplyConfigurationFromAttributes))? + .MakeGenericMethod(propertyType); + populatorMethod?.Invoke(this, [generatorInstance, propertyType.GetCustomAttributes()]); + + _adminPages.Add(propertyType, generatorInstance); + } + + context.OnModelCreating(this); + + foreach (var property in properties) { + var modelType = property.PropertyType.GenericTypeArguments[0]; + var method = compileMethod?.MakeGenericMethod(modelType); + property.SetValue(context, method?.Invoke(this, [])); + } + + return context; + } + + public void ApplyConfigurationFromAttributes(IAdminPageGenerator generator, object[] attributes) { + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + generator.Title(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + generator.Description(attribute?.Description); + } + + if (attributes.Any(a => a is AdminPermissionsAttribute)) { + var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; + generator.CreatePermission(attribute?.Permissions.Create); + generator.UpdatePermission(attribute?.Permissions.Update); + generator.ViewPermission(attribute?.Permissions.View); + generator.DeletePermission(attribute?.Permissions.Delete); + } + + if (attributes.Any(a => a is AdminButtonConfigAttribute)) { + var attribute = attributes.Single(a => a is AdminButtonConfigAttribute) as AdminButtonConfigAttribute; + generator.ShowCreateButton(attribute?.ShowCreateButton == true); + generator.ShowUpdateButton(attribute?.ShowUpdateButton == true); + generator.ShowDeleteButton(attribute?.ShowDeleteButton == true); + } + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs new file mode 100644 index 0000000..a2219ce --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -0,0 +1,180 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq.Expressions; +using System.Reflection; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal sealed class AdminPageGenerator : IAdminPageGenerator, IGenerator> { + + private readonly AdminPage _page; + private readonly IDictionary _propertyGenerators; + + public AdminPageGenerator() { + _page = new AdminPage { + Permissions = new AdminPagePermissions() + }; + _propertyGenerators = new Dictionary(); + + var type = typeof(TModel); + var properties = type.GetProperties(); + + foreach (var property in properties) { + var attributes = property.GetCustomAttributes(false); + var ignoreProperty = attributes + .SingleOrDefault(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; + if (ignoreProperty?.OnlyForListing == false) continue; + + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; + + ApplyConfigurationFromAttributes(generator, attributes, property); + + _propertyGenerators.Add(property.Name, generator); + } + } + + public AdminPageGenerator(string title) : this() { + Title(title); + } + + public IAdminPageGenerator Title(string title) { + _page.Title = title; + return this; + } + + public IAdminPageGenerator Description(string description) { + _page.Description = description; + return this; + } + + public IAdminPageGenerator ViewPermission(string permission) { + _page.Permissions.View = permission; + return this; + } + + public IAdminPageGenerator CreatePermission(string permission) { + _page.Permissions.Create = permission; + return this; + } + + public IAdminPageGenerator UpdatePermission(string permission) { + _page.Permissions.Update = permission; + return this; + } + + public IAdminPageGenerator DeletePermission(string permission) { + _page.Permissions.Delete = permission; + return this; + } + + public IAdminPageGenerator ShowCreateButton(bool show) { + _page.ShowCreateButton = show; + return this; + } + + public IAdminPageGenerator ShowDeleteButton(bool show) { + _page.ShowDeleteButton = show; + return this; + } + + public IAdminPageGenerator ShowUpdateButton(bool show) { + _page.ShowUpdateButton = show; + return this; + } + + public IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction) { + var property = GetPropertyInfo(propertyExpression); + + _page.DefaultSortPropertyName = property.Name; + _page.DefaultSortDirection = direction; + return this; + } + + public IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository { + _page.RepositoryProvider = typeof(TRepository); + return this; + } + + public IAdminPropertyGenerator Property(Expression> propertyExpression) { + var property = GetPropertyInfo(propertyExpression); + + if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) + return propertyGenerator; + + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; + ApplyConfigurationFromAttributes(generator, property.GetCustomAttributes(false), property); + _propertyGenerators.Add(property.Name, generator); + + return generator; + } + + public AdminPage Compile() { + var properties = new List(); + + foreach (var generator in _propertyGenerators.Values){ + properties.Add(generator.Compile()); + } + + _page.Properties = properties; + + return _page; + } + + private void ApplyConfigurationFromAttributes(AdminPropertyGenerator generator, object[] attributes, PropertyInfo property) { + if (attributes.Any(a => a is KeyAttribute)) { + _page.DefaultSortPropertyName = property.Name; + generator.Bold(); + generator.Editable(false); + } + + if (attributes.Any(a => a is AdminUnsortableAttribute)) + generator.Sortable(false); + + if (attributes.Any(a => a is AdminUneditableAttribute)) + generator.Editable(false); + + if (attributes.Any(a => a is AdminBoldAttribute)) + generator.Bold(); + + if (attributes.Any(a => a is AdminIgnoreAttribute)) + generator.DisplayInListing(false); + + if (attributes.Any(a => a is AdminHideValueAttribute)) + generator.DisplayValueWhileEditing(false); + + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + generator.DisplayName(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + generator.Description(attribute?.Description); + } + } + + private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { + if (propertyLambda.Body is not MemberExpression member) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); + } + + if (member.Member is not PropertyInfo propInfo) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + } + + Type type = typeof(TSource); + if (propInfo.ReflectedType != null && type != propInfo.ReflectedType && + !type.IsSubclassOf(propInfo.ReflectedType)) { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a property that is not from type {type}."); + } + + if (propInfo.Name is null) + throw new ArgumentException($"Expression '{propertyLambda}' refers a not existing property."); + + return propInfo; + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs new file mode 100644 index 0000000..f37cd80 --- /dev/null +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -0,0 +1,52 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Generators.Implementation; + +internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { + + private readonly AdminPageProperty _property = new() { + Name = name, + Type = type + }; + + public IAdminPropertyGenerator Sortable(bool sortable) { + _property.Sortable = sortable; + return this; + } + + public IAdminPropertyGenerator Editable(bool editable) { + _property.Editable = editable; + return this; + } + + public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { + _property.EditDisplayValue = display; + return this; + } + + public IAdminPropertyGenerator DisplayInListing(bool display = true) { + _property.DisplayInListing = display; + return this; + } + + public IAdminPropertyGenerator Bold(bool isBold = true) { + _property.Bold = isBold; + return this; + } + + public IAdminPropertyGenerator DisplayName(string displayName) { + _property.DisplayName = displayName; + return this; + } + + public IAdminPropertyGenerator Description(string description) { + _property.Description = description; + return this; + } + + public AdminPageProperty Compile() { + _property.DisplayName ??= _property.Name; + + return _property; + } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj b/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj new file mode 100644 index 0000000..096a35f --- /dev/null +++ b/src/HopFrame.Web.Admin/HopFrame.Web.Admin.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + disable + + + + + + + diff --git a/src/HopFrame.Web.Admin/IModelRepository.cs b/src/HopFrame.Web.Admin/IModelRepository.cs new file mode 100644 index 0000000..6bdffe0 --- /dev/null +++ b/src/HopFrame.Web.Admin/IModelRepository.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin; + +public interface IModelRepository { + Task> ReadAll(); + Task Create(TModel model); + Task Update(TModel model); + Task Delete(TModel model); +} diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs new file mode 100644 index 0000000..bea528f --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; + +namespace HopFrame.Web.Admin.Models; + +public class AdminPage : IAdminPageEntry { + public string Title { get; set; } + public string Description { get; set; } + public AdminPagePermissions Permissions { get; set; } + public IList Properties { get; set; } + [JsonIgnore] + public Type RepositoryProvider { get; set; } + + public string DefaultSortPropertyName { get; set; } + public ListSortDirection DefaultSortDirection { get; set; } + + public bool ShowCreateButton { get; set; } = true; + public bool ShowDeleteButton { get; set; } = true; + public bool ShowUpdateButton { get; set; } = true; +} + +public interface IAdminPageEntry; diff --git a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs new file mode 100644 index 0000000..fc99101 --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs @@ -0,0 +1,8 @@ +namespace HopFrame.Web.Admin.Models; + +public class AdminPagePermissions { + public string View { get; set; } + public string Create { get; set; } + public string Update { get; set; } + public string Delete { get; set; } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs new file mode 100644 index 0000000..5444fae --- /dev/null +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace HopFrame.Web.Admin.Models; + +public class AdminPageProperty { + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + + public bool DisplayInListing { get; set; } = true; + public bool Sortable { get; set; } = true; + public bool Editable { get; set; } = true; + public bool EditDisplayValue { get; set; } = true; + public bool Bold { get; set; } + [JsonIgnore] + public Type Type { get; set; } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..4b3c007 --- /dev/null +++ b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using HopFrame.Web.Admin.Generators.Implementation; +using Microsoft.Extensions.DependencyInjection; + +namespace HopFrame.Web.Admin; + +public static class ServiceCollectionExtensions { + + public static IServiceCollection AddAdminContext(this IServiceCollection services) where TContext : AdminPagesContext { + services.AddSingleton(_ => { + var generator = new AdminContextGenerator(); + return generator.CompileContext(); + }); + + return services; + } + +} \ No newline at end of file diff --git a/src/HopFrame.Web/HopFrame.Web.csproj b/src/HopFrame.Web/HopFrame.Web.csproj index ab3c31a..bec6f04 100644 --- a/src/HopFrame.Web/HopFrame.Web.csproj +++ b/src/HopFrame.Web/HopFrame.Web.csproj @@ -20,6 +20,7 @@ + diff --git a/test/FrontendTest/FrontendTest.csproj b/test/FrontendTest/FrontendTest.csproj index 312aa4b..e043848 100644 --- a/test/FrontendTest/FrontendTest.csproj +++ b/test/FrontendTest/FrontendTest.csproj @@ -2,7 +2,7 @@ net8.0 - enable + disable enable diff --git a/test/FrontendTest/Program.cs b/test/FrontendTest/Program.cs index af54f28..77989c6 100644 --- a/test/FrontendTest/Program.cs +++ b/test/FrontendTest/Program.cs @@ -1,6 +1,7 @@ using FrontendTest; using FrontendTest.Components; using HopFrame.Web; +using HopFrame.Web.Admin; var builder = WebApplication.CreateBuilder(args); diff --git a/test/RestApiTest/AdminContext.cs b/test/RestApiTest/AdminContext.cs new file mode 100644 index 0000000..00bf00e --- /dev/null +++ b/test/RestApiTest/AdminContext.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using HopFrame.Database.Models; +using HopFrame.Web.Admin; +using HopFrame.Web.Admin.Generators; +using HopFrame.Web.Admin.Models; + +namespace RestApiTest; + +public class AdminContext : AdminPagesContext { + + public AdminPage Users { get; set; } + public AdminPage Groups { get; set; } + + public override void OnModelCreating(IAdminContextGenerator generator) { + + /*generator.Page() + .Title("Users") + .Description("On this page you can manage all user accounts.") + .UpdatePermission("update") + .ViewPermission("view") + .DeletePermission("delete") + .CreatePermission("create") + .DefaultSort(u => u.Id, ListSortDirection.Descending);*/ + + } +} \ No newline at end of file diff --git a/test/RestApiTest/Controllers/TestController.cs b/test/RestApiTest/Controllers/TestController.cs index 092784f..0d5a1f6 100644 --- a/test/RestApiTest/Controllers/TestController.cs +++ b/test/RestApiTest/Controllers/TestController.cs @@ -10,7 +10,7 @@ namespace RestApiTest.Controllers; [ApiController] [Route("test")] -public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase { +public class TestController(ITokenContext userContext, DatabaseContext context, AdminContext adminContext) : ControllerBase { [HttpGet("permissions"), Authorized] public ActionResult> Permissions() { @@ -50,5 +50,10 @@ public class TestController(ITokenContext userContext, DatabaseContext context) public async Task>> GetAddresses() { return LogicResult>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync()); } + + [HttpGet("adminContext")] + public ActionResult GetAdminContext() { + return LogicResult.Ok(adminContext); + } } \ No newline at end of file diff --git a/test/RestApiTest/Program.cs b/test/RestApiTest/Program.cs index bfcbc38..632e62f 100644 --- a/test/RestApiTest/Program.cs +++ b/test/RestApiTest/Program.cs @@ -1,5 +1,6 @@ using RestApiTest; using HopFrame.Api.Extensions; +using HopFrame.Web.Admin; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -13,6 +14,7 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(); +builder.Services.AddAdminContext(); builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { From 9cf818c55d6e5656205ff3f1d1d2acf73f46ad48 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 5 Oct 2024 12:18:32 +0200 Subject: [PATCH 02/16] Created static object provider + added some properties --- .../HopFrame.Database.csproj | 4 -- .../Models/PermissionGroup.cs | 9 +-- src/HopFrame.Database/Models/User.cs | 10 +--- src/HopFrame.Web.Admin/AdminPagesContext.cs | 2 +- .../Attributes/AdminDescriptionAttribute.cs | 2 +- .../Attributes/AdminNameAttribute.cs | 2 +- .../Classes/AdminButtonConfigAttribute.cs | 2 +- .../Classes/AdminPermissionsAttribute.cs | 9 ++- .../Attributes/Members/AdminBoldAttribute.cs | 2 +- .../Members/AdminHideValueAttribute.cs | 2 +- .../Members/AdminIgnoreAttribute.cs | 2 +- .../Members/AdminPrefixAttribute.cs | 6 ++ .../Members/AdminUneditableAttribute.cs | 2 +- .../Members/AdminUnsortableAttribute.cs | 2 +- .../Generators/IAdminPropertyGenerator.cs | 2 + .../Implementation/AdminContextGenerator.cs | 14 ++++- .../Implementation/AdminPageGenerator.cs | 14 +++-- .../Implementation/AdminPropertyGenerator.cs | 11 ++++ src/HopFrame.Web.Admin/Models/AdminPage.cs | 7 ++- .../Models/AdminPagePermissions.cs | 2 +- .../Models/AdminPageProperty.cs | 4 +- .../Providers/IAdminPagesProvider.cs | 11 ++++ .../Implementation/AdminPagesProvider.cs | 19 +++++++ .../ServiceCollectionExtensions.cs | 20 +++++-- src/HopFrame.Web/HopAdminContext.cs | 55 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 2 + .../Components/Pages/Counter.razor | 11 ++++ test/RestApiTest/AdminContext.cs | 26 --------- .../RestApiTest/Controllers/TestController.cs | 7 +-- test/RestApiTest/Program.cs | 2 - 30 files changed, 186 insertions(+), 77 deletions(-) create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminPrefixAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs create mode 100644 src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs create mode 100644 src/HopFrame.Web/HopAdminContext.cs delete mode 100644 test/RestApiTest/AdminContext.cs diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj index 1216958..f2bcba7 100644 --- a/src/HopFrame.Database/HopFrame.Database.csproj +++ b/src/HopFrame.Database/HopFrame.Database.csproj @@ -22,8 +22,4 @@ - - - - diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index 0a0c669..7a70ebd 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,27 +1,22 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using HopFrame.Web.Admin.Attributes; -using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; -[AdminName("Groups")] -[AdminDescription("On this page you can view, create, edit and delete permission groups.")] public class PermissionGroup : IPermissionOwner { [Key, Required, MaxLength(50)] public string Name { get; init; } - [Required, DefaultValue(false), AdminUnsortable] + [Required, DefaultValue(false)] public bool IsDefaultGroup { get; set; } [MaxLength(500)] public string Description { get; set; } - [Required, AdminUneditable] + [Required] public DateTime CreatedAt { get; set; } - [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index ab944f0..971d899 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,11 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using HopFrame.Web.Admin.Attributes; -using HopFrame.Web.Admin.Attributes.Members; namespace HopFrame.Database.Models; -[AdminDescription("On this page you can manage all user accounts.")] public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] @@ -17,16 +14,15 @@ public class User : IPermissionOwner { [Required, MaxLength(50), EmailAddress] public string Email { get; set; } - [Required, MinLength(8), MaxLength(255), JsonIgnore, AdminIgnore(onlyForListing: true), AdminHideValue] + [Required, MinLength(8), MaxLength(255), JsonIgnore] public string Password { get; set; } - [Required, AdminUneditable] + [Required] public DateTime CreatedAt { get; set; } - [AdminIgnore(onlyForListing: true)] public virtual IList Permissions { get; set; } - [JsonIgnore, AdminIgnore] + [JsonIgnore] public virtual IList Tokens { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/AdminPagesContext.cs b/src/HopFrame.Web.Admin/AdminPagesContext.cs index 7be251e..39b769c 100644 --- a/src/HopFrame.Web.Admin/AdminPagesContext.cs +++ b/src/HopFrame.Web.Admin/AdminPagesContext.cs @@ -4,6 +4,6 @@ namespace HopFrame.Web.Admin; public abstract class AdminPagesContext { - public abstract void OnModelCreating(IAdminContextGenerator generator); + public virtual void OnModelCreating(IAdminContextGenerator generator) {} } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs index 843f250..92e3fe3 100644 --- a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs @@ -1,6 +1,6 @@ namespace HopFrame.Web.Admin.Attributes; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] -public class AdminDescriptionAttribute(string description) : Attribute { +public sealed class AdminDescriptionAttribute(string description) : Attribute { public string Description { get; set; } = description; } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs index aaa8bac..90821c3 100644 --- a/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/AdminNameAttribute.cs @@ -1,6 +1,6 @@ namespace HopFrame.Web.Admin.Attributes; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] -public class AdminNameAttribute(string name) : Attribute { +public sealed class AdminNameAttribute(string name) : Attribute { public string Name { get; set; } = name; } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs index 2995164..ccd3c8f 100644 --- a/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminButtonConfigAttribute.cs @@ -1,7 +1,7 @@ namespace HopFrame.Web.Admin.Attributes.Classes; [AttributeUsage(AttributeTargets.Class)] -public class AdminButtonConfigAttribute(bool showCreateButton = true, bool showDeleteButton = true, bool showUpdateButton = true) : Attribute { +public sealed class AdminButtonConfigAttribute(bool showCreateButton = true, bool showDeleteButton = true, bool showUpdateButton = true) : Attribute { public bool ShowCreateButton { get; set; } = showCreateButton; public bool ShowDeleteButton { get; set; } = showDeleteButton; public bool ShowUpdateButton { get; set; } = showUpdateButton; diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs index 59b0747..7d68eab 100644 --- a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs @@ -3,6 +3,11 @@ using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Attributes.Classes; [AttributeUsage(AttributeTargets.Class)] -public class AdminPermissionsAttribute(AdminPagePermissions permissions) : Attribute { - public AdminPagePermissions Permissions { get; set; } = permissions; +public sealed class AdminPermissionsAttribute(string view = null, string create = null, string update = null, string delete = null) : Attribute { + public AdminPagePermissions Permissions { get; set; } = new() { + Create = create, + Update = update, + Delete = delete, + View = view + }; } diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs index 70fa63e..68311b2 100644 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs @@ -1,4 +1,4 @@ namespace HopFrame.Web.Admin.Attributes.Members; [AttributeUsage(AttributeTargets.Property)] -public class AdminBoldAttribute : Attribute; +public sealed class AdminBoldAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs index c40288d..deb8092 100644 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminHideValueAttribute.cs @@ -1,4 +1,4 @@ namespace HopFrame.Web.Admin.Attributes.Members; [AttributeUsage(AttributeTargets.Property)] -public class AdminHideValueAttribute : Attribute; +public sealed class AdminHideValueAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs index 6022cf3..7b33e04 100644 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminIgnoreAttribute.cs @@ -1,6 +1,6 @@ namespace HopFrame.Web.Admin.Attributes.Members; [AttributeUsage(AttributeTargets.Property)] -public class AdminIgnoreAttribute(bool onlyForListing = false) : Attribute { +public sealed class AdminIgnoreAttribute(bool onlyForListing = false) : Attribute { public bool OnlyForListing { get; set; } = onlyForListing; } diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminPrefixAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminPrefixAttribute.cs new file mode 100644 index 0000000..a5247db --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminPrefixAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public sealed class AdminPrefixAttribute(string prefix) : Attribute { + public string Prefix { get; set; } = prefix; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs index f29e575..78eb491 100644 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUneditableAttribute.cs @@ -1,4 +1,4 @@ namespace HopFrame.Web.Admin.Attributes.Members; [AttributeUsage(AttributeTargets.Property)] -public class AdminUneditableAttribute : Attribute; +public sealed class AdminUneditableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs index 6ddb865..37935b2 100644 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUnsortableAttribute.cs @@ -1,4 +1,4 @@ namespace HopFrame.Web.Admin.Attributes.Members; [AttributeUsage(AttributeTargets.Property)] -public class AdminUnsortableAttribute : Attribute; +public sealed class AdminUnsortableAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index d6b644d..45802bb 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -7,8 +7,10 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator DisplayValueWhileEditing(bool display); IAdminPropertyGenerator DisplayInListing(bool display = true); IAdminPropertyGenerator Bold(bool isBold = true); + IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); + IAdminPropertyGenerator Prefix(string prefix); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index 314c490..26ab887 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -2,6 +2,7 @@ using System.Reflection; using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Attributes.Classes; using HopFrame.Web.Admin.Models; +using HopFrame.Web.Admin.Providers; namespace HopFrame.Web.Admin.Generators.Implementation; @@ -40,7 +41,7 @@ internal class AdminContextGenerator : IAdminContextGenerator { foreach (var property in properties) { var propertyType = property.PropertyType.GenericTypeArguments[0]; var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); - var generatorInstance = Activator.CreateInstance(pageGeneratorType, [propertyType.Name]); + var generatorInstance = Activator.CreateInstance(pageGeneratorType, [property.Name]); // Calls constructor with title attribute var populatorMethod = typeof(AdminContextGenerator) .GetMethod(nameof(ApplyConfigurationFromAttributes))? @@ -88,4 +89,15 @@ internal class AdminContextGenerator : IAdminContextGenerator { } } + public static void RegisterPages(AdminPagesContext context, IAdminPagesProvider provider) { + var properties = context.GetType().GetProperties(); + + foreach (var property in properties) { + var page = property.GetValue(context) as AdminPage; + if (page is null) continue; + + provider.RegisterAdminPage(page.Title.ToLower(), page); + } + } + } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index a2219ce..b114e34 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -24,9 +24,6 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, foreach (var property in properties) { var attributes = property.GetCustomAttributes(false); - var ignoreProperty = attributes - .SingleOrDefault(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; - if (ignoreProperty?.OnlyForListing == false) continue; var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; @@ -139,8 +136,12 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, if (attributes.Any(a => a is AdminBoldAttribute)) generator.Bold(); - if (attributes.Any(a => a is AdminIgnoreAttribute)) + if (attributes.Any(a => a is AdminIgnoreAttribute)) { + var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; generator.DisplayInListing(false); + generator.Sortable(false); + generator.Ignore(attribute?.OnlyForListing == false); + } if (attributes.Any(a => a is AdminHideValueAttribute)) generator.DisplayValueWhileEditing(false); @@ -154,6 +155,11 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; generator.Description(attribute?.Description); } + + if (attributes.Any(a => a is AdminPrefixAttribute)) { + var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; + generator.Prefix(attribute?.Prefix); + } } private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index f37cd80..cab1b2d 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -26,6 +26,7 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro public IAdminPropertyGenerator DisplayInListing(bool display = true) { _property.DisplayInListing = display; + _property.Sortable = false; return this; } @@ -34,6 +35,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Ignore(bool ignore = false) { + _property.Ignore = ignore; + return this; + } + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; @@ -44,6 +50,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Prefix(string prefix) { + _property.Prefix = prefix; + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index bea528f..5878e6f 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -3,11 +3,14 @@ using System.Text.Json.Serialization; namespace HopFrame.Web.Admin.Models; -public class AdminPage : IAdminPageEntry { +public sealed class AdminPage : AdminPage; + +public class AdminPage { public string Title { get; set; } public string Description { get; set; } public AdminPagePermissions Permissions { get; set; } public IList Properties { get; set; } + [JsonIgnore] public Type RepositoryProvider { get; set; } @@ -18,5 +21,3 @@ public class AdminPage : IAdminPageEntry { public bool ShowDeleteButton { get; set; } = true; public bool ShowUpdateButton { get; set; } = true; } - -public interface IAdminPageEntry; diff --git a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs index fc99101..e9629a6 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs @@ -1,6 +1,6 @@ namespace HopFrame.Web.Admin.Models; -public class AdminPagePermissions { +public sealed class AdminPagePermissions { public string View { get; set; } public string Create { get; set; } public string Update { get; set; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 5444fae..daaab90 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -2,16 +2,18 @@ using System.Text.Json.Serialization; namespace HopFrame.Web.Admin.Models; -public class AdminPageProperty { +public sealed class AdminPageProperty { public string Name { get; set; } public string DisplayName { get; set; } public string Description { get; set; } + public string Prefix { get; set; } public bool DisplayInListing { get; set; } = true; public bool Sortable { get; set; } = true; public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; public bool Bold { get; set; } + public bool Ignore { get; set; } [JsonIgnore] public Type Type { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs new file mode 100644 index 0000000..d20c66c --- /dev/null +++ b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs @@ -0,0 +1,11 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Providers; + +public interface IAdminPagesProvider { + + internal void RegisterAdminPage(string url, AdminPage page); + AdminPage LoadAdminPage(string url); + IList LoadRegisteredAdminPages(); + +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs new file mode 100644 index 0000000..d68a5ae --- /dev/null +++ b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs @@ -0,0 +1,19 @@ +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web.Admin.Providers.Implementation; + +public class AdminPagesProvider : IAdminPagesProvider { + private readonly IDictionary _pages = new Dictionary(); + + public void RegisterAdminPage(string url, AdminPage page) { + _pages.Add(url, page); + } + + public AdminPage LoadAdminPage(string url) { + return _pages.TryGetValue(url, out var page) ? page : null; + } + + public IList LoadRegisteredAdminPages() { + return _pages.Values.ToList(); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs index 4b3c007..b8ee37e 100644 --- a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs @@ -1,17 +1,29 @@ using HopFrame.Web.Admin.Generators.Implementation; +using HopFrame.Web.Admin.Providers; +using HopFrame.Web.Admin.Providers.Implementation; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace HopFrame.Web.Admin; public static class ServiceCollectionExtensions { + private static IAdminPagesProvider _provider; + public static IServiceCollection AddAdminContext(this IServiceCollection services) where TContext : AdminPagesContext { - services.AddSingleton(_ => { - var generator = new AdminContextGenerator(); - return generator.CompileContext(); - }); + var provider = GetProvider(); + services.TryAddSingleton(provider); + + var generator = new AdminContextGenerator(); + var context = generator.CompileContext(); + AdminContextGenerator.RegisterPages(context, provider); + services.AddSingleton(context); return services; } + + private static IAdminPagesProvider GetProvider() { + return _provider ??= new AdminPagesProvider(); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs new file mode 100644 index 0000000..3a5c067 --- /dev/null +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -0,0 +1,55 @@ +using HopFrame.Database.Models; +using HopFrame.Security; +using HopFrame.Web.Admin; +using HopFrame.Web.Admin.Generators; +using HopFrame.Web.Admin.Models; + +namespace HopFrame.Web; + +public class HopAdminContext : AdminPagesContext { + + public AdminPage Users { get; set; } + public AdminPage Groups { get; set; } + + public override void OnModelCreating(IAdminContextGenerator generator) { + generator.Page() + .Description("On this page you can manage all user accounts.") + .ViewPermission(AdminPermissions.ViewUsers) + .CreatePermission(AdminPermissions.AddUser) + .UpdatePermission(AdminPermissions.EditUser) + .DeletePermission(AdminPermissions.DeleteUser); + + generator.Page().Property(u => u.Password) + .DisplayInListing(false) + .DisplayValueWhileEditing(false); + + generator.Page().Property(u => u.CreatedAt) + .Editable(false); + + generator.Page().Property(u => u.Permissions) + .DisplayInListing(false); + + generator.Page().Property(u => u.Tokens) + .Ignore(); + + + generator.Page() + .Description("On this page you can view, create, edit and delete permission groups.") + .ViewPermission(AdminPermissions.ViewGroups) + .CreatePermission(AdminPermissions.AddGroup) + .UpdatePermission(AdminPermissions.EditGroup) + .DeletePermission(AdminPermissions.DeleteGroup); + + generator.Page().Property(g => g.Name) + .Prefix("group."); + + generator.Page().Property(g => g.IsDefaultGroup) + .Sortable(false); + + generator.Page().Property(g => g.CreatedAt) + .Editable(false); + + generator.Page().Property(g => g.Permissions) + .DisplayInListing(false); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs index a9ab84e..548e2e9 100644 --- a/src/HopFrame.Web/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using BlazorStrap; using CurrieTechnologies.Razor.SweetAlert2; using HopFrame.Database; using HopFrame.Security.Authentication; +using HopFrame.Web.Admin; using HopFrame.Web.Services; using HopFrame.Web.Services.Implementation; using Microsoft.AspNetCore.Builder; @@ -15,6 +16,7 @@ public static class ServiceCollectionExtensions { services.AddHopFrameRepositories(); services.AddScoped(); services.AddTransient(); + services.AddAdminContext(); // Component library's services.AddSweetAlert2(); diff --git a/test/FrontendTest/Components/Pages/Counter.razor b/test/FrontendTest/Components/Pages/Counter.razor index 4fdbec5..4ac3989 100644 --- a/test/FrontendTest/Components/Pages/Counter.razor +++ b/test/FrontendTest/Components/Pages/Counter.razor @@ -1,4 +1,7 @@ @page "/counter" +@using System.Text.Json +@using HopFrame.Web +@using HopFrame.Web.Admin.Providers @rendermode InteractiveServer Counter @@ -9,12 +12,20 @@ +@inject IAdminPagesProvider Provider + @code { private int currentCount = 0; private string[] permissions = ["web.counter"]; private void IncrementCount() { currentCount++; + + string json = JsonSerializer.Serialize(Provider.LoadRegisteredAdminPages(), new JsonSerializerOptions { + WriteIndented = true + }); + + Console.WriteLine(json); } } \ No newline at end of file diff --git a/test/RestApiTest/AdminContext.cs b/test/RestApiTest/AdminContext.cs deleted file mode 100644 index 00bf00e..0000000 --- a/test/RestApiTest/AdminContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel; -using HopFrame.Database.Models; -using HopFrame.Web.Admin; -using HopFrame.Web.Admin.Generators; -using HopFrame.Web.Admin.Models; - -namespace RestApiTest; - -public class AdminContext : AdminPagesContext { - - public AdminPage Users { get; set; } - public AdminPage Groups { get; set; } - - public override void OnModelCreating(IAdminContextGenerator generator) { - - /*generator.Page() - .Title("Users") - .Description("On this page you can manage all user accounts.") - .UpdatePermission("update") - .ViewPermission("view") - .DeletePermission("delete") - .CreatePermission("create") - .DefaultSort(u => u.Id, ListSortDirection.Descending);*/ - - } -} \ No newline at end of file diff --git a/test/RestApiTest/Controllers/TestController.cs b/test/RestApiTest/Controllers/TestController.cs index 0d5a1f6..092784f 100644 --- a/test/RestApiTest/Controllers/TestController.cs +++ b/test/RestApiTest/Controllers/TestController.cs @@ -10,7 +10,7 @@ namespace RestApiTest.Controllers; [ApiController] [Route("test")] -public class TestController(ITokenContext userContext, DatabaseContext context, AdminContext adminContext) : ControllerBase { +public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase { [HttpGet("permissions"), Authorized] public ActionResult> Permissions() { @@ -50,10 +50,5 @@ public class TestController(ITokenContext userContext, DatabaseContext context, public async Task>> GetAddresses() { return LogicResult>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync()); } - - [HttpGet("adminContext")] - public ActionResult GetAdminContext() { - return LogicResult.Ok(adminContext); - } } \ No newline at end of file diff --git a/test/RestApiTest/Program.cs b/test/RestApiTest/Program.cs index 632e62f..bfcbc38 100644 --- a/test/RestApiTest/Program.cs +++ b/test/RestApiTest/Program.cs @@ -1,6 +1,5 @@ using RestApiTest; using HopFrame.Api.Extensions; -using HopFrame.Web.Admin; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -14,7 +13,6 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(); -builder.Services.AddAdminContext(); builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { From 6a110d5b8bf45b58b0c49618868e55839af45139 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 6 Oct 2024 11:09:00 +0200 Subject: [PATCH 03/16] Added AdminPages to admin dashboard and navigation + created 2.0 todo list --- README.md | 8 +++++ .../Attributes/Classes/AdminUrlAttribute.cs | 6 ++++ .../Generators/IAdminPageGenerator.cs | 1 + .../Implementation/AdminContextGenerator.cs | 5 ++++ .../Implementation/AdminPageGenerator.cs | 6 ++++ src/HopFrame.Web.Admin/Models/AdminPage.cs | 1 + .../Pages/Administration/AdminDashboard.razor | 22 ++++++++++---- .../Administration/Layout/AdminMenu.razor | 30 +++++-------------- test/FrontendTest/AdminContext.cs | 12 ++++++++ test/FrontendTest/Models/Address.cs | 18 +++++++++++ test/FrontendTest/Models/Employee.cs | 8 +++++ test/FrontendTest/Program.cs | 1 + 12 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs create mode 100644 test/FrontendTest/AdminContext.cs create mode 100644 test/FrontendTest/Models/Address.cs create mode 100644 test/FrontendTest/Models/Employee.cs diff --git a/README.md b/README.md index b7ed1b7..7f1e8a4 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,14 @@ A simple backend management api for ASP.NET Core Web APIs - [x] Permission management - [x] Frontend dashboards +## 2.0 Todo list +- [x] 1.0 bug fixes +- [x] Code cleanup +- [x] Relations in database +- [ ] Generated Admin pages +- [ ] Pretty Login page for administration +- [ ] Clean documentation + # Usage There are two different versions of HopFrame, either the Web API version or the full Blazor web version. diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs new file mode 100644 index 0000000..ab87a2e --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs @@ -0,0 +1,6 @@ +namespace HopFrame.Web.Admin.Attributes.Classes; + +[AttributeUsage(AttributeTargets.Class)] +public class AdminUrlAttribute(string url) : Attribute { + public string Url { get; set; } = url; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index c4f7c36..f9902fb 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -7,6 +7,7 @@ public interface IAdminPageGenerator { IAdminPageGenerator Title(string title); IAdminPageGenerator Description(string description); + IAdminPageGenerator Url(string url); IAdminPageGenerator ViewPermission(string permission); IAdminPageGenerator CreatePermission(string permission); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index 26ab887..6596a2f 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -73,6 +73,11 @@ internal class AdminContextGenerator : IAdminContextGenerator { generator.Description(attribute?.Description); } + if (attributes.Any(a => a is AdminUrlAttribute)) { + var attribute = attributes.Single(a => a is AdminUrlAttribute) as AdminUrlAttribute; + generator.Url(attribute?.Url); + } + if (attributes.Any(a => a is AdminPermissionsAttribute)) { var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; generator.CreatePermission(attribute?.Permissions.Create); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index b114e34..bad320e 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -39,6 +39,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, public IAdminPageGenerator Title(string title) { _page.Title = title; + _page.Url ??= title.ToLower(); return this; } @@ -47,6 +48,11 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } + public IAdminPageGenerator Url(string url) { + _page.Url = url; + return this; + } + public IAdminPageGenerator ViewPermission(string permission) { _page.Permissions.View = permission; return this; diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index 5878e6f..241f69c 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -8,6 +8,7 @@ public sealed class AdminPage : AdminPage; public class AdminPage { public string Title { get; set; } public string Description { get; set; } + public string Url { get; set; } public AdminPagePermissions Permissions { get; set; } public IList Properties { get; set; } diff --git a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor index e939548..9484d82 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor @@ -5,6 +5,7 @@ @using BlazorStrap @using HopFrame.Web.Pages.Administration.Layout @using BlazorStrap.V5 +@using HopFrame.Web.Admin.Providers @using HopFrame.Web.Components @using Microsoft.AspNetCore.Components.Web @layout AdminLayout @@ -13,15 +14,15 @@ - @foreach (var view in AdminMenu.Subpages) { - + @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { + - @view.Name - @view.Permission - @view.Description - Open + @adminPage.Title + @adminPage.Permissions.View + @adminPage.Description + Open @@ -31,3 +32,12 @@ @inject NavigationManager Navigator +@inject IAdminPagesProvider Pages + +@code { + + public void NavigateTo(string url) { + Navigator.NavigateTo("administration/" + url, true); + } + +} diff --git a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor index a66f311..ae96859 100644 --- a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor +++ b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor @@ -3,10 +3,10 @@ @using BlazorStrap @using BlazorStrap.V5 @using HopFrame.Security.Claims +@using HopFrame.Web.Admin.Providers @using HopFrame.Web.Services @using static Microsoft.AspNetCore.Components.Web.RenderMode @using HopFrame.Web.Components.Administration -@using HopFrame.Web.Model @using HopFrame.Web.Components @@ -23,9 +23,9 @@ Dashboard - @foreach (var nav in Subpages) { - - @nav.Name + @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { + + @adminPage.Title } @@ -46,25 +46,11 @@ @inject NavigationManager Navigator @inject ITokenContext Context @inject IAuthService Auth +@inject IAdminPagesProvider Pages @code { - public static IList Subpages = new List { - new () { - Name = "Users", - Url = "administration/users", - Description = "On this page you can manage all user accounts.", - Permission = Security.AdminPermissions.ViewUsers - }, - new () { - Name = "Groups", - Url = "administration/groups", - Description = "On this page you can view, create, edit and delete permission groups.", - Permission = Security.AdminPermissions.ViewGroups - } - }; - private bool IsNavItemActive(string element) { - return Navigator.Uri.Contains(element); + return Navigator.Uri.TrimEnd('/').EndsWith(element); } private bool IsDashboardActive() { @@ -72,11 +58,11 @@ } private void NavigateToDashboard() { - Navigate("administration"); + Navigator.NavigateTo("administration", true); } private void Navigate(string url) { - Navigator.NavigateTo(url, true); + Navigator.NavigateTo("administration/" + url, true); } private void Logout() { diff --git a/test/FrontendTest/AdminContext.cs b/test/FrontendTest/AdminContext.cs new file mode 100644 index 0000000..016c488 --- /dev/null +++ b/test/FrontendTest/AdminContext.cs @@ -0,0 +1,12 @@ +using HopFrame.Web.Admin; +using HopFrame.Web.Admin.Models; +using RestApiTest.Models; + +namespace FrontendTest; + +public class AdminContext : AdminPagesContext { + + public AdminPage
Addresses { get; set; } + public AdminPage Employees { get; set; } + +} \ No newline at end of file diff --git a/test/FrontendTest/Models/Address.cs b/test/FrontendTest/Models/Address.cs new file mode 100644 index 0000000..386114d --- /dev/null +++ b/test/FrontendTest/Models/Address.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace RestApiTest.Models; + +public class Address { + [ForeignKey("Employee")] + public int AddressId { get; set; } + public string AddressDetails { get; set; } + public string City { get; set; } + public int ZipCode { get; set; } + public string State { get; set; } + public string Country { get; set; } + + [JsonIgnore] + public virtual Employee Employee { get; set; } +} \ No newline at end of file diff --git a/test/FrontendTest/Models/Employee.cs b/test/FrontendTest/Models/Employee.cs new file mode 100644 index 0000000..6f70edc --- /dev/null +++ b/test/FrontendTest/Models/Employee.cs @@ -0,0 +1,8 @@ +namespace RestApiTest.Models; + +public class Employee { + public int EmployeeId { get; set; } + public string Name { get; set; } + + public virtual Address Address { get; set; } +} \ No newline at end of file diff --git a/test/FrontendTest/Program.cs b/test/FrontendTest/Program.cs index 77989c6..7547722 100644 --- a/test/FrontendTest/Program.cs +++ b/test/FrontendTest/Program.cs @@ -7,6 +7,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext(); builder.Services.AddHopFrame(); +builder.Services.AddAdminContext(); // Add services to the container. builder.Services.AddRazorComponents() From dd67bba07ddc3fd8b205e8d2ec36ec4fd5156eb6 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 6 Oct 2024 11:26:54 +0200 Subject: [PATCH 04/16] Moved populator methods to corresponding classes --- .../Generators/IAdminPropertyGenerator.cs | 1 + .../Implementation/AdminContextGenerator.cs | 44 +------ .../Implementation/AdminPageGenerator.cs | 117 ++++++++---------- .../Implementation/AdminPropertyGenerator.cs | 56 ++++++++- .../Models/AdminPageProperty.cs | 1 + 5 files changed, 113 insertions(+), 106 deletions(-) diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 45802bb..d9349af 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -8,6 +8,7 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator DisplayInListing(bool display = true); IAdminPropertyGenerator Bold(bool isBold = true); IAdminPropertyGenerator Ignore(bool ignore = true); + IAdminPropertyGenerator Generated(bool generated = true); IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index 6596a2f..0446be9 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -1,6 +1,3 @@ -using System.Reflection; -using HopFrame.Web.Admin.Attributes; -using HopFrame.Web.Admin.Attributes.Classes; using HopFrame.Web.Admin.Models; using HopFrame.Web.Admin.Providers; @@ -14,9 +11,8 @@ internal class AdminContextGenerator : IAdminContextGenerator { if (_adminPages.TryGetValue(typeof(TModel), out var pageGenerator)) return pageGenerator as IAdminPageGenerator; - var generator = Activator.CreateInstance(typeof(IAdminPageGenerator)) as IAdminPageGenerator; - - ApplyConfigurationFromAttributes(generator, typeof(TModel).GetCustomAttributes(false)); + var generator = Activator.CreateInstance(typeof(IAdminPageGenerator)) as AdminPageGenerator; + generator?.ApplyConfigurationFromAttributes(typeof(TModel).GetCustomAttributes(false)); _adminPages.Add(typeof(TModel), generator); @@ -43,10 +39,8 @@ internal class AdminContextGenerator : IAdminContextGenerator { var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); var generatorInstance = Activator.CreateInstance(pageGeneratorType, [property.Name]); // Calls constructor with title attribute - var populatorMethod = typeof(AdminContextGenerator) - .GetMethod(nameof(ApplyConfigurationFromAttributes))? - .MakeGenericMethod(propertyType); - populatorMethod?.Invoke(this, [generatorInstance, propertyType.GetCustomAttributes()]); + var populatorMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.ApplyConfigurationFromAttributes)); + populatorMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); _adminPages.Add(propertyType, generatorInstance); } @@ -62,37 +56,7 @@ internal class AdminContextGenerator : IAdminContextGenerator { return context; } - public void ApplyConfigurationFromAttributes(IAdminPageGenerator generator, object[] attributes) { - if (attributes.Any(a => a is AdminNameAttribute)) { - var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; - generator.Title(attribute?.Name); - } - - if (attributes.Any(a => a is AdminDescriptionAttribute)) { - var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; - generator.Description(attribute?.Description); - } - if (attributes.Any(a => a is AdminUrlAttribute)) { - var attribute = attributes.Single(a => a is AdminUrlAttribute) as AdminUrlAttribute; - generator.Url(attribute?.Url); - } - - if (attributes.Any(a => a is AdminPermissionsAttribute)) { - var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; - generator.CreatePermission(attribute?.Permissions.Create); - generator.UpdatePermission(attribute?.Permissions.Update); - generator.ViewPermission(attribute?.Permissions.View); - generator.DeletePermission(attribute?.Permissions.Delete); - } - - if (attributes.Any(a => a is AdminButtonConfigAttribute)) { - var attribute = attributes.Single(a => a is AdminButtonConfigAttribute) as AdminButtonConfigAttribute; - generator.ShowCreateButton(attribute?.ShowCreateButton == true); - generator.ShowUpdateButton(attribute?.ShowUpdateButton == true); - generator.ShowDeleteButton(attribute?.ShowDeleteButton == true); - } - } public static void RegisterPages(AdminPagesContext context, IAdminPagesProvider provider) { var properties = context.GetType().GetProperties(); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index bad320e..2975892 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -1,20 +1,19 @@ using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using System.Reflection; using HopFrame.Web.Admin.Attributes; -using HopFrame.Web.Admin.Attributes.Members; +using HopFrame.Web.Admin.Attributes.Classes; using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Generators.Implementation; internal sealed class AdminPageGenerator : IAdminPageGenerator, IGenerator> { - private readonly AdminPage _page; + public readonly AdminPage Page; private readonly IDictionary _propertyGenerators; public AdminPageGenerator() { - _page = new AdminPage { + Page = new AdminPage { Permissions = new AdminPagePermissions() }; _propertyGenerators = new Dictionary(); @@ -26,8 +25,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, var attributes = property.GetCustomAttributes(false); var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; - - ApplyConfigurationFromAttributes(generator, attributes, property); + generator?.ApplyConfigurationFromAttributes(this, attributes, property); _propertyGenerators.Add(property.Name, generator); } @@ -38,66 +36,66 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, } public IAdminPageGenerator Title(string title) { - _page.Title = title; - _page.Url ??= title.ToLower(); + Page.Title = title; + Page.Url ??= title.ToLower(); return this; } public IAdminPageGenerator Description(string description) { - _page.Description = description; + Page.Description = description; return this; } public IAdminPageGenerator Url(string url) { - _page.Url = url; + Page.Url = url; return this; } public IAdminPageGenerator ViewPermission(string permission) { - _page.Permissions.View = permission; + Page.Permissions.View = permission; return this; } public IAdminPageGenerator CreatePermission(string permission) { - _page.Permissions.Create = permission; + Page.Permissions.Create = permission; return this; } public IAdminPageGenerator UpdatePermission(string permission) { - _page.Permissions.Update = permission; + Page.Permissions.Update = permission; return this; } public IAdminPageGenerator DeletePermission(string permission) { - _page.Permissions.Delete = permission; + Page.Permissions.Delete = permission; return this; } public IAdminPageGenerator ShowCreateButton(bool show) { - _page.ShowCreateButton = show; + Page.ShowCreateButton = show; return this; } public IAdminPageGenerator ShowDeleteButton(bool show) { - _page.ShowDeleteButton = show; + Page.ShowDeleteButton = show; return this; } public IAdminPageGenerator ShowUpdateButton(bool show) { - _page.ShowUpdateButton = show; + Page.ShowUpdateButton = show; return this; } public IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction) { var property = GetPropertyInfo(propertyExpression); - _page.DefaultSortPropertyName = property.Name; - _page.DefaultSortDirection = direction; + Page.DefaultSortPropertyName = property.Name; + Page.DefaultSortDirection = direction; return this; } public IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository { - _page.RepositoryProvider = typeof(TRepository); + Page.RepositoryProvider = typeof(TRepository); return this; } @@ -108,7 +106,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return propertyGenerator; var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; - ApplyConfigurationFromAttributes(generator, property.GetCustomAttributes(false), property); + generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property); _propertyGenerators.Add(property.Name, generator); return generator; @@ -121,51 +119,9 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, properties.Add(generator.Compile()); } - _page.Properties = properties; + Page.Properties = properties; - return _page; - } - - private void ApplyConfigurationFromAttributes(AdminPropertyGenerator generator, object[] attributes, PropertyInfo property) { - if (attributes.Any(a => a is KeyAttribute)) { - _page.DefaultSortPropertyName = property.Name; - generator.Bold(); - generator.Editable(false); - } - - if (attributes.Any(a => a is AdminUnsortableAttribute)) - generator.Sortable(false); - - if (attributes.Any(a => a is AdminUneditableAttribute)) - generator.Editable(false); - - if (attributes.Any(a => a is AdminBoldAttribute)) - generator.Bold(); - - if (attributes.Any(a => a is AdminIgnoreAttribute)) { - var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; - generator.DisplayInListing(false); - generator.Sortable(false); - generator.Ignore(attribute?.OnlyForListing == false); - } - - if (attributes.Any(a => a is AdminHideValueAttribute)) - generator.DisplayValueWhileEditing(false); - - if (attributes.Any(a => a is AdminNameAttribute)) { - var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; - generator.DisplayName(attribute?.Name); - } - - if (attributes.Any(a => a is AdminDescriptionAttribute)) { - var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; - generator.Description(attribute?.Description); - } - - if (attributes.Any(a => a is AdminPrefixAttribute)) { - var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; - generator.Prefix(attribute?.Prefix); - } + return Page; } private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { @@ -189,4 +145,35 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return propInfo; } + public void ApplyConfigurationFromAttributes(object[] attributes) { + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + Title(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + Description(attribute?.Description); + } + + if (attributes.Any(a => a is AdminUrlAttribute)) { + var attribute = attributes.Single(a => a is AdminUrlAttribute) as AdminUrlAttribute; + Url(attribute?.Url); + } + + if (attributes.Any(a => a is AdminPermissionsAttribute)) { + var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; + CreatePermission(attribute?.Permissions.Create); + UpdatePermission(attribute?.Permissions.Update); + ViewPermission(attribute?.Permissions.View); + DeletePermission(attribute?.Permissions.Delete); + } + + if (attributes.Any(a => a is AdminButtonConfigAttribute)) { + var attribute = attributes.Single(a => a is AdminButtonConfigAttribute) as AdminButtonConfigAttribute; + ShowCreateButton(attribute?.ShowCreateButton == true); + ShowUpdateButton(attribute?.ShowUpdateButton == true); + ShowDeleteButton(attribute?.ShowDeleteButton == true); + } + } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index cab1b2d..ca756a0 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -1,3 +1,8 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; +using HopFrame.Web.Admin.Attributes; +using HopFrame.Web.Admin.Attributes.Members; using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Generators.Implementation; @@ -40,6 +45,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Generated(bool generated = true) { + _property.Generated = generated; + return this; + } + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; @@ -57,7 +67,51 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; - return _property; } + + public void ApplyConfigurationFromAttributes(AdminPageGenerator pageGenerator, object[] attributes, PropertyInfo property) { + if (attributes.Any(a => a is KeyAttribute)) { + pageGenerator.Page.DefaultSortPropertyName = property.Name; + Bold(); + Editable(false); + } + + if (attributes.Any(a => a is AdminUnsortableAttribute)) + Sortable(false); + + if (attributes.Any(a => a is AdminUneditableAttribute)) + Editable(false); + + if (attributes.Any(a => a is AdminBoldAttribute)) + Bold(); + + if (attributes.Any(a => a is AdminIgnoreAttribute)) { + var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; + DisplayInListing(false); + Sortable(false); + Ignore(attribute?.OnlyForListing == false); + } + + if (attributes.Any(a => a is AdminHideValueAttribute)) + DisplayValueWhileEditing(false); + + if (attributes.Any(a => a is DatabaseGeneratedAttribute)) + Generated(); + + if (attributes.Any(a => a is AdminNameAttribute)) { + var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; + DisplayName(attribute?.Name); + } + + if (attributes.Any(a => a is AdminDescriptionAttribute)) { + var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; + Description(attribute?.Description); + } + + if (attributes.Any(a => a is AdminPrefixAttribute)) { + var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; + Prefix(attribute?.Prefix); + } + } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index daaab90..906618d 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -12,6 +12,7 @@ public sealed class AdminPageProperty { public bool Sortable { get; set; } = true; public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; + public bool Generated { get; set; } public bool Bold { get; set; } public bool Ignore { get; set; } [JsonIgnore] From 6a781990e49458c4d01aba4d4adb05a487ceac49 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 6 Oct 2024 12:48:05 +0200 Subject: [PATCH 05/16] Started creating generated admin page --- .../Attributes/Members/AdminBoldAttribute.cs | 4 - .../Generators/IAdminPropertyGenerator.cs | 1 - .../Implementation/AdminPropertyGenerator.cs | 9 -- .../Models/AdminPageProperty.cs | 1 - .../Pages/Administration/AdminPageList.razor | 114 ++++++++++++++++++ .../Administration/AdminPageList.razor.css | 26 ++++ 6 files changed, 140 insertions(+), 15 deletions(-) delete mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs create mode 100644 src/HopFrame.Web/Pages/Administration/AdminPageList.razor create mode 100644 src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs deleted file mode 100644 index 68311b2..0000000 --- a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace HopFrame.Web.Admin.Attributes.Members; - -[AttributeUsage(AttributeTargets.Property)] -public sealed class AdminBoldAttribute : Attribute; diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index d9349af..d32fe15 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -6,7 +6,6 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator Editable(bool editable); IAdminPropertyGenerator DisplayValueWhileEditing(bool display); IAdminPropertyGenerator DisplayInListing(bool display = true); - IAdminPropertyGenerator Bold(bool isBold = true); IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator Generated(bool generated = true); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index ca756a0..68fea2b 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -35,11 +35,6 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } - public IAdminPropertyGenerator Bold(bool isBold = true) { - _property.Bold = isBold; - return this; - } - public IAdminPropertyGenerator Ignore(bool ignore = false) { _property.Ignore = ignore; return this; @@ -73,7 +68,6 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro public void ApplyConfigurationFromAttributes(AdminPageGenerator pageGenerator, object[] attributes, PropertyInfo property) { if (attributes.Any(a => a is KeyAttribute)) { pageGenerator.Page.DefaultSortPropertyName = property.Name; - Bold(); Editable(false); } @@ -82,9 +76,6 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro if (attributes.Any(a => a is AdminUneditableAttribute)) Editable(false); - - if (attributes.Any(a => a is AdminBoldAttribute)) - Bold(); if (attributes.Any(a => a is AdminIgnoreAttribute)) { var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 906618d..eeaf3a2 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -13,7 +13,6 @@ public sealed class AdminPageProperty { public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; public bool Generated { get; set; } - public bool Bold { get; set; } public bool Ignore { get; set; } [JsonIgnore] public Type Type { get; set; } diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor new file mode 100644 index 0000000..af1bbd4 --- /dev/null +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -0,0 +1,114 @@ +@page "/administration/{url}" +@layout AdminLayout +@rendermode InteractiveServer + +@using System.ComponentModel +@using BlazorStrap +@using Microsoft.AspNetCore.Components.Web +@using HopFrame.Web.Admin.Models +@using HopFrame.Web.Admin.Providers +@using HopFrame.Web.Pages.Administration.Layout +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using HopFrame.Web.Components.Administration +@using BlazorStrap.V5 +@using HopFrame.Web.Components + +@_pageData.Title + + +
+

+ @_pageData.Title administration + + + +

+ + + + Add Group + +
+ + + + + @foreach (var prop in GetListingProperties()) { + + @if (prop.Sortable) { + @prop.DisplayName + @if (_currentSortProperty == prop.Name) { + + } + } + else { + @prop.DisplayName + } + + } + + + + + + + + +@inject IAdminPagesProvider Pages + +@code { + [Parameter] + public string Url { get; set; } + + private AdminPage _pageData; + + private string _currentSortProperty; + private ListSortDirection _currentSortDirection; + private DateTime _lastSearch; + + protected override void OnInitialized() { + _pageData = Pages.LoadAdminPage(Url); + + _currentSortProperty = _pageData.DefaultSortPropertyName; + _currentSortDirection = _pageData.DefaultSortDirection; + } + + private IList GetListingProperties() { + return _pageData.Properties + .Where(p => p.Ignore == false) + .Where(p => p.DisplayInListing) + .ToList(); + } + + private void Reload() { + + } + + private void OrderBy(string property, bool changeDir = true) { + if (_currentSortProperty == property && changeDir) + _currentSortDirection = (ListSortDirection)(((int)_currentSortDirection + 1) % 2); + if (_currentSortProperty != property) + _currentSortDirection = ListSortDirection.Ascending; + + //TODO: Handle ordering + + _currentSortProperty = property; + } + + private void TriggerSearch(ChangeEventArgs e) { + var search = ((string)e.Value)?.Trim(); + Search(search); + } + + private async void Search(string search) { + _lastSearch = DateTime.Now; + await Task.Delay(500); + var timeSinceLastKeyPress = DateTime.Now - _lastSearch; + if (timeSinceLastKeyPress < TimeSpan.FromMilliseconds(500)) return; + + //TODO: Handle searching + Console.WriteLine(search); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css new file mode 100644 index 0000000..cf49df0 --- /dev/null +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css @@ -0,0 +1,26 @@ +.title { + display: flex; + flex-direction: row; + gap: 10px; + margin-bottom: 10px; +} + +#search { + margin-left: auto; +} + +th, h3, .sorter { + user-select: none; +} + +h3 { + color: white; +} + +.reload, .sorter { + cursor: pointer; +} + +.bold { + font-weight: bold; +} From 075ca2286f80fb7b45dcf679767aa15c868e4225 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 7 Oct 2024 17:39:05 +0200 Subject: [PATCH 06/16] Worked on admin page listing --- .../Attributes/Members/AdminBoldAttribute.cs | 6 ++ .../Generators/IAdminPageGenerator.cs | 2 +- .../Generators/IAdminPropertyGenerator.cs | 1 + .../Implementation/AdminContextGenerator.cs | 6 +- .../Implementation/AdminPageGenerator.cs | 2 +- .../Implementation/AdminPropertyGenerator.cs | 11 +++ src/HopFrame.Web.Admin/IModelRepository.cs | 8 -- src/HopFrame.Web.Admin/ModelRepository.cs | 33 +++++++ .../Models/AdminPageProperty.cs | 1 + .../ServiceCollectionExtensions.cs | 2 +- src/HopFrame.Web/HopAdminContext.cs | 3 + .../Pages/Administration/AdminPageList.razor | 85 ++++++++++++++++++- .../Administration/AdminPageList.razor.css | 4 - .../Pages/Administration/GroupsPage.razor | 2 +- .../Pages/Administration/UsersPage.razor | 2 +- .../Repositories/GroupProvider.cs | 24 ++++++ src/HopFrame.Web/Repositories/UserProvider.cs | 24 ++++++ 17 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs delete mode 100644 src/HopFrame.Web.Admin/IModelRepository.cs create mode 100644 src/HopFrame.Web.Admin/ModelRepository.cs create mode 100644 src/HopFrame.Web/Repositories/GroupProvider.cs create mode 100644 src/HopFrame.Web/Repositories/UserProvider.cs diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs new file mode 100644 index 0000000..ffc3798 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminBoldAttribute.cs @@ -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; +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index f9902fb..0e112f1 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -20,7 +20,7 @@ public interface IAdminPageGenerator { IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction); - IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository; + IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; IAdminPropertyGenerator Property(Expression> propertyExpression); diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index d32fe15..1e0091a 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -8,6 +8,7 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator DisplayInListing(bool display = true); IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator Generated(bool generated = true); + IAdminPropertyGenerator Bold(bool bold = true); IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index 0446be9..aa90737 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -1,5 +1,6 @@ using HopFrame.Web.Admin.Models; using HopFrame.Web.Admin.Providers; +using Microsoft.Extensions.DependencyInjection; 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(); foreach (var property in properties) { @@ -66,6 +67,9 @@ internal class AdminContextGenerator : IAdminContextGenerator { if (page is null) continue; provider.RegisterAdminPage(page.Title.ToLower(), page); + + if (page.RepositoryProvider is not null) + services.AddScoped(page.RepositoryProvider); } } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index 2975892..33db4d7 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -94,7 +94,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } - public IAdminPageGenerator ConfigureRepository() where TRepository : IModelRepository { + public IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository { Page.RepositoryProvider = typeof(TRepository); return this; } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 68fea2b..92a755a 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -45,6 +45,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Bold(bool bold = true) { + _property.Bold = bold; + return this; + } + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; @@ -69,6 +74,7 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro if (attributes.Any(a => a is KeyAttribute)) { pageGenerator.Page.DefaultSortPropertyName = property.Name; Editable(false); + Bold(); } 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; 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)) { var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; diff --git a/src/HopFrame.Web.Admin/IModelRepository.cs b/src/HopFrame.Web.Admin/IModelRepository.cs deleted file mode 100644 index 6bdffe0..0000000 --- a/src/HopFrame.Web.Admin/IModelRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HopFrame.Web.Admin; - -public interface IModelRepository { - Task> ReadAll(); - Task Create(TModel model); - Task Update(TModel model); - Task Delete(TModel model); -} diff --git a/src/HopFrame.Web.Admin/ModelRepository.cs b/src/HopFrame.Web.Admin/ModelRepository.cs new file mode 100644 index 0000000..45de247 --- /dev/null +++ b/src/HopFrame.Web.Admin/ModelRepository.cs @@ -0,0 +1,33 @@ +namespace HopFrame.Web.Admin; + +public abstract class ModelRepository : IModelRepository { + public abstract Task> ReadAll(); + public abstract Task Create(TModel model); + public abstract Task Update(TModel model); + public abstract Task Delete(TModel model); + + + public async Task> ReadAllO() { + var models = await ReadAll(); + return models.Select(m => (object)m); + } + + public async Task CreateO(object model) { + return await Create((TModel)model); + } + + public async Task UpdateO(object model) { + return await Update((TModel)model); + } + + public Task DeleteO(object model) { + return Delete((TModel)model); + } +} + +public interface IModelRepository { + Task> ReadAllO(); + Task CreateO(object model); + Task UpdateO(object model); + Task DeleteO(object model); +} diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index eeaf3a2..cf5bbc6 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -13,6 +13,7 @@ public sealed class AdminPageProperty { public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; public bool Generated { get; set; } + public bool Bold { get; set; } = false; public bool Ignore { get; set; } [JsonIgnore] public Type Type { get; set; } diff --git a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs index b8ee37e..f3e8370 100644 --- a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs @@ -16,7 +16,7 @@ public static class ServiceCollectionExtensions { var generator = new AdminContextGenerator(); var context = generator.CompileContext(); - AdminContextGenerator.RegisterPages(context, provider); + AdminContextGenerator.RegisterPages(context, provider, services); services.AddSingleton(context); return services; diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 3a5c067..e4914c9 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -3,6 +3,7 @@ using HopFrame.Security; using HopFrame.Web.Admin; using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Models; +using HopFrame.Web.Repositories; namespace HopFrame.Web; @@ -14,6 +15,7 @@ public class HopAdminContext : AdminPagesContext { public override void OnModelCreating(IAdminContextGenerator generator) { generator.Page() .Description("On this page you can manage all user accounts.") + .ConfigureRepository() .ViewPermission(AdminPermissions.ViewUsers) .CreatePermission(AdminPermissions.AddUser) .UpdatePermission(AdminPermissions.EditUser) @@ -35,6 +37,7 @@ public class HopAdminContext : AdminPagesContext { generator.Page() .Description("On this page you can view, create, edit and delete permission groups.") + .ConfigureRepository() .ViewPermission(AdminPermissions.ViewGroups) .CreatePermission(AdminPermissions.AddGroup) .UpdatePermission(AdminPermissions.EditGroup) diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index af1bbd4..c72a1fd 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -11,6 +11,9 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using HopFrame.Web.Components.Administration @using BlazorStrap.V5 +@using HopFrame.Database.Repositories +@using HopFrame.Security.Claims +@using HopFrame.Web.Admin @using HopFrame.Web.Components @_pageData.Title @@ -48,25 +51,66 @@ } } + + @if (_hasEditPermission || _hasDeletePermission) { + Actions + } - + @foreach (var entry in _displayedModels) { + + @foreach (var prop in GetListingProperties()) { + + @GetValue(entry, prop).GetAwaiter().GetResult() + + } + + @if (_hasEditPermission || _hasDeletePermission) { + + + @if (_hasEditPermission) { + Edit + } + + @if (_hasDeletePermission) { + Delete + } + + + } + + } + + @inject IAdminPagesProvider Pages +@inject IServiceProvider Provider +@inject ITokenContext Auth +@inject IPermissionRepository Permissions @code { [Parameter] public string Url { get; set; } private AdminPage _pageData; + private IModelRepository _modelRepository; + private IEnumerable _modelBuffer; + + private bool _hasEditPermission; + private bool _hasDeletePermission; private string _currentSortProperty; private ListSortDirection _currentSortDirection; private DateTime _lastSearch; + private IList _displayedModels; protected override void OnInitialized() { _pageData = Pages.LoadAdminPage(Url); @@ -75,6 +119,17 @@ _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 GetListingProperties() { return _pageData.Properties .Where(p => p.Ignore == false) @@ -82,8 +137,11 @@ .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) { @@ -93,6 +151,7 @@ _currentSortDirection = ListSortDirection.Ascending; //TODO: Handle ordering + _displayedModels = _modelBuffer.ToList(); _currentSortProperty = property; } @@ -109,6 +168,26 @@ if (timeSinceLastKeyPress < TimeSpan.FromMilliseconds(500)) return; //TODO: Handle searching - Console.WriteLine(search); + OrderBy(_currentSortProperty, false); + } + + private async Task 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) { + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css index cf49df0..6f4b803 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor.css @@ -20,7 +20,3 @@ h3 { .reload, .sorter { cursor: pointer; } - -.bold { - font-weight: bold; -} diff --git a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor index 4591f60..42269da 100644 --- a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor +++ b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor @@ -1,4 +1,4 @@ -@page "/administration/groups" +@page "/administration/group" @rendermode InteractiveServer @layout AdminLayout diff --git a/src/HopFrame.Web/Pages/Administration/UsersPage.razor b/src/HopFrame.Web/Pages/Administration/UsersPage.razor index 47b871c..767dc98 100644 --- a/src/HopFrame.Web/Pages/Administration/UsersPage.razor +++ b/src/HopFrame.Web/Pages/Administration/UsersPage.razor @@ -1,4 +1,4 @@ -@page "/administration/users" +@page "/administration/user" @rendermode InteractiveServer @layout AdminLayout diff --git a/src/HopFrame.Web/Repositories/GroupProvider.cs b/src/HopFrame.Web/Repositories/GroupProvider.cs new file mode 100644 index 0000000..953a9d7 --- /dev/null +++ b/src/HopFrame.Web/Repositories/GroupProvider.cs @@ -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 { + public override async Task> ReadAll() { + return await repo.GetPermissionGroups(); + } + + public override async Task Create(PermissionGroup model) { + return await repo.CreatePermissionGroup(model); + } + + public override async Task Update(PermissionGroup model) { + await repo.EditPermissionGroup(model); + return model; + } + + public override Task Delete(PermissionGroup model) { + return repo.DeletePermissionGroup(model); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web/Repositories/UserProvider.cs b/src/HopFrame.Web/Repositories/UserProvider.cs new file mode 100644 index 0000000..49ca30f --- /dev/null +++ b/src/HopFrame.Web/Repositories/UserProvider.cs @@ -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 { + public override async Task> ReadAll() { + return await repo.GetUsers(); + } + + public override Task Create(User model) { + return repo.AddUser(model); + } + + public override async Task Update(User model) { + await repo.UpdateUser(model); + return model; + } + + public override Task Delete(User model) { + return repo.DeleteUser(model); + } +} \ No newline at end of file From bc0651cb75f130e2072fb4edc1a18081804f803c Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 8 Oct 2024 19:21:46 +0200 Subject: [PATCH 07/16] Added order, search and delete functionality to admin page --- .../Pages/Administration/AdminPageList.razor | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index c72a1fd..4798210 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -11,6 +11,7 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using HopFrame.Web.Components.Administration @using BlazorStrap.V5 +@using CurrieTechnologies.Razor.SweetAlert2 @using HopFrame.Database.Repositories @using HopFrame.Security.Claims @using HopFrame.Web.Admin @@ -31,7 +32,7 @@ - Add Group + Add Entry @@ -63,7 +64,7 @@ @foreach (var prop in GetListingProperties()) { - @GetValue(entry, prop).GetAwaiter().GetResult() + @GetValue(entry, prop) } @@ -95,6 +96,7 @@ @inject IServiceProvider Provider @inject ITokenContext Auth @inject IPermissionRepository Permissions +@inject SweetAlertService Alerts @code { [Parameter] @@ -139,9 +141,11 @@ private async void Reload() { _modelBuffer = await _modelRepository.ReadAllO(); + _displayedModels = _modelBuffer.ToList(); _currentSortDirection = _pageData.DefaultSortDirection; OrderBy(_pageData.DefaultSortPropertyName, false); + StateHasChanged(); } private void OrderBy(string property, bool changeDir = true) { @@ -150,9 +154,29 @@ if (_currentSortProperty != property) _currentSortDirection = ListSortDirection.Ascending; - //TODO: Handle ordering - _displayedModels = _modelBuffer.ToList(); + var prop = GetListingProperties() + .SingleOrDefault(p => p.Name == property); + var comparer = Comparer.Create((x, y) => { + if (prop.Type == typeof(DateTime)) { + DateTime dateX = (DateTime) x.GetType().GetProperty(prop.Name)?.GetValue(x)!; + DateTime dateY = (DateTime) y.GetType().GetProperty(prop.Name)?.GetValue(y)!; + return DateTime.Compare(dateX, dateY); + } + + var propX = GetValue(x, prop); + var propY = GetValue(y, prop); + + return String.CompareOrdinal(propX, propY); + }); + + if (_currentSortDirection == ListSortDirection.Ascending) { + _displayedModels = _displayedModels.Order(comparer).ToList(); + } + else { + _displayedModels = _displayedModels.OrderDescending(comparer).ToList(); + } + _currentSortProperty = property; } @@ -167,11 +191,22 @@ var timeSinceLastKeyPress = DateTime.Now - _lastSearch; if (timeSinceLastKeyPress < TimeSpan.FromMilliseconds(500)) return; - //TODO: Handle searching + if (string.IsNullOrWhiteSpace(search)) { + _displayedModels = _modelBuffer.ToList(); + } + else { + var props = GetListingProperties(); + + _displayedModels = _modelBuffer + .Where(model => props.Any(prop => GetValue(model, prop).Contains(search))) + .ToList(); + } + OrderBy(_currentSortProperty, false); + StateHasChanged(); } - private async Task GetValue(object entry, AdminPageProperty property) { + private string GetValue(object entry, AdminPageProperty property) { object propValue = entry.GetType().GetProperty(property.Name)?.GetValue(entry); return propValue?.ToString(); @@ -183,11 +218,34 @@ return ""; } + private async void Create() { + //TODO: Open create modal + } + private async void Edit(object entry) { - + //TODO: Open edit modal } private async void Delete(object entry) { - + var result = await Alerts.FireAsync(new SweetAlertOptions { + Title = "Do you really want to delete this entry?", + Text = "You won't be able to revert this!", + Icon = SweetAlertIcon.Warning, + ConfirmButtonText = "Yes", + ShowCancelButton = true, + ShowConfirmButton = true + }); + + if (result.IsConfirmed) { + await _modelRepository.DeleteO(entry); + Reload(); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Deleted!", + Icon = SweetAlertIcon.Success, + Timer = 1500, + ShowConfirmButton = false + }); + } } } \ No newline at end of file From d2729870e3aa9a29ce87857d46fe9a321128c5e3 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 8 Oct 2024 21:34:00 +0200 Subject: [PATCH 08/16] Started working on admin page modal --- .../Generators/IAdminPropertyGenerator.cs | 1 + .../Implementation/AdminPageGenerator.cs | 3 +- .../Implementation/AdminPropertyGenerator.cs | 9 ++ src/HopFrame.Web.Admin/Models/AdminPage.cs | 2 + .../Models/AdminPageProperty.cs | 17 ++- .../Administration/AdminPageModal.razor | 119 ++++++++++++++++++ .../Pages/Administration/AdminPageList.razor | 58 +++++---- 7 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 src/HopFrame.Web/Components/Administration/AdminPageModal.razor diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 1e0091a..b62e7af 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -13,5 +13,6 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Prefix(string prefix); + IAdminPropertyGenerator Validator(Func validator); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index 33db4d7..fab851a 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -14,7 +14,8 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, public AdminPageGenerator() { Page = new AdminPage { - Permissions = new AdminPagePermissions() + Permissions = new AdminPagePermissions(), + ModelType = typeof(TModel) }; _propertyGenerators = new Dictionary(); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 92a755a..e14ff75 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -65,6 +65,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Validator(Func validator) { + _property.Validator = validator; + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; return _property; @@ -111,6 +116,10 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro Bold(attribute?.Bold == true); } + if (attributes.Any(a => a is RequiredAttribute)) { + _property.Required = true; + } + if (attributes.Any(a => a is AdminPrefixAttribute)) { var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; Prefix(attribute?.Prefix); diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index 241f69c..509fed8 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -14,6 +14,8 @@ public class AdminPage { [JsonIgnore] public Type RepositoryProvider { get; set; } + + public Type ModelType { get; set; } public string DefaultSortPropertyName { get; set; } public ListSortDirection DefaultSortDirection { get; set; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index cf5bbc6..db2f743 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -13,8 +13,23 @@ public sealed class AdminPageProperty { public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; public bool Generated { get; set; } - public bool Bold { get; set; } = false; + public bool Bold { get; set; } + public bool Required { get; set; } public bool Ignore { get; set; } [JsonIgnore] public Type Type { get; set; } + + public Func Validator { get; set; } + + public object GetValue(object entry) { + return entry.GetType().GetProperty(Name)?.GetValue(entry); + } + + public T GetValue(object entry) { + return (T)entry.GetType().GetProperty(Name)?.GetValue(entry); + } + + public void SetValue(object entry, object value) { + entry.GetType().GetProperty(Name)?.SetValue(entry, value); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor new file mode 100644 index 0000000..04241f2 --- /dev/null +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -0,0 +1,119 @@ +@rendermode InteractiveServer + +@using BlazorStrap +@using BlazorStrap.Shared.Components.Modal +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using BlazorStrap.V5 +@using HopFrame.Web.Admin +@using HopFrame.Web.Admin.Models +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Web + + + + @if (!_isEdit) { + Create entry + } + else { + Edit entry + } + + + @foreach (var prop in GetEditableProperties()) { + @if (!_isEdit && !prop.Editable) continue; + +
+ @prop.DisplayName + + +
+ } +
+ + + Cancel + Save + +
+
+ +@inject IServiceProvider Provider + +@code { + [Parameter] + public Func ReloadDelegate { get; set; } + + private BSModalBase _modal; + private EditContext _context; + private IDictionary _values; + private IModelRepository _repository; + + private AdminPage _currentPage; + private object _entry; + private bool _isEdit; + + public async Task Show(AdminPage page, object entryToEdit = null) { + _entry = null; + + _currentPage = page; + _entry = entryToEdit; + _isEdit = entryToEdit is not null; + _repository = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository; + + _entry ??= Activator.CreateInstance(_currentPage.ModelType); + _context = new EditContext(_entry); + _context.OnValidationRequested += Validate; + + _values = new Dictionary(); + foreach (var property in _currentPage.Properties) { + _values.Add(property, property.GetValue(_entry)); + } + + await _modal.ShowAsync(); + } + + private IList GetEditableProperties() { + return _currentPage.Properties + .Where(p => !p.Ignore) + .OrderBy(p => p.Editable) + .ToList(); + } + + private bool IsDisabled(AdminPageProperty prop) => !prop.Editable; + private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue; + + private string GetPropertyValue(AdminPageProperty property) { + if (!_isEdit) return ""; + if (!property.EditDisplayValue) return ""; + return property.GetValue(_entry)?.ToString(); + } + + private string GetInputType(AdminPageProperty property) { + if (!property.EditDisplayValue) + return "password"; + + return "text"; + } + + private void Validate(object sender, ValidationRequestedEventArgs e) { + foreach (var value in _values) { + if (value.Key.Validator?.Invoke(value.Value) == true) continue; + Console.WriteLine("INVALID"); + } + } + + private async void Save() { + foreach (var value in _values) { + value.Key.SetValue(_entry, value.Value); + } + + if (!_isEdit) { + await _repository.CreateO(_entry); + } + else { + await _repository.UpdateO(_entry); + } + + await ReloadDelegate.Invoke(); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 4798210..a04a374 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -20,6 +20,8 @@ @_pageData.Title + +

@_pageData.Title administration @@ -63,9 +65,16 @@ @foreach (var entry in _displayedModels) { @foreach (var prop in GetListingProperties()) { - - @GetValue(entry, prop) - + @if (prop.Bold) { + + @GetPrintableValue(entry, prop) + + } + else { + + @GetPrintableValue(entry, prop) + + } } @if (_hasEditPermission || _hasDeletePermission) { @@ -105,6 +114,7 @@ private AdminPage _pageData; private IModelRepository _modelRepository; private IEnumerable _modelBuffer; + private AdminPageModal _modal; private bool _hasEditPermission; private bool _hasDeletePermission; @@ -114,14 +124,12 @@ private DateTime _lastSearch; private IList _displayedModels; - protected override void OnInitialized() { + protected override async Task OnInitializedAsync() { _pageData = Pages.LoadAdminPage(Url); _currentSortProperty = _pageData.DefaultSortPropertyName; _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!'"); @@ -129,7 +137,7 @@ _hasEditPermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); _hasDeletePermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); - Reload(); + await Reload(); } private IList GetListingProperties() { @@ -139,7 +147,7 @@ .ToList(); } - private async void Reload() { + private async Task Reload() { _modelBuffer = await _modelRepository.ReadAllO(); _displayedModels = _modelBuffer.ToList(); @@ -157,15 +165,15 @@ var prop = GetListingProperties() .SingleOrDefault(p => p.Name == property); var comparer = Comparer.Create((x, y) => { - if (prop.Type == typeof(DateTime)) { + if (prop?.Type == typeof(DateTime)) { DateTime dateX = (DateTime) x.GetType().GetProperty(prop.Name)?.GetValue(x)!; DateTime dateY = (DateTime) y.GetType().GetProperty(prop.Name)?.GetValue(y)!; return DateTime.Compare(dateX, dateY); } - - var propX = GetValue(x, prop); - var propY = GetValue(y, prop); + + var propX = GetPrintableValue(x, prop); + var propY = GetPrintableValue(y, prop); return String.CompareOrdinal(propX, propY); }); @@ -198,7 +206,7 @@ var props = GetListingProperties(); _displayedModels = _modelBuffer - .Where(model => props.Any(prop => GetValue(model, prop).Contains(search))) + .Where(model => props.Any(prop => GetPrintableValue(model, prop).Contains(search))) .ToList(); } @@ -206,29 +214,25 @@ StateHasChanged(); } - private string GetValue(object entry, AdminPageProperty property) { - object propValue = entry.GetType().GetProperty(property.Name)?.GetValue(entry); + private static string GetPrintableValue(object element, AdminPageProperty prop) { + var entry = prop.GetValue(element); + + //TODO: convert relational types - return propValue?.ToString(); - } - - private string GetClass(AdminPageProperty property) { - if (property.Bold) - return "bold"; - return ""; + return entry?.ToString(); } private async void Create() { - //TODO: Open create modal + await _modal.Show(_pageData); } private async void Edit(object entry) { - //TODO: Open edit modal + await _modal.Show(_pageData, entry); } private async void Delete(object entry) { var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Do you really want to delete this entry?", + Title = "Are you sure?", Text = "You won't be able to revert this!", Icon = SweetAlertIcon.Warning, ConfirmButtonText = "Yes", @@ -238,7 +242,7 @@ if (result.IsConfirmed) { await _modelRepository.DeleteO(entry); - Reload(); + await Reload(); await Alerts.FireAsync(new SweetAlertOptions { Title = "Deleted!", From 599ce2bf439e983716812a4494f2b0dd1d7b941c Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 13 Oct 2024 17:42:40 +0200 Subject: [PATCH 09/16] Working on Admin page modal --- .../Attributes/ListingPropertyAttribute.cs | 4 + src/HopFrame.Database/Models/Permission.cs | 3 +- .../Models/PermissionGroup.cs | 3 +- src/HopFrame.Database/Models/User.cs | 3 +- .../Generators/IAdminPageGenerator.cs | 1 + .../Generators/IAdminPropertyGenerator.cs | 1 + .../Implementation/AdminPageGenerator.cs | 6 + .../Implementation/AdminPropertyGenerator.cs | 5 + src/HopFrame.Web.Admin/Models/AdminPage.cs | 1 + .../Models/AdminPageProperty.cs | 2 + .../Providers/IAdminPagesProvider.cs | 1 + .../Implementation/AdminPagesProvider.cs | 7 + .../Administration/AdminPageModal.razor | 123 +++++++++++++++++- src/HopFrame.Web/HopAdminContext.cs | 3 +- .../Pages/Administration/AdminPageList.razor | 8 +- 15 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs diff --git a/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs b/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs new file mode 100644 index 0000000..b5fc86a --- /dev/null +++ b/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Database.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public sealed class ListingPropertyAttribute : Attribute; diff --git a/src/HopFrame.Database/Models/Permission.cs b/src/HopFrame.Database/Models/Permission.cs index db111ba..475632e 100644 --- a/src/HopFrame.Database/Models/Permission.cs +++ b/src/HopFrame.Database/Models/Permission.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -9,7 +10,7 @@ public class Permission { [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; init; } - [Required, MaxLength(255)] + [Required, MaxLength(255), ListingProperty] public string PermissionName { get; set; } [Required] diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index 7a70ebd..b6613bb 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,11 +1,12 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; public class PermissionGroup : IPermissionOwner { - [Key, Required, MaxLength(50)] + [Key, Required, MaxLength(50), ListingProperty] public string Name { get; init; } [Required, DefaultValue(false)] diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 971d899..2fe6c8e 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; +using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -8,7 +9,7 @@ public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] public Guid Id { get; init; } - [MaxLength(50)] + [MaxLength(50), ListingProperty] public string Username { get; set; } [Required, MaxLength(50), EmailAddress] diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index 0e112f1..0e7a72c 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -23,5 +23,6 @@ public interface IAdminPageGenerator { IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; IAdminPropertyGenerator Property(Expression> propertyExpression); + IAdminPageGenerator ListingProperty(Expression> propertyExpression); } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index b62e7af..2d75abe 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -14,5 +14,6 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Prefix(string prefix); IAdminPropertyGenerator Validator(Func validator); + IAdminPropertyGenerator IsSelector(); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index fab851a..4ea22e5 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -113,6 +113,12 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return generator; } + public IAdminPageGenerator ListingProperty(Expression> propertyExpression) { + var property = GetPropertyInfo(propertyExpression); + Page.ListingProperty = property.Name; + return this; + } + public AdminPage Compile() { var properties = new List(); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index e14ff75..92df826 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -70,6 +70,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator IsSelector() { + _property.SelectorType = typeof(TSelector); + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; return _property; diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index 509fed8..eedd4a5 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -11,6 +11,7 @@ public class AdminPage { public string Url { get; set; } public AdminPagePermissions Permissions { get; set; } public IList Properties { get; set; } + public string ListingProperty { get; set; } [JsonIgnore] public Type RepositoryProvider { get; set; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index db2f743..6707458 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -19,6 +19,8 @@ public sealed class AdminPageProperty { [JsonIgnore] public Type Type { get; set; } + public Type SelectorType { get; set; } + public Func Validator { get; set; } public object GetValue(object entry) { diff --git a/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs index d20c66c..564f52a 100644 --- a/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs +++ b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs @@ -7,5 +7,6 @@ public interface IAdminPagesProvider { internal void RegisterAdminPage(string url, AdminPage page); AdminPage LoadAdminPage(string url); IList LoadRegisteredAdminPages(); + AdminPage HasPageFor(Type type); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs index d68a5ae..b2e38b4 100644 --- a/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs +++ b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs @@ -16,4 +16,11 @@ public class AdminPagesProvider : IAdminPagesProvider { public IList LoadRegisteredAdminPages() { return _pages.Values.ToList(); } + + public AdminPage HasPageFor(Type type) { + return _pages + .Where(p => p.Value.ModelType == type) + .Select(p => p.Value) + .SingleOrDefault(); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 04241f2..5fa3ad8 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -1,11 +1,15 @@ @rendermode InteractiveServer +@using System.Collections +@using System.ComponentModel.DataAnnotations @using BlazorStrap @using BlazorStrap.Shared.Components.Modal @using static Microsoft.AspNetCore.Components.Web.RenderMode @using BlazorStrap.V5 +@using HopFrame.Database.Attributes @using HopFrame.Web.Admin @using HopFrame.Web.Admin.Models +@using HopFrame.Web.Admin.Providers @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Web @@ -23,9 +27,54 @@ @if (!_isEdit && !prop.Editable) continue;
- @prop.DisplayName - - + @if (IsListType(prop)) { + @prop.DisplayName + + + + @foreach (var element in GetListPropertyValues(prop).Select((e, i) => new {e, i})) { + + + + + + @element.e + + } + + + +
+ @if (prop.SelectorType is null) { + + Add + } + else { + @* TODO: implement selector + + + @foreach (var group in _allGroups) { + @if (_group.Permissions.All(g => g.PermissionName != group.Name) && group.Name != _group.Name) { + + } + } + + Add*@ + } +
+
+
+ } + else if (IsSwitch(prop)) { +
+ @prop.DisplayName + +
+ } + else { + @prop.DisplayName + + }
} @@ -38,6 +87,7 @@ @inject IServiceProvider Provider +@inject IAdminPagesProvider PageProvider @code { [Parameter] @@ -51,9 +101,11 @@ private AdminPage _currentPage; private object _entry; private bool _isEdit; + private IDictionary _inputValues; public async Task Show(AdminPage page, object entryToEdit = null) { _entry = null; + _inputValues = new Dictionary(); _currentPage = page; _entry = entryToEdit; @@ -81,11 +133,67 @@ private bool IsDisabled(AdminPageProperty prop) => !prop.Editable; private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue; + private bool IsSwitch(AdminPageProperty prop) => prop.Type == typeof(bool); + + private bool IsListType(AdminPageProperty prop) { + if (!prop.Type.IsGenericType) return false; + var generic = prop.Type.GenericTypeArguments[0]; + var listType = typeof(IList<>).MakeGenericType(generic); + return prop.Type.IsAssignableFrom(listType); + } + + private IList GetListPropertyValues(AdminPageProperty prop) { + if (!IsListType(prop)) return new List(); + var list = new List(); + + var values = prop.GetValue(_entry); + + if (values is null) { + prop.SetValue(_entry, Activator.CreateInstance(prop.Type)); + return list; + } + + foreach (var value in (IEnumerable)values) { + list.Add(MapPropertyValue(value, prop)); + } + + return list; + } private string GetPropertyValue(AdminPageProperty property) { if (!_isEdit) return ""; if (!property.EditDisplayValue) return ""; - return property.GetValue(_entry)?.ToString(); + return MapPropertyValue(property.GetValue(_entry), property); + } + + public string MapPropertyValue(object value, AdminPageProperty property) { + if (value is null) return string.Empty; + var type = value.GetType(); + + var page = PageProvider.HasPageFor(type); + if (page is not null && !string.IsNullOrWhiteSpace(page.ListingProperty)) { + var prop = page.Properties + .SingleOrDefault(p => p.Name == page.ListingProperty); + + if (prop is not null) { + return MapPropertyValue(prop.GetValue(value), prop); + } + } + + if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { + var prop = type.GetProperties() + .SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute)); + + return MapPropertyValue(prop?.GetValue(value), property); + } + + var stringValue = value.ToString(); + + if (!string.IsNullOrWhiteSpace(property.Prefix)) { + return stringValue?.Replace(property.Prefix, ""); + } + + return stringValue; } private string GetInputType(AdminPageProperty property) { @@ -97,11 +205,18 @@ private void Validate(object sender, ValidationRequestedEventArgs e) { foreach (var value in _values) { + if (value.Key.Validator is null) continue; if (value.Key.Validator?.Invoke(value.Value) == true) continue; Console.WriteLine("INVALID"); } } + private void DeleteListItem(AdminPageProperty prop, int index) { + Console.WriteLine(index); + var list = prop.GetValue(_entry); + list.RemoveAt(index); + } + private async void Save() { foreach (var value in _values) { value.Key.SetValue(_entry, value.Value); diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index e4914c9..54be7d4 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -29,7 +29,8 @@ public class HopAdminContext : AdminPagesContext { .Editable(false); generator.Page().Property(u => u.Permissions) - .DisplayInListing(false); + .DisplayInListing(false) + .IsSelector(); generator.Page().Property(u => u.Tokens) .Ignore(); diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index a04a374..17c5f64 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -214,12 +214,8 @@ StateHasChanged(); } - private static string GetPrintableValue(object element, AdminPageProperty prop) { - var entry = prop.GetValue(element); - - //TODO: convert relational types - - return entry?.ToString(); + private string GetPrintableValue(object element, AdminPageProperty prop) { + return _modal?.MapPropertyValue(prop.GetValue(element), prop); } private async void Create() { From ce15717c7d6111e5888999025fd90db5fdba6ec2 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 26 Oct 2024 11:54:02 +0200 Subject: [PATCH 10/16] Made AdminPageProperty generic + added dynamic display property selecting --- .../Attributes/ListingPropertyAttribute.cs | 4 -- src/HopFrame.Database/Models/Permission.cs | 3 +- .../Models/PermissionGroup.cs | 3 +- src/HopFrame.Database/Models/User.cs | 3 +- .../Members/ListingPropertyAttribute.cs | 4 ++ .../Generators/IAdminPageGenerator.cs | 2 +- .../Generators/IAdminPropertyGenerator.cs | 30 +++++++------ .../Implementation/AdminContextGenerator.cs | 9 ++-- .../Implementation/AdminPageGenerator.cs | 32 ++++++++------ .../Implementation/AdminPropertyGenerator.cs | 44 +++++++++++++------ .../Models/AdminPageProperty.cs | 1 + .../Administration/AdminPageModal.razor | 13 ++++-- src/HopFrame.Web/HopAdminContext.cs | 6 ++- .../Pages/Administration/AdminPageList.razor | 7 +-- test/FrontendTest/DatabaseContext.cs | 2 +- test/RestApiTest/DatabaseContext.cs | 2 +- 16 files changed, 97 insertions(+), 68 deletions(-) delete mode 100644 src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs diff --git a/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs b/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs deleted file mode 100644 index b5fc86a..0000000 --- a/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace HopFrame.Database.Attributes; - -[AttributeUsage(AttributeTargets.Property)] -public sealed class ListingPropertyAttribute : Attribute; diff --git a/src/HopFrame.Database/Models/Permission.cs b/src/HopFrame.Database/Models/Permission.cs index 475632e..db111ba 100644 --- a/src/HopFrame.Database/Models/Permission.cs +++ b/src/HopFrame.Database/Models/Permission.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -10,7 +9,7 @@ public class Permission { [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; init; } - [Required, MaxLength(255), ListingProperty] + [Required, MaxLength(255)] public string PermissionName { get; set; } [Required] diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index b6613bb..7a70ebd 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,12 +1,11 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; public class PermissionGroup : IPermissionOwner { - [Key, Required, MaxLength(50), ListingProperty] + [Key, Required, MaxLength(50)] public string Name { get; init; } [Required, DefaultValue(false)] diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 2fe6c8e..971d899 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,6 +1,5 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -9,7 +8,7 @@ public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] public Guid Id { get; init; } - [MaxLength(50), ListingProperty] + [MaxLength(50)] public string Username { get; set; } [Required, MaxLength(50), EmailAddress] diff --git a/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs new file mode 100644 index 0000000..eeb10f8 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public sealed class ListingPropertyAttribute : Attribute; \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index 0e7a72c..dc09056 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -22,7 +22,7 @@ public interface IAdminPageGenerator { IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; - IAdminPropertyGenerator Property(Expression> propertyExpression); + IAdminPropertyGenerator Property(Expression> propertyExpression); IAdminPageGenerator ListingProperty(Expression> propertyExpression); } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 2d75abe..745dbb5 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -1,19 +1,23 @@ +using System.Linq.Expressions; + namespace HopFrame.Web.Admin.Generators; -public interface IAdminPropertyGenerator { +public interface IAdminPropertyGenerator { - IAdminPropertyGenerator Sortable(bool sortable); - IAdminPropertyGenerator Editable(bool editable); - IAdminPropertyGenerator DisplayValueWhileEditing(bool display); - IAdminPropertyGenerator DisplayInListing(bool display = true); - IAdminPropertyGenerator Ignore(bool ignore = true); - IAdminPropertyGenerator Generated(bool generated = true); - IAdminPropertyGenerator Bold(bool bold = true); + IAdminPropertyGenerator Sortable(bool sortable); + IAdminPropertyGenerator Editable(bool editable); + IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + IAdminPropertyGenerator DisplayInListing(bool display = true); + IAdminPropertyGenerator Ignore(bool ignore = true); + IAdminPropertyGenerator Generated(bool generated = true); + IAdminPropertyGenerator Bold(bool bold = true); - IAdminPropertyGenerator DisplayName(string displayName); - IAdminPropertyGenerator Description(string description); - IAdminPropertyGenerator Prefix(string prefix); - IAdminPropertyGenerator Validator(Func validator); - IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator DisplayName(string displayName); + IAdminPropertyGenerator Description(string description); + IAdminPropertyGenerator Prefix(string prefix); + IAdminPropertyGenerator Validator(Func validator); + IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); + IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index aa90737..ae62af6 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -38,10 +38,13 @@ internal class AdminContextGenerator : IAdminContextGenerator { foreach (var property in properties) { var propertyType = property.PropertyType.GenericTypeArguments[0]; var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); - var generatorInstance = Activator.CreateInstance(pageGeneratorType, [property.Name]); // Calls constructor with title attribute + var generatorInstance = Activator.CreateInstance(pageGeneratorType); - var populatorMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.ApplyConfigurationFromAttributes)); - populatorMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); + var titleMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.Title)); + titleMethod?.Invoke(generatorInstance, [property.Name]); + + var populateMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.ApplyConfigurationFromAttributes)); + populateMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); _adminPages.Add(propertyType, generatorInstance); } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index 4ea22e5..f61225b 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -10,32 +10,34 @@ namespace HopFrame.Web.Admin.Generators.Implementation; internal sealed class AdminPageGenerator : IAdminPageGenerator, IGenerator> { public readonly AdminPage Page; - private readonly IDictionary _propertyGenerators; + private readonly Dictionary _propertyGenerators; public AdminPageGenerator() { Page = new AdminPage { Permissions = new AdminPagePermissions(), ModelType = typeof(TModel) }; - _propertyGenerators = new Dictionary(); + _propertyGenerators = new Dictionary(); var type = typeof(TModel); var properties = type.GetProperties(); + var generatorType = typeof(AdminPropertyGenerator<>); foreach (var property in properties) { var attributes = property.GetCustomAttributes(false); + var genericType = generatorType.MakeGenericType(property.PropertyType); - var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; - generator?.ApplyConfigurationFromAttributes(this, attributes, property); + var generator = Activator.CreateInstance(genericType, [property.Name, property.PropertyType]); + + var method = genericType + .GetMethod(nameof(AdminPropertyGenerator.ApplyConfigurationFromAttributes))? + .MakeGenericMethod(type); + method?.Invoke(generator, [this, attributes, property]); _propertyGenerators.Add(property.Name, generator); } } - public AdminPageGenerator(string title) : this() { - Title(title); - } - public IAdminPageGenerator Title(string title) { Page.Title = title; Page.Url ??= title.ToLower(); @@ -100,13 +102,13 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } - public IAdminPropertyGenerator Property(Expression> propertyExpression) { + public IAdminPropertyGenerator Property(Expression> propertyExpression) { var property = GetPropertyInfo(propertyExpression); if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) - return propertyGenerator; + return propertyGenerator as AdminPropertyGenerator; - var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property); _propertyGenerators.Add(property.Name, generator); @@ -122,8 +124,10 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, public AdminPage Compile() { var properties = new List(); - foreach (var generator in _propertyGenerators.Values){ - properties.Add(generator.Compile()); + foreach (var generator in _propertyGenerators.Values) { + var method = generator.GetType().GetMethod(nameof(AdminPropertyGenerator.Compile)); + var prop = method?.Invoke(generator, []) as AdminPageProperty; + properties.Add(prop); } Page.Properties = properties; @@ -131,7 +135,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return Page; } - private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { + public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { if (propertyLambda.Body is not MemberExpression member) { throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 92df826..b626463 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq.Expressions; using System.Reflection; using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Attributes.Members; @@ -7,74 +8,86 @@ using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Generators.Implementation; -internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { +internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { private readonly AdminPageProperty _property = new() { Name = name, Type = type }; - public IAdminPropertyGenerator Sortable(bool sortable) { + public IAdminPropertyGenerator Sortable(bool sortable) { _property.Sortable = sortable; return this; } - public IAdminPropertyGenerator Editable(bool editable) { + public IAdminPropertyGenerator Editable(bool editable) { _property.Editable = editable; return this; } - public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { + public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { _property.EditDisplayValue = display; return this; } - public IAdminPropertyGenerator DisplayInListing(bool display = true) { + public IAdminPropertyGenerator DisplayInListing(bool display = true) { _property.DisplayInListing = display; _property.Sortable = false; return this; } - public IAdminPropertyGenerator Ignore(bool ignore = false) { + public IAdminPropertyGenerator Ignore(bool ignore = false) { _property.Ignore = ignore; return this; } - public IAdminPropertyGenerator Generated(bool generated = true) { + public IAdminPropertyGenerator Generated(bool generated = true) { _property.Generated = generated; return this; } - public IAdminPropertyGenerator Bold(bool bold = true) { + public IAdminPropertyGenerator Bold(bool bold = true) { _property.Bold = bold; return this; } - public IAdminPropertyGenerator DisplayName(string displayName) { + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; } - public IAdminPropertyGenerator Description(string description) { + public IAdminPropertyGenerator Description(string description) { _property.Description = description; return this; } - public IAdminPropertyGenerator Prefix(string prefix) { + public IAdminPropertyGenerator Prefix(string prefix) { _property.Prefix = prefix; return this; } - public IAdminPropertyGenerator Validator(Func validator) { + public IAdminPropertyGenerator Validator(Func validator) { _property.Validator = validator; return this; } - public IAdminPropertyGenerator IsSelector() { + public IAdminPropertyGenerator IsSelector() { _property.SelectorType = typeof(TSelector); return this; } + public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { + var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); + _property.DisplayPropertyName = property.Name; + return this; + } + + public IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression) { + var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); + _property.DisplayPropertyName = property.Name; + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; return _property; @@ -129,5 +142,10 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; Prefix(attribute?.Prefix); } + + if (attributes.Any(a => a is ListingPropertyAttribute)) { + var attribute = attributes.Single(a => a is ListingPropertyAttribute) as ListingPropertyAttribute; + _property.DisplayPropertyName = property.Name; + } } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 6707458..f1b1ddd 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -7,6 +7,7 @@ public sealed class AdminPageProperty { public string DisplayName { get; set; } public string Description { get; set; } public string Prefix { get; set; } + public string DisplayPropertyName { get; set; } public bool DisplayInListing { get; set; } = true; public bool Sortable { get; set; } = true; diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 5fa3ad8..0141634 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -1,12 +1,10 @@ @rendermode InteractiveServer @using System.Collections -@using System.ComponentModel.DataAnnotations @using BlazorStrap @using BlazorStrap.Shared.Components.Modal @using static Microsoft.AspNetCore.Components.Web.RenderMode @using BlazorStrap.V5 -@using HopFrame.Database.Attributes @using HopFrame.Web.Admin @using HopFrame.Web.Admin.Models @using HopFrame.Web.Admin.Providers @@ -166,7 +164,7 @@ return MapPropertyValue(property.GetValue(_entry), property); } - public string MapPropertyValue(object value, AdminPageProperty property) { + public string MapPropertyValue(object value, AdminPageProperty property, bool isSubProperty = false) { if (value is null) return string.Empty; var type = value.GetType(); @@ -180,11 +178,18 @@ } } - if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { + /*if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { var prop = type.GetProperties() .SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute)); return MapPropertyValue(prop?.GetValue(value), property); + }*/ + + if (!string.IsNullOrEmpty(property.DisplayPropertyName) && !isSubProperty) { + var prop = type.GetProperties() + .SingleOrDefault(p => p.Name == property.DisplayPropertyName); + + return MapPropertyValue(prop?.GetValue(value), property, true); } var stringValue = value.ToString(); diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 54be7d4..6da2927 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -30,7 +30,8 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(u => u.Permissions) .DisplayInListing(false) - .IsSelector(); + .IsSelector() + .DisplayPropertyForListType(p => p.PermissionName); generator.Page().Property(u => u.Tokens) .Ignore(); @@ -54,6 +55,7 @@ public class HopAdminContext : AdminPagesContext { .Editable(false); generator.Page().Property(g => g.Permissions) - .DisplayInListing(false); + .DisplayInListing(false) + .DisplayPropertyForListType(p => p.PermissionName); } } \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 17c5f64..8932a56 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -178,12 +178,7 @@ return String.CompareOrdinal(propX, propY); }); - if (_currentSortDirection == ListSortDirection.Ascending) { - _displayedModels = _displayedModels.Order(comparer).ToList(); - } - else { - _displayedModels = _displayedModels.OrderDescending(comparer).ToList(); - } + _displayedModels = _currentSortDirection == ListSortDirection.Ascending ? _displayedModels.Order(comparer).ToList() : _displayedModels.OrderDescending(comparer).ToList(); _currentSortProperty = property; } diff --git a/test/FrontendTest/DatabaseContext.cs b/test/FrontendTest/DatabaseContext.cs index 5da7d59..9a89c5d 100644 --- a/test/FrontendTest/DatabaseContext.cs +++ b/test/FrontendTest/DatabaseContext.cs @@ -7,6 +7,6 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } } \ No newline at end of file diff --git a/test/RestApiTest/DatabaseContext.cs b/test/RestApiTest/DatabaseContext.cs index 1167277..ef370c7 100644 --- a/test/RestApiTest/DatabaseContext.cs +++ b/test/RestApiTest/DatabaseContext.cs @@ -12,7 +12,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { From 85a45ece55d1e1c624ec03919c5d583021f7972f Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 27 Oct 2024 11:33:50 +0100 Subject: [PATCH 11/16] finished list input + added proper prefix rendering --- HopFrame.sln.DotSettings.user | 1 + .../Models/PermissionGroup.cs | 2 +- src/HopFrame.Database/Models/User.cs | 4 +- .../Generators/IAdminPropertyGenerator.cs | 2 + .../Implementation/AdminPropertyGenerator.cs | 10 ++ .../Models/AdminPageProperty.cs | 1 + .../Administration/AdminPageModal.razor | 91 +++++++++++++++---- src/HopFrame.Web/HopAdminContext.cs | 24 ++++- .../Pages/Administration/AdminPageList.razor | 4 +- 9 files changed, 114 insertions(+), 25 deletions(-) diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index f66ed72..baebe71 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -1,4 +1,5 @@  + ForceIncluded <AssemblyExplorer> <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index 7a70ebd..aa0c92c 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -17,6 +17,6 @@ public class PermissionGroup : IPermissionOwner { [Required] public DateTime CreatedAt { get; set; } - public virtual IList Permissions { get; set; } + public virtual List Permissions { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 971d899..2b73010 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -20,9 +20,9 @@ public class User : IPermissionOwner { [Required] public DateTime CreatedAt { get; set; } - public virtual IList Permissions { get; set; } + public virtual List Permissions { get; set; } [JsonIgnore] - public virtual IList Tokens { get; set; } + public virtual List Tokens { get; set; } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 745dbb5..720b103 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -17,6 +17,8 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator Prefix(string prefix); IAdminPropertyGenerator Validator(Func validator); IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator Parser(Func parser); + IAdminPropertyGenerator ParserForListType(Func parser); IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index b626463..3cb2368 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -76,6 +76,16 @@ internal sealed class AdminPropertyGenerator(string name, Type type) return this; } + public IAdminPropertyGenerator Parser(Func parser) { + _property.Parser = (o, s) => parser.Invoke((TModel)o, s); + return this; + } + + public IAdminPropertyGenerator ParserForListType(Func parser) { + _property.Parser = (o, s) => parser.Invoke((TModel)o, s); + return this; + } + public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); _property.DisplayPropertyName = property.Name; diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index f1b1ddd..95eadc4 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -23,6 +23,7 @@ public sealed class AdminPageProperty { public Type SelectorType { get; set; } public Func Validator { get; set; } + public Func Parser { get; set; } public object GetValue(object entry) { return entry.GetType().GetProperty(Name)?.GetValue(entry); diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 0141634..5c05d68 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -5,6 +5,9 @@ @using BlazorStrap.Shared.Components.Modal @using static Microsoft.AspNetCore.Components.Web.RenderMode @using BlazorStrap.V5 +@using CurrieTechnologies.Razor.SweetAlert2 +@using HopFrame.Database.Repositories +@using HopFrame.Security.Claims @using HopFrame.Web.Admin @using HopFrame.Web.Admin.Models @using HopFrame.Web.Admin.Providers @@ -22,7 +25,7 @@ @foreach (var prop in GetEditableProperties()) { - @if (!_isEdit && !prop.Editable) continue; + @if (!_isEdit && prop.Generated) continue;
@if (IsListType(prop)) { @@ -42,10 +45,12 @@ -
+
@if (prop.SelectorType is null) { - - Add +
+ + Add +
} else { @* TODO: implement selector @@ -69,6 +74,12 @@
} + else if (prop.Prefix is not null && !_isEdit) { + + @prop.Prefix + + + } else { @prop.DisplayName @@ -86,6 +97,9 @@ @inject IServiceProvider Provider @inject IAdminPagesProvider PageProvider +@inject SweetAlertService Alerts +@inject IPermissionRepository Permissions +@inject ITokenContext Auth @code { [Parameter] @@ -129,15 +143,16 @@ .ToList(); } - private bool IsDisabled(AdminPageProperty prop) => !prop.Editable; + private bool IsDisabled(AdminPageProperty prop) => (_isEdit && !prop.Editable) || prop.Generated; private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue; private bool IsSwitch(AdminPageProperty prop) => prop.Type == typeof(bool); private bool IsListType(AdminPageProperty prop) { if (!prop.Type.IsGenericType) return false; var generic = prop.Type.GenericTypeArguments[0]; - var listType = typeof(IList<>).MakeGenericType(generic); - return prop.Type.IsAssignableFrom(listType); + var gListType = typeof(IList<>).MakeGenericType(generic); + var iListType = typeof(List<>).MakeGenericType(generic); + return prop.Type.IsAssignableFrom(gListType) || prop.Type.IsAssignableFrom(iListType); } private IList GetListPropertyValues(AdminPageProperty prop) { @@ -178,13 +193,6 @@ } } - /*if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { - var prop = type.GetProperties() - .SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute)); - - return MapPropertyValue(prop?.GetValue(value), property); - }*/ - if (!string.IsNullOrEmpty(property.DisplayPropertyName) && !isSubProperty) { var prop = type.GetProperties() .SingleOrDefault(p => p.Name == property.DisplayPropertyName); @@ -213,25 +221,76 @@ if (value.Key.Validator is null) continue; if (value.Key.Validator?.Invoke(value.Value) == true) continue; Console.WriteLine("INVALID"); + //TODO: implement validation } } private void DeleteListItem(AdminPageProperty prop, int index) { - Console.WriteLine(index); var list = prop.GetValue(_entry); list.RemoveAt(index); } + private void AddListItem(AdminPageProperty prop) { + if (!_inputValues.TryGetValue(prop, out var input)) { + Alerts.FireAsync(new SweetAlertOptions { + Title = "Error!", + Text = "Please enter a value!", + Icon = SweetAlertIcon.Error + }); + return; + } + + var list = prop.GetValue(_entry); + var value = prop.Parser?.Invoke(_entry, input) ?? input; + list?.Add(value); + } + private async void Save() { + if (_isEdit && _currentPage.Permissions.Update is not null) { + if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Update)) { + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Unauthorized!", + Text = "You don't have the required permissions to edit an entry!", + Icon = SweetAlertIcon.Error + }); + return; + } + }else if (_currentPage.Permissions.Create is not null) { + if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Create)) { + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Unauthorized!", + Text = "You don't have the required permissions to add an entry!", + Icon = SweetAlertIcon.Error + }); + return; + } + } + foreach (var value in _values) { - value.Key.SetValue(_entry, value.Value); + if (IsListType(value.Key)) continue; + value.Key.SetValue(_entry, value.Key.Parser?.Invoke(_entry, (string)value.Value) ?? value.Value); } if (!_isEdit) { await _repository.CreateO(_entry); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "New entry added!", + Icon = SweetAlertIcon.Success, + ShowConfirmButton = false, + Timer = 1500 + }); } else { await _repository.UpdateO(_entry); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Entry updated!", + Icon = SweetAlertIcon.Success, + ShowConfirmButton = false, + Timer = 1500 + + }); } await ReloadDelegate.Invoke(); diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 6da2927..84a87a5 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -30,8 +30,18 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(u => u.Permissions) .DisplayInListing(false) - .IsSelector() - .DisplayPropertyForListType(p => p.PermissionName); + .DisplayPropertyForListType(p => p.PermissionName) + .ParserForListType((user, perm) => new Permission { + GrantedAt = DateTime.Now, + PermissionName = perm, + User = user + }); + + generator.Page().Property(u => u.CreatedAt) + .Generated(); + + generator.Page().Property(u => u.Id) + .Generated(); generator.Page().Property(u => u.Tokens) .Ignore(); @@ -49,13 +59,19 @@ public class HopAdminContext : AdminPagesContext { .Prefix("group."); generator.Page().Property(g => g.IsDefaultGroup) + .DisplayName("Default Group") .Sortable(false); generator.Page().Property(g => g.CreatedAt) - .Editable(false); + .Generated(); generator.Page().Property(g => g.Permissions) .DisplayInListing(false) - .DisplayPropertyForListType(p => p.PermissionName); + .DisplayPropertyForListType(p => p.PermissionName) + .ParserForListType((group, perm) => new Permission { + GrantedAt = DateTime.Now, + PermissionName = perm, + Group = group + }); } } \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 8932a56..947f961 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -130,9 +130,9 @@ _currentSortProperty = _pageData.DefaultSortPropertyName; _currentSortDirection = _pageData.DefaultSortDirection; - _modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository; - if (_modelRepository is null) + if (_pageData.RepositoryProvider is null) throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'"); + _modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository; _hasEditPermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); _hasDeletePermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); From d38cce6dc2c6d261fccd1e57e9bcf9f7758a375f Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 27 Oct 2024 15:26:25 +0100 Subject: [PATCH 12/16] Added validation to admin pages --- HopFrame.sln.DotSettings.user | 2 + src/HopFrame.Database/Models/User.cs | 2 +- .../Members/AdminUniqueAttribute.cs | 4 ++ .../Generators/IAdminPropertyGenerator.cs | 3 +- .../Implementation/AdminPropertyGenerator.cs | 12 +++++- .../Models/AdminPageProperty.cs | 4 +- .../Administration/AdminPageModal.razor | 43 ++++++++++++++----- src/HopFrame.Web/HopAdminContext.cs | 12 +++++- 8 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 src/HopFrame.Web.Admin/Attributes/Members/AdminUniqueAttribute.cs diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index baebe71..a38eed3 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -1,5 +1,7 @@  + ForceIncluded ForceIncluded + ForceIncluded <AssemblyExplorer> <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 2b73010..feec39c 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -8,7 +8,7 @@ public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] public Guid Id { get; init; } - [MaxLength(50)] + [Required, MaxLength(50)] public string Username { get; set; } [Required, MaxLength(50), EmailAddress] diff --git a/src/HopFrame.Web.Admin/Attributes/Members/AdminUniqueAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/AdminUniqueAttribute.cs new file mode 100644 index 0000000..5247777 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/AdminUniqueAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public class AdminUniqueAttribute : Attribute; \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 720b103..3d35d52 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -11,11 +11,12 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator Generated(bool generated = true); IAdminPropertyGenerator Bold(bool bold = true); + IAdminPropertyGenerator Unique(bool unique = true); IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Prefix(string prefix); - IAdminPropertyGenerator Validator(Func validator); + IAdminPropertyGenerator Validator(Func validator); IAdminPropertyGenerator IsSelector(); IAdminPropertyGenerator Parser(Func parser); IAdminPropertyGenerator ParserForListType(Func parser); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 3cb2368..9673508 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -51,6 +51,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) return this; } + public IAdminPropertyGenerator Unique(bool unique = true) { + _property.Unique = unique; + return this; + } + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; @@ -66,8 +71,8 @@ internal sealed class AdminPropertyGenerator(string name, Type type) return this; } - public IAdminPropertyGenerator Validator(Func validator) { - _property.Validator = validator; + public IAdminPropertyGenerator Validator(Func validator) { + _property.Validator = o => validator.Invoke((TProperty)o); return this; } @@ -115,6 +120,9 @@ internal sealed class AdminPropertyGenerator(string name, Type type) if (attributes.Any(a => a is AdminUneditableAttribute)) Editable(false); + + if (attributes.Any(a => a is AdminUniqueAttribute)) + Unique(); if (attributes.Any(a => a is AdminIgnoreAttribute)) { var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute; diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 95eadc4..2ae5497 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -17,12 +17,14 @@ public sealed class AdminPageProperty { public bool Bold { get; set; } public bool Required { get; set; } public bool Ignore { get; set; } + public bool Unique { get; set; } + [JsonIgnore] public Type Type { get; set; } public Type SelectorType { get; set; } - public Func Validator { get; set; } + public Func Validator { get; set; } public Func Parser { get; set; } public object GetValue(object entry) { diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 5c05d68..6a97503 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -26,14 +26,14 @@ @foreach (var prop in GetEditableProperties()) { @if (!_isEdit && prop.Generated) continue; - +
@if (IsListType(prop)) { @prop.DisplayName - @foreach (var element in GetListPropertyValues(prop).Select((e, i) => new {e, i})) { + @foreach (var element in GetListPropertyValues(prop).Select((e, i) => new { e, i })) { @@ -77,12 +77,17 @@ else if (prop.Prefix is not null && !_isEdit) { @prop.Prefix - + } else { @prop.DisplayName - + + @if (_validation[_validationIdentifiers[prop]].Any()) { +
+ @_validation[_validationIdentifiers[prop]].First() +
+ } }
} @@ -102,11 +107,15 @@ @inject ITokenContext Auth @code { + #pragma warning disable CS4014 + [Parameter] public Func ReloadDelegate { get; set; } private BSModalBase _modal; private EditContext _context; + private ValidationMessageStore _validation; + private Dictionary _validationIdentifiers; private IDictionary _values; private IModelRepository _repository; @@ -126,11 +135,14 @@ _entry ??= Activator.CreateInstance(_currentPage.ModelType); _context = new EditContext(_entry); + _validation = new ValidationMessageStore(_context); + _validationIdentifiers = new Dictionary(); _context.OnValidationRequested += Validate; _values = new Dictionary(); foreach (var property in _currentPage.Properties) { _values.Add(property, property.GetValue(_entry)); + _validationIdentifiers.Add(property, new FieldIdentifier(_entry, property.Name)); } await _modal.ShowAsync(); @@ -217,11 +229,23 @@ } private void Validate(object sender, ValidationRequestedEventArgs e) { + _validation.Clear(); foreach (var value in _values) { + if (value.Key.Unique) { + var repo = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository; + var data = repo!.ReadAllO().GetAwaiter().GetResult(); + foreach (var entry in data) { + var other = value.Key.GetValue(entry); + if (!other.Equals(value.Value)) continue; + _validation.Add(_validationIdentifiers[value.Key], $"This {value.Key.DisplayName ?? value.Key.Name} already exists!"); + break; + } + } + if (value.Key.Validator is null) continue; - if (value.Key.Validator?.Invoke(value.Value) == true) continue; - Console.WriteLine("INVALID"); - //TODO: implement validation + var error = value.Key.Validator?.Invoke(value.Value); + if (string.IsNullOrEmpty(error)) continue; + _validation.Add(_validationIdentifiers[value.Key], error); } } @@ -274,7 +298,7 @@ if (!_isEdit) { await _repository.CreateO(_entry); - await Alerts.FireAsync(new SweetAlertOptions { + Alerts.FireAsync(new SweetAlertOptions { Title = "New entry added!", Icon = SweetAlertIcon.Success, ShowConfirmButton = false, @@ -284,12 +308,11 @@ else { await _repository.UpdateO(_entry); - await Alerts.FireAsync(new SweetAlertOptions { + Alerts.FireAsync(new SweetAlertOptions { Title = "Entry updated!", Icon = SweetAlertIcon.Success, ShowConfirmButton = false, Timer = 1500 - }); } diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 84a87a5..68fdd90 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using HopFrame.Database.Models; using HopFrame.Security; using HopFrame.Web.Admin; @@ -23,7 +24,16 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(u => u.Password) .DisplayInListing(false) - .DisplayValueWhileEditing(false); + .DisplayValueWhileEditing(false) + .Validator(passwd => passwd.Length >= 8 ? null : "The password needs to be at least 8 characters long!"); + + generator.Page().Property(u => u.Email) + .Validator(email => Regex.Match(email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").Success ? null : "Invalid E-Mail address!") + .Unique(); + + generator.Page().Property(u => u.Username) + .Validator(uname => uname.Length >= 4 ? null : "The username needs to be at least 4 characters long!") + .Unique(); generator.Page().Property(u => u.CreatedAt) .Editable(false); From 0cc4eb44dabf1864d261d7588e50d58c3f9a6602 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 5 Nov 2024 18:45:34 +0100 Subject: [PATCH 13/16] Implemented selector properties for admin pages --- .../Generators/IAdminPropertyGenerator.cs | 5 +- .../Implementation/AdminPropertyGenerator.cs | 24 +++++- .../Models/AdminPageProperty.cs | 6 +- .../Administration/AdminPageModal.razor | 86 +++++++++++++++---- src/HopFrame.Web/HopAdminContext.cs | 3 +- .../Pages/Administration/AdminPageList.razor | 4 +- test/FrontendTest/AdminContext.cs | 28 +++++- test/FrontendTest/DatabaseContext.cs | 14 ++- .../FrontendTest/Providers/AddressProvider.cs | 29 +++++++ .../Providers/EmployeeProvider.cs | 31 +++++++ 10 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 test/FrontendTest/Providers/AddressProvider.cs create mode 100644 test/FrontendTest/Providers/EmployeeProvider.cs diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 3d35d52..e8442b1 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -17,9 +17,12 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Prefix(string prefix); IAdminPropertyGenerator Validator(Func validator); - IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator IsSelector(bool selector = true); + IAdminPropertyGenerator IsSelector(bool selector = true); IAdminPropertyGenerator Parser(Func parser); + IAdminPropertyGenerator Parser(Func parser); IAdminPropertyGenerator ParserForListType(Func parser); + IAdminPropertyGenerator ParserForListType(Func parser); IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 9673508..5bbe01f 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -76,18 +76,34 @@ internal sealed class AdminPropertyGenerator(string name, Type type) return this; } - public IAdminPropertyGenerator IsSelector() { - _property.SelectorType = typeof(TSelector); + public IAdminPropertyGenerator IsSelector(bool selector = true) { + _property.Selector = selector; + return this; + } + + public IAdminPropertyGenerator IsSelector(bool selector = true) { + _property.Selector = true; + _property.SelectorType = typeof(TSelectorType); return this; } public IAdminPropertyGenerator Parser(Func parser) { - _property.Parser = (o, s) => parser.Invoke((TModel)o, s); + _property.Parser = (o, s) => parser.Invoke((TModel)o, s.ToString()); + return this; + } + + public IAdminPropertyGenerator Parser(Func parser) { + _property.Parser = (o, s) => parser.Invoke((TModel)o, (TInput)s); return this; } public IAdminPropertyGenerator ParserForListType(Func parser) { - _property.Parser = (o, s) => parser.Invoke((TModel)o, s); + _property.Parser = (o, s) => parser.Invoke((TModel)o, s.ToString()); + return this; + } + + public IAdminPropertyGenerator ParserForListType(Func parser) { + _property.Parser = (o, s) => parser.Invoke((TModel)o, (TInput)s); return this; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 2ae5497..a86cf47 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -18,14 +18,14 @@ public sealed class AdminPageProperty { public bool Required { get; set; } public bool Ignore { get; set; } public bool Unique { get; set; } + public bool Selector { get; set; } + public Type SelectorType { get; set; } [JsonIgnore] public Type Type { get; set; } - public Type SelectorType { get; set; } - public Func Validator { get; set; } - public Func Parser { get; set; } + public Func Parser { get; set; } public object GetValue(object entry) { return entry.GetType().GetProperty(Name)?.GetValue(entry); diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 6a97503..f410739 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -1,6 +1,7 @@ @rendermode InteractiveServer @using System.Collections +@using System.Globalization @using BlazorStrap @using BlazorStrap.Shared.Components.Modal @using static Microsoft.AspNetCore.Components.Web.RenderMode @@ -46,23 +47,23 @@
- @if (prop.SelectorType is null) { + @if (!prop.Selector) {
Add
} else { - @* TODO: implement selector - +
+ + Add +
}
@@ -80,6 +81,16 @@ } + else if (prop.Selector) { + @prop.DisplayName + + } else { @prop.DisplayName @@ -117,16 +128,18 @@ private ValidationMessageStore _validation; private Dictionary _validationIdentifiers; private IDictionary _values; + private Dictionary _selectorValues; private IModelRepository _repository; private AdminPage _currentPage; private object _entry; private bool _isEdit; - private IDictionary _inputValues; + private IDictionary _inputValues; public async Task Show(AdminPage page, object entryToEdit = null) { _entry = null; - _inputValues = new Dictionary(); + _inputValues = new Dictionary(); + _selectorValues = new Dictionary(); _currentPage = page; _entry = entryToEdit; @@ -158,13 +171,14 @@ private bool IsDisabled(AdminPageProperty prop) => (_isEdit && !prop.Editable) || prop.Generated; private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue; private bool IsSwitch(AdminPageProperty prop) => prop.Type == typeof(bool); + private bool IsListType(AdminPageProperty prop) => IsListType(prop.Type); - private bool IsListType(AdminPageProperty prop) { - if (!prop.Type.IsGenericType) return false; - var generic = prop.Type.GenericTypeArguments[0]; + private bool IsListType(Type type) { + if (!type.IsGenericType) return false; + var generic = type.GenericTypeArguments[0]; var gListType = typeof(IList<>).MakeGenericType(generic); var iListType = typeof(List<>).MakeGenericType(generic); - return prop.Type.IsAssignableFrom(gListType) || prop.Type.IsAssignableFrom(iListType); + return type.IsAssignableFrom(gListType) || type.IsAssignableFrom(iListType); } private IList GetListPropertyValues(AdminPageProperty prop) { @@ -232,6 +246,7 @@ _validation.Clear(); foreach (var value in _values) { if (value.Key.Unique) { + if (value.Value == value.Key.GetValue(_entry)) continue; var repo = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository; var data = repo!.ReadAllO().GetAwaiter().GetResult(); foreach (var entry in data) { @@ -255,7 +270,7 @@ } private void AddListItem(AdminPageProperty prop) { - if (!_inputValues.TryGetValue(prop, out var input)) { + if (!_inputValues.TryGetValue(prop, out var input) || input is null) { Alerts.FireAsync(new SweetAlertOptions { Title = "Error!", Text = "Please enter a value!", @@ -269,6 +284,43 @@ list?.Add(value); } + private async Task<(string, int)[]> SetupSelectorProperty(AdminPageProperty property) { + var type = property.SelectorType ?? property.Type; + if (IsListType(type)) { + type = type.GenericTypeArguments[0]; + } + + var page = PageProvider.HasPageFor(type); + if (page is null) { + throw new ArgumentException($"'{property.Name}' cannot be a selector because a admin page for '{type.Name}' does not exist!"); + } + + var repo = Provider.GetService(page.RepositoryProvider) as IModelRepository; + var objects = (await repo!.ReadAllO()).ToArray(); + _selectorValues[property] = objects; + + var data = new List<(string, int)>(); + for (var i = 0; i < objects.Length; i++) { + data.Add((MapPropertyValue(objects[i], property), i)); + } + + return data.ToArray(); + } + + private bool IsIndexSelected(AdminPageProperty property, int index) { + var value = property.GetValue(_entry); + if (value is null) return false; + return _selectorValues[property][index] == value; + } + + private object ReadSelectorValue(AdminPageProperty property, object value) { + if (!int.TryParse(value.ToString(), out int result)) { + return null; + } + + return _selectorValues[property][result]; + } + private async void Save() { if (_isEdit && _currentPage.Permissions.Update is not null) { if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Update)) { @@ -292,7 +344,7 @@ foreach (var value in _values) { if (IsListType(value.Key)) continue; - value.Key.SetValue(_entry, value.Key.Parser?.Invoke(_entry, (string)value.Value) ?? value.Value); + value.Key.SetValue(_entry, value.Key.Parser?.Invoke(_entry, value.Value) ?? Convert.ChangeType(value.Value, value.Key.Type)); } if (!_isEdit) { diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 68fdd90..4e45dfe 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -63,7 +63,8 @@ public class HopAdminContext : AdminPagesContext { .ViewPermission(AdminPermissions.ViewGroups) .CreatePermission(AdminPermissions.AddGroup) .UpdatePermission(AdminPermissions.EditGroup) - .DeletePermission(AdminPermissions.DeleteGroup); + .DeletePermission(AdminPermissions.DeleteGroup) + .ListingProperty(g => g.Name); generator.Page().Property(g => g.Name) .Prefix("group."); diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 947f961..c794eaf 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -134,8 +134,8 @@ throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'"); _modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository; - _hasEditPermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); - _hasDeletePermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); + _hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); + _hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); await Reload(); } diff --git a/test/FrontendTest/AdminContext.cs b/test/FrontendTest/AdminContext.cs index 016c488..6be48c9 100644 --- a/test/FrontendTest/AdminContext.cs +++ b/test/FrontendTest/AdminContext.cs @@ -1,4 +1,6 @@ +using FrontendTest.Providers; using HopFrame.Web.Admin; +using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Models; using RestApiTest.Models; @@ -8,5 +10,29 @@ public class AdminContext : AdminPagesContext { public AdminPage
Addresses { get; set; } public AdminPage Employees { get; set; } - + + public override void OnModelCreating(IAdminContextGenerator generator) { + base.OnModelCreating(generator); + + generator.Page() + .Property(e => e.Address) + .IsSelector(); + + generator.Page
() + .Property(a => a.Employee) + .Ignore(); + + generator.Page
() + .Property(a => a.AddressId) + .IsSelector() + .Parser((model, e) => model.AddressId = e.EmployeeId); + + generator.Page() + .ConfigureRepository() + .ListingProperty(e => e.Name); + + generator.Page
() + .ConfigureRepository() + .ListingProperty(a => a.City); + } } \ No newline at end of file diff --git a/test/FrontendTest/DatabaseContext.cs b/test/FrontendTest/DatabaseContext.cs index 9a89c5d..0dede8a 100644 --- a/test/FrontendTest/DatabaseContext.cs +++ b/test/FrontendTest/DatabaseContext.cs @@ -1,12 +1,24 @@ using HopFrame.Database; using Microsoft.EntityFrameworkCore; +using RestApiTest.Models; namespace FrontendTest; public class DatabaseContext : HopDbContextBase { + public DbSet Employees { get; set; } + public DbSet
Addresses { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasOne(e => e.Address) + .WithOne(a => a.Employee); + } } \ No newline at end of file diff --git a/test/FrontendTest/Providers/AddressProvider.cs b/test/FrontendTest/Providers/AddressProvider.cs new file mode 100644 index 0000000..94b203e --- /dev/null +++ b/test/FrontendTest/Providers/AddressProvider.cs @@ -0,0 +1,29 @@ +using HopFrame.Web.Admin; +using Microsoft.EntityFrameworkCore; +using RestApiTest.Models; + +namespace FrontendTest.Providers; + +public class AddressProvider(DatabaseContext context) : ModelRepository
{ + + public override async Task> ReadAll() { + return await context.Addresses.ToArrayAsync(); + } + + public override async Task
Create(Address model) { + await context.Addresses.AddAsync(model); + await context.SaveChangesAsync(); + return model; + } + + public override async Task
Update(Address model) { + context.Addresses.Update(model); + await context.SaveChangesAsync(); + return model; + } + + public override async Task Delete(Address model) { + context.Addresses.Remove(model); + await context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/test/FrontendTest/Providers/EmployeeProvider.cs b/test/FrontendTest/Providers/EmployeeProvider.cs new file mode 100644 index 0000000..0eb2aff --- /dev/null +++ b/test/FrontendTest/Providers/EmployeeProvider.cs @@ -0,0 +1,31 @@ +using HopFrame.Web.Admin; +using Microsoft.EntityFrameworkCore; +using RestApiTest.Models; + +namespace FrontendTest.Providers; + +public class EmployeeProvider(DatabaseContext context) : ModelRepository { + + public override async Task> ReadAll() { + return await context.Employees + .Include(e => e.Address) + .ToArrayAsync(); + } + + public override async Task Create(Employee model) { + await context.Employees.AddAsync(model); + await context.SaveChangesAsync(); + return model; + } + + public override async Task Update(Employee model) { + context.Employees.Update(model); + await context.SaveChangesAsync(); + return model; + } + + public override async Task Delete(Employee model) { + context.Employees.Remove(model); + await context.SaveChangesAsync(); + } +} \ No newline at end of file From 601b502c8c5882869d4c545123290376f780cf6c Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 9 Nov 2024 10:39:17 +0100 Subject: [PATCH 14/16] Improved developer experience for AdminContext's --- .../Generators/IAdminPageGenerator.cs | 2 +- .../Generators/IAdminPropertyGenerator.cs | 41 +++++++++-------- .../Implementation/AdminPageGenerator.cs | 14 +++--- .../Implementation/AdminPropertyGenerator.cs | 45 +++++++++---------- src/HopFrame.Web/HopAdminContext.cs | 8 ++-- test/FrontendTest/AdminContext.cs | 2 +- 6 files changed, 53 insertions(+), 59 deletions(-) diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index dc09056..12591bb 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -22,7 +22,7 @@ public interface IAdminPageGenerator { IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; - IAdminPropertyGenerator Property(Expression> propertyExpression); + IAdminPropertyGenerator Property(Expression> propertyExpression); IAdminPageGenerator ListingProperty(Expression> propertyExpression); } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index e8442b1..7f138bb 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -2,28 +2,27 @@ using System.Linq.Expressions; namespace HopFrame.Web.Admin.Generators; -public interface IAdminPropertyGenerator { +public interface IAdminPropertyGenerator { - IAdminPropertyGenerator Sortable(bool sortable); - IAdminPropertyGenerator Editable(bool editable); - IAdminPropertyGenerator DisplayValueWhileEditing(bool display); - IAdminPropertyGenerator DisplayInListing(bool display = true); - IAdminPropertyGenerator Ignore(bool ignore = true); - IAdminPropertyGenerator Generated(bool generated = true); - IAdminPropertyGenerator Bold(bool bold = true); - IAdminPropertyGenerator Unique(bool unique = true); + IAdminPropertyGenerator Sortable(bool sortable); + IAdminPropertyGenerator Editable(bool editable); + IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + IAdminPropertyGenerator DisplayInListing(bool display = true); + IAdminPropertyGenerator Ignore(bool ignore = true); + IAdminPropertyGenerator Generated(bool generated = true); + IAdminPropertyGenerator Bold(bool bold = true); + IAdminPropertyGenerator Unique(bool unique = true); - IAdminPropertyGenerator DisplayName(string displayName); - IAdminPropertyGenerator Description(string description); - IAdminPropertyGenerator Prefix(string prefix); - IAdminPropertyGenerator Validator(Func validator); - IAdminPropertyGenerator IsSelector(bool selector = true); - IAdminPropertyGenerator IsSelector(bool selector = true); - IAdminPropertyGenerator Parser(Func parser); - IAdminPropertyGenerator Parser(Func parser); - IAdminPropertyGenerator ParserForListType(Func parser); - IAdminPropertyGenerator ParserForListType(Func parser); - IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); - IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression); + IAdminPropertyGenerator DisplayName(string displayName); + IAdminPropertyGenerator Description(string description); + IAdminPropertyGenerator Prefix(string prefix); + IAdminPropertyGenerator Validator(Func validator); + IAdminPropertyGenerator IsSelector(bool selector = true); + IAdminPropertyGenerator IsSelector(bool selector = true); + IAdminPropertyGenerator Parser(Func parser); + IAdminPropertyGenerator Parser(Func parser); + IAdminPropertyGenerator Parser(Func parser); + IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); + IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index f61225b..2718e15 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -21,16 +21,16 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, var type = typeof(TModel); var properties = type.GetProperties(); - var generatorType = typeof(AdminPropertyGenerator<>); + var generatorType = typeof(AdminPropertyGenerator<,>); foreach (var property in properties) { var attributes = property.GetCustomAttributes(false); - var genericType = generatorType.MakeGenericType(property.PropertyType); + var genericType = generatorType.MakeGenericType(property.PropertyType, type); var generator = Activator.CreateInstance(genericType, [property.Name, property.PropertyType]); var method = genericType - .GetMethod(nameof(AdminPropertyGenerator.ApplyConfigurationFromAttributes))? + .GetMethod(nameof(AdminPropertyGenerator.ApplyConfigurationFromAttributes))? .MakeGenericMethod(type); method?.Invoke(generator, [this, attributes, property]); @@ -102,13 +102,13 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } - public IAdminPropertyGenerator Property(Expression> propertyExpression) { + public IAdminPropertyGenerator Property(Expression> propertyExpression) { var property = GetPropertyInfo(propertyExpression); if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) - return propertyGenerator as AdminPropertyGenerator; + return propertyGenerator as AdminPropertyGenerator; - var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property); _propertyGenerators.Add(property.Name, generator); @@ -125,7 +125,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, var properties = new List(); foreach (var generator in _propertyGenerators.Values) { - var method = generator.GetType().GetMethod(nameof(AdminPropertyGenerator.Compile)); + var method = generator.GetType().GetMethod(nameof(AdminPropertyGenerator.Compile)); var prop = method?.Invoke(generator, []) as AdminPageProperty; properties.Add(prop); } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 5bbe01f..73767a6 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -8,112 +8,107 @@ using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Generators.Implementation; -internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { +internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { private readonly AdminPageProperty _property = new() { Name = name, Type = type }; - public IAdminPropertyGenerator Sortable(bool sortable) { + public IAdminPropertyGenerator Sortable(bool sortable) { _property.Sortable = sortable; return this; } - public IAdminPropertyGenerator Editable(bool editable) { + public IAdminPropertyGenerator Editable(bool editable) { _property.Editable = editable; return this; } - public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { + public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { _property.EditDisplayValue = display; return this; } - public IAdminPropertyGenerator DisplayInListing(bool display = true) { + public IAdminPropertyGenerator DisplayInListing(bool display = true) { _property.DisplayInListing = display; _property.Sortable = false; return this; } - public IAdminPropertyGenerator Ignore(bool ignore = false) { + public IAdminPropertyGenerator Ignore(bool ignore = false) { _property.Ignore = ignore; return this; } - public IAdminPropertyGenerator Generated(bool generated = true) { + public IAdminPropertyGenerator Generated(bool generated = true) { _property.Generated = generated; return this; } - public IAdminPropertyGenerator Bold(bool bold = true) { + public IAdminPropertyGenerator Bold(bool bold = true) { _property.Bold = bold; return this; } - public IAdminPropertyGenerator Unique(bool unique = true) { + public IAdminPropertyGenerator Unique(bool unique = true) { _property.Unique = unique; return this; } - public IAdminPropertyGenerator DisplayName(string displayName) { + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; } - public IAdminPropertyGenerator Description(string description) { + public IAdminPropertyGenerator Description(string description) { _property.Description = description; return this; } - public IAdminPropertyGenerator Prefix(string prefix) { + public IAdminPropertyGenerator Prefix(string prefix) { _property.Prefix = prefix; return this; } - public IAdminPropertyGenerator Validator(Func validator) { + public IAdminPropertyGenerator Validator(Func validator) { _property.Validator = o => validator.Invoke((TProperty)o); return this; } - public IAdminPropertyGenerator IsSelector(bool selector = true) { + public IAdminPropertyGenerator IsSelector(bool selector = true) { _property.Selector = selector; return this; } - public IAdminPropertyGenerator IsSelector(bool selector = true) { + public IAdminPropertyGenerator IsSelector(bool selector = true) { _property.Selector = true; _property.SelectorType = typeof(TSelectorType); return this; } - public IAdminPropertyGenerator Parser(Func parser) { + public IAdminPropertyGenerator Parser(Func parser) { _property.Parser = (o, s) => parser.Invoke((TModel)o, s.ToString()); return this; } - public IAdminPropertyGenerator Parser(Func parser) { + public IAdminPropertyGenerator Parser(Func parser) { _property.Parser = (o, s) => parser.Invoke((TModel)o, (TInput)s); return this; } - public IAdminPropertyGenerator ParserForListType(Func parser) { - _property.Parser = (o, s) => parser.Invoke((TModel)o, s.ToString()); - return this; - } - - public IAdminPropertyGenerator ParserForListType(Func parser) { + public IAdminPropertyGenerator Parser(Func parser) { _property.Parser = (o, s) => parser.Invoke((TModel)o, (TInput)s); return this; } - public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { + public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); _property.DisplayPropertyName = property.Name; return this; } - public IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression) { + public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); _property.DisplayPropertyName = property.Name; return this; diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 4e45dfe..e0ab493 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -40,8 +40,8 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(u => u.Permissions) .DisplayInListing(false) - .DisplayPropertyForListType(p => p.PermissionName) - .ParserForListType((user, perm) => new Permission { + .DisplayProperty(p => p.PermissionName) + .Parser((user, perm) => new Permission { GrantedAt = DateTime.Now, PermissionName = perm, User = user @@ -78,8 +78,8 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(g => g.Permissions) .DisplayInListing(false) - .DisplayPropertyForListType(p => p.PermissionName) - .ParserForListType((group, perm) => new Permission { + .DisplayProperty(p => p.PermissionName) + .Parser((group, perm) => new Permission { GrantedAt = DateTime.Now, PermissionName = perm, Group = group diff --git a/test/FrontendTest/AdminContext.cs b/test/FrontendTest/AdminContext.cs index 6be48c9..3c9af86 100644 --- a/test/FrontendTest/AdminContext.cs +++ b/test/FrontendTest/AdminContext.cs @@ -25,7 +25,7 @@ public class AdminContext : AdminPagesContext { generator.Page
() .Property(a => a.AddressId) .IsSelector() - .Parser((model, e) => model.AddressId = e.EmployeeId); + .Parser((model, e) => model.AddressId = e.EmployeeId); generator.Page() .ConfigureRepository() From bc7dfa8e6a3264c1203982f5813ee727b545a4c7 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 9 Nov 2024 11:17:50 +0100 Subject: [PATCH 15/16] Removed unnecessary files + added proper documentation to admin page generators --- .../Generators/IAdminContextGenerator.cs | 5 + .../Generators/IAdminPageGenerator.cs | 81 +++++ .../Generators/IAdminPropertyGenerator.cs | 97 +++++- .../Generators/IGenerator.cs | 4 + .../Implementation/AdminPropertyGenerator.cs | 10 - src/HopFrame.Web.Admin/Models/AdminPage.cs | 3 +- .../Models/AdminPageProperty.cs | 2 - .../Administration/GroupAddModal.razor | 292 ----------------- .../Administration/UserAddModal.razor | 136 -------- .../Administration/UserEditModal.razor | 306 ------------------ src/HopFrame.Web/Model/NavigationItem.cs | 8 - src/HopFrame.Web/Model/PermissionGroupAdd.cs | 7 - src/HopFrame.Web/Model/RegisterData.cs | 11 - src/HopFrame.Web/Model/UserAdd.cs | 5 - .../Pages/Administration/GroupsPage.razor | 192 ----------- .../Pages/Administration/GroupsPage.razor.css | 26 -- .../Pages/Administration/UsersPage.razor | 222 ------------- .../Pages/Administration/UsersPage.razor.css | 26 -- 18 files changed, 187 insertions(+), 1246 deletions(-) delete mode 100644 src/HopFrame.Web/Components/Administration/GroupAddModal.razor delete mode 100644 src/HopFrame.Web/Components/Administration/UserAddModal.razor delete mode 100644 src/HopFrame.Web/Components/Administration/UserEditModal.razor delete mode 100644 src/HopFrame.Web/Model/NavigationItem.cs delete mode 100644 src/HopFrame.Web/Model/PermissionGroupAdd.cs delete mode 100644 src/HopFrame.Web/Model/RegisterData.cs delete mode 100644 src/HopFrame.Web/Model/UserAdd.cs delete mode 100644 src/HopFrame.Web/Pages/Administration/GroupsPage.razor delete mode 100644 src/HopFrame.Web/Pages/Administration/GroupsPage.razor.css delete mode 100644 src/HopFrame.Web/Pages/Administration/UsersPage.razor delete mode 100644 src/HopFrame.Web/Pages/Administration/UsersPage.razor.css diff --git a/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs index 49cf115..970d82c 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminContextGenerator.cs @@ -2,6 +2,11 @@ namespace HopFrame.Web.Admin.Generators; public interface IAdminContextGenerator { + /// + /// Returns the generator object for the specified Admin Page. This needs to be within the same Admin Context. + /// + /// The Model of the Admin Page + /// IAdminPageGenerator Page(); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index 12591bb..2a71a75 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -5,24 +5,105 @@ namespace HopFrame.Web.Admin.Generators; public interface IAdminPageGenerator { + /// + /// Sets the title of the Admin Page + /// + /// the specified title + /// IAdminPageGenerator Title(string title); + + /// + /// Sets the description of the Admin Page + /// + /// the specified description + /// IAdminPageGenerator Description(string description); + + /// + /// Sets the url for the Admin Page + /// + /// the specified url (administration/{url}) + /// IAdminPageGenerator Url(string url); + /// + /// Sets the permission needed to view the Admin Page + /// + /// the specified permission + /// IAdminPageGenerator ViewPermission(string permission); + + /// + /// Sets the permission needed to create a new Entry + /// + /// the specified permission + /// IAdminPageGenerator CreatePermission(string permission); + + /// + /// Sets the permission needed to update an Entry + /// + /// the specified permission + /// IAdminPageGenerator UpdatePermission(string permission); + + /// + /// Sets the permission needed to delete an Entry + /// + /// the specified permission + /// IAdminPageGenerator DeletePermission(string permission); + + /// + /// Enables or disables the create button + /// + /// the specified state + /// IAdminPageGenerator ShowCreateButton(bool show); + + /// + /// Enables or disables the delete button + /// + /// the specified state + /// IAdminPageGenerator ShowDeleteButton(bool show); + + /// + /// Enables or disables the update button + /// + /// the specified state + /// IAdminPageGenerator ShowUpdateButton(bool show); + /// + /// Specifies the default sort property and direction + /// + /// Which property should be sorted + /// In which direction should be sorted + /// IAdminPageGenerator DefaultSort(Expression> propertyExpression, ListSortDirection direction); + /// + /// Specifies the repository for the page + /// + /// The specified repository + /// IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; + + /// + /// Returns the generator of the specified property + /// + /// The property + /// IAdminPropertyGenerator Property(Expression> propertyExpression); + + /// + /// Specifies the default property that should be displayed as a property in other listings + /// + /// The property + /// IAdminPageGenerator ListingProperty(Expression> propertyExpression); } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 7f138bb..4fbab5b 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -4,25 +4,120 @@ namespace HopFrame.Web.Admin.Generators; public interface IAdminPropertyGenerator { + /// + /// Should the property be sortable or not + /// + /// IAdminPropertyGenerator Sortable(bool sortable); + + /// + /// Should the admin be able to edit the property after creation or not + /// + /// IAdminPropertyGenerator Editable(bool editable); + + /// + /// Should the value of the property be displayed while editing or not (useful for passwords and tokens) + /// + /// IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + + /// + /// Should the property be a column on the page list or not + /// + /// IAdminPropertyGenerator DisplayInListing(bool display = true); + + /// + /// Should the property be ignored completely + /// + /// IAdminPropertyGenerator Ignore(bool ignore = true); + + /// + /// Is the value of the property database generated and is not meant to be changed + /// + /// IAdminPropertyGenerator Generated(bool generated = true); + + /// + /// Should the property value be bold in the listing or not + /// + /// IAdminPropertyGenerator Bold(bool bold = true); + + /// + /// Is the value of the property unique under all other entries in the dataset + /// + /// + /// IAdminPropertyGenerator Unique(bool unique = true); + /// + /// Specifies the display name in the listing and editing/creation + /// + /// IAdminPropertyGenerator DisplayName(string displayName); - IAdminPropertyGenerator Description(string description); + + /// + /// Has the value of the property a never changing prefix that doesn't need to be specified or displayed + /// + /// IAdminPropertyGenerator Prefix(string prefix); + + /// + /// The specified function gets called before creation/edit to verify that the entered value matches the property requirements + /// + /// IAdminPropertyGenerator Validator(Func validator); + + /// + /// Sets the input type in creation/edit to a selector for the property type. The property type needs to have its own admin page in order for the selector to work! + /// + /// IAdminPropertyGenerator IsSelector(bool selector = true); + + /// + /// Sets the input type in creation/edit to a selector for the specified type. The specified type needs to have its own admin page in order for the selector to work! + /// + /// + /// + /// IAdminPropertyGenerator IsSelector(bool selector = true); + + /// + /// The specified function gets called, whenever the entry is changed/created in order to convert the raw string input to the proper property type + /// + /// IAdminPropertyGenerator Parser(Func parser); + + /// + /// The specified function gets called, whenever the entry is changed/created in order to convert the raw string input to the proper property type + /// + /// Needs to be specified if the field is not a plain string field (like a selector with a different type) + /// IAdminPropertyGenerator Parser(Func parser); + + /// + /// The specified function gets called, whenever the entry is changed/created in order to convert the raw string input to the proper property type + /// + /// Needs to be specified if the field is not a plain string field (like a selector with a different type) + /// Needs to be specified if the property type is a List + /// IAdminPropertyGenerator Parser(Func parser); + + /// + /// Specifies the default property that should be displayed as a value + /// + /// + /// IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); + + /// + /// Specifies the default property that should be displayed as a value + /// + /// Needs to be specified if the property type is a List + /// IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IGenerator.cs b/src/HopFrame.Web.Admin/Generators/IGenerator.cs index 5cf9d6f..68f5013 100644 --- a/src/HopFrame.Web.Admin/Generators/IGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IGenerator.cs @@ -2,6 +2,10 @@ namespace HopFrame.Web.Admin.Generators; public interface IGenerator { + /// + /// Compiles the generator with all specified options + /// + /// The compiled data structure TGeneratedType Compile(); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 73767a6..d6a5792 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -61,11 +61,6 @@ internal sealed class AdminPropertyGenerator(string name, Typ return this; } - public IAdminPropertyGenerator Description(string description) { - _property.Description = description; - return this; - } - public IAdminPropertyGenerator Prefix(string prefix) { _property.Prefix = prefix; return this; @@ -152,11 +147,6 @@ internal sealed class AdminPropertyGenerator(string name, Typ var attribute = attributes.Single(a => a is AdminNameAttribute) as AdminNameAttribute; DisplayName(attribute?.Name); } - - if (attributes.Any(a => a is AdminDescriptionAttribute)) { - var attribute = attributes.Single(a => a is AdminDescriptionAttribute) as AdminDescriptionAttribute; - Description(attribute?.Description); - } if (attributes.Any(a => a is AdminBoldAttribute)) { var attribute = attributes.Single(a => a is AdminBoldAttribute) as AdminBoldAttribute; diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index eedd4a5..748eb00 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -13,9 +13,8 @@ public class AdminPage { public IList Properties { get; set; } public string ListingProperty { get; set; } - [JsonIgnore] public Type RepositoryProvider { get; set; } - + public Type ModelType { get; set; } public string DefaultSortPropertyName { get; set; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index a86cf47..8347e36 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -5,7 +5,6 @@ namespace HopFrame.Web.Admin.Models; public sealed class AdminPageProperty { public string Name { get; set; } public string DisplayName { get; set; } - public string Description { get; set; } public string Prefix { get; set; } public string DisplayPropertyName { get; set; } @@ -21,7 +20,6 @@ public sealed class AdminPageProperty { public bool Selector { get; set; } public Type SelectorType { get; set; } - [JsonIgnore] public Type Type { get; set; } public Func Validator { get; set; } diff --git a/src/HopFrame.Web/Components/Administration/GroupAddModal.razor b/src/HopFrame.Web/Components/Administration/GroupAddModal.razor deleted file mode 100644 index 8f432e7..0000000 --- a/src/HopFrame.Web/Components/Administration/GroupAddModal.razor +++ /dev/null @@ -1,292 +0,0 @@ -@rendermode InteractiveServer - -@using BlazorStrap -@using BlazorStrap.Shared.Components.Modal -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using BlazorStrap.V5 -@using CurrieTechnologies.Razor.SweetAlert2 -@using HopFrame.Database.Models -@using HopFrame.Database.Repositories -@using HopFrame.Security.Claims -@using HopFrame.Web.Model - - - - @if (_isEdit) { - Edit group - } - else { - Add group - } - -
- Name - @if (!_isEdit) { - - group. - - - } - else { - - } -
- - @if (_isEdit) { -
- Created at - -
- } - -
- Description - -
- -
- - Default group - -
- -
- Inherits from - - - - @foreach (var group in _group.Permissions.Where(g => g.PermissionName.StartsWith("group."))) { - - - - - - @group.PermissionName.Replace("group.", "") - - } - - - -
- - - - @foreach (var group in _allGroups) { - @if (_group.Permissions.All(g => g.PermissionName != group.Name) && group.Name != _group.Name) { - - } - } - - Add -
-
-
-
- -
- Permissions - - - - @foreach (var perm in _group.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) { - - - - - - @perm.PermissionName - - } - - - -
- - Add -
-
-
-
-
- - Cancel - Save - -
-
- -@inject IGroupRepository Groups -@inject IPermissionRepository Permissions -@inject SweetAlertService Alerts -@inject ITokenContext Context - -@code { - [Parameter] public Func ReloadPage { get; set; } - - private PermissionGroupAdd _group; - - private BSModalBase _modal; - private string _permissionToAdd; - private string _groupToAdd; - - private IList _allGroups; - - private bool _isEdit; - - public async Task ShowAsync(PermissionGroup group = null) { - _allGroups = await Groups.GetPermissionGroups(); - - if (group is not null) { - _group = new PermissionGroupAdd { - CreatedAt = group.CreatedAt, - Description = group.Description, - Name = group.Name, - IsDefaultGroup = group.IsDefaultGroup, - Permissions = group.Permissions - }; - _isEdit = true; - } - else { - _group = new PermissionGroupAdd { - Permissions = new List(), - IsDefaultGroup = false - }; - _isEdit = false; - } - - await _modal.ShowAsync(); - } - - private async Task AddPermission() { - if (string.IsNullOrWhiteSpace(_permissionToAdd)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Enter a permission name!", - Icon = SweetAlertIcon.Error, - ShowConfirmButton = true - }); - return; - } - - if (_isEdit) { - if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) { - await NoEditPermissions(); - return; - } - - await Permissions.AddPermission(_group, _permissionToAdd); - } - - _group.Permissions.Add(new Permission { - PermissionName = _permissionToAdd, - GrantedAt = DateTime.Now - }); - - _permissionToAdd = null; - } - - private async Task RemovePermission(Permission permission) { - if (_isEdit) { - await Permissions.RemovePermission(_group, permission.PermissionName); - } - - _group.Permissions.Remove(permission); - } - - private async Task AddInheritanceGroup() { - if (string.IsNullOrWhiteSpace(_groupToAdd)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Select a group!", - Icon = SweetAlertIcon.Error, - ShowConfirmButton = true - }); - return; - } - - if (_isEdit) { - if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) { - await NoEditPermissions(); - return; - } - - await Permissions.AddPermission(_group, _groupToAdd); - } - - _group.Permissions.Add(new Permission { - PermissionName = _groupToAdd - }); - - _groupToAdd = null; - } - - private async Task AddGroup() { - if (_isEdit) { - if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) { - await NoEditPermissions(); - return; - } - - await Groups.EditPermissionGroup(_group); - - if (ReloadPage is not null) - await ReloadPage.Invoke(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Group edited!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - - return; - } - - if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.AddGroup)) { - await NoAddPermissions(); - return; - } - - if (_allGroups.Any(group => group.Name == _group.Name)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Something went wrong!", - Text = "This group already exists!", - Icon = SweetAlertIcon.Error, - ShowConfirmButton = false, - Timer = 1500 - }); - return; - } - - await Groups.CreatePermissionGroup(new PermissionGroup { - Description = _group.Description, - IsDefaultGroup = _group.IsDefaultGroup, - Permissions = _group.Permissions, - Name = "group." + _group.GroupName - }); - - if (ReloadPage is not null) - await ReloadPage.Invoke(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Group added!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - - private async Task NoEditPermissions() { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Unauthorized!", - Text = "You don't have the required permissions to edit a group!", - Icon = SweetAlertIcon.Error - }); - } - - private async Task NoAddPermissions() { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Unauthorized!", - Text = "You don't have the required permissions to add a group!", - Icon = SweetAlertIcon.Error - }); - } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Administration/UserAddModal.razor b/src/HopFrame.Web/Components/Administration/UserAddModal.razor deleted file mode 100644 index 44b47b3..0000000 --- a/src/HopFrame.Web/Components/Administration/UserAddModal.razor +++ /dev/null @@ -1,136 +0,0 @@ -@rendermode InteractiveServer - -@using BlazorStrap -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using BlazorStrap.Shared.Components.Modal -@using BlazorStrap.V5 -@using CurrieTechnologies.Razor.SweetAlert2 -@using HopFrame.Database.Models -@using HopFrame.Database.Repositories -@using HopFrame.Security.Claims -@using HopFrame.Web.Model - - - - Add user - -
- E-Mail - -
- -
- Username - -
- -
- Password - -
- -
- Primary group - - - - @foreach (var group in _allGroups) { - - } - -
-
- - Cancel - Save - -
-
- -@inject IUserRepository Users -@inject IPermissionRepository Permissions -@inject IGroupRepository Groups -@inject SweetAlertService Alerts -@inject ITokenContext Auth - -@code { - [Parameter] public Func ReloadPage { get; set; } - - private IList _allGroups = new List(); - private IList _allUsers = new List(); - private UserAdd _user; - - private BSModalBase _modal; - - public async Task ShowAsync() { - _allGroups = await Groups.GetPermissionGroups(); - _allUsers = await Users.GetUsers(); - - await _modal.ShowAsync(); - } - - private async Task AddUser() { - if (!(await Permissions.HasPermission(Auth.User, Security.AdminPermissions.AddUser))) { - await NoAddPermissions(); - return; - } - - string errorMessage = null; - - if (_allUsers.Any(user => user.Username == _user.Username)) { - errorMessage = "Username is already taken!"; - } - else if (_allUsers.Any(user => user.Email == _user.Email)) { - errorMessage = "E-Mail is already taken!"; - } - else if (!_user.PasswordIsValid) { - errorMessage = "The password needs to be at least 8 characters long!"; - } - else if (!_user.EmailIsValid) { - errorMessage = "Invalid E-Mail address!"; - } - else if (string.IsNullOrWhiteSpace(_user.Username)) { - errorMessage = "You need to set a username!"; - } - - if (!string.IsNullOrWhiteSpace(errorMessage)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Something went wrong!", - Text = errorMessage, - Icon = SweetAlertIcon.Error, - ShowConfirmButton = false, - Timer = 1500 - }); - - return; - } - - var user = await Users.AddUser(new User { - Username = _user.Username, - Email = _user.Email, - Password = _user.Password - }); - - if (!string.IsNullOrWhiteSpace(_user.Group)) { - await Permissions.AddPermission(user, _user.Group); - } - - await ReloadPage.Invoke(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "New user added!", - Icon = SweetAlertIcon.Success, - ShowConfirmButton = false, - Timer = 1500 - - }); - } - - private async Task NoAddPermissions() { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Unauthorized!", - Text = "You don't have the required permissions to add a user!", - Icon = SweetAlertIcon.Error - }); - } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Administration/UserEditModal.razor b/src/HopFrame.Web/Components/Administration/UserEditModal.razor deleted file mode 100644 index f4bbe36..0000000 --- a/src/HopFrame.Web/Components/Administration/UserEditModal.razor +++ /dev/null @@ -1,306 +0,0 @@ -@rendermode InteractiveServer - -@using BlazorStrap -@using BlazorStrap.Shared.Components.Modal -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using BlazorStrap.V5 -@using CurrieTechnologies.Razor.SweetAlert2 -@using HopFrame.Database.Models -@using HopFrame.Database.Repositories -@using HopFrame.Security.Claims -@using HopFrame.Web.Model - - - - Edit @_user.Username - -
- User id - -
-
- Created at - -
-
- E-Mail - -
-
- Username - -
-
- Password - -
- -
- Groups - - - - @foreach (var group in _userGroups) { - - - - - - @group.Name.Replace("group.", "") - - } - - - -
- - - - @foreach (var group in _allGroups) { - @if (_userGroups?.All(g => g.Name != group.Name) == true) { - - } - } - - Add -
-
-
-
- -
- Permissions - - - - @foreach (var perm in _user.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) { - - - - - - @perm.PermissionName - - } - - - -
- - Add -
-
-
-
-
- - Cancel - Save - -
-
- -@inject IUserRepository Users -@inject IPermissionRepository Permissions -@inject IGroupRepository Groups -@inject SweetAlertService Alerts -@inject ITokenContext Auth - -@code { - [Parameter] public Func ReloadPage { get; set; } - - private BSModalBase _modal; - private User _user; - private string _newPassword; - - private IList _userGroups; - private IList _allGroups; - private string _selectedGroup; - private string _permissionToAdd; - - public async Task ShowAsync(User user) { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - _user = user; - _userGroups = await Groups.GetUserGroups(user); - _allGroups = await Groups.GetPermissionGroups(); - await _modal.ShowAsync(); - } - - private async Task AddGroup() { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - if (string.IsNullOrWhiteSpace(_selectedGroup)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Select a group!", - Icon = SweetAlertIcon.Error, - ShowConfirmButton = true - }); - return; - } - - var group = _allGroups.SingleOrDefault(group => group.Name == _selectedGroup); - - await Permissions.AddPermission(_user, group?.Name); - _userGroups.Add(group); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Group added!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - - private async Task RemoveGroup(PermissionGroup group) { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Are you sure?", - Icon = SweetAlertIcon.Warning, - ConfirmButtonText = "Yes", - ShowCancelButton = true, - ShowConfirmButton = true - }); - - if (result.IsConfirmed) { - await Permissions.RemovePermission(_user, group.Name); - _userGroups.Remove(group); - StateHasChanged(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Group removed!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - } - - private async Task AddPermission() { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - if (string.IsNullOrWhiteSpace(_permissionToAdd)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Enter a permission name!", - Icon = SweetAlertIcon.Error, - ShowConfirmButton = true - }); - return; - } - - _user.Permissions.Add(await Permissions.AddPermission(_user, _permissionToAdd)); - _permissionToAdd = ""; - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Permission added!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - - private async Task RemovePermission(Permission perm) { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Are you sure?", - Icon = SweetAlertIcon.Warning, - ConfirmButtonText = "Yes", - ShowCancelButton = true, - ShowConfirmButton = true - }); - - if (result.IsConfirmed) { - await Permissions.RemovePermission(perm.User, perm.PermissionName); - _user.Permissions.Remove(perm); - StateHasChanged(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Permission removed!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - } - - private async void EditUser() { - if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) { - await NoEditPermissions(); - return; - } - - string errorMessage = null; - var validator = new RegisterData { - Password = _newPassword, - Email = _user.Email - }; - - var allUsers = await Users.GetUsers(); - - if (allUsers.Any(user => user.Username == _user.Username && user.Id != _user.Id)) { - errorMessage = "Username is already taken!"; - } - else if (allUsers.Any(user => user.Email == _user.Email && user.Id != _user.Id)) { - errorMessage = "E-Mail is already taken!"; - } - else if (!string.IsNullOrWhiteSpace(_newPassword) && !validator.PasswordIsValid) { - errorMessage = "The password needs to be at least 8 characters long!"; - } - else if (!validator.EmailIsValid) { - errorMessage = "Invalid E-Mail address!"; - } - - if (!string.IsNullOrWhiteSpace(errorMessage)) { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Something went wrong!", - Text = errorMessage, - Icon = SweetAlertIcon.Error, - ShowConfirmButton = false, - Timer = 1500 - }); - - return; - } - - await Users.UpdateUser(_user); - - if (!string.IsNullOrWhiteSpace(_newPassword)) { - await Users.ChangePassword(_user, _newPassword); - } - - if (ReloadPage is not null) - await ReloadPage.Invoke(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "User edited!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - - private async Task NoEditPermissions() { - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Unauthorized!", - Text = "You don't have the required permissions to edit a user!", - Icon = SweetAlertIcon.Error - }); - } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Model/NavigationItem.cs b/src/HopFrame.Web/Model/NavigationItem.cs deleted file mode 100644 index 6e255a0..0000000 --- a/src/HopFrame.Web/Model/NavigationItem.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HopFrame.Web.Model; - -public sealed class NavigationItem { - public string Name { get; set; } - public string Url { get; set; } - public string Permission { get; set; } - public string Description { get; set; } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Model/PermissionGroupAdd.cs b/src/HopFrame.Web/Model/PermissionGroupAdd.cs deleted file mode 100644 index 0cdc9d2..0000000 --- a/src/HopFrame.Web/Model/PermissionGroupAdd.cs +++ /dev/null @@ -1,7 +0,0 @@ -using HopFrame.Database.Models; - -namespace HopFrame.Web.Model; - -internal sealed class PermissionGroupAdd : PermissionGroup { - public string GroupName { get; set; } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Model/RegisterData.cs b/src/HopFrame.Web/Model/RegisterData.cs deleted file mode 100644 index 6d92531..0000000 --- a/src/HopFrame.Web/Model/RegisterData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using HopFrame.Security.Models; - -namespace HopFrame.Web.Model; - -internal class RegisterData : UserRegister { - public string RepeatedPassword { get; set; } - - public bool PasswordsMatch => Password == RepeatedPassword; - public bool PasswordIsValid => Password?.Length >= 8; - public bool EmailIsValid => Email?.Contains('@') == true && Email?.Contains('.') == true && Email?.EndsWith('.') == false; -} \ No newline at end of file diff --git a/src/HopFrame.Web/Model/UserAdd.cs b/src/HopFrame.Web/Model/UserAdd.cs deleted file mode 100644 index e138395..0000000 --- a/src/HopFrame.Web/Model/UserAdd.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace HopFrame.Web.Model; - -internal sealed class UserAdd : RegisterData { - public string Group { get; set; } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor deleted file mode 100644 index 42269da..0000000 --- a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor +++ /dev/null @@ -1,192 +0,0 @@ -@page "/administration/group" -@rendermode InteractiveServer -@layout AdminLayout - -@using System.Globalization -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using BlazorStrap -@using Microsoft.AspNetCore.Components.Web -@using HopFrame.Web.Components -@using HopFrame.Web.Components.Administration -@using BlazorStrap.V5 -@using CurrieTechnologies.Razor.SweetAlert2 -@using HopFrame.Database.Models -@using HopFrame.Database.Repositories -@using HopFrame.Security.Claims -@using HopFrame.Web.Pages.Administration.Layout - -Groups - - - - -
-

- Groups administration - - - -

- - - - Add Group - -
- - - - - - Name - @if (_currentOrder == OrderType.Name) { - - } - - Description - Default - - Created - @if (_currentOrder == OrderType.Created) { - - } - - - @if (_hasEditPrivileges || _hasDeletePrivileges) { - Actions - } - - - - - @foreach (var group in _groups) { - - @group.Name.Replace("group.", "") - @group.Description - - @if (group.IsDefaultGroup) { - Yes - } - else { - No - } - - @group.CreatedAt - - @if (_hasEditPrivileges || _hasDeletePrivileges) { - - - @if (_hasEditPrivileges) { - Edit - } - - @if (_hasDeletePrivileges) { - Delete - } - - - } - - } - - - -@inject IGroupRepository Groups -@inject IPermissionRepository Permissions -@inject ITokenContext Auth -@inject SweetAlertService Alerts - -@code { - private IList _groups = new List(); - - private bool _hasEditPrivileges = false; - private bool _hasDeletePrivileges = false; - private string _searchText; - private OrderType _currentOrder = OrderType.None; - private OrderDirection _currentOrderDirection = OrderDirection.Asc; - - private GroupAddModal _groupAddModal; - - protected override async Task OnInitializedAsync() { - _groups = await Groups.GetPermissionGroups(); - - _hasEditPrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditGroup); - _hasDeletePrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.DeleteGroup); - } - - private async Task Reload() { - _groups = new List(); - - _groups = await Groups.GetPermissionGroups(); - - OrderBy(_currentOrder, false); - StateHasChanged(); - } - - private async Task Search() { - var groups = await Groups.GetPermissionGroups(); - - if (!string.IsNullOrWhiteSpace(_searchText)) { - groups = groups - .Where(group => group.Name.Contains(_searchText) || - group.Description?.Contains(_searchText) == true || - group.CreatedAt.ToString(CultureInfo.InvariantCulture).Contains(_searchText) || - group.Permissions.Any(perm => perm.PermissionName.Contains(_searchText))) - .ToList(); - } - - _groups = groups; - OrderBy(_currentOrder, false); - } - - private void OrderBy(OrderType type, bool changeDir = true) { - if (_currentOrder == type && changeDir) _currentOrderDirection = (OrderDirection)(((byte)_currentOrderDirection + 1) % 2); - if (_currentOrder != type) _currentOrderDirection = OrderDirection.Asc; - - if (type == OrderType.Name) { - _groups = _currentOrderDirection == OrderDirection.Asc ? _groups.OrderBy(group => group.Name).ToList() : _groups.OrderByDescending(group => group.Name).ToList(); - } - else if (type == OrderType.Created) { - _groups = _currentOrderDirection == OrderDirection.Asc ? _groups.OrderBy(group => group.CreatedAt).ToList() : _groups.OrderByDescending(group => group.CreatedAt).ToList(); - } - - _currentOrder = type; - } - - private async Task Delete(PermissionGroup group) { - var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Are you sure?", - Text = "You won't be able to revert this!", - Icon = SweetAlertIcon.Warning, - ConfirmButtonText = "Yes", - ShowCancelButton = true, - ShowConfirmButton = true - }); - - if (result.IsConfirmed) { - await Groups.DeletePermissionGroup(group); - await Reload(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Deleted!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - } - - private enum OrderType { - None, - Name, - Created - } - - private enum OrderDirection : byte { - Asc = 0, - Desc = 1 - } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor.css b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor.css deleted file mode 100644 index 445d132..0000000 --- a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor.css +++ /dev/null @@ -1,26 +0,0 @@ -.title { - display: flex; - flex-direction: row; - gap: 10px; - margin-bottom: 10px; -} - -#search { - margin-left: auto; -} - -th, h3 { - user-select: none; -} - -h3 { - color: white; -} - -.reload, .sorter { - cursor: pointer; -} - -.bold { - font-weight: bold; -} diff --git a/src/HopFrame.Web/Pages/Administration/UsersPage.razor b/src/HopFrame.Web/Pages/Administration/UsersPage.razor deleted file mode 100644 index 767dc98..0000000 --- a/src/HopFrame.Web/Pages/Administration/UsersPage.razor +++ /dev/null @@ -1,222 +0,0 @@ -@page "/administration/user" -@rendermode InteractiveServer -@layout AdminLayout - -@using System.Globalization -@using BlazorStrap -@using CurrieTechnologies.Razor.SweetAlert2 -@using HopFrame.Database.Models -@using HopFrame.Security.Claims -@using HopFrame.Web.Pages.Administration.Layout -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using Microsoft.AspNetCore.Components.Web -@using HopFrame.Web.Components -@using BlazorStrap.V5 -@using HopFrame.Database.Repositories -@using HopFrame.Web.Components.Administration - -Users - - - - - -
-

- Users administration - - - -

- - - - Add User - -
- - - - - # - - E-Mail - @if (_currentOrder == OrderType.Email) { - - } - - - Username - @if (_currentOrder == OrderType.Username) { - - } - - - Registered - @if (_currentOrder == OrderType.Registered) { - - } - - Primary Group - - @if (_hasEditPrivileges || _hasDeletePrivileges) { - Actions - } - - - - - @foreach (var user in _users) { - - @user.Id - @user.Email - @user.Username - @user.CreatedAt - @GetFriendlyGroupName(user) - - @if (_hasEditPrivileges || _hasDeletePrivileges) { - - - @if (_hasEditPrivileges) { - Edit - } - - @if (_hasDeletePrivileges) { - Delete - } - - - } - - } - - - -@inject IUserRepository UserService -@inject IPermissionRepository PermissionsService -@inject IGroupRepository Groups -@inject SweetAlertService Alerts -@inject ITokenContext Auth - -@code { - private IList _users = new List(); - private IDictionary _userGroups = new Dictionary(); - - private OrderType _currentOrder = OrderType.None; - private OrderDirection _currentOrderDirection = OrderDirection.Asc; - - private string _searchText; - - private bool _hasEditPrivileges = false; - private bool _hasDeletePrivileges = false; - - private UserAddModal _userAddModal; - private UserEditModal _userEditModal; - - protected override async Task OnInitializedAsync() { - _users = await UserService.GetUsers(); - - foreach (var user in _users) { - var groups = await Groups.GetUserGroups(user); - _userGroups.Add(user.Id, groups.LastOrDefault()); - } - - _hasEditPrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.EditUser); - _hasDeletePrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.DeleteUser); - } - - private async Task Reload() { - _users = new List(); - _userGroups = new Dictionary(); - - _users = await UserService.GetUsers(); - - foreach (var user in _users) { - var groups = await Groups.GetUserGroups(user); - _userGroups.Add(user.Id, groups.LastOrDefault()); - } - - OrderBy(_currentOrder, false); - StateHasChanged(); - } - - private async Task Search() { - var users = await UserService.GetUsers(); - - if (!string.IsNullOrWhiteSpace(_searchText)) { - users = users - .Where(user => - user.Email.Contains(_searchText) || - user.Username.Contains(_searchText) || - user.Id.ToString().Contains(_searchText) || - user.CreatedAt.ToString(CultureInfo.InvariantCulture).Contains(_searchText) || - _userGroups[user.Id]?.Name.Contains(_searchText) == true) - .ToList(); - } - - _users = users; - OrderBy(_currentOrder, false); - } - - private string GetFriendlyGroupName(User user) { - var group = _userGroups[user.Id]; - if (group is null) return null; - - return group.Name.Replace("group.", ""); - } - - private void OrderBy(OrderType type, bool changeDir = true) { - if (_currentOrder == type && changeDir) _currentOrderDirection = (OrderDirection)(((byte)_currentOrderDirection + 1) % 2); - if (_currentOrder != type) _currentOrderDirection = OrderDirection.Asc; - - if (type == OrderType.Email) { - _users = _currentOrderDirection == OrderDirection.Asc ? _users.OrderBy(user => user.Email).ToList() : _users.OrderByDescending(user => user.Email).ToList(); - } - else if (type == OrderType.Username) { - _users = _currentOrderDirection == OrderDirection.Asc ? _users.OrderBy(user => user.Username).ToList() : _users.OrderByDescending(user => user.Username).ToList(); - } - else if (type == OrderType.Registered) { - _users = _currentOrderDirection == OrderDirection.Asc ? _users.OrderBy(user => user.CreatedAt).ToList() : _users.OrderByDescending(user => user.CreatedAt).ToList(); - } - - _currentOrder = type; - } - - private async Task Delete(User user) { - var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Are you sure?", - Text = "You won't be able to revert this!", - Icon = SweetAlertIcon.Warning, - ConfirmButtonText = "Yes", - ShowCancelButton = true, - ShowConfirmButton = true - }); - - if (result.IsConfirmed) { - await UserService.DeleteUser(user); - await Reload(); - - await Alerts.FireAsync(new SweetAlertOptions { - Title = "Deleted!", - Icon = SweetAlertIcon.Success, - Timer = 1500, - ShowConfirmButton = false - }); - } - } - - private enum OrderType { - None, - Email, - Username, - Registered - } - - private enum OrderDirection : byte { - Asc = 0, - Desc = 1 - } -} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/UsersPage.razor.css b/src/HopFrame.Web/Pages/Administration/UsersPage.razor.css deleted file mode 100644 index 445d132..0000000 --- a/src/HopFrame.Web/Pages/Administration/UsersPage.razor.css +++ /dev/null @@ -1,26 +0,0 @@ -.title { - display: flex; - flex-direction: row; - gap: 10px; - margin-bottom: 10px; -} - -#search { - margin-left: auto; -} - -th, h3 { - user-select: none; -} - -h3 { - color: white; -} - -.reload, .sorter { - cursor: pointer; -} - -.bold { - font-weight: bold; -} From 89a3185c8bcfe1a9e79acd4802cc792f415633d2 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 9 Nov 2024 11:18:28 +0100 Subject: [PATCH 16/16] cleanup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f1e8a4..1366239 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A simple backend management api for ASP.NET Core Web APIs - [x] 1.0 bug fixes - [x] Code cleanup - [x] Relations in database -- [ ] Generated Admin pages +- [x] Generated Admin pages - [ ] Pretty Login page for administration - [ ] Clean documentation