Support for non-admin commands to be available to non-admin users

This commit is contained in:
Alexander Shabarshov 2025-11-12 15:12:07 +00:00
parent e08cfa84bb
commit 7bcbbdb5ce
6 changed files with 81 additions and 33 deletions

View File

@ -20,9 +20,10 @@
*@ *@
[Inject] protected IUserSession UserSession { get; set; } = null!; [Inject] protected IUserSession UserSession { get; set; } = null!;
public class CommandParams(string name, Action<CommandParams> changed) public class CommandParams(string name, CmdType commandType, Action<CommandParams> changed)
{ {
public string Name { get; } = name; public string Name { get; } = name;
public CmdType CommandType { get; } = commandType;
public string CommandJson public string CommandJson
{ {
@ -148,7 +149,7 @@
Validate(); Validate();
Parameters.ResultChanged = _ => OnResultReceived(); Parameters.ResultChanged = _ => OnResultReceived();
StateHasChanged(); StateHasChanged();
} }
} }
@ -227,4 +228,10 @@
return result; return result;
} }
public MongoCommandAttribute? GetCommandAttribute() =>
GetType().GetCustomAttributes(typeof(MongoCommandAttribute), true)
.OfType<MongoCommandAttribute>()
.FirstOrDefault();
public CmdType CommandType => GetCommandAttribute()?.Type ?? CmdType.Admin;
} }

View File

@ -6,9 +6,11 @@
@using System.Reflection @using System.Reflection
@using Rms.Risk.Mango.Components.Commands @using Rms.Risk.Mango.Components.Commands
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserSession UserSession @inject IUserSession UserSession
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime
@inject AuthenticationStateProvider AuthProvider;
@inject IAuthorizationService AuthService;
@* @*
* dbMango * dbMango
@ -84,7 +86,7 @@
<h3 class="mt-3">Commands</h3> <h3 class="mt-3">Commands</h3>
<AuthorizedOnly Policy="AdminAccess" Resource="@UserSession.Database"> <AuthorizedOnly Policy="ReadAccess" Resource="@UserSession.Database">
<EditForm EditContext="EditContext"> <EditForm EditContext="EditContext">
<div class="form-row"> <div class="form-row">
@ -100,10 +102,10 @@
</EditForm> </EditForm>
<TabControl Class="row flex-stack-horizontal px-3" Vertical="true" @bind-ActivePage="SelectedCommand" TabGroupClass="tab-group" PersistAllTabs="true"> <TabControl Class="row flex-stack-horizontal px-3" Vertical="true" @bind-ActivePage="SelectedCommand" TabGroupClass="tab-group" PersistAllTabs="true">
@foreach (var (group, controls) in _availableCommands) @foreach (var (group, controls) in _userAvailableCommands)
{ {
<TabPage Text="@group" IsSelectable="false"/> <TabPage Text="@group" IsSelectable="false"/>
@foreach (var (name, control) in controls) @foreach (var (name, _, control) in controls)
{ {
var parameters = new Dictionary<string, object>() var parameters = new Dictionary<string, object>()
{ {
@ -184,7 +186,7 @@
} }
} = "Statistics"; } = "Statistics";
private static string[] _groupsOrder = private static readonly string[] _groupsOrder =
[ [
"Informational", "Informational",
"Admin", "Admin",
@ -195,23 +197,30 @@
private string Timeout { get; set; } = "20"; private string Timeout { get; set; } = "20";
private string Error { get; set; } = ""; private string Error { get; set; } = "";
private bool IsReady { get; set; }
private bool IsReady { get; set; }
private bool IsReadyToRun => IsReady && CanExecute && !string.IsNullOrWhiteSpace(CommandJson); private bool IsReadyToRun => IsReady && CanExecute && !string.IsNullOrWhiteSpace(CommandJson);
private EditContext? _editContext; private EditContext? _editContext;
private EditContext EditContext => _editContext!; private EditContext EditContext => _editContext!;
private string CommandName => _commandParams[SelectedCommand].Name; private string CommandName => _commandParams[SelectedCommand].Name;
private CmdType CommandType => _commandParams[SelectedCommand].CommandType;
private string CommandJson => _commandParams[SelectedCommand].CommandJson; private string CommandJson => _commandParams[SelectedCommand].CommandJson;
private bool CanExecute => _commandParams[SelectedCommand].CanExecute; private bool CanExecute => _commandParams[SelectedCommand].CanExecute;
private bool NeedConfirmation => _commandParams[SelectedCommand].NeedConfirmation; private bool NeedConfirmation => _commandParams[SelectedCommand].NeedConfirmation;
private CmdBase.CommandParams.DatabaseConnectionType RequiredConnectionType => _commandParams[SelectedCommand].RequiredConnectionType; private CmdBase.CommandParams.DatabaseConnectionType RequiredConnectionType => _commandParams[SelectedCommand].RequiredConnectionType;
private static readonly List<(string Group, List<(string Name, Type Control)> Controls)> _availableCommands; private static readonly List<(string Group, List<(string Name, CmdType CommandType, Type Control)> Controls)> _availableCommands;
private Dictionary<string, CmdBase.CommandParams> _commandParams = null!; private List<(string Group, List<(string Name, CmdType CommandType, Type Control)> Controls)> _userAvailableCommands = [];
private readonly Dictionary<string, Shell.ShardProperties> _shardConnectionStrings = [];
private Dictionary<string, CmdBase.CommandParams> _commandParams = null!;
private readonly Dictionary<string, Shell.ShardProperties> _shardConnectionStrings = [];
private bool _isWrite;
private bool _isRead;
private bool _isAdmin;
private string Shard { get; set; } = ""; private string Shard { get; set; } = "";
private IReadOnlyCollection<string> Shards => _shardConnectionStrings.Keys.OrderBy(x => x).ToList(); private IReadOnlyCollection<string> Shards => _shardConnectionStrings.Keys.OrderBy(x => x).ToList();
@ -232,7 +241,7 @@
}) })
.Where(x => x.Attr != null) .Where(x => x.Attr != null)
.GroupBy(x => x.Attr!.Group) .GroupBy(x => x.Attr!.Group)
.Select(x => (Group: x.Key, Commands: x.OrderBy(y => y.Attr!.Name).Select(y => (y.Attr!.Name, Control: y.Type) ).ToList())) .Select(x => (Group: x.Key, Commands: x.OrderBy(y => y.Attr!.Name).Select(y => (y.Attr!.Name, CommandType: y.Attr.Type, Control: y.Type) ).ToList()))
.Where(x => _groupsOrder.Contains(x.Group)) .Where(x => _groupsOrder.Contains(x.Group))
.OrderBy(x => Array.IndexOf(_groupsOrder, x.Group)) .OrderBy(x => Array.IndexOf(_groupsOrder, x.Group))
.ToList() .ToList()
@ -248,7 +257,7 @@
_commandParams = new( _commandParams = new(
_availableCommands _availableCommands
.SelectMany(x => x.Controls) .SelectMany(x => x.Controls)
.Select(x => new KeyValuePair<string, CmdBase.CommandParams>(x.Name, new (x.Name, OnChanged)) .Select(x => new KeyValuePair<string, CmdBase.CommandParams>(x.Name, new (x.Name, x.CommandType, OnChanged))
) )
); );
} }
@ -258,7 +267,7 @@
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
protected override void OnAfterRender(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (!firstRender) if (!firstRender)
return; return;
@ -280,6 +289,8 @@
SelectedCollection = CollectionStr; SelectedCollection = CollectionStr;
SyncUrl(); SyncUrl();
await Authorize();
SelectAvailableCommands();
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
@ -299,6 +310,41 @@
StateHasChanged(); StateHasChanged();
} }
private void SelectAvailableCommands()
{
_userAvailableCommands = _availableCommands
.Select(x =>
x with {
Controls = x.Controls
.Where(y =>
y.CommandType switch
{
CmdType.Admin => _isAdmin,
CmdType.Read => _isRead,
CmdType.Write => _isWrite,
_ => false
})
.ToList()
})
.Where(x => x.Controls.Count > 0)
.ToList();
}
private async Task Authorize()
{
var user = (await AuthProvider.GetAuthenticationStateAsync()).User;
var readTask = AuthService.AuthorizeAsync(user, UserSession.Database, "ReadAccess");
var writeTask = AuthService.AuthorizeAsync(user, UserSession.Database, "WriteAccess");
var adminTask = AuthService.AuthorizeAsync(user, UserSession.Database, "AdminAccess");
var results = await Task.WhenAll(readTask, writeTask, adminTask);
_isRead = results[0].Succeeded;
_isWrite = results[1].Succeeded;
_isAdmin = results[2].Succeeded;
}
private async Task LoadCollections() private async Task LoadCollections()
{ {
var admin = UserSession.MongoDbAdmin; var admin = UserSession.MongoDbAdmin;
@ -372,7 +418,7 @@
return; return;
} }
var ticket = await CanExecuteCommand(); var ticket = CommandType == CmdType.Read ? "N/A" : await CanExecuteCommand();
if (string.IsNullOrWhiteSpace(ticket)) if (string.IsNullOrWhiteSpace(ticket))
return; return;

View File

@ -155,7 +155,7 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
_editContext = new(this); _editContext = new(this);
_commandParams = new("Delete Documents", OnChanged); _commandParams = new("Delete Documents", CmdType.Write, OnChanged);
} }
private void OnChanged(CmdBase.CommandParams arg) private void OnChanged(CmdBase.CommandParams arg)

View File

@ -155,7 +155,7 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
_editContext = new(this); _editContext = new(this);
_commandParams = new("Update Documents", OnChanged); _commandParams = new("Update Documents", CmdType.Write, OnChanged);
} }
private void OnChanged(CmdBase.CommandParams arg) private void OnChanged(CmdBase.CommandParams arg)

View File

@ -96,7 +96,7 @@ public class TempFileStorage : ITempFileStorage, IDisposable
{ {
if ( Directory.Exists( TempFolder ) ) if ( Directory.Exists( TempFolder ) )
{ {
SafeDeleteFolder( TempFolder ); FileUtils.SafeDeleteFolder( TempFolder );
} }
} }
@ -114,20 +114,14 @@ public class TempFileStorage : ITempFileStorage, IDisposable
foreach ( var name in outdatedFolders ) foreach ( var name in outdatedFolders )
{ {
SafeDeleteFolder(name); _log.Debug( $"Deleting temporary Folder=\"{name}\"" );
var sw = Stopwatch.StartNew();
FileUtils.SafeDeleteFolder(name);
_log.Debug( $"Temporary Folder=\"{name}\" deleted. Elapsed=\"{sw.Elapsed:g}\"" );
} }
} }
private static void SafeDeleteFolder(string name)
{
_log.Debug( $"Deleting temporary Folder=\"{name}\"" );
var sw = Stopwatch.StartNew();
Directory.Delete(name);
_log.Debug( $"Temporary Folder=\"{name}\" deleted. Elapsed=\"{sw.Elapsed:g}\"" );
}
public string TempFolder { get; } public string TempFolder { get; }
public string LocalPersistentFolder { get; } public string LocalPersistentFolder { get; }

View File

@ -66,6 +66,7 @@
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown2"> <div class="dropdown-menu" aria-labelledby="navbarDropdown2">
<NavLink class="dropdown-item" href="#">Change database</NavLink> <NavLink class="dropdown-item" href="#">Change database</NavLink>
<NavLink class="dropdown-item" href="admin/commands">Commands</NavLink>
<NavLink class="dropdown-item" href="user/browse">Browse</NavLink> <NavLink class="dropdown-item" href="user/browse">Browse</NavLink>
<NavLink class="dropdown-item" href="user/find">Find</NavLink> <NavLink class="dropdown-item" href="user/find">Find</NavLink>
<NavLink class="dropdown-item" href="user/aggregate">Aggregate</NavLink> <NavLink class="dropdown-item" href="user/aggregate">Aggregate</NavLink>