Resolve all high-severity architecture review findings
This commit is contained in:
+27
-53
@@ -1,3 +1,7 @@
|
||||
@using Microsoft.Extensions.DependencyInjection
|
||||
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management
|
||||
@inject IServiceProvider MutationServices
|
||||
@page "/deployment/instances/{Id:int}/configure"
|
||||
@using ZB.MOM.WW.ScadaBridge.Security
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances
|
||||
@@ -5,14 +9,12 @@
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Types
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums
|
||||
@using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening
|
||||
@using ZB.MOM.WW.ScadaBridge.TemplateEngine.Services
|
||||
@using ZB.MOM.WW.ScadaBridge.DeploymentManager
|
||||
@using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Dialogs
|
||||
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDeployment)]
|
||||
@inject ITemplateEngineRepository TemplateEngineRepository
|
||||
@@ -963,8 +965,14 @@
|
||||
|
||||
// CentralUI-024: delegates to the shared helper so the claim type stays
|
||||
// resolved through JwtTokenService rather than a duplicated magic string.
|
||||
private Task<string> GetCurrentUserAsync()
|
||||
=> AuthStateProvider.GetCurrentUsernameAsync();
|
||||
private async Task<string> GetCurrentUserAsync()
|
||||
{
|
||||
var user = await AuthStateProvider.RequireRoleAsync(ZB.MOM.WW.ScadaBridge.Security.Roles.Deployer);
|
||||
var current = await TemplateEngineRepository.GetInstanceByIdAsync(Id);
|
||||
if (current is null || !await SiteScope.IsSiteAllowedAsync(current.SiteId))
|
||||
throw new UnauthorizedAccessException("The instance is outside your current site scope.");
|
||||
return user;
|
||||
}
|
||||
|
||||
// ── Bindings ────────────────────────────────────────────
|
||||
|
||||
@@ -1243,7 +1251,7 @@
|
||||
_bindingOverrides.GetValueOrDefault(kv.Key)))
|
||||
.ToList();
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await InstanceService.SetConnectionBindingsAsync(Id, bindings, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.SetConnectionBindingsAsync(Id, bindings, currentUser));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
SnapshotSavedBindingState();
|
||||
@@ -1323,8 +1331,7 @@
|
||||
{
|
||||
if (_existingOverrides.TryGetValue(attrName, out var ovr))
|
||||
{
|
||||
await TemplateEngineRepository.DeleteInstanceAttributeOverrideAsync(ovr.Id);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
await MutationServices.ExecuteConfigurationAsync(new DeleteInstanceAttributeOverrideCommand(Id, attrName));
|
||||
_existingOverrides.Remove(attrName);
|
||||
}
|
||||
_overrideValues.Remove(attrName);
|
||||
@@ -1377,7 +1384,7 @@
|
||||
var failures = new List<string>();
|
||||
foreach (var (attrName, value) in toSave)
|
||||
{
|
||||
var result = await InstanceService.SetAttributeOverrideAsync(Id, attrName, value, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.SetAttributeOverrideAsync(Id, attrName, value, currentUser));
|
||||
if (result.IsSuccess)
|
||||
_existingOverrides[attrName] = result.Value!;
|
||||
else
|
||||
@@ -1454,7 +1461,7 @@
|
||||
var failures = new List<string>();
|
||||
foreach (var (attrName, value) in outcome.Overrides)
|
||||
{
|
||||
var result = await InstanceService.SetAttributeOverrideAsync(Id, attrName, value, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.SetAttributeOverrideAsync(Id, attrName, value, currentUser));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_existingOverrides[attrName] = result.Value!;
|
||||
@@ -1622,7 +1629,7 @@
|
||||
{
|
||||
if (_existingAlarmOverrides.ContainsKey(alarmName))
|
||||
{
|
||||
var del = await InstanceService.DeleteAlarmOverrideAsync(Id, alarmName, user);
|
||||
var del = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.DeleteAlarmOverrideAsync(Id, alarmName, currentUser));
|
||||
if (!del.IsSuccess)
|
||||
{
|
||||
_editingError = del.Error;
|
||||
@@ -1639,8 +1646,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await InstanceService.SetAlarmOverrideAsync(
|
||||
Id, alarmName, overrideJson, priority, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.SetAlarmOverrideAsync(
|
||||
Id, alarmName, overrideJson, priority, currentUser));
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_editingError = result.Error;
|
||||
@@ -1675,7 +1682,7 @@
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await InstanceService.DeleteAlarmOverrideAsync(Id, alarmName, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.DeleteAlarmOverrideAsync(Id, alarmName, currentUser));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_existingAlarmOverrides.Remove(alarmName);
|
||||
@@ -1718,7 +1725,6 @@
|
||||
}
|
||||
|
||||
await UpsertNativeOverrideCore(sourceName, conn, sref, filt);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
_toast.ShowSuccess($"Saved native alarm source override on '{sourceName}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1751,27 +1757,8 @@
|
||||
/// </summary>
|
||||
private async Task UpsertNativeOverrideCore(string sourceName, string? conn, string? sref, string? filt)
|
||||
{
|
||||
var existing = await TemplateEngineRepository.GetNativeAlarmSourceOverrideAsync(Id, sourceName);
|
||||
if (existing == null)
|
||||
{
|
||||
var ovr = new InstanceNativeAlarmSourceOverride(sourceName)
|
||||
{
|
||||
InstanceId = Id,
|
||||
ConnectionNameOverride = conn,
|
||||
SourceReferenceOverride = sref,
|
||||
ConditionFilterOverride = filt
|
||||
};
|
||||
await TemplateEngineRepository.AddInstanceNativeAlarmSourceOverrideAsync(ovr);
|
||||
_existingNativeOverrides[sourceName] = ovr;
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.ConnectionNameOverride = conn;
|
||||
existing.SourceReferenceOverride = sref;
|
||||
existing.ConditionFilterOverride = filt;
|
||||
await TemplateEngineRepository.UpdateInstanceNativeAlarmSourceOverrideAsync(existing);
|
||||
_existingNativeOverrides[sourceName] = existing;
|
||||
}
|
||||
var result = await MutationServices.ExecuteConfigurationAsync(new SetInstanceNativeAlarmSourceOverrideCommand(Id, sourceName, conn, sref, filt));
|
||||
if (result is InstanceNativeAlarmSourceOverride saved) _existingNativeOverrides[sourceName] = saved;
|
||||
|
||||
// Keep the inline editor showing what was just persisted.
|
||||
_nasConnEdit[sourceName] = conn;
|
||||
@@ -1781,12 +1768,7 @@
|
||||
|
||||
private async Task ClearNativeOverrideCore(string sourceName)
|
||||
{
|
||||
var existing = await TemplateEngineRepository.GetNativeAlarmSourceOverrideAsync(Id, sourceName);
|
||||
if (existing != null)
|
||||
{
|
||||
await TemplateEngineRepository.DeleteInstanceNativeAlarmSourceOverrideAsync(existing.Id);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
}
|
||||
await MutationServices.ExecuteConfigurationAsync(new DeleteInstanceNativeAlarmSourceOverrideCommand(Id, sourceName));
|
||||
_existingNativeOverrides.Remove(sourceName);
|
||||
_nasConnEdit[sourceName] = null;
|
||||
_nasRefEdit[sourceName] = null;
|
||||
@@ -1849,17 +1831,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Every row was validated up front, so the batch applies as a unit through
|
||||
// the same repository upsert/clear the inline editor uses.
|
||||
foreach (var row in outcome.Rows)
|
||||
{
|
||||
if (row.IsClear)
|
||||
await ClearNativeOverrideCore(row.SourceName);
|
||||
else
|
||||
await UpsertNativeOverrideCore(
|
||||
row.SourceName, row.Connection, row.SourceReference, row.Filter);
|
||||
}
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
await MutationServices.ExecuteConfigurationAsync(new SetInstanceNativeAlarmSourceOverridesCommand(Id,
|
||||
outcome.Rows.Select(row => new NativeAlarmSourceOverrideEntry(row.SourceName, row.Connection, row.SourceReference, row.Filter)).ToList()));
|
||||
await OnInitializedAsync();
|
||||
|
||||
_nasCsvImportSucceeded = true;
|
||||
_nasCsvImportResult = $"Imported {outcome.Rows.Count} native alarm source override(s).";
|
||||
@@ -1957,7 +1931,7 @@
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await InstanceService.AssignToAreaAsync(Id, _reassignAreaId == 0 ? null : _reassignAreaId, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(Id, null, (instanceService, currentUser) => instanceService.AssignToAreaAsync(Id, _reassignAreaId == 0 ? null : _reassignAreaId, currentUser));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_areaName = _siteAreas.FirstOrDefault(a => a.Id == _reassignAreaId)?.Name ?? "";
|
||||
|
||||
+6
-4
@@ -1,3 +1,5 @@
|
||||
@using Microsoft.Extensions.DependencyInjection
|
||||
@inject IServiceProvider MutationServices
|
||||
@page "/deployment/instances/create"
|
||||
@using ZB.MOM.WW.ScadaBridge.Security
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances
|
||||
@@ -324,8 +326,8 @@
|
||||
try
|
||||
{
|
||||
user = await GetCurrentUserAsync();
|
||||
var result = await InstanceService.CreateInstanceAsync(
|
||||
_createName.Trim(), _createTemplateId, _createSiteId, _createAreaId == 0 ? null : _createAreaId, user);
|
||||
var result = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(null, _createSiteId, (instanceService, currentUser) => instanceService.CreateInstanceAsync(
|
||||
_createName.Trim(), _createTemplateId, _createSiteId, _createAreaId == 0 ? null : _createAreaId, currentUser));
|
||||
if (result.IsFailure)
|
||||
{
|
||||
_formError = result.Error;
|
||||
@@ -355,7 +357,7 @@
|
||||
string? bindError = null;
|
||||
try
|
||||
{
|
||||
var bind = await InstanceService.SetConnectionBindingsAsync(id, bindings, user);
|
||||
var bind = await MutationServices.GetRequiredService<ConfigurationCommandService>().ExecuteInstanceAsync(id, null, (instanceService, currentUser) => instanceService.SetConnectionBindingsAsync(id, bindings, currentUser));
|
||||
if (bind.IsFailure) bindError = bind.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -378,7 +380,7 @@
|
||||
// CentralUI-024: delegates to the shared helper so the claim type stays
|
||||
// resolved through JwtTokenService rather than a duplicated magic string.
|
||||
private Task<string> GetCurrentUserAsync()
|
||||
=> AuthStateProvider.GetCurrentUsernameAsync();
|
||||
=> AuthStateProvider.RequireRoleAsync(ZB.MOM.WW.ScadaBridge.Security.Roles.Deployer);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the folder-tree template picker. A pick clears any dangling-id hint and
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management
|
||||
@inject IServiceProvider MutationServices
|
||||
@page "/deployment/topology"
|
||||
@page "/deployment/instances"
|
||||
@using ZB.MOM.WW.ScadaBridge.Security
|
||||
@@ -705,8 +708,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await AreaService.UpdateAreaAsync(node.EntityId, _renameBuffer.Trim(), user);
|
||||
var result = await ExecuteConfigurationAsync<Area>(new UpdateAreaCommand(node.EntityId, _renameBuffer.Trim()));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
CancelRename();
|
||||
@@ -765,8 +767,7 @@
|
||||
if (req.SiteId == 0) { _createAreaError = "Select a site."; return; }
|
||||
if (string.IsNullOrWhiteSpace(req.Name)) { _createAreaError = "Area name is required."; return; }
|
||||
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await AreaService.CreateAreaAsync(req.Name, req.SiteId, req.ParentAreaId, user);
|
||||
var result = await ExecuteConfigurationAsync<Area>(new CreateAreaCommand(req.SiteId, req.Name, req.ParentAreaId));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_showCreateAreaDialog = false;
|
||||
@@ -800,8 +801,7 @@
|
||||
private async Task SubmitMoveArea((int AreaId, int? NewParentId) req)
|
||||
{
|
||||
_moveAreaError = null;
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await AreaService.MoveAreaAsync(req.AreaId, req.NewParentId, user);
|
||||
var result = await ExecuteConfigurationAsync<Area>(new MoveAreaCommand(req.AreaId, req.NewParentId));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_showMoveAreaDialog = false;
|
||||
@@ -836,8 +836,7 @@
|
||||
private async Task SubmitMoveInstance((int InstanceId, int? NewAreaId) req)
|
||||
{
|
||||
_moveInstanceError = null;
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await InstanceService.AssignToAreaAsync(req.InstanceId, req.NewAreaId, user);
|
||||
var result = await ExecuteConfigurationAsync<Instance>(new SetInstanceAreaCommand(req.InstanceId, req.NewAreaId));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_showMoveInstanceDialog = false;
|
||||
@@ -860,8 +859,7 @@
|
||||
if (!confirmed) return;
|
||||
|
||||
_actionInProgress = true;
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await AreaService.DeleteAreaAsync(node.EntityId, user);
|
||||
var result = await ExecuteConfigurationAsync<bool>(new DeleteAreaCommand(node.EntityId));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_toast.ShowSuccess($"Area '{node.Label}' deleted.");
|
||||
@@ -885,7 +883,8 @@
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var user = await GetCurrentUserAsync(inst.Id);
|
||||
if (user is null) return;
|
||||
var result = await DeploymentService.DeleteInstanceAsync(inst.Id, user);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -910,7 +909,8 @@
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var user = await GetCurrentUserAsync(inst.Id);
|
||||
if (user is null) return;
|
||||
var result = await DeploymentService.EnableInstanceAsync(inst.Id, user);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -941,7 +941,8 @@
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var user = await GetCurrentUserAsync(inst.Id);
|
||||
if (user is null) return;
|
||||
var result = await DeploymentService.DisableInstanceAsync(inst.Id, user);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -965,7 +966,8 @@
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
var user = await GetCurrentUserAsync(inst.Id);
|
||||
if (user is null) return;
|
||||
var result = await DeploymentService.DeployInstanceAsync(inst.Id, user);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -1239,6 +1241,35 @@
|
||||
|
||||
// CentralUI-024: delegates to the shared helper so the claim type stays
|
||||
// resolved through JwtTokenService rather than a duplicated magic string.
|
||||
private Task<string> GetCurrentUserAsync()
|
||||
=> AuthStateProvider.GetCurrentUsernameAsync();
|
||||
private async Task<string?> GetCurrentUserAsync(int instanceId)
|
||||
{
|
||||
var user = await AuthStateProvider.RequireRoleAsync(ZB.MOM.WW.ScadaBridge.Security.Roles.Deployer);
|
||||
// Re-fetch outside the circuit tracker: the instance may have moved since rendering.
|
||||
using var scope = ScopeFactory.CreateScope();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<ITemplateEngineRepository>();
|
||||
var instance = await repository.GetInstanceByIdAsync(instanceId);
|
||||
if (instance is null || !await SiteScope.IsSiteAllowedAsync(instance.SiteId))
|
||||
{
|
||||
_toast.ShowError("This instance is no longer available within your permitted sites.");
|
||||
_actionInProgress = false;
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task<ZB.MOM.WW.ScadaBridge.Commons.Types.Result<T>> ExecuteConfigurationAsync<T>(object command)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = await MutationServices.ExecuteConfigurationAsync(command);
|
||||
return ZB.MOM.WW.ScadaBridge.Commons.Types.Result<T>.Success((T)value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = ex is UnauthorizedAccessException
|
||||
or ZB.MOM.WW.ScadaBridge.ManagementService.ManagementCommandException
|
||||
? ex.Message : "The configuration change failed. Refresh the page and try again.";
|
||||
return ZB.MOM.WW.ScadaBridge.Commons.Types.Result<T>.Failure(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user