@page "/admin/roles"
@page "/admin/roles/{DatabaseStr}"
@page "/admin/roles/{DatabaseStr}/{DatabaseInstanceStr}"
@using Rms.Risk.Mango.Pivot.Core.MongoDb
@attribute [Authorize]
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject IJSRuntime JsRuntime
@inject IMongoDbServiceFactory MongoDbServiceFactory;
@*
* dbMango
*
* Copyright 2025 Deutsche Bank AG
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*@
Roles Management
@if (Error != null)
{
}
@context.RoleName
@if (context.IsBuiltin)
{
BuiltIn
}
else if (!string.IsNullOrWhiteSpace(context.Db))
{
@context.Db
}
@code {
[CascadingParameter] public IModalService Modal { get; set; } = null!;
[Parameter] public string? DatabaseStr { get; set; }
[Parameter] public string? DatabaseInstanceStr { get; set; }
private string Database
{
get => UserSession.Database;
set
{
if (UserSession.Database == value)
return;
UserSession.Database = value;
SyncUrl();
}
}
private string DatabaseInstance
{
get => UserSession.DatabaseInstance;
set
{
if (UserSession.DatabaseInstance == value)
return;
UserSession.DatabaseInstance = value;
SyncUrl();
}
}
private Exception? Error { get; set; }
private bool IsReady { get; set; }
private RoleInfoModel SelectedRole
{
get;
set
{
if ( field == value )
return;
field = value;
// Populate AllRoles with non-built-in roles from the same database and built-in roles paired with it.
AllRoles = RolesList.Where(x => !x.IsBuiltin && x.Db == SelectedRole.Db)
.Select(x => new RoleInDbModel
{
Role = x.RoleName,
Db = x.Db
}
)
.Concat(
RolesList
.Where(x => x.IsBuiltin)
.Select(x => new RoleInDbModel
{
Role = x.RoleName,
Db = ""
})
)
.DistinctBy(x => $"{x.Db}, {x.Role}")
.OrderBy(x => $"{x.Db}, {x.Role}")
.ToList()
;
EditableSelectedRole = field.Clone();
}
} = new();
private RoleInfoModel EditableSelectedRole { get; set; } = new();
private List RolesList { get; set; } = [];
private bool IsNew => RolesList.All(x => x.RoleName != EditableSelectedRole.RoleName);
private List AllDatabases { get; set; } = [];
private List AllRoles { get; set; } = [];
private List AllActions { get; set; } = [];
private bool CanUpdate => !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace(EditableSelectedRole.Db);
private bool CanDelete => !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace(EditableSelectedRole.Db) && !IsNew;
private bool IsSelectable(RoleInfoModel arg) => true;
protected override void OnAfterRender(bool firstRender)
{
if (!firstRender)
return;
if (string.IsNullOrWhiteSpace(DatabaseStr))
DatabaseStr = Database;
else
Database = DatabaseStr;
if (string.IsNullOrWhiteSpace(DatabaseInstanceStr))
DatabaseInstanceStr = DatabaseInstance;
else
DatabaseInstance = DatabaseInstanceStr;
SyncUrl();
StateHasChanged();
Task.Run(LoadRoles);
}
private Task CanExecuteCommand()
=> Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal);
private async Task Run(Func body)
{
var ticket = await CanExecuteCommand();
if (string.IsNullOrWhiteSpace(ticket))
return;
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
IsReady = false;
await InvokeAsync(StateHasChanged);
await body(ticket, cts.Token);
}
catch (Exception e)
{
await Display(e);
}
finally
{
IsReady = true;
await InvokeAsync(StateHasChanged);
}
}
private Task LoadRoles()
=> Run(async (_, token) =>
{
try
{
AllDatabases = (await UserSession.MongoDbAdminForAdminDatabase.ListDatabases(token))
.Select(x => x.Name)
.ToList();
}
catch (Exception)
{
AllDatabases = [UserSession.DatabaseInstance];
}
var adminTask = UserSession.MongoDbAdminForAdminDatabase.GetRolesInfo(showBuiltInRoles: true, token: token);
var customTasks = AllDatabases.Select(x => LoadRolesForDatabaseInstance(x, token)).ToList();
await Task.WhenAll(customTasks.Concat([adminTask]));
var admin = await adminTask;
var all = new RolesInfoModel();
all.Roles.AddRange(admin.Roles.Where(x => x.IsBuiltin));
foreach (var customTask in customTasks)
{
var custom = await customTask;
all.Roles.AddRange(custom.Roles);
}
await Display(all);
});
private Task LoadRolesForDatabaseInstance(string instanceName, CancellationToken token)
{
try
{
var db = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, instanceName);
return db.GetRolesInfo(showBuiltInRoles: false, token: token);
}
catch (Exception)
{
return Task.FromResult(new RolesInfoModel() );
}
}
private Task Display(RolesInfoModel model)
{
Error = null;
var oldRoleName = SelectedRole.RoleName;
foreach (var role in model.Roles.Where(role => role.IsBuiltin || string.IsNullOrWhiteSpace(role.Db)))
{
role.Db = "";
}
RolesList = model.Roles
.OrderBy(x => $"{x.Db}, {x.RoleName}")
.ToList()
;
SelectedRole = !string.IsNullOrWhiteSpace(oldRoleName)
? model.Roles.FirstOrDefault(x => x.RoleName == oldRoleName) ?? model.Roles.FirstOrDefault() ?? new()
: model.Roles.FirstOrDefault() ?? new RoleInfoModel()
;
AllActions = RolesList
.SelectMany(x => x.Privileges.SelectMany(y => y.Actions))
.Distinct()
.OrderBy(x => x)
.ToList()
;
return InvokeAsync(StateHasChanged);
}
private Task Display(Exception e)
{
Error = e;
RolesList.Clear();
AllRoles.Clear();
AllActions.Clear();
AddRole();
return InvokeAsync(StateHasChanged);
}
private void SyncUrl()
{
var url = NavigationManager.BaseUri + $"admin/roles/{Database}";
if (!string.IsNullOrWhiteSpace(DatabaseInstance))
url += $"/{DatabaseInstance}";
JsRuntime.InvokeAsync("DashboardUtils.ChangeUrl", url);
}
private static string GetRoleClass(RoleInfoModel r) => r.IsBuiltin ? "builtin" : "";
private static int _count;
private Task AddRole()
{
SelectedRole = new ()
{
RoleName = $"newRole{++_count}",
Db = UserSession.DatabaseInstance,
IsBuiltin = false
};
return InvokeAsync(StateHasChanged);
}
private async Task DeleteRole()
{
if ( IsNew || string.IsNullOrWhiteSpace(SelectedRole.RoleName) || SelectedRole.IsBuiltin )
{
await ModalDialogUtils.ShowInfoDialog( Modal, "Oops!", "Cannot delete the selected role.");
return;
}
var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Confirm Deletion", $"Are you sure you want to delete the role '{SelectedRole.RoleName}'?");
if (!rc.Confirmed)
return;
// Logic for deleting the selected role goes here
await Run(async (_, token) =>
{
// Attention: use the admin service corresponding to the database you are going to delete role in!
var service = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, SelectedRole.Db);
await service.DropRole(SelectedRole.RoleName, token);
await LoadRoles();
});
}
private async Task UpdateRole()
{
var role = EditableSelectedRole;
var add = IsNew;
if (role.IsBuiltin || string.IsNullOrWhiteSpace(role.Db))
{
await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Can't delete built-in role '{role.RoleName}'.");
return;
}
if (add && RolesList.Any(x => x.RoleName == role.RoleName && x.Db == role.Db ))
{
await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Role '{role.RoleName}' already exists. Please choose a different name.");
return;
}
var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Warning", $"Are you sure you want to {(add ? "add" : "update")} the role '{role.RoleName}'?");
if (!rc.Confirmed)
return;
var command = role.CreateUpdateRoleCommand(add);
// var json = command.ToJson( new () { Indent = true });
// await ModalDialogUtils.ShowTextDialog(Modal, "Oops!", json, $"Role '{role.RoleName}'");
// Proceed with updating or adding the role logic here
await Run(async (_, token) =>
{
// Attention: use the admin service corresponding to the database you are going to create role in!
var service = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, role.Db);
await service.RunCommand(command, token);
await LoadRoles();
});
}
private async Task CopyRole()
{
try
{
var json = JsonUtils.ToJson(SelectedRole, new() { WriteIndented = true });
await JsRuntime.InvokeVoidAsync("DashboardUtils.CopyToClipboard", json);
await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", "Role copied to clipboard.");
}
catch (Exception ex)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
}
private async Task PasteRole()
{
try
{
var json = await JsRuntime.InvokeAsync("DashboardUtils.PasteFromClipboard");
var clone = JsonUtils.FromJson(json);
if (clone == null)
return;
// Search for the existing role by name
var existingRole = RolesList.FirstOrDefault(x => x.RoleName == clone.RoleName);
if ( existingRole != null )
{
if ( existingRole.IsBuiltin )
{
// If the role is built-in, show a warning and do not overwrite
await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Role '{clone.RoleName}' is a built-in role and cannot be changed.");
return;
}
// If the role already exists, show a warning and do not overwrite
var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Warning", $"Role '{clone.RoleName}' already exists. Please choose a different name.");
if (!rc.Confirmed)
return;
}
SelectedRole = clone;
await InvokeAsync(StateHasChanged);
await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", $"Role {SelectedRole.RoleName} was successfully parsed. You still need to save it!");
}
catch (Exception ex)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
}
}