469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
@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.
|
|
*@
|
|
|
|
<style>
|
|
.panel{
|
|
height: calc(100vh - 90px);
|
|
}
|
|
|
|
.panel .form-control {
|
|
height: 100%;
|
|
}
|
|
|
|
.panel .list-body {
|
|
height: calc(100vh - 150px - 14px);
|
|
max-height: calc(100vh - 150px - 14px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.builtin {
|
|
color: darkgrey;
|
|
}
|
|
|
|
</style>
|
|
|
|
<h3 class="mt-3">Roles Management</h3>
|
|
|
|
<AuthorizedOnly Policy="AdminAccess" Resource="@UserSession.Database">
|
|
@if (Error != null)
|
|
{
|
|
<ExceptionControl Exception="@Error"/>
|
|
}
|
|
|
|
<SplitPanel Orientation="horizontal" InitialSplit="0.2">
|
|
<First>
|
|
<div class="panel">
|
|
<div class="mr-3">
|
|
<div class="form-row mb-3 ml-0">
|
|
<FormButton Enabled="@IsReady" Name="Refresh" Icon="icon-reload-sm" OnClick="LoadRoles" IsPrimary="false" />
|
|
<FormButton Enabled="@IsReady" Name="Add" Icon="icon-plus-sm" OnClick="AddRole" IsPrimary="false" />
|
|
</div>
|
|
|
|
|
|
<FormItemList Enabled="@IsReady" Class="mr-2" Name="Roles" @bind-Value="SelectedRole" Values="@RolesList" IsSelectable="@IsSelectable">
|
|
<OptionTemplate>
|
|
<div class="d-flex">
|
|
<div class="flex-grow-1 m-1 @GetRoleClass(context)">@context.RoleName</div>
|
|
@if (context.IsBuiltin)
|
|
{
|
|
<div class="m-1 builtin">BuiltIn</div>
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(context.Db))
|
|
{
|
|
<div class="m-1">@context.Db</div>
|
|
}
|
|
</div>
|
|
</OptionTemplate>
|
|
</FormItemList>
|
|
</div>
|
|
</div>
|
|
</First>
|
|
<Second>
|
|
<div class="panel">
|
|
<div class="ml-3">
|
|
<div class="form-row fit-content mb-3">
|
|
<FormButton Name="Save" OnClick="UpdateRole" Icon="icon-document-upload-sm" Enabled="@CanUpdate"/>
|
|
<FormButton Name="Delete" OnClick="DeleteRole" Icon="icon-trash-sm" Enabled="@CanDelete" />
|
|
<FormButton Name="" OnClick="CopyRole" Icon="icon-duplicate-document" />
|
|
<FormButton Name="" OnClick="PasteRole" Icon="icon-paste" />
|
|
</div>
|
|
|
|
<RoleEditControl
|
|
IsEnabled="@(IsReady && !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace( EditableSelectedRole.Db ))"
|
|
Value="@EditableSelectedRole"
|
|
AllRoles="@AllRoles"
|
|
AllActions="@AllActions"
|
|
AllDatabases="@AllDatabases"
|
|
IsNew="@IsNew"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Second>
|
|
</SplitPanel>
|
|
|
|
|
|
</AuthorizedOnly>
|
|
|
|
@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<RoleInfoModel> RolesList { get; set; } = [];
|
|
private bool IsNew => RolesList.All(x => x.RoleName != EditableSelectedRole.RoleName);
|
|
private List<string> AllDatabases { get; set; } = [];
|
|
private List<RoleInDbModel> AllRoles { get; set; } = [];
|
|
private List<string> 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<string?> CanExecuteCommand()
|
|
=> Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal);
|
|
|
|
private async Task Run(Func<string, CancellationToken,Task> 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<RolesInfoModel> 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<string>("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<string>("DashboardUtils.PasteFromClipboard");
|
|
|
|
var clone = JsonUtils.FromJson<RoleInfoModel>(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);
|
|
}
|
|
}
|
|
|
|
}
|