Added audit log

This commit is contained in:
2025-07-06 16:35:46 +02:00
parent 10913b0a21
commit 827e0eae6c
23 changed files with 312 additions and 131 deletions

View File

@@ -11,7 +11,31 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="" /> <list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IPrimaryKeyFinder.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/PrimaryKeyFinder.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/AuditLogging/AuditLogContext.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/AuditLogging/AuditLogEntry.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/AuditLogging/AuditLogPlugin.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/AuditLogging/ConfiguratorExtensions.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/AuthHandler.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/ServiceCollectionExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/ServiceCollectionExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/EntryEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/EntryEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/HopFrameEventArgs.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/HopFrameEventArgs.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/PageChangeEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/PageChangeEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/ReloadEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/ReloadEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/SearchEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/SearchEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/TableInitializedEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/TableInitializedEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/ValidationEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/ValidationEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Internal/ExporterPlugin.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Internal/ExporterPlugin.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Internal/PluginOrchestrator.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Internal/PluginOrchestrator.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Services/IFileService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Services/IFileService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Services/Implementation/FileService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Services/Implementation/FileService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -52,53 +76,13 @@
} }
}</component> }</component>
<component name="HighlightingSettingsPerFile"> <component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/02/6ae7626a/IList.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/5b/a350be00/IEnumerable.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/62/1fb63ed0/IDisposable.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/8b/db8582a3/IList`1.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/a0/0a968c53/IEnumerable`1.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/ad/ba9a50e7/ICollection.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/c73b3c6c598640c592fd3c6fa226c286e90908/fc/6f7933d2/ICollection`1.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/0f73968de5cdfe0aa57817b8dd2a3c5d1db615ba4ae4629a5af59bb6c8922/RemoteNavigationManager.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/0f73968de5cdfe0aa57817b8dd2a3c5d1db615ba4ae4629a5af59bb6c8922/RemoteNavigationManager.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/10c66a9a1e137111895f7182a2ae246eabe06a261578c3fa495a45f6f177d35/IconVariant.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2f2b1d92f1ffcf2dabd664473f8c9dafa6039595ac228ce4b75a0f1619c5991/AsyncTaskMethodBuilderT.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1b81cb3be224213a6a73519b6e340a628d9a1fb8629c351a186a26f6376669/List.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/24dd1164ba47541cb1d3eb011e638e16953dbea3ae3f4dc208c3bbf3e96298a/ServiceCollectionServiceExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/26c9a2fb5243863babc926e4be763daf4128d4f97c4a769cdce1e2e3e5c532/FluentButton.razor.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2751d5afefca5424bfc4b21347f581372f7a739c0ae4df661ea557fcb97ef20/EnumExtensions.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2a4d2ce4c06ab596b3676c5cf06066b4391ec7dd93cdf8f0334b69dc1a9de/TextReader.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/439c4ee753b23e743cc14119593bc889751f9eb0b38997577d8e4c47c4fed/ToCollection.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4c41a7d338749915157d56585365d1693fbad6be8231d3d583b1cf10d16896d9/FluentIcon.razor.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4ee221fd7e91e9a4c14ff82aae2ee938edecde35a934133e991aba56aa9499/Icon.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/558c1d46e1e21d2e78ee2ab67a674f6927bf95355b2f245f35d74bb5ec0f92/CancellationTokenSource.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/5a69b82eed595b731b82667db08722b69b82482e275cf32dfb219190e3dc49/CollectionEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/60e7b22380df80ef6fefe43138047f49ec6eff4b25c12b42ce3d6ed5aac/MethodInvokerCommon.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5/ThrowHelper.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/642391624bd5c30b3411a11434588aba4906207335166b784bf3a4325f6c7/NavigationEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/6d1d64f05e7045295fa180276a8c2aef0302c9e96eb53b3431ab13db4579/FluentAppBarItem.razor.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/6fe785cceb29ca2d1da78e157315815a7c4372b582a20a71c28b210f9d56e/IconsExtensions.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/7ad7d2d0ae865063993eb8a03427815ea3bdb6a774e0a2f95512e9f669a4f489/MemberEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/876cd892fc66a9dc8f6afd3704c264acebdfc46aed08089463e8117c21a532/String.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/87c584767b46b5fd42769be76547105558e6690f785614efddca134b2d682/Type.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/8d5d6cbff46ddc7b152381f92ae1ae51d3e7b57b14dd23840a11f5aaaaed396/InternalEntityEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/a882d183338544fdbcbdfc7b6d3dcb78916630765551644a221b5be9c45a121b/Int32.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/aa3ea54f92373c58ec1149fbd41215869a98bd385c30584bc6db2fa3c6e88443/Filled24.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/adcd2c45092dd8e4fc412325c8adb75d6e7d8b3e90a9523f167583fb9c60/ServiceCollectionExtensions.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b3ccb66df3646cb51df73ad51716136ebd2eefb4edb1308dd52a7e999582d59e/IBindableColumn.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bfff78ecaa39c818519fc918bb2d4bbdca6ad93d7170f5cf325f67ccd0b97d43/BooleanAsserts.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d0165cb640e16fb3b8fe6932c042fc2917cd7f2770ff123cf7b9d11b5bfc6/Task.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d04a416cac8afac0341a8be0e859b230f2eae64924298eef48c317ba35916/RenderTreeBuilder.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d1287462d4ec4078c61b8e92a0952fb7de3e7e877d279e390a4c136a6365126/Stream.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d39923abb31e6a6e7a9e8173e217da584c54925ce63e568126a2b89b9ab/DefaultRazorComponentsServiceOptionsConfiguration.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/dac3553c90d47a746e7e7f02faecb1a5e581090/Components_AppBar_FluentAppBarItem_razor.g.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/e26a4f2df232f16e374b9719f883c1b2419f6341838d94b7581db9c7d2de17/IconInfo.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/eab2d6b892f743a27cb49a139ba782855897baf1233febd2dfd2092f3/EntityEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ee4d234452e240d83e3de396c2e85cbf9ac9fb9add618b955eea196c81aaf8/IDialogContentComponent.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/fc2027f7e776fc105cddb56b1a25eeb3895b3ae6f3aac854d786e63bd01f75e2/CallSiteFactory.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/fd57398b7dc3a8ce7da2786f2c67289c3d974658a9e90d0c1e84db3d965fbf1/Console.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/HopFrame.Web/AuditLogging/AuditLogEntry.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/tests/HopFrame.Tests.Core/Services/ContextExplorerTests.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/tests/HopFrame.Tests.Core/Services/DisplayPropertyTests.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/tests/HopFrame.Tests.Core/Services/TableManagerTests.cs" root0="SKIP_HIGHLIGHTING" />
</component> </component>
<component name="KubernetesApiPersistence">{}</component> <component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{ <component name="KubernetesApiProvider">{
@@ -128,7 +112,7 @@
"RunOnceActivity.git.unshallow": "true", "RunOnceActivity.git.unshallow": "true",
"b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug", "b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug",
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug", "dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
"git-widget-placeholder": "feature/test-reports", "git-widget-placeholder": "!37 on feature/audit-logging",
"list.type.of.created.stylesheet": "CSS", "list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true", "node.js.detected.package.tslint": "true",
@@ -267,14 +251,7 @@
<workItem from="1744966207145" duration="5231000" /> <workItem from="1744966207145" duration="5231000" />
<workItem from="1751713720880" duration="8243000" /> <workItem from="1751713720880" duration="8243000" />
<workItem from="1751741813788" duration="4623000" /> <workItem from="1751741813788" duration="4623000" />
</task> <workItem from="1751803290506" duration="7489000" />
<task id="LOCAL-00016" summary="Added documentation for the configurators and service extensions methods">
<option name="closed" value="true" />
<created>1737208088933</created>
<option name="number" value="00016" />
<option name="presentableId" value="LOCAL-00016" />
<option name="project" value="LOCAL" />
<updated>1737208088933</updated>
</task> </task>
<task id="LOCAL-00017" summary="Created tests for the core module"> <task id="LOCAL-00017" summary="Created tests for the core module">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -660,7 +637,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1751750495636</updated> <updated>1751750495636</updated>
</task> </task>
<option name="localTasksCounter" value="65" /> <task id="LOCAL-00065" summary="pipeline cleanup">
<option name="closed" value="true" />
<created>1751803366875</created>
<option name="number" value="00065" />
<option name="presentableId" value="LOCAL-00065" />
<option name="project" value="LOCAL" />
<updated>1751803366876</updated>
</task>
<option name="localTasksCounter" value="66" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -690,7 +675,6 @@
<component name="UnityProjectConfiguration" hasMinimizedUI="false" /> <component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" /> <option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Added default button removal feature" />
<MESSAGE value="Added custom search functionality" /> <MESSAGE value="Added custom search functionality" />
<MESSAGE value="Added fully virtual properties" /> <MESSAGE value="Added fully virtual properties" />
<MESSAGE value="Added basic export and import feature" /> <MESSAGE value="Added basic export and import feature" />
@@ -715,6 +699,19 @@
<MESSAGE value="Updated coverage extraction" /> <MESSAGE value="Updated coverage extraction" />
<MESSAGE value="fixed coverage percentage printing" /> <MESSAGE value="fixed coverage percentage printing" />
<MESSAGE value="fixed echo cmd" /> <MESSAGE value="fixed echo cmd" />
<option name="LAST_COMMIT_MESSAGE" value="fixed echo cmd" /> <MESSAGE value="pipeline cleanup" />
<option name="LAST_COMMIT_MESSAGE" value="pipeline cleanup" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="Microsoft.AspNetCore.Components.NavigationException" breakIfHandledByUserCode="true" breakIfHandledByOtherCode="false" isInternal="false" displayValue="NavigationException" namespaceName="Microsoft.AspNetCore.Components">
<option name="internal" value="false" />
</properties>
<option name="timeStamp" value="3" />
</breakpoint>
</breakpoints>
</breakpoint-manager>
</component> </component>
</project> </project>

View File

@@ -18,6 +18,7 @@ public static class ServiceCollectionExtensions {
services.TryAddScoped<IHopFrameAuthHandler, DefaultAuthHandler>(); services.TryAddScoped<IHopFrameAuthHandler, DefaultAuthHandler>();
services.TryAddScoped<ICallbackEmitter, CallbackEmitter>(); services.TryAddScoped<ICallbackEmitter, CallbackEmitter>();
services.AddScoped<ISearchExpressionBuilder, SearchExpressionBuilder>(); services.AddScoped<ISearchExpressionBuilder, SearchExpressionBuilder>();
services.AddScoped<IPrimaryKeyFinder, PrimaryKeyFinder>();
return services; return services;
} }

View File

@@ -0,0 +1,8 @@
using System.Reflection;
using HopFrame.Core.Config;
namespace HopFrame.Core.Services;
public interface IPrimaryKeyFinder {
PropertyInfo? GetPrimaryKeyInfo(TableConfig config);
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using HopFrame.Core.Config;
namespace HopFrame.Core.Services.Implementations;
internal sealed class PrimaryKeyFinder(IContextExplorer explorer) : IPrimaryKeyFinder {
public PropertyInfo? GetPrimaryKeyInfo(TableConfig config) {
if (config.ContextConfig is RepositoryGroupConfig repoConfig) {
return repoConfig.KeyProperty;
}
return config.TableType
.GetProperties()
.FirstOrDefault(prop => prop
.GetCustomAttributes(true)
.Any(attr => attr is KeyAttribute));
}
}

View File

@@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Web.AuditLogging;
public sealed class AuditLogContext(DbContextOptions<AuditLogContext> options) : DbContext(options) {
public DbSet<AuditLogEntry> AuditLog { get; set; }
}

View File

@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HopFrame.Web.AuditLogging;
public sealed class AuditLogEntry {
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[MaxLength(255)]
public required string User { get; init; }
[MaxLength(255)]
public required string Table { get; init; }
[MaxLength(255)]
public required string Record { get; init; }
public required AuditLogType Type { get; init; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
public enum AuditLogType : byte {
Create = 0x00,
Update = 0x01,
Delete = 0x02
}

View File

@@ -0,0 +1,72 @@
using HopFrame.Core.Config;
using HopFrame.Core.Services;
using HopFrame.Web.Plugins.Annotations;
using HopFrame.Web.Plugins.Events;
namespace HopFrame.Web.AuditLogging;
internal sealed class AuditLogPlugin(AuditLogContext context, IContextExplorer explorer, IPrimaryKeyFinder keyFinder) {
[EventHandler]
public void OnInitialized(TableInitializedEvent e) {
if (e.Table.TableType != typeof(AuditLogEntry)) return;
e.DefaultButtons.ShowAddEntityButton = false;
e.DefaultButtons.ShowDeleteButton = false;
e.PluginButtons.Clear();
}
[EventHandler]
public async Task OnCreate(CreateEntryEvent e) {
if (e.Table.TableType == typeof(AuditLogEntry)) return;
var record = new AuditLogEntry {
Type = AuditLogType.Create,
Table = e.Table.DisplayName,
Record = PrintEntity(e.Entity, e.Table),
User = e.Username
};
await context.AuditLog.AddAsync(record);
await context.SaveChangesAsync();
}
[EventHandler]
public async Task OnUpdate(UpdateEntryEvent e) {
if (e.Table.TableType == typeof(AuditLogEntry)) return;
var record = new AuditLogEntry {
Type = AuditLogType.Update,
Table = e.Table.DisplayName,
Record = PrintEntity(e.Entity, e.Table),
User = e.Username
};
await context.AuditLog.AddAsync(record);
await context.SaveChangesAsync();
}
[EventHandler]
public async Task OnDelete(DeleteEntryEvent e) {
if (e.Table.TableType == typeof(AuditLogEntry)) return;
var record = new AuditLogEntry {
Type = AuditLogType.Delete,
Table = e.Table.DisplayName,
Record = PrintEntity(e.Entity, e.Table),
User = e.Username
};
await context.AuditLog.AddAsync(record);
await context.SaveChangesAsync();
}
private string PrintEntity(object entity, TableConfig config) {
var manager = explorer.GetTableManager(config.TableType);
var key = keyFinder.GetPrimaryKeyInfo(config);
return key?.GetValue(entity)?.ToString() ?? string.Empty;
}
}

View File

@@ -0,0 +1,30 @@
using HopFrame.Core.Config;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web.AuditLogging;
public static class ConfiguratorExtensions {
public static HopFrameConfigurator AddAuditLogging(this HopFrameConfigurator configurator, IServiceCollection services, Action<DbContextOptionsBuilder> optionsBuilder) {
services.AddDbContext<AuditLogContext>(optionsBuilder);
configurator
.AddDbContext<AuditLogContext>()
.Table<AuditLogEntry>(table => {
table.Property(l => l.Id)
.List(false);
table.InnerConfig.Properties
.ForEach(p => p.Editable = false);
table.SetOrderIndex(int.MinValue);
table.SetDisplayName("Audit Log");
});
configurator.AddPlugin<AuditLogPlugin>();
return configurator;
}
}

View File

@@ -385,7 +385,7 @@
if (value is null && property.IsRequired) if (value is null && property.IsRequired)
errorList.Add($"{property.Name} is required"); errorList.Add($"{property.Name} is required");
var eventResult = await PluginOrchestrator.DispatchEvent(new ValidationEvent(this) { var eventResult = await PluginOrchestrator.DispatchEvent(new ValidationEvent(this, await Handler.GetCurrentUserDisplayNameAsync()) {
Errors = errorList, Errors = errorList,
Property = property, Property = property,
Table = Content.Config Table = Content.Config

View File

@@ -97,7 +97,7 @@
Sortable="@property.Sortable"/> Sortable="@property.Sortable"/>
} }
@if (DisplayActions && (_hasDeletePolicy || _hasUpdatePolicy)) { @if (DisplayActions && (_hasDeletePolicy || _hasUpdatePolicy) && (_buttonToggles.ShowEditButton || _buttonToggles.ShowDeleteButton && _pluginButtons.Any(pb => pb.IsForTable(_config)))) {
<TemplateColumn Title="Actions" Align="@Align.End" Style="min-height: 44px; min-width: max-content"> <TemplateColumn Title="Actions" Align="@Align.End" Style="min-height: 44px; min-width: max-content">
@foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.OnEntry)) { @foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.OnEntry)) {
<FluentButton OnClick="() => button.Handler.Invoke(context, _config!)"> <FluentButton OnClick="() => button.Handler.Invoke(context, _config!)">
@@ -226,10 +226,9 @@
private List<PluginButton> _pluginButtons = new(); private List<PluginButton> _pluginButtons = new();
private DefaultButtonToggles _buttonToggles = new(); private DefaultButtonToggles _buttonToggles = new();
internal static HopFrameTablePage? CurrentInstance { get; private set; } private string? _currentUser;
protected override void OnInitialized() { protected override void OnInitialized() {
CurrentInstance = this;
_config ??= Explorer.GetTable(TableDisplayName); _config ??= Explorer.GetTable(TableDisplayName);
if (_config is null || (_config.Ignored && DialogData is null)) { if (_config is null || (_config.Ignored && DialogData is null)) {
@@ -243,7 +242,9 @@
return; return;
} }
var eventResult = await PluginOrchestrator.DispatchEvent(new TableInitializedEvent(this) { _currentUser = await Handler.GetCurrentUserDisplayNameAsync();
var eventResult = await PluginOrchestrator.DispatchEvent(new TableInitializedEvent(this, _currentUser!) {
Table = _config! Table = _config!
}); });
if (eventResult.IsCanceled) return; if (eventResult.IsCanceled) return;
@@ -283,7 +284,7 @@
await Task.Delay(500, _searchCancel.Token); await Task.Delay(500, _searchCancel.Token);
var eventResult = await PluginOrchestrator.DispatchEvent(new SearchEvent(this) { var eventResult = await PluginOrchestrator.DispatchEvent(new SearchEvent(this, _currentUser!) {
SearchTerm = _searchTerm, SearchTerm = _searchTerm,
Table = _config!, Table = _config!,
CurrentPage = _currentPage CurrentPage = _currentPage
@@ -333,7 +334,7 @@
public async Task Reload() { public async Task Reload() {
_loading = true; _loading = true;
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) { var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this, _currentUser!) {
Table = _config! Table = _config!
}, _tokenSource.Token); }, _tokenSource.Token);
if (eventResult.IsCanceled) { if (eventResult.IsCanceled) {
@@ -352,7 +353,7 @@
} }
public async Task ChangePage(int page) { public async Task ChangePage(int page) {
var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this) { var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this, _currentUser!) {
CurrentPage = _currentPage, CurrentPage = _currentPage,
NewPage = page, NewPage = page,
TotalPages = _totalPages, TotalPages = _totalPages,
@@ -372,16 +373,16 @@
return; return;
} }
var eventResult = await PluginOrchestrator.DispatchEvent(new DeleteEntryEvent(this) { var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
var result = await dialog.Result;
if (result.Cancelled) return;
var eventResult = await PluginOrchestrator.DispatchEvent(new DeleteEntryEvent(this, _currentUser!) {
Entity = element, Entity = element,
Table = _config! Table = _config!
}, _tokenSource.Token); }, _tokenSource.Token);
if (eventResult.IsCanceled) return; if (eventResult.IsCanceled) return;
var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
var result = await dialog.Result;
if (result.Cancelled) return;
await _manager!.DeleteItem(element); await _manager!.DeleteItem(element);
await Emitter.DispatchCallback(CallbackTypes.DeleteEntry(_config!), element); await Emitter.DispatchCallback(CallbackTypes.DeleteEntry(_config!), element);
await Reload(); await Reload();
@@ -393,22 +394,6 @@
return; return;
} }
HopFrameTablePageEventArgs eventArgs;
if (element is null) {
eventArgs = new CreateEntryEvent(this) {
Table = _config!
};
}
else {
eventArgs = new UpdateEntryEvent(this) {
Table = _config!,
Entity = element
};
}
var eventResult = await PluginOrchestrator.DispatchEvent(eventArgs, _tokenSource.Token);
if (eventResult.IsCanceled) return;
var panel = await Dialogs.ShowPanelAsync<HopFrameEditor>(new EditorDialogData(_config!, element), new DialogParameters { var panel = await Dialogs.ShowPanelAsync<HopFrameEditor>(new EditorDialogData(_config!, element), new DialogParameters {
TrapFocus = false TrapFocus = false
}); });
@@ -417,6 +402,23 @@
if (result.Cancelled) return; if (result.Cancelled) return;
HopFrameTablePageEventArgs eventArgs;
if (element is null) {
eventArgs = new CreateEntryEvent(this, _currentUser!) {
Table = _config!,
Entity = data!.CurrentObject!
};
}
else {
eventArgs = new UpdateEntryEvent(this, _currentUser!) {
Table = _config!,
Entity = data!.CurrentObject!
};
}
var eventResult = await PluginOrchestrator.DispatchEvent(eventArgs, _tokenSource.Token);
if (eventResult.IsCanceled) return;
if (element is null) { if (element is null) {
await _manager!.AddItem(data!.CurrentObject!); await _manager!.AddItem(data!.CurrentObject!);
await Emitter.DispatchCallback(CallbackTypes.CreateEntry(_config!), data.CurrentObject!); await Emitter.DispatchCallback(CallbackTypes.CreateEntry(_config!), data.CurrentObject!);
@@ -430,7 +432,7 @@
} }
private void SelectItem(object item, bool selected) { private void SelectItem(object item, bool selected) {
var eventResult = PluginOrchestrator.DispatchEvent(new SelectEntryEvent(this) { var eventResult = PluginOrchestrator.DispatchEvent(new SelectEntryEvent(this, _currentUser!) {
Entity = item, Entity = item,
Selected = selected, Selected = selected,
Table = _config! Table = _config!

View File

@@ -2,17 +2,19 @@
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public sealed class DeleteEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public sealed class DeleteEntryEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public required object Entity { get; init; } public required object Entity { get; init; }
} }
public sealed class CreateEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender); public sealed class CreateEntryEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public sealed class UpdateEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) {
public required object Entity { get; init; } public required object Entity { get; init; }
} }
public sealed class SelectEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public sealed class UpdateEntryEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public required object Entity { get; init; }
}
public sealed class SelectEntryEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public required object Entity { get; init; } public required object Entity { get; init; }
public required bool Selected { get; set; } public required bool Selected { get; set; }
} }

View File

@@ -4,24 +4,25 @@ using HopFrame.Web.Components.Pages;
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public abstract class HopFrameEventArgs(object internalSender) { public abstract class HopFrameEventArgs(object internalSender, string user) {
internal object InternalSender { get; } = internalSender; internal object InternalSender { get; } = internalSender;
public bool IsCanceled { get; protected set; } public bool IsCanceled { get; protected set; }
public string Username { get; set; } = user;
public void SetCancelled(bool canceled) => IsCanceled = canceled; public void SetCancelled(bool canceled) => IsCanceled = canceled;
} }
public abstract class HopFrameEventArgs<TSender>(TSender sender) : HopFrameEventArgs(sender) where TSender : class { public abstract class HopFrameEventArgs<TSender>(TSender sender, string user) : HopFrameEventArgs(sender, user) where TSender : class {
public TSender Sender => (TSender)InternalSender; public TSender Sender => (TSender)InternalSender;
} }
public abstract class HopFrameTablePageEventArgs(HopFrameTablePage sender) public abstract class HopFrameTablePageEventArgs(HopFrameTablePage sender, string user)
: HopFrameEventArgs<HopFrameTablePage>(sender) { : HopFrameEventArgs<HopFrameTablePage>(sender, user) {
public required TableConfig Table { get; init; } public required TableConfig Table { get; init; }
} }
public abstract class HopFrameEditorEventArgs(HopFrameEditor sender) public abstract class HopFrameEditorEventArgs(HopFrameEditor sender, string user)
: HopFrameEventArgs<HopFrameEditor>(sender) { : HopFrameEventArgs<HopFrameEditor>(sender, user) {
public required TableConfig Table { get; init; } public required TableConfig Table { get; init; }
} }

View File

@@ -2,7 +2,7 @@
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public sealed class PageChangeEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public sealed class PageChangeEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public required int CurrentPage { get; init; } public required int CurrentPage { get; init; }
public required int TotalPages { get; init; } public required int TotalPages { get; init; }
public required int NewPage { get; set; } public required int NewPage { get; set; }

View File

@@ -2,6 +2,6 @@
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public sealed class ReloadEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public sealed class ReloadEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
} }

View File

@@ -3,7 +3,7 @@ using HopFrame.Web.Components.Pages;
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public sealed class SearchEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public sealed class SearchEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public required string SearchTerm { get; set; } public required string SearchTerm { get; set; }
public required int CurrentPage { get; init; } public required int CurrentPage { get; init; }
internal IEnumerable<object>? SearchResult { get; set; } internal IEnumerable<object>? SearchResult { get; set; }

View File

@@ -4,7 +4,7 @@ using Microsoft.FluentUI.AspNetCore.Components;
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public class TableInitializedEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) { public class TableInitializedEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
public List<PluginButton> PluginButtons { get; } = new(); public List<PluginButton> PluginButtons { get; } = new();
public DefaultButtonToggles DefaultButtons { get; set; } = new(); public DefaultButtonToggles DefaultButtons { get; set; } = new();
@@ -93,7 +93,7 @@ public enum PluginButtonPosition {
OnEntry = 2 OnEntry = 2
} }
public struct DefaultButtonToggles() { public class DefaultButtonToggles {
public bool ShowRefreshButton { get; set; } = true; public bool ShowRefreshButton { get; set; } = true;
public bool ShowAddEntityButton { get; set; } = true; public bool ShowAddEntityButton { get; set; } = true;
public bool ShowDeleteButton { get; set; } = true; public bool ShowDeleteButton { get; set; } = true;

View File

@@ -3,7 +3,7 @@ using HopFrame.Web.Components.Dialogs;
namespace HopFrame.Web.Plugins.Events; namespace HopFrame.Web.Plugins.Events;
public sealed class ValidationEvent(HopFrameEditor sender) : HopFrameEditorEventArgs(sender) { public sealed class ValidationEvent(HopFrameEditor sender, string user) : HopFrameEditorEventArgs(sender, user) {
public required IList<string> Errors { get; init; } public required IList<string> Errors { get; init; }
public required PropertyConfig Property { get; init; } public required PropertyConfig Property { get; init; }
} }

View File

@@ -12,7 +12,7 @@ using Microsoft.FluentUI.AspNetCore.Components;
namespace HopFrame.Web.Plugins.Internal; namespace HopFrame.Web.Plugins.Internal;
internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService toasts, IFileService files) { internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService toasts, IFileService files, IPrimaryKeyFinder finder) {
public const char Separator = ';'; public const char Separator = ';';
[EventHandler] [EventHandler]
@@ -51,7 +51,7 @@ internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService to
} }
private async Task Import(TableConfig table, HopFrameTablePage target) { private async Task Import(TableConfig table, HopFrameTablePage target) {
var file = await files.UploadFile(); var file = await files.UploadFile(target);
var stream = file.OpenReadStream(); var stream = file.OpenReadStream();
var reader = new StreamReader(stream); var reader = new StreamReader(stream);
@@ -178,17 +178,8 @@ internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService to
} }
private Type? GetPrimaryKeyType(Type tableType) { private Type? GetPrimaryKeyType(Type tableType) {
var table = explorer.GetTable(tableType); var table = explorer.GetTable(tableType)!;
if (table?.ContextConfig is RepositoryGroupConfig repoConfig) { return finder.GetPrimaryKeyInfo(table)?.PropertyType;
return repoConfig.KeyProperty.PropertyType;
}
return tableType
.GetProperties()
.FirstOrDefault(prop => prop
.GetCustomAttributes(true)
.Any(attr => attr is KeyAttribute))?
.PropertyType;
} }
private object? ParseString(string input, Type targetType) { private object? ParseString(string input, Type targetType) {

View File

@@ -1,6 +1,7 @@
using HopFrame.Web.Plugins.Annotations; using HopFrame.Web.Plugins.Annotations;
using HopFrame.Web.Plugins.Events; using HopFrame.Web.Plugins.Events;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace HopFrame.Web.Plugins.Internal; namespace HopFrame.Web.Plugins.Internal;
@@ -24,15 +25,18 @@ internal sealed class PluginOrchestrator(IServiceProvider services) : IPluginOrc
Handler = method Handler = method
}; };
collection.AddSingleton(container); collection.AddSingleton(container);
collection.AddScoped(plugin);
} }
collection.AddScoped(plugin);
} }
public async Task<TEvent> DispatchEvent<TEvent>(TEvent @event, CancellationToken ct = new()) where TEvent : HopFrameEventArgs { public async Task<TEvent> DispatchEvent<TEvent>(TEvent @event, CancellationToken ct = new()) where TEvent : HopFrameEventArgs {
var eventContainers = services.GetRequiredService<IEnumerable<PluginEventContainer>>() var eventContainers = services.GetRequiredService<IEnumerable<PluginEventContainer>>()
.Where(container => container.EventType == typeof(TEvent)); .Where(container => container.EventType == @event.GetType())
.ToArray();
var eventType = typeof(TEvent);
var eventType = @event.GetType();
var tokenType = typeof(CancellationToken); var tokenType = typeof(CancellationToken);
foreach (var container in eventContainers) { foreach (var container in eventContainers) {
var plugin = services.GetRequiredService(container.Handler.DeclaringType!); var plugin = services.GetRequiredService(container.Handler.DeclaringType!);

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Forms; using HopFrame.Web.Components.Pages;
using Microsoft.AspNetCore.Components.Forms;
namespace HopFrame.Web.Services; namespace HopFrame.Web.Services;
@@ -18,6 +19,6 @@ public interface IFileService {
/// Allows the user to upload a file and returns the uploaded file for processing. /// Allows the user to upload a file and returns the uploaded file for processing.
/// </summary> /// </summary>
/// <returns>A task that returns an IBrowserFile representing the uploaded file.</returns> /// <returns>A task that returns an IBrowserFile representing the uploaded file.</returns>
public Task<IBrowserFile> UploadFile(); public Task<IBrowserFile> UploadFile(HopFrameTablePage page);
} }

View File

@@ -12,19 +12,16 @@ internal sealed class FileService(IJSRuntime runtime) : IFileService {
await runtime.InvokeVoidAsync("downloadFileFromStream", name, stream); await runtime.InvokeVoidAsync("downloadFileFromStream", name, stream);
} }
public Task<IBrowserFile> UploadFile() { public Task<IBrowserFile> UploadFile(HopFrameTablePage page) {
var result = new TaskCompletionSource<IBrowserFile>(); var result = new TaskCompletionSource<IBrowserFile>();
if (HopFrameTablePage.CurrentInstance is null) page.OnFileUpload = files => {
result.SetException(new InvalidOperationException("No table page visible"));
HopFrameTablePage.CurrentInstance!.OnFileUpload = files => {
result.SetResult(files.First()); result.SetResult(files.First());
HopFrameTablePage.CurrentInstance.OnFileUpload = null; page.OnFileUpload = null;
return Task.CompletedTask; return Task.CompletedTask;
}; };
runtime.InvokeVoidAsync("triggerClick", HopFrameTablePage.CurrentInstance.FileInputElement!.Element); runtime.InvokeVoidAsync("triggerClick", page.FileInputElement!.Element);
return result.Task; return result.Task;
} }

View File

@@ -0,0 +1,12 @@
using HopFrame.Core.Services;
namespace HopFrame.Testing;
public class AuthHandler : IHopFrameAuthHandler {
public Task<bool> IsAuthenticatedAsync(string? policy) {
return Task.FromResult(true);
}
public Task<string> GetCurrentUserDisplayNameAsync() {
return Task.FromResult("Leon Hoppe");
}
}

View File

@@ -1,8 +1,10 @@
using HopFrame.Core.Services;
using HopFrame.Testing; using HopFrame.Testing;
using Microsoft.FluentUI.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components;
using HopFrame.Testing.Components; using HopFrame.Testing.Components;
using HopFrame.Testing.Models; using HopFrame.Testing.Models;
using HopFrame.Web; using HopFrame.Web;
using HopFrame.Web.AuditLogging;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Message = HopFrame.Testing.Models.Message; using Message = HopFrame.Testing.Models.Message;
@@ -113,10 +115,15 @@ builder.Services.AddHopFrame(options => {
.ForceRelation() .ForceRelation()
.Format((u, _) => u.Username ?? string.Empty); .Format((u, _) => u.Username ?? string.Empty);
}); });
options.AddAuditLogging(builder.Services, contextBuilder => {
contextBuilder.UseInMemoryDatabase("audit-logging");
});
}); });
builder.Services.AddSingleton<MessageRepository>(); builder.Services.AddSingleton<MessageRepository>();
builder.Services.AddSingleton<GuestRepository>(); builder.Services.AddSingleton<GuestRepository>();
builder.Services.AddScoped<IHopFrameAuthHandler, AuthHandler>();
var app = builder.Build(); var app = builder.Build();