+
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Date))" />
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Time))" />
}
@@ -77,7 +77,7 @@
Style="width: 100%"
Disabled="@(!property.Editable)"
Required="@property.IsRequired"
- ValueChanged="@(v => SetPropertyValue(property, v, InputType.Date))" />
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Date))" />
}
else if (property.Info.PropertyType == typeof(TimeOnly)) {
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Time))" />
}
else if (property.Info.PropertyType.IsEnum) {
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Enum))" />
+ }
+ else if (property.Info.PropertyType.IsNullableEnum()) {
+
}
else {
+ ValueChanged="@(async v => await SetPropertyValue(property, v, InputType.Text))" />
}
@foreach (var error in _validationErrors[property.Info.Name]) {
@@ -119,6 +140,7 @@
@inject IContextExplorer Explorer
@inject IDialogService Dialogs
+@inject IHopFrameAuthHandler Handler
@code {
[Parameter]
@@ -141,6 +163,7 @@
Content.CurrentObject ??= Activator.CreateInstance(Content.Config.TableType);
foreach (var property in Content.Config.Properties) {
+ if (property.IsListingProperty) continue;
_validationErrors.Add(property.Info.Name, []);
}
}
@@ -157,15 +180,18 @@
return (TValue)value;
if (typeof(TValue) == typeof(string))
- return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config.Info, Content.Config);
+ return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config, Content.Config);
return (TValue)Convert.ChangeType(value, typeof(TValue));
}
- private void SetPropertyValue(PropertyConfig config, object? value, InputType senderType) {
+ private async Task SetPropertyValue(PropertyConfig config, object? value, InputType senderType) {
+ if (!await Handler.IsAuthenticatedAsync(_currentlyEditing ? Content.Config.UpdatePolicy : Content.Config.CreatePolicy))
+ return;
+
object? result = null;
- if (value is not null) {
+ if (value is not null && config.Parser is null) {
switch (senderType) {
case InputType.Number:
result = Convert.ChangeType(value, config.Info.PropertyType);
@@ -180,7 +206,10 @@
break;
case InputType.Enum:
- result = Enum.Parse(config.Info.PropertyType, (string)value);
+ var type = Nullable.GetUnderlyingType(config.Info.PropertyType);
+ if (type is not null && string.IsNullOrEmpty((string)value)) break;
+ type ??= config.Info.PropertyType;
+ result = Enum.Parse(type, (string)value);
break;
case InputType.Date:
@@ -218,6 +247,9 @@
}
private async Task OpenRelationalPicker(PropertyConfig config) {
+ if (!await Handler.IsAuthenticatedAsync(_currentlyEditing ? Content.Config.UpdatePolicy : Content.Config.CreatePolicy))
+ return;
+
var relationTable = Explorer.GetTable(config.Info.PropertyType);
if (relationTable is null) return;
@@ -227,11 +259,16 @@
if (result.Cancelled) return;
var data = (RelationPickerDialogData)result.Data!;
- SetPropertyValue(config, data.Object, InputType.Relation);
+ await SetPropertyValue(config, data.Object, InputType.Relation);
}
private async Task
ValidateInputs() {
+ if (!await Handler.IsAuthenticatedAsync(_currentlyEditing ? Content.Config.UpdatePolicy : Content.Config.CreatePolicy))
+ return false;
+
foreach (var property in Content.Config.Properties) {
+ if (property.IsListingProperty) continue;
+
var errorList = _validationErrors[property.Info.Name];
errorList.Clear();
var value = property.Info.GetValue(Content.CurrentObject);
@@ -242,7 +279,7 @@
}
if (value is null && property.IsRequired)
- errorList.Add("Value cannot be null");
+ errorList.Add($"{property.Name} is required");
}
StateHasChanged();
diff --git a/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor b/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor
index 75447ea..c78f3c7 100644
--- a/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor
+++ b/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor
@@ -1,4 +1,6 @@
-@using HopFrame.Core.Services
+@using HopFrame.Core.Config
+@using HopFrame.Core.Services
+
- @foreach (var table in Explorer.GetTableNames()) {
+ @foreach (var table in _tables.OrderBy(t => t.Order).Select(t => t.DisplayName)) {
@inject IContextExplorer Explorer
+@inject IHopFrameAuthHandler Handler
+
+@code {
+
+ private readonly List _tables = [];
+
+ protected override async Task OnInitializedAsync() {
+ foreach (var table in Explorer.GetTables()) {
+ if (table.Ignored) continue;
+ if (!await Handler.IsAuthenticatedAsync(table.ViewPolicy)) continue;
+ _tables.Add(table);
+ }
+ }
+
+}
diff --git a/src/HopFrame.Web/Components/Pages/HopFrameHome.razor b/src/HopFrame.Web/Components/Pages/HopFrameHome.razor
index b7e9e2e..6be85aa 100644
--- a/src/HopFrame.Web/Components/Pages/HopFrameHome.razor
+++ b/src/HopFrame.Web/Components/Pages/HopFrameHome.razor
@@ -1,8 +1,45 @@
@page "/admin"
+@using HopFrame.Core.Config
+@using HopFrame.Core.Services
@layout HopFrameLayout
-HopFrameHome
+HopFrame
+
+
+
Tables
+
+
+ @foreach (var table in _tables.OrderBy(t => t.Order)) {
+
+ @table.DisplayName
+ @table.ViewPolicy
+ @table.Description
+
+
+
+ }
+
+
+
+@inject IContextExplorer Explorer
+@inject IHopFrameAuthHandler Handler
@code {
+ private readonly List _tables = [];
+
+ protected override async Task OnInitializedAsync() {
+ foreach (var table in Explorer.GetTables()) {
+ if (table.Ignored) continue;
+ if (!await Handler.IsAuthenticatedAsync(table.ViewPolicy)) continue;
+ _tables.Add(table);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor b/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor
index 8af32a8..bd03a2f 100644
--- a/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor
+++ b/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor
@@ -9,23 +9,29 @@
@using Microsoft.JSInterop
@using Microsoft.EntityFrameworkCore
+@if (!DisplaySelection) {
+ @_config?.DisplayName
+}
+
@_config?.DisplayName
-
- Refresh
-
+ @if (!DisplaySelection) {
+
+ Refresh
+
+ }
-
+
- @if (!DisplaySelection) {
+ @if (!DisplaySelection && _hasCreatePolicy) {
Add Entry
}
@@ -47,25 +53,30 @@
- @foreach (var property in _config!.Properties.Where(prop => prop.List)) {
+ @foreach (var property in _config!.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order)) {
}
- @if (DisplayActions) {
+ @if (DisplayActions && (_hasDeletePolicy || _hasUpdatePolicy)) {
var dataIndex = 0;
@{ var currentElement = _currentlyDisplayedModels.ElementAtOrDefault(dataIndex); }
-
-
-
+
+ @if (_hasUpdatePolicy) {
+
+
+
+ }
-
-
-
+ @if (_hasDeletePolicy) {
+
+
+
+ }
@{
dataIndex++;
@@ -116,6 +127,7 @@
@inject NavigationManager Navigator
@inject IJSRuntime Js
@inject IDialogService Dialogs
+@inject IHopFrameAuthHandler Handler
@code {
@@ -142,7 +154,10 @@
private int _totalPages;
private string? _searchTerm;
private bool _loading;
- private int _selectedIndex = -1;
+
+ private bool _hasUpdatePolicy;
+ private bool _hasDeletePolicy;
+ private bool _hasCreatePolicy;
protected override void OnInitialized() {
_config ??= Explorer.GetTable(TableDisplayName);
@@ -153,6 +168,15 @@
}
protected override async Task OnInitializedAsync() {
+ if (!await Handler.IsAuthenticatedAsync(_config?.ViewPolicy)) {
+ Navigator.NavigateTo("/admin", true);
+ return;
+ }
+
+ _hasUpdatePolicy = await Handler.IsAuthenticatedAsync(_config?.UpdatePolicy);
+ _hasDeletePolicy = await Handler.IsAuthenticatedAsync(_config?.DeletePolicy);
+ _hasCreatePolicy = await Handler.IsAuthenticatedAsync(_config?.CreatePolicy);
+
_manager ??= Explorer.GetTableManager(_config!.PropertyName);
_currentlyDisplayedModels = await _manager!.LoadPage(_currentPage, PerPage).ToArrayAsync();
_totalPages = await _manager.TotalPages(PerPage);
@@ -201,6 +225,11 @@
}
private async Task DeleteEntry(object element) {
+ if (!await Handler.IsAuthenticatedAsync(_config?.DeletePolicy)) {
+ Navigator.NavigateTo("/admin", true);
+ return;
+ }
+
var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
var result = await dialog.Result;
if (result.Cancelled) return;
@@ -210,6 +239,11 @@
}
private async Task CreateOrEdit(object? element) {
+ if (!await Handler.IsAuthenticatedAsync(element is null ? _config?.CreatePolicy : _config?.UpdatePolicy)) {
+ Navigator.NavigateTo("/admin", true);
+ return;
+ }
+
var panel = await Dialogs.ShowPanelAsync(new EditorDialogData(_config!, element), new DialogParameters {
TrapFocus = false
});
diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs
index c68c7a4..ef238e8 100644
--- a/src/HopFrame.Web/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs
@@ -1,20 +1,22 @@
using HopFrame.Core;
using HopFrame.Core.Config;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.FluentUI.AspNetCore.Components;
namespace HopFrame.Web;
public static class ServiceCollectionExtensions {
- public static IServiceCollection AddHopFrame(this IServiceCollection services, Action configurator) {
+ public static IServiceCollection AddHopFrame(this IServiceCollection services, Action configurator, LibraryConfiguration? fluentUiLibraryConfiguration = null) {
var config = new HopFrameConfig();
configurator.Invoke(new HopFrameConfigurator(config));
- return AddHopFrame(services, config);
+ return AddHopFrame(services, config, fluentUiLibraryConfiguration);
}
- public static IServiceCollection AddHopFrame(this IServiceCollection services, HopFrameConfig config) {
+ public static IServiceCollection AddHopFrame(this IServiceCollection services, HopFrameConfig config, LibraryConfiguration? fluentUiLibraryConfiguration = null) {
services.AddSingleton(config);
services.AddHopFrameServices();
+ services.AddFluentUIComponents(fluentUiLibraryConfiguration);
return services;
}
diff --git a/testing/HopFrame.Testing/Models/Post.cs b/testing/HopFrame.Testing/Models/Post.cs
index e73af9a..f575222 100644
--- a/testing/HopFrame.Testing/Models/Post.cs
+++ b/testing/HopFrame.Testing/Models/Post.cs
@@ -25,4 +25,6 @@ public class Post {
public TimeOnly At { get; set; }
public ListSortDirection Type { get; set; }
+
+ public TypeCode? TypeCode { get; set; }
}
\ No newline at end of file
diff --git a/testing/HopFrame.Testing/Program.cs b/testing/HopFrame.Testing/Program.cs
index c3d433a..d3b7f37 100644
--- a/testing/HopFrame.Testing/Program.cs
+++ b/testing/HopFrame.Testing/Program.cs
@@ -1,9 +1,7 @@
-using HopFrame.Core.Services;
using HopFrame.Testing;
using Microsoft.FluentUI.AspNetCore.Components;
using HopFrame.Testing.Components;
using HopFrame.Testing.Models;
-using HopFrame.Testing.Services;
using HopFrame.Web;
using HopFrame.Web.Components.Pages;
using Microsoft.EntityFrameworkCore;
@@ -20,21 +18,29 @@ builder.Services.AddDbContext(options => {
});
builder.Services.AddHopFrame(options => {
+ options.DisplayUserInfo(false);
options.AddDbContext(context => {
context.Table(table => {
table.Property(u => u.Password)
.ValueParser(pwd => pwd + "-edited");
table.Property(u => u.FirstName)
- .SetDisplayName("First Name");
+ .List(false);
table.Property(u => u.LastName)
- .SetDisplayName("Last Name");
+ .List(false);
table.Property(u => u.Id)
- .Sortable(false);
+ .Sortable(false)
+ .OrderIndex(3);
+
+ table.AddListingProperty("Name", user => $"{user.FirstName} {user.LastName}")
+ .OrderIndex(2);
table.SetDisplayName("Benutzer");
+ table.SetDescription("This table is used for user data store and user authentication");
+
+ table.SetViewPolicy("policy");
});
context.Table()
@@ -61,11 +67,12 @@ builder.Services.AddHopFrame(options => {
return errors;
});
+
+ context.Table()
+ .OrderIndex(-1);
});
});
-builder.Services.AddTransient();
-
var app = builder.Build();
// Configure the HTTP request pipeline.