dbMango/Rms.Risk.Mango/Components/DatabaseOnboardingControl.razor
Alexander Shabarshov 2a7a24c9e7 Initial contribution
2025-11-03 14:43:26 +00:00

389 lines
14 KiB
Plaintext

@using Microsoft.Extensions.Options
@using MongoDB.Driver
@using Rms.Risk.Mango.Pivot.Core.MongoDb
@using Rms.Risk.Mango.Services.Context
@using Rms.Service.Bootstrap.Security
@inject IDatabaseConfigurationService DatabaseConfig
@inject IUserSession UserSession
@inject IJSRuntime JsRuntime
@inject IPasswordManager PasswordManager
@inject IOptions<DbMangoSettings> Settings
@inject IUserService UserService
@*
* 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>
.full-width {
width: 408px !important;
}
</style>
<EditForm EditContext="EditContext">
<div class="d-flex flex-row fit-content">
<div class="fit-content mr-3">
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="Configuration name" @bind-Value="Name" Icon="icon-document-blank-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="Contacts" @bind-Value="Value.Contacts" Icon="icon-mail-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="mr-2" Name="Database" @bind-Value="Value.Config.MongoDbDatabase" Icon="icon-annotate-sm" />
<FormItemCheckBox Class="mr-2" Name="Use TLS" @bind-Value="Value.Config.UseTls" Icon="icon-wrench-outline-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="URL" @bind-Value="Value.Config.MongoDbUrl" Icon="icon-wrench-outline-sm" />
</div>
<div class="form-row fit-content">
<FormItemCheckBox Class="mr-2" Name="Direct connection" @bind-Value="Value.Config.DirectConnection" Icon="icon-wrench-outline-sm"/>
<FormItemCheckBox Class="mr-2" Name="Shard access" @bind-Value="Value.Config.AllowShardAccess" Icon="icon-wrench-outline-sm"/>
</div>
<div class="form-row fit-content">
<FormItemText Class="mr-2" Name="Auth database" @bind-Value="AuthDatabase" Icon="icon-cog-sm" />
<FormItemText Class="mr-2" Name="Auth method" @bind-Value="AuthMethod" Icon="icon-cog-sm" />
</div>
</div>
<div class="fit-content">
<div class="d-flex flex-column">
<div class="form-row fit-content">
<FormItemText Class="mr-2" Name="User" @bind-Value="UserUser" Icon="icon-wrench-outline-sm" />
<FormItemPassword Class="mr-2" Name="Password" @bind-Value="UserPassword" Icon="icon-unlock-outline-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="mr-2" Name="Admin User" @bind-Value="AdminUser" Icon="icon-wrench-outline-sm" />
<FormItemPassword Class="mr-2" Name="Admin Password" @bind-Value="AdminPassword" Icon="icon-unlock-outline-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="LDAP Read group" @bind-Value="Value.Groups.ReadOnly" Icon="icon-layers-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="LDAP Write group" @bind-Value="Value.Groups.ReadWrite" Icon="icon-layers-sm" />
</div>
<div class="form-row fit-content">
<FormItemText Class="full-width" Name="LDAP Admin group" @bind-Value="Value.Groups.Admin" Icon="icon-layers-sm" />
</div>
</div>
</div>
</div>
<div class="form-row fit-content">
<FormButton Name="Update" OnClick="Update" Icon="icon-document-upload-sm" />
<FormButton Name="Delete" OnClick="Delete" Icon="icon-trash-sm" />
<FormButton Name="" OnClick="Copy" Icon="icon-duplicate-document" />
<FormButton Name="" OnClick="Paste" Icon="icon-paste" />
<FormButton Name="Test" OnClick="Test" Icon="icon-flame" />
</div>
</EditForm>
@code {
[CascadingParameter] public IModalService Modal { get; set; } = null!;
[Parameter] public string Name { get; set; } = "New connection";
[Parameter] public DatabasesConfig.DatabaseConfig Value { get; set; } = new();
[Parameter] public EventCallback OnRefresh { get; set; }
private EditContext EditContext => _editContext!;
private EditContext? _editContext;
private string UserUser
{
get => Value.Config.Auth?.User ?? "";
set
{
Value.Config.Auth ??= new();
Value.Config.Auth.User = value;
}
}
private string UserPassword
{
get => Value.Config.Auth?.Password ?? "";
set
{
Value.Config.Auth ??= new();
Value.Config.Auth.Password = value;
}
}
private string AuthDatabase
{
get => Value.Config.Auth?.AuthDatabase ?? "";
set
{
Value.Config.Auth ??= new();
Value.Config.Auth.AuthDatabase = value;
}
}
private string AuthMethod
{
get => Value.Config.Auth?.Method ?? "";
set
{
Value.Config.Auth ??= new();
Value.Config.Auth.Method = value;
}
}
private string AdminUser
{
get => Value.Config.AdminAuth?.User ?? "";
set
{
Value.Config.AdminAuth ??= new();
Value.Config.AdminAuth.User = value;
}
}
private string AdminPassword
{
get => Value.Config.AdminAuth?.Password ?? "";
set
{
Value.Config.AdminAuth ??= new();
Value.Config.AdminAuth.Password = value;
}
}
protected override void OnInitialized()
{
_editContext = new(this);
}
private Task<string?> CanExecuteCommand()
=> Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal);
private async Task Update()
{
var ticket = await CanExecuteCommand();
if (string.IsNullOrWhiteSpace(ticket))
return;
var res = await ModalDialogUtils.ShowConfirmationDialog(
Modal,
"Update configuration",
$"Are you sure want to update configuration for {Name}?"
);
if (res.Cancelled)
return;
try
{
PrepareForSaving();
await DatabaseConfig.Update(Name, Value, UserSession.User.GetEmail());
await ModalDialogUtils.ShowInfoDialog(Modal, "Success", $"Configuration {Name} was updated successfully.");
// exception in Audit() call can lead to 2 dialogs shown in total: Success in config change + error recording audit
try
{
await Audit(ticket, "update", Name);
}
catch (Exception ex2)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex2);
}
}
catch (Exception ex)
{
try
{
await Audit(ticket, "update", Name, ex);
}
catch (Exception ex2)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex2);
}
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
await OnRefresh.InvokeAsync();
}
private async Task Audit(string ticket, string action, string configName, Exception? exception = null)
{
var doc = new BsonDocument
{
["dbMangoOnboarding"] = exception?.Message ?? "Success",
["action"] = action,
["configName"] = configName,
["ticket"] = ticket
};
var auditRecord = new AuditRecord(UserSession.Database, DateTime.UtcNow, UserService.GetEmail(), ticket, exception == null, doc, exception?.Message);
await UserSession.Audit.Record(auditRecord);
}
private async Task Delete()
{
var ticket = await CanExecuteCommand();
if (string.IsNullOrWhiteSpace(ticket))
return;
var res = await ModalDialogUtils.ShowConfirmationDialog(
Modal,
"Delete configuration",
$"Are you sure want to delete configuration for {Name}? (Note that pre-configured databases can't be deleted.)"
);
if (res.Cancelled)
return;
try
{
await DatabaseConfig.Delete(Name, UserSession.User.GetEmail());
await ModalDialogUtils.ShowInfoDialog(Modal, "Success", $"Configuration {Name} was deleted successfully.");
try
{
await Audit(ticket, "delete", Name);
}
catch (Exception ex2)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex2);
}
}
catch (Exception ex)
{
try
{
await Audit(ticket, "delete", Name, ex);
}
catch (Exception ex2)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex2);
}
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
await OnRefresh.InvokeAsync();
}
private void PrepareForSaving()
{
if (string.IsNullOrWhiteSpace(Value.Config.Auth?.Password) || string.IsNullOrWhiteSpace(Value.Config.Auth?.User))
Value.Config.Auth = null;
if (string.IsNullOrWhiteSpace(Value.Config.AdminAuth?.Password) || string.IsNullOrWhiteSpace(Value.Config.AdminAuth?.User))
Value.Config.AdminAuth = null;
}
private class ClipboardFormat
{
public string Name { get; set; } = "";
public DatabasesConfig.DatabaseConfig Value { get; set; } = new();
}
private async Task Copy()
{
try
{
PrepareForSaving();
var obj = new ClipboardFormat
{
Name = Name,
Value = Value
};
var json = JsonUtils.ToJson(obj, new() {WriteIndented = true});
var clone = JsonUtils.FromJson<ClipboardFormat>(json);
if (clone == null)
return;
if (!string.IsNullOrWhiteSpace(clone.Value.Config.Auth?.Password))
clone.Value.Config.Auth.Password = PasswordManager.EncryptPassword(clone.Value.Config.Auth.Password);
if (!string.IsNullOrWhiteSpace(clone.Value.Config.AdminAuth?.Password))
clone.Value.Config.AdminAuth.Password = PasswordManager.EncryptPassword(clone.Value.Config.AdminAuth.Password);
json = JsonUtils.ToJson(clone, new() { WriteIndented = true });
await JsRuntime.InvokeVoidAsync("DashboardUtils.CopyToClipboard", json);
await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", $"Configuration {Name} was copied to clipboard.");
}
catch (Exception ex)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
}
private async Task Paste()
{
try
{
var json = await JsRuntime.InvokeAsync<string>("DashboardUtils.PasteFromClipboard");
var clone = JsonUtils.FromJson<ClipboardFormat>(json);
if (clone == null)
return;
if (clone.Value.Config.Auth?.Password.StartsWith('*') ?? false)
clone.Value.Config.Auth.Password = PasswordManager.DecryptPassword(clone.Value.Config.Auth.Password);
if (clone.Value.Config.AdminAuth?.Password.StartsWith('*') ?? false)
clone.Value.Config.AdminAuth.Password = PasswordManager.DecryptPassword(clone.Value.Config.AdminAuth.Password);
Name = clone.Name;
Value = clone.Value;
await InvokeAsync(StateHasChanged);
await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", $"Configuration {Name} was successfully parsed. You still need to save it!");
}
catch (Exception ex)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
}
private async Task Test()
{
try
{
var databaseInstance = !string.IsNullOrWhiteSpace(Value.Config.MongoDbDatabase) ? Value.Config.MongoDbDatabase : "admin";
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var db = MongoDbHelper.GetDatabase(Value.Config, Settings.Value.Settings, databaseInstance);
_ = await db.RunCommandAsync(
new BsonDocumentCommand<BsonDocument>(BsonDocument.Parse( @"{ ""ping"" : 1 }")),
cancellationToken: cts.Token);
await ModalDialogUtils.ShowInfoDialog(Modal, "Success", $"Successfully connected to {Name}.");
}
catch (Exception ex)
{
await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
}
}
}