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!;
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
{
@ -148,7 +149,7 @@
Validate();
Parameters.ResultChanged = _ => OnResultReceived();
StateHasChanged();
}
}
@ -227,4 +228,10 @@
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 Rms.Risk.Mango.Components.Commands
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject IJSRuntime JsRuntime
@inject AuthenticationStateProvider AuthProvider;
@inject IAuthorizationService AuthService;
@*
* dbMango
@ -84,7 +86,7 @@
<h3 class="mt-3">Commands</h3>
<AuthorizedOnly Policy="AdminAccess" Resource="@UserSession.Database">
<AuthorizedOnly Policy="ReadAccess" Resource="@UserSession.Database">
<EditForm EditContext="EditContext">
<div class="form-row">
@ -100,10 +102,10 @@
</EditForm>
<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"/>
@foreach (var (name, control) in controls)
@foreach (var (name, _, control) in controls)
{
var parameters = new Dictionary<string, object>()
{
@ -184,7 +186,7 @@
}
} = "Statistics";
private static string[] _groupsOrder =
private static readonly string[] _groupsOrder =
[
"Informational",
"Admin",
@ -195,23 +197,30 @@
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 EditContext? _editContext;
private EditContext EditContext => _editContext!;
private string CommandName => _commandParams[SelectedCommand].Name;
private CmdType CommandType => _commandParams[SelectedCommand].CommandType;
private string CommandJson => _commandParams[SelectedCommand].CommandJson;
private bool CanExecute => _commandParams[SelectedCommand].CanExecute;
private bool NeedConfirmation => _commandParams[SelectedCommand].NeedConfirmation;
private CmdBase.CommandParams.DatabaseConnectionType RequiredConnectionType => _commandParams[SelectedCommand].RequiredConnectionType;
private static readonly List<(string Group, List<(string Name, Type Control)> Controls)> _availableCommands;
private Dictionary<string, CmdBase.CommandParams> _commandParams = null!;
private readonly Dictionary<string, Shell.ShardProperties> _shardConnectionStrings = [];
private static readonly List<(string Group, List<(string Name, CmdType CommandType, Type Control)> Controls)> _availableCommands;
private List<(string Group, List<(string Name, CmdType CommandType, Type Control)> Controls)> _userAvailableCommands = [];
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 IReadOnlyCollection<string> Shards => _shardConnectionStrings.Keys.OrderBy(x => x).ToList();
@ -232,7 +241,7 @@
})
.Where(x => x.Attr != null)
.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))
.OrderBy(x => Array.IndexOf(_groupsOrder, x.Group))
.ToList()
@ -248,7 +257,7 @@
_commandParams = new(
_availableCommands
.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);
}
protected override void OnAfterRender(bool firstRender)
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;
@ -280,6 +289,8 @@
SelectedCollection = CollectionStr;
SyncUrl();
await Authorize();
SelectAvailableCommands();
_ = Task.Run(async () =>
{
@ -299,6 +310,41 @@
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()
{
var admin = UserSession.MongoDbAdmin;
@ -372,7 +418,7 @@
return;
}
var ticket = await CanExecuteCommand();
var ticket = CommandType == CmdType.Read ? "N/A" : await CanExecuteCommand();
if (string.IsNullOrWhiteSpace(ticket))
return;

View File

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

View File

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

View File

@ -96,7 +96,7 @@ public class TempFileStorage : ITempFileStorage, IDisposable
{
if ( Directory.Exists( TempFolder ) )
{
SafeDeleteFolder( TempFolder );
FileUtils.SafeDeleteFolder( TempFolder );
}
}
@ -114,20 +114,14 @@ public class TempFileStorage : ITempFileStorage, IDisposable
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 LocalPersistentFolder { get; }

View File

@ -66,6 +66,7 @@
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown2">
<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/find">Find</NavLink>
<NavLink class="dropdown-item" href="user/aggregate">Aggregate</NavLink>