@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); } } }