Added audit log
This commit is contained in:
@@ -18,6 +18,7 @@ public static class ServiceCollectionExtensions {
|
||||
services.TryAddScoped<IHopFrameAuthHandler, DefaultAuthHandler>();
|
||||
services.TryAddScoped<ICallbackEmitter, CallbackEmitter>();
|
||||
services.AddScoped<ISearchExpressionBuilder, SearchExpressionBuilder>();
|
||||
services.AddScoped<IPrimaryKeyFinder, PrimaryKeyFinder>();
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
8
src/HopFrame.Core/Services/IPrimaryKeyFinder.cs
Normal file
8
src/HopFrame.Core/Services/IPrimaryKeyFinder.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Reflection;
|
||||
using HopFrame.Core.Config;
|
||||
|
||||
namespace HopFrame.Core.Services;
|
||||
|
||||
public interface IPrimaryKeyFinder {
|
||||
PropertyInfo? GetPrimaryKeyInfo(TableConfig config);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
9
src/HopFrame.Web/AuditLogging/AuditLogContext.cs
Normal file
9
src/HopFrame.Web/AuditLogging/AuditLogContext.cs
Normal 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; }
|
||||
|
||||
}
|
||||
28
src/HopFrame.Web/AuditLogging/AuditLogEntry.cs
Normal file
28
src/HopFrame.Web/AuditLogging/AuditLogEntry.cs
Normal 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
|
||||
}
|
||||
72
src/HopFrame.Web/AuditLogging/AuditLogPlugin.cs
Normal file
72
src/HopFrame.Web/AuditLogging/AuditLogPlugin.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
30
src/HopFrame.Web/AuditLogging/ConfiguratorExtensions.cs
Normal file
30
src/HopFrame.Web/AuditLogging/ConfiguratorExtensions.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -385,7 +385,7 @@
|
||||
if (value is null && property.IsRequired)
|
||||
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,
|
||||
Property = property,
|
||||
Table = Content.Config
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
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">
|
||||
@foreach (var button in _pluginButtons.Where(pb => pb.IsForTable(_config)).Where(pb => pb.Position == PluginButtonPosition.OnEntry)) {
|
||||
<FluentButton OnClick="() => button.Handler.Invoke(context, _config!)">
|
||||
@@ -226,10 +226,9 @@
|
||||
private List<PluginButton> _pluginButtons = new();
|
||||
private DefaultButtonToggles _buttonToggles = new();
|
||||
|
||||
internal static HopFrameTablePage? CurrentInstance { get; private set; }
|
||||
private string? _currentUser;
|
||||
|
||||
protected override void OnInitialized() {
|
||||
CurrentInstance = this;
|
||||
_config ??= Explorer.GetTable(TableDisplayName);
|
||||
|
||||
if (_config is null || (_config.Ignored && DialogData is null)) {
|
||||
@@ -242,8 +241,10 @@
|
||||
Navigator.NavigateTo("/admin", true);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentUser = await Handler.GetCurrentUserDisplayNameAsync();
|
||||
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new TableInitializedEvent(this) {
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new TableInitializedEvent(this, _currentUser!) {
|
||||
Table = _config!
|
||||
});
|
||||
if (eventResult.IsCanceled) return;
|
||||
@@ -283,7 +284,7 @@
|
||||
|
||||
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,
|
||||
Table = _config!,
|
||||
CurrentPage = _currentPage
|
||||
@@ -333,7 +334,7 @@
|
||||
public async Task Reload() {
|
||||
_loading = true;
|
||||
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) {
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this, _currentUser!) {
|
||||
Table = _config!
|
||||
}, _tokenSource.Token);
|
||||
if (eventResult.IsCanceled) {
|
||||
@@ -352,7 +353,7 @@
|
||||
}
|
||||
|
||||
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,
|
||||
NewPage = page,
|
||||
TotalPages = _totalPages,
|
||||
@@ -371,17 +372,17 @@
|
||||
Navigator.NavigateTo("/admin", true);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new DeleteEntryEvent(this) {
|
||||
Entity = element,
|
||||
Table = _config!
|
||||
}, _tokenSource.Token);
|
||||
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;
|
||||
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new DeleteEntryEvent(this, _currentUser!) {
|
||||
Entity = element,
|
||||
Table = _config!
|
||||
}, _tokenSource.Token);
|
||||
if (eventResult.IsCanceled) return;
|
||||
|
||||
await _manager!.DeleteItem(element);
|
||||
await Emitter.DispatchCallback(CallbackTypes.DeleteEntry(_config!), element);
|
||||
await Reload();
|
||||
@@ -392,22 +393,6 @@
|
||||
Navigator.NavigateTo("/admin", true);
|
||||
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 {
|
||||
TrapFocus = false
|
||||
@@ -416,6 +401,23 @@
|
||||
var data = result.Data as EditorDialogData;
|
||||
|
||||
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) {
|
||||
await _manager!.AddItem(data!.CurrentObject!);
|
||||
@@ -430,7 +432,7 @@
|
||||
}
|
||||
|
||||
private void SelectItem(object item, bool selected) {
|
||||
var eventResult = PluginOrchestrator.DispatchEvent(new SelectEntryEvent(this) {
|
||||
var eventResult = PluginOrchestrator.DispatchEvent(new SelectEntryEvent(this, _currentUser!) {
|
||||
Entity = item,
|
||||
Selected = selected,
|
||||
Table = _config!
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
|
||||
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 sealed class CreateEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender);
|
||||
|
||||
public sealed class UpdateEntryEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) {
|
||||
public sealed class CreateEntryEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
|
||||
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 bool Selected { get; set; }
|
||||
}
|
||||
|
||||
@@ -4,24 +4,25 @@ using HopFrame.Web.Components.Pages;
|
||||
|
||||
namespace HopFrame.Web.Plugins.Events;
|
||||
|
||||
public abstract class HopFrameEventArgs(object internalSender) {
|
||||
public abstract class HopFrameEventArgs(object internalSender, string user) {
|
||||
internal object InternalSender { get; } = internalSender;
|
||||
public bool IsCanceled { get; protected set; }
|
||||
public string Username { get; set; } = user;
|
||||
|
||||
|
||||
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 abstract class HopFrameTablePageEventArgs(HopFrameTablePage sender)
|
||||
: HopFrameEventArgs<HopFrameTablePage>(sender) {
|
||||
public abstract class HopFrameTablePageEventArgs(HopFrameTablePage sender, string user)
|
||||
: HopFrameEventArgs<HopFrameTablePage>(sender, user) {
|
||||
public required TableConfig Table { get; init; }
|
||||
}
|
||||
|
||||
public abstract class HopFrameEditorEventArgs(HopFrameEditor sender)
|
||||
: HopFrameEventArgs<HopFrameEditor>(sender) {
|
||||
public abstract class HopFrameEditorEventArgs(HopFrameEditor sender, string user)
|
||||
: HopFrameEventArgs<HopFrameEditor>(sender, user) {
|
||||
public required TableConfig Table { get; init; }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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 TotalPages { get; init; }
|
||||
public required int NewPage { get; set; }
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
namespace HopFrame.Web.Plugins.Events;
|
||||
|
||||
public sealed class ReloadEvent(HopFrameTablePage sender) : HopFrameTablePageEventArgs(sender) {
|
||||
public sealed class ReloadEvent(HopFrameTablePage sender, string user) : HopFrameTablePageEventArgs(sender, user) {
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using HopFrame.Web.Components.Pages;
|
||||
|
||||
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 int CurrentPage { get; init; }
|
||||
internal IEnumerable<object>? SearchResult { get; set; }
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.FluentUI.AspNetCore.Components;
|
||||
|
||||
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 DefaultButtonToggles DefaultButtons { get; set; } = new();
|
||||
|
||||
@@ -93,7 +93,7 @@ public enum PluginButtonPosition {
|
||||
OnEntry = 2
|
||||
}
|
||||
|
||||
public struct DefaultButtonToggles() {
|
||||
public class DefaultButtonToggles {
|
||||
public bool ShowRefreshButton { get; set; } = true;
|
||||
public bool ShowAddEntityButton { get; set; } = true;
|
||||
public bool ShowDeleteButton { get; set; } = true;
|
||||
|
||||
@@ -3,7 +3,7 @@ using HopFrame.Web.Components.Dialogs;
|
||||
|
||||
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 PropertyConfig Property { get; init; }
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using Microsoft.FluentUI.AspNetCore.Components;
|
||||
|
||||
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 = ';';
|
||||
|
||||
[EventHandler]
|
||||
@@ -51,7 +51,7 @@ internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService to
|
||||
}
|
||||
|
||||
private async Task Import(TableConfig table, HopFrameTablePage target) {
|
||||
var file = await files.UploadFile();
|
||||
var file = await files.UploadFile(target);
|
||||
|
||||
var stream = file.OpenReadStream();
|
||||
var reader = new StreamReader(stream);
|
||||
@@ -178,17 +178,8 @@ internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService to
|
||||
}
|
||||
|
||||
private Type? GetPrimaryKeyType(Type tableType) {
|
||||
var table = explorer.GetTable(tableType);
|
||||
if (table?.ContextConfig is RepositoryGroupConfig repoConfig) {
|
||||
return repoConfig.KeyProperty.PropertyType;
|
||||
}
|
||||
|
||||
return tableType
|
||||
.GetProperties()
|
||||
.FirstOrDefault(prop => prop
|
||||
.GetCustomAttributes(true)
|
||||
.Any(attr => attr is KeyAttribute))?
|
||||
.PropertyType;
|
||||
var table = explorer.GetTable(tableType)!;
|
||||
return finder.GetPrimaryKeyInfo(table)?.PropertyType;
|
||||
}
|
||||
|
||||
private object? ParseString(string input, Type targetType) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using HopFrame.Web.Plugins.Annotations;
|
||||
using HopFrame.Web.Plugins.Events;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HopFrame.Web.Plugins.Internal;
|
||||
|
||||
@@ -24,15 +25,18 @@ internal sealed class PluginOrchestrator(IServiceProvider services) : IPluginOrc
|
||||
Handler = method
|
||||
};
|
||||
collection.AddSingleton(container);
|
||||
collection.AddScoped(plugin);
|
||||
}
|
||||
|
||||
collection.AddScoped(plugin);
|
||||
}
|
||||
|
||||
public async Task<TEvent> DispatchEvent<TEvent>(TEvent @event, CancellationToken ct = new()) where TEvent : HopFrameEventArgs {
|
||||
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);
|
||||
foreach (var container in eventContainers) {
|
||||
var plugin = services.GetRequiredService(container.Handler.DeclaringType!);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using HopFrame.Web.Components.Pages;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
/// <returns>A task that returns an IBrowserFile representing the uploaded file.</returns>
|
||||
public Task<IBrowserFile> UploadFile();
|
||||
public Task<IBrowserFile> UploadFile(HopFrameTablePage page);
|
||||
|
||||
}
|
||||
@@ -12,19 +12,16 @@ internal sealed class FileService(IJSRuntime runtime) : IFileService {
|
||||
await runtime.InvokeVoidAsync("downloadFileFromStream", name, stream);
|
||||
}
|
||||
|
||||
public Task<IBrowserFile> UploadFile() {
|
||||
public Task<IBrowserFile> UploadFile(HopFrameTablePage page) {
|
||||
var result = new TaskCompletionSource<IBrowserFile>();
|
||||
|
||||
if (HopFrameTablePage.CurrentInstance is null)
|
||||
result.SetException(new InvalidOperationException("No table page visible"));
|
||||
|
||||
HopFrameTablePage.CurrentInstance!.OnFileUpload = files => {
|
||||
page.OnFileUpload = files => {
|
||||
result.SetResult(files.First());
|
||||
HopFrameTablePage.CurrentInstance.OnFileUpload = null;
|
||||
page.OnFileUpload = null;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
runtime.InvokeVoidAsync("triggerClick", HopFrameTablePage.CurrentInstance.FileInputElement!.Element);
|
||||
runtime.InvokeVoidAsync("triggerClick", page.FileInputElement!.Element);
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user