751248feb6
Adds a new HiLo alarm trigger type with four configurable setpoints
(LoLo / Lo / Hi / HiHi). Each setpoint carries an optional priority,
deadband (for hysteresis), and operator message. The site runtime emits
AlarmStateChanged with an AlarmLevel field so consumers can differentiate
warning vs critical bands.
Plumbing:
- new AlarmLevel enum + AlarmStateChanged.Level/Message init properties
- AlarmTriggerEditor (Blazor) gets a HiLo render with severity tinting
- AlarmTriggerConfigCodec extracted from the editor for testability
- sitestream.proto carries level + message over gRPC
- SemanticValidator enforces numeric attribute, setpoint ordering,
non-negative deadband
- on-trigger scripts get an Alarm global (Name/Level/Priority/Message)
so notification routing can branch by severity
- per-instance InstanceAlarmOverride entity + EF migration + flattening
step + CLI commands; HiLo overrides merge setpoint-by-setpoint, binary
types whole-replace
- DebugView shows a Level badge + per-band message tooltip
- App.razor auto-reloads on permanent Blazor circuit failure
- docker/regen-proto.sh automates the proto regen workflow (the linux/arm64
protoc segfault means generated files are checked in for now)
1516 lines
75 KiB
C#
1516 lines
75 KiB
C#
using System.Security.Cryptography;
|
|
using Akka.Actor;
|
|
using Newtonsoft.Json;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using ScadaLink.Commons.Entities.ExternalSystems;
|
|
using ScadaLink.Commons.Entities.InboundApi;
|
|
using ScadaLink.Commons.Entities.Instances;
|
|
using ScadaLink.Commons.Entities.Scripts;
|
|
using ScadaLink.Commons.Entities.Templates;
|
|
using ScadaLink.Commons.Entities.Notifications;
|
|
using ScadaLink.Commons.Entities.Security;
|
|
using ScadaLink.Commons.Entities.Sites;
|
|
using ScadaLink.Commons.Interfaces.Repositories;
|
|
using ScadaLink.Commons.Interfaces.Services;
|
|
using ScadaLink.Commons.Messages.DebugView;
|
|
using ScadaLink.Commons.Messages.Management;
|
|
using ScadaLink.Commons.Messages.RemoteQuery;
|
|
using ScadaLink.DeploymentManager;
|
|
using ScadaLink.HealthMonitoring;
|
|
using ScadaLink.Communication;
|
|
using ScadaLink.Security;
|
|
using ScadaLink.TemplateEngine;
|
|
using ScadaLink.TemplateEngine.Services;
|
|
|
|
namespace ScadaLink.ManagementService;
|
|
|
|
/// <summary>
|
|
/// Central actor that handles all management commands from the CLI (via ClusterClient).
|
|
/// Receives ManagementEnvelope messages, authorizes based on roles, then delegates to
|
|
/// the appropriate service or repository using scoped DI.
|
|
/// </summary>
|
|
public class ManagementActor : ReceiveActor
|
|
{
|
|
private readonly IServiceProvider _serviceProvider;
|
|
private readonly ILogger<ManagementActor> _logger;
|
|
|
|
public ManagementActor(IServiceProvider serviceProvider, ILogger<ManagementActor> logger)
|
|
{
|
|
_serviceProvider = serviceProvider;
|
|
_logger = logger;
|
|
Receive<ManagementEnvelope>(HandleEnvelope);
|
|
}
|
|
|
|
private void HandleEnvelope(ManagementEnvelope envelope)
|
|
{
|
|
var sender = Sender;
|
|
var correlationId = envelope.CorrelationId;
|
|
var user = envelope.User;
|
|
|
|
// Check authorization
|
|
var requiredRole = GetRequiredRole(envelope.Command);
|
|
if (requiredRole != null && !user.Roles.Contains(requiredRole, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
sender.Tell(new ManagementUnauthorized(correlationId,
|
|
$"Role '{requiredRole}' required for {envelope.Command.GetType().Name}"));
|
|
return;
|
|
}
|
|
|
|
// Process command asynchronously with scoped DI
|
|
Task.Run(async () =>
|
|
{
|
|
using var scope = _serviceProvider.CreateScope();
|
|
try
|
|
{
|
|
var result = await DispatchCommand(scope.ServiceProvider, envelope.Command, user);
|
|
var json = JsonConvert.SerializeObject(result, Formatting.None);
|
|
sender.Tell(new ManagementSuccess(correlationId, json));
|
|
}
|
|
catch (SiteScopeViolationException ex)
|
|
{
|
|
sender.Tell(new ManagementUnauthorized(correlationId, ex.Message));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Management command {Command} failed (CorrelationId={CorrelationId})",
|
|
envelope.Command.GetType().Name, correlationId);
|
|
sender.Tell(new ManagementError(correlationId, ex.Message, "COMMAND_FAILED"));
|
|
}
|
|
});
|
|
}
|
|
|
|
private static string? GetRequiredRole(object command) => command switch
|
|
{
|
|
// Admin operations
|
|
CreateSiteCommand or UpdateSiteCommand or DeleteSiteCommand
|
|
or ListRoleMappingsCommand or CreateRoleMappingCommand
|
|
or UpdateRoleMappingCommand or DeleteRoleMappingCommand
|
|
or ListApiKeysCommand or CreateApiKeyCommand or DeleteApiKeyCommand
|
|
or UpdateApiKeyCommand
|
|
or ListScopeRulesCommand or AddScopeRuleCommand or DeleteScopeRuleCommand => "Admin",
|
|
|
|
// Design operations
|
|
CreateAreaCommand or DeleteAreaCommand
|
|
or CreateTemplateCommand or UpdateTemplateCommand or DeleteTemplateCommand
|
|
or ValidateTemplateCommand
|
|
or CreateExternalSystemCommand or UpdateExternalSystemCommand
|
|
or DeleteExternalSystemCommand
|
|
or CreateExternalSystemMethodCommand or UpdateExternalSystemMethodCommand
|
|
or DeleteExternalSystemMethodCommand
|
|
or CreateNotificationListCommand or UpdateNotificationListCommand
|
|
or DeleteNotificationListCommand
|
|
or UpdateSmtpConfigCommand
|
|
or CreateDataConnectionCommand or UpdateDataConnectionCommand
|
|
or DeleteDataConnectionCommand
|
|
or AddTemplateAttributeCommand or UpdateTemplateAttributeCommand or DeleteTemplateAttributeCommand
|
|
or AddTemplateAlarmCommand or UpdateTemplateAlarmCommand or DeleteTemplateAlarmCommand
|
|
or AddTemplateScriptCommand or UpdateTemplateScriptCommand or DeleteTemplateScriptCommand
|
|
or AddTemplateCompositionCommand or DeleteTemplateCompositionCommand
|
|
or CreateSharedScriptCommand or UpdateSharedScriptCommand or DeleteSharedScriptCommand
|
|
or CreateDatabaseConnectionDefCommand or UpdateDatabaseConnectionDefCommand or DeleteDatabaseConnectionDefCommand
|
|
or CreateApiMethodCommand or UpdateApiMethodCommand or DeleteApiMethodCommand
|
|
or UpdateAreaCommand
|
|
or CreateTemplateFolderCommand or RenameTemplateFolderCommand
|
|
or MoveTemplateFolderCommand or DeleteTemplateFolderCommand
|
|
or MoveTemplateToFolderCommand => "Design",
|
|
|
|
// Deployment operations
|
|
CreateInstanceCommand or MgmtDeployInstanceCommand or MgmtEnableInstanceCommand
|
|
or MgmtDisableInstanceCommand or MgmtDeleteInstanceCommand
|
|
or SetConnectionBindingsCommand or SetInstanceOverridesCommand or SetInstanceAreaCommand
|
|
or SetInstanceAlarmOverrideCommand or DeleteInstanceAlarmOverrideCommand
|
|
or GetDeploymentDiffCommand
|
|
or MgmtDeployArtifactsCommand
|
|
or RetryParkedMessageCommand or DiscardParkedMessageCommand
|
|
or DebugSnapshotCommand => "Deployment",
|
|
|
|
// Read-only queries -- any authenticated user
|
|
_ => null
|
|
};
|
|
|
|
private async Task<object?> DispatchCommand(IServiceProvider sp, object command, AuthenticatedUser user)
|
|
{
|
|
return command switch
|
|
{
|
|
// Templates
|
|
ListTemplatesCommand => await HandleListTemplates(sp),
|
|
GetTemplateCommand cmd => await HandleGetTemplate(sp, cmd),
|
|
CreateTemplateCommand cmd => await HandleCreateTemplate(sp, cmd, user.Username),
|
|
UpdateTemplateCommand cmd => await HandleUpdateTemplate(sp, cmd, user.Username),
|
|
DeleteTemplateCommand cmd => await HandleDeleteTemplate(sp, cmd, user.Username),
|
|
ValidateTemplateCommand cmd => await HandleValidateTemplate(sp, cmd),
|
|
|
|
// Template members
|
|
AddTemplateAttributeCommand cmd => await HandleAddAttribute(sp, cmd, user.Username),
|
|
UpdateTemplateAttributeCommand cmd => await HandleUpdateAttribute(sp, cmd, user.Username),
|
|
DeleteTemplateAttributeCommand cmd => await HandleDeleteAttribute(sp, cmd, user.Username),
|
|
AddTemplateAlarmCommand cmd => await HandleAddAlarm(sp, cmd, user.Username),
|
|
UpdateTemplateAlarmCommand cmd => await HandleUpdateAlarm(sp, cmd, user.Username),
|
|
DeleteTemplateAlarmCommand cmd => await HandleDeleteAlarm(sp, cmd, user.Username),
|
|
AddTemplateScriptCommand cmd => await HandleAddScript(sp, cmd, user.Username),
|
|
UpdateTemplateScriptCommand cmd => await HandleUpdateScript(sp, cmd, user.Username),
|
|
DeleteTemplateScriptCommand cmd => await HandleDeleteScript(sp, cmd, user.Username),
|
|
AddTemplateCompositionCommand cmd => await HandleAddComposition(sp, cmd, user.Username),
|
|
DeleteTemplateCompositionCommand cmd => await HandleDeleteComposition(sp, cmd, user.Username),
|
|
|
|
// Template folders
|
|
ListTemplateFoldersCommand => await HandleListTemplateFolders(sp),
|
|
CreateTemplateFolderCommand cmd => await HandleCreateTemplateFolder(sp, cmd, user.Username),
|
|
RenameTemplateFolderCommand cmd => await HandleRenameTemplateFolder(sp, cmd, user.Username),
|
|
MoveTemplateFolderCommand cmd => await HandleMoveTemplateFolder(sp, cmd, user.Username),
|
|
DeleteTemplateFolderCommand cmd => await HandleDeleteTemplateFolder(sp, cmd, user.Username),
|
|
MoveTemplateToFolderCommand cmd => await HandleMoveTemplateToFolder(sp, cmd, user.Username),
|
|
|
|
// Instances
|
|
ListInstancesCommand cmd => await HandleListInstances(sp, cmd, user),
|
|
GetInstanceCommand cmd => await HandleGetInstance(sp, cmd),
|
|
CreateInstanceCommand cmd => await HandleCreateInstance(sp, cmd, user),
|
|
MgmtDeployInstanceCommand cmd => await HandleDeployInstance(sp, cmd, user),
|
|
MgmtEnableInstanceCommand cmd => await HandleEnableInstance(sp, cmd, user),
|
|
MgmtDisableInstanceCommand cmd => await HandleDisableInstance(sp, cmd, user),
|
|
MgmtDeleteInstanceCommand cmd => await HandleDeleteInstance(sp, cmd, user),
|
|
SetConnectionBindingsCommand cmd => await HandleSetConnectionBindings(sp, cmd, user),
|
|
SetInstanceOverridesCommand cmd => await HandleSetInstanceOverrides(sp, cmd, user),
|
|
SetInstanceAreaCommand cmd => await HandleSetInstanceArea(sp, cmd, user),
|
|
SetInstanceAlarmOverrideCommand cmd => await HandleSetInstanceAlarmOverride(sp, cmd, user),
|
|
DeleteInstanceAlarmOverrideCommand cmd => await HandleDeleteInstanceAlarmOverride(sp, cmd, user),
|
|
ListInstanceAlarmOverridesCommand cmd => await HandleListInstanceAlarmOverrides(sp, cmd, user),
|
|
|
|
// Sites
|
|
ListSitesCommand => await HandleListSites(sp, user),
|
|
GetSiteCommand cmd => await HandleGetSite(sp, cmd),
|
|
CreateSiteCommand cmd => await HandleCreateSite(sp, cmd, user.Username),
|
|
UpdateSiteCommand cmd => await HandleUpdateSite(sp, cmd, user.Username),
|
|
DeleteSiteCommand cmd => await HandleDeleteSite(sp, cmd, user.Username),
|
|
ListAreasCommand cmd => await HandleListAreas(sp, cmd),
|
|
CreateAreaCommand cmd => await HandleCreateArea(sp, cmd, user.Username),
|
|
DeleteAreaCommand cmd => await HandleDeleteArea(sp, cmd, user.Username),
|
|
UpdateAreaCommand cmd => await HandleUpdateArea(sp, cmd, user.Username),
|
|
|
|
// Data Connections
|
|
ListDataConnectionsCommand cmd => await HandleListDataConnections(sp, cmd),
|
|
GetDataConnectionCommand cmd => await HandleGetDataConnection(sp, cmd),
|
|
CreateDataConnectionCommand cmd => await HandleCreateDataConnection(sp, cmd, user.Username),
|
|
UpdateDataConnectionCommand cmd => await HandleUpdateDataConnection(sp, cmd, user.Username),
|
|
DeleteDataConnectionCommand cmd => await HandleDeleteDataConnection(sp, cmd, user.Username),
|
|
|
|
// External Systems
|
|
ListExternalSystemsCommand => await HandleListExternalSystems(sp),
|
|
GetExternalSystemCommand cmd => await HandleGetExternalSystem(sp, cmd),
|
|
CreateExternalSystemCommand cmd => await HandleCreateExternalSystem(sp, cmd, user.Username),
|
|
UpdateExternalSystemCommand cmd => await HandleUpdateExternalSystem(sp, cmd, user.Username),
|
|
DeleteExternalSystemCommand cmd => await HandleDeleteExternalSystem(sp, cmd, user.Username),
|
|
ListExternalSystemMethodsCommand cmd => await HandleListExternalSystemMethods(sp, cmd),
|
|
GetExternalSystemMethodCommand cmd => await HandleGetExternalSystemMethod(sp, cmd),
|
|
CreateExternalSystemMethodCommand cmd => await HandleCreateExternalSystemMethod(sp, cmd, user.Username),
|
|
UpdateExternalSystemMethodCommand cmd => await HandleUpdateExternalSystemMethod(sp, cmd, user.Username),
|
|
DeleteExternalSystemMethodCommand cmd => await HandleDeleteExternalSystemMethod(sp, cmd, user.Username),
|
|
|
|
// Notification Lists
|
|
ListNotificationListsCommand => await HandleListNotificationLists(sp),
|
|
GetNotificationListCommand cmd => await HandleGetNotificationList(sp, cmd),
|
|
CreateNotificationListCommand cmd => await HandleCreateNotificationList(sp, cmd, user.Username),
|
|
UpdateNotificationListCommand cmd => await HandleUpdateNotificationList(sp, cmd, user.Username),
|
|
DeleteNotificationListCommand cmd => await HandleDeleteNotificationList(sp, cmd, user.Username),
|
|
ListSmtpConfigsCommand => await HandleListSmtpConfigs(sp),
|
|
UpdateSmtpConfigCommand cmd => await HandleUpdateSmtpConfig(sp, cmd, user.Username),
|
|
|
|
// Shared Scripts
|
|
ListSharedScriptsCommand => await HandleListSharedScripts(sp),
|
|
GetSharedScriptCommand cmd => await HandleGetSharedScript(sp, cmd),
|
|
CreateSharedScriptCommand cmd => await HandleCreateSharedScript(sp, cmd, user.Username),
|
|
UpdateSharedScriptCommand cmd => await HandleUpdateSharedScript(sp, cmd, user.Username),
|
|
DeleteSharedScriptCommand cmd => await HandleDeleteSharedScript(sp, cmd, user.Username),
|
|
|
|
// Database Connections (External System)
|
|
ListDatabaseConnectionsCommand => await HandleListDatabaseConnections(sp),
|
|
GetDatabaseConnectionCommand cmd => await HandleGetDatabaseConnection(sp, cmd),
|
|
CreateDatabaseConnectionDefCommand cmd => await HandleCreateDatabaseConnection(sp, cmd, user.Username),
|
|
UpdateDatabaseConnectionDefCommand cmd => await HandleUpdateDatabaseConnection(sp, cmd, user.Username),
|
|
DeleteDatabaseConnectionDefCommand cmd => await HandleDeleteDatabaseConnection(sp, cmd, user.Username),
|
|
|
|
// Inbound API Methods
|
|
ListApiMethodsCommand => await HandleListApiMethods(sp),
|
|
GetApiMethodCommand cmd => await HandleGetApiMethod(sp, cmd),
|
|
CreateApiMethodCommand cmd => await HandleCreateApiMethod(sp, cmd, user.Username),
|
|
UpdateApiMethodCommand cmd => await HandleUpdateApiMethod(sp, cmd, user.Username),
|
|
DeleteApiMethodCommand cmd => await HandleDeleteApiMethod(sp, cmd, user.Username),
|
|
|
|
// Security
|
|
ListRoleMappingsCommand => await HandleListRoleMappings(sp),
|
|
CreateRoleMappingCommand cmd => await HandleCreateRoleMapping(sp, cmd, user.Username),
|
|
UpdateRoleMappingCommand cmd => await HandleUpdateRoleMapping(sp, cmd, user.Username),
|
|
DeleteRoleMappingCommand cmd => await HandleDeleteRoleMapping(sp, cmd, user.Username),
|
|
ListApiKeysCommand => await HandleListApiKeys(sp),
|
|
CreateApiKeyCommand cmd => await HandleCreateApiKey(sp, cmd, user.Username),
|
|
DeleteApiKeyCommand cmd => await HandleDeleteApiKey(sp, cmd, user.Username),
|
|
UpdateApiKeyCommand cmd => await HandleUpdateApiKey(sp, cmd, user.Username),
|
|
ListScopeRulesCommand cmd => await HandleListScopeRules(sp, cmd),
|
|
AddScopeRuleCommand cmd => await HandleAddScopeRule(sp, cmd, user.Username),
|
|
DeleteScopeRuleCommand cmd => await HandleDeleteScopeRule(sp, cmd, user.Username),
|
|
|
|
// Deployments
|
|
MgmtDeployArtifactsCommand cmd => await HandleDeployArtifacts(sp, cmd, user.Username),
|
|
QueryDeploymentsCommand cmd => await HandleQueryDeployments(sp, cmd),
|
|
GetDeploymentDiffCommand cmd => await HandleGetDeploymentDiff(sp, cmd, user),
|
|
|
|
// Audit Log
|
|
QueryAuditLogCommand cmd => await HandleQueryAuditLog(sp, cmd),
|
|
|
|
// Health
|
|
GetHealthSummaryCommand => HandleGetHealthSummary(sp),
|
|
GetSiteHealthCommand cmd => HandleGetSiteHealth(sp, cmd),
|
|
|
|
// Remote Queries
|
|
QueryEventLogsCommand cmd => await HandleQueryEventLogs(sp, cmd),
|
|
QueryParkedMessagesCommand cmd => await HandleQueryParkedMessages(sp, cmd),
|
|
RetryParkedMessageCommand cmd => await HandleRetryParkedMessage(sp, cmd),
|
|
DiscardParkedMessageCommand cmd => await HandleDiscardParkedMessage(sp, cmd),
|
|
DebugSnapshotCommand cmd => await HandleDebugSnapshot(sp, cmd),
|
|
|
|
// Role resolution (for CLI LDAP auth)
|
|
ResolveRolesCommand cmd => await HandleResolveRoles(sp, cmd),
|
|
|
|
_ => throw new NotSupportedException($"Unknown command type: {command.GetType().Name}")
|
|
};
|
|
}
|
|
|
|
// ========================================================================
|
|
// Role resolution
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleResolveRoles(IServiceProvider sp, ResolveRolesCommand cmd)
|
|
{
|
|
var roleMapper = new RoleMapper(sp.GetRequiredService<ISecurityRepository>());
|
|
var result = await roleMapper.MapGroupsToRolesAsync(cmd.LdapGroups);
|
|
return new
|
|
{
|
|
Roles = result.Roles,
|
|
PermittedSiteIds = result.PermittedSiteIds,
|
|
IsSystemWideDeployment = result.IsSystemWideDeployment
|
|
};
|
|
}
|
|
|
|
// ========================================================================
|
|
// Site-scope enforcement
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// Throws SiteScopeViolationException if the user has site-scoped Deployment
|
|
/// and the target site is not in their permitted sites.
|
|
/// Users with Admin or Design roles, or system-wide Deployment, are not restricted.
|
|
/// </summary>
|
|
private static void EnforceSiteScope(AuthenticatedUser user, int? targetSiteId)
|
|
{
|
|
if (targetSiteId == null) return;
|
|
if (user.PermittedSiteIds.Length == 0) return; // system-wide access
|
|
if (user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase)) return;
|
|
|
|
if (!user.PermittedSiteIds.Contains(targetSiteId.Value.ToString()))
|
|
{
|
|
throw new SiteScopeViolationException(
|
|
$"Access denied: your Deployment role is scoped to sites [{string.Join(", ", user.PermittedSiteIds)}] " +
|
|
$"and does not include site {targetSiteId.Value}.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the site ID for an instance and enforces site-scope.
|
|
/// </summary>
|
|
private static async Task EnforceSiteScopeForInstance(IServiceProvider sp, AuthenticatedUser user, int instanceId)
|
|
{
|
|
if (user.PermittedSiteIds.Length == 0) return;
|
|
if (user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase)) return;
|
|
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
var instance = await repo.GetInstanceByIdAsync(instanceId);
|
|
if (instance != null)
|
|
EnforceSiteScope(user, instance.SiteId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to log an audit entry after a successful mutation.
|
|
/// </summary>
|
|
private static async Task AuditAsync(IServiceProvider sp, string user, string action, string entityType, string entityId, string entityName, object? afterState)
|
|
{
|
|
var auditService = sp.GetRequiredService<IAuditService>();
|
|
await auditService.LogAsync(user, action, entityType, entityId, entityName, afterState);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Template handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListTemplates(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
return await repo.GetAllTemplatesAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetTemplate(IServiceProvider sp, GetTemplateCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
return await repo.GetTemplateWithChildrenAsync(cmd.TemplateId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateTemplate(IServiceProvider sp, CreateTemplateCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.CreateTemplateAsync(cmd.Name, cmd.Description, cmd.ParentTemplateId, user);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateTemplate(IServiceProvider sp, UpdateTemplateCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.UpdateTemplateAsync(cmd.TemplateId, cmd.Name, cmd.Description, cmd.ParentTemplateId, user);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteTemplate(IServiceProvider sp, DeleteTemplateCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.DeleteTemplateAsync(cmd.TemplateId, user);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleValidateTemplate(IServiceProvider sp, ValidateTemplateCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
|
|
// Load the template with all members
|
|
var template = await repo.GetTemplateWithChildrenAsync(cmd.TemplateId)
|
|
?? throw new InvalidOperationException($"Template with ID {cmd.TemplateId} not found.");
|
|
|
|
var attributes = await repo.GetAttributesByTemplateIdAsync(cmd.TemplateId);
|
|
var alarms = await repo.GetAlarmsByTemplateIdAsync(cmd.TemplateId);
|
|
var scripts = await repo.GetScriptsByTemplateIdAsync(cmd.TemplateId);
|
|
|
|
// Build a FlattenedConfiguration from the template for the full validation pipeline
|
|
var flatConfig = new Commons.Types.Flattening.FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = $"validation-{template.Name}",
|
|
TemplateId = template.Id,
|
|
Attributes = attributes.Select(a => new Commons.Types.Flattening.ResolvedAttribute
|
|
{
|
|
CanonicalName = a.Name,
|
|
Value = a.Value,
|
|
DataType = a.DataType.ToString(),
|
|
IsLocked = a.IsLocked,
|
|
DataSourceReference = a.DataSourceReference
|
|
}).ToList(),
|
|
Alarms = alarms.Select(a => new Commons.Types.Flattening.ResolvedAlarm
|
|
{
|
|
CanonicalName = a.Name,
|
|
PriorityLevel = a.PriorityLevel,
|
|
IsLocked = a.IsLocked,
|
|
TriggerType = a.TriggerType.ToString(),
|
|
TriggerConfiguration = a.TriggerConfiguration
|
|
}).ToList(),
|
|
Scripts = scripts.Select(s => new Commons.Types.Flattening.ResolvedScript
|
|
{
|
|
CanonicalName = s.Name,
|
|
Code = s.Code,
|
|
IsLocked = s.IsLocked,
|
|
TriggerType = s.TriggerType,
|
|
TriggerConfiguration = s.TriggerConfiguration,
|
|
ParameterDefinitions = s.ParameterDefinitions,
|
|
ReturnDefinition = s.ReturnDefinition
|
|
}).ToList()
|
|
};
|
|
|
|
// Run full validation pipeline (collisions, script compilation, trigger refs, bindings)
|
|
var validationService = new TemplateEngine.Validation.ValidationService();
|
|
var validationResult = validationService.Validate(flatConfig);
|
|
|
|
// Also detect naming collisions across the inheritance/composition graph
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var collisions = await svc.DetectCollisionsAsync(cmd.TemplateId);
|
|
if (collisions.Count > 0)
|
|
{
|
|
var collisionErrors = collisions.Select(c =>
|
|
Commons.Types.Flattening.ValidationEntry.Error(
|
|
Commons.Types.Flattening.ValidationCategory.NamingCollision, c)).ToArray();
|
|
var collisionResult = new Commons.Types.Flattening.ValidationResult { Errors = collisionErrors };
|
|
validationResult = Commons.Types.Flattening.ValidationResult.Merge(validationResult, collisionResult);
|
|
}
|
|
|
|
return validationResult;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Template folder handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListTemplateFolders(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
return await repo.GetAllFoldersAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateTemplateFolder(IServiceProvider sp, CreateTemplateFolderCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateFolderService>();
|
|
var result = await svc.CreateFolderAsync(cmd.Name, cmd.ParentFolderId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleRenameTemplateFolder(IServiceProvider sp, RenameTemplateFolderCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateFolderService>();
|
|
var result = await svc.RenameFolderAsync(cmd.FolderId, cmd.NewName, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleMoveTemplateFolder(IServiceProvider sp, MoveTemplateFolderCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateFolderService>();
|
|
var result = await svc.MoveFolderAsync(cmd.FolderId, cmd.NewParentFolderId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteTemplateFolder(IServiceProvider sp, DeleteTemplateFolderCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateFolderService>();
|
|
var result = await svc.DeleteFolderAsync(cmd.FolderId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleMoveTemplateToFolder(IServiceProvider sp, MoveTemplateToFolderCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.MoveTemplateAsync(cmd.TemplateId, cmd.NewFolderId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Instance handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListInstances(IServiceProvider sp, ListInstancesCommand cmd, AuthenticatedUser user)
|
|
{
|
|
var repo = sp.GetRequiredService<ICentralUiRepository>();
|
|
var instances = await repo.GetInstancesFilteredAsync(cmd.SiteId, cmd.TemplateId, cmd.SearchTerm);
|
|
// Filter by permitted sites for site-scoped users
|
|
if (user.PermittedSiteIds.Length > 0 && !user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
var permittedIds = new HashSet<string>(user.PermittedSiteIds);
|
|
instances = instances.Where(i => permittedIds.Contains(i.SiteId.ToString())).ToList();
|
|
}
|
|
return instances;
|
|
}
|
|
|
|
private static async Task<object?> HandleGetInstance(IServiceProvider sp, GetInstanceCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
return await repo.GetInstanceByIdAsync(cmd.InstanceId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateInstance(IServiceProvider sp, CreateInstanceCommand cmd, AuthenticatedUser user)
|
|
{
|
|
EnforceSiteScope(user, cmd.SiteId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var result = await svc.CreateInstanceAsync(cmd.UniqueName, cmd.TemplateId, cmd.SiteId, cmd.AreaId, user.Username);
|
|
if (!result.IsSuccess) throw new InvalidOperationException(result.Error);
|
|
await AuditAsync(sp, user.Username, "Create", "Instance", result.Value.Id.ToString(), result.Value.UniqueName, result.Value);
|
|
return result.Value;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeployInstance(IServiceProvider sp, MgmtDeployInstanceCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<DeploymentService>();
|
|
var result = await svc.DeployInstanceAsync(cmd.InstanceId, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleEnableInstance(IServiceProvider sp, MgmtEnableInstanceCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<DeploymentService>();
|
|
var result = await svc.EnableInstanceAsync(cmd.InstanceId, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDisableInstance(IServiceProvider sp, MgmtDisableInstanceCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<DeploymentService>();
|
|
var result = await svc.DisableInstanceAsync(cmd.InstanceId, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteInstance(IServiceProvider sp, MgmtDeleteInstanceCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<DeploymentService>();
|
|
var result = await svc.DeleteInstanceAsync(cmd.InstanceId, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleSetConnectionBindings(IServiceProvider sp, SetConnectionBindingsCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var result = await svc.SetConnectionBindingsAsync(cmd.InstanceId, cmd.Bindings, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleSetInstanceOverrides(IServiceProvider sp, SetInstanceOverridesCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var results = new List<InstanceAttributeOverride>();
|
|
foreach (var (attrName, overrideValue) in cmd.Overrides)
|
|
{
|
|
var result = await svc.SetAttributeOverrideAsync(cmd.InstanceId, attrName, overrideValue, user.Username);
|
|
if (!result.IsSuccess) throw new InvalidOperationException(result.Error);
|
|
results.Add(result.Value);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static async Task<object?> HandleSetInstanceArea(IServiceProvider sp, SetInstanceAreaCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var result = await svc.AssignToAreaAsync(cmd.InstanceId, cmd.AreaId, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleSetInstanceAlarmOverride(IServiceProvider sp, SetInstanceAlarmOverrideCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var result = await svc.SetAlarmOverrideAsync(
|
|
cmd.InstanceId, cmd.AlarmCanonicalName,
|
|
cmd.TriggerConfigurationOverride, cmd.PriorityLevelOverride,
|
|
user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteInstanceAlarmOverride(IServiceProvider sp, DeleteInstanceAlarmOverrideCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<InstanceService>();
|
|
var result = await svc.DeleteAlarmOverrideAsync(
|
|
cmd.InstanceId, cmd.AlarmCanonicalName, user.Username);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleListInstanceAlarmOverrides(IServiceProvider sp, ListInstanceAlarmOverridesCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
return await repo.GetAlarmOverridesByInstanceIdAsync(cmd.InstanceId);
|
|
}
|
|
|
|
private static async Task<object?> HandleGetDeploymentDiff(IServiceProvider sp, GetDeploymentDiffCommand cmd, AuthenticatedUser user)
|
|
{
|
|
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
|
|
var svc = sp.GetRequiredService<DeploymentService>();
|
|
var result = await svc.GetDeploymentComparisonAsync(cmd.InstanceId);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleRetryParkedMessage(IServiceProvider sp, RetryParkedMessageCommand cmd)
|
|
{
|
|
var commService = sp.GetRequiredService<CommunicationService>();
|
|
var request = new Commons.Messages.RemoteQuery.ParkedMessageRetryRequest(
|
|
Guid.NewGuid().ToString("N"), cmd.SiteIdentifier, cmd.MessageId, DateTimeOffset.UtcNow);
|
|
return await commService.RetryParkedMessageAsync(cmd.SiteIdentifier, request);
|
|
}
|
|
|
|
private static async Task<object?> HandleDiscardParkedMessage(IServiceProvider sp, DiscardParkedMessageCommand cmd)
|
|
{
|
|
var commService = sp.GetRequiredService<CommunicationService>();
|
|
var request = new Commons.Messages.RemoteQuery.ParkedMessageDiscardRequest(
|
|
Guid.NewGuid().ToString("N"), cmd.SiteIdentifier, cmd.MessageId, DateTimeOffset.UtcNow);
|
|
return await commService.DiscardParkedMessageAsync(cmd.SiteIdentifier, request);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Site handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListSites(IServiceProvider sp, AuthenticatedUser user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var sites = await repo.GetAllSitesAsync();
|
|
if (user.PermittedSiteIds.Length > 0 && !user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
var permittedIds = new HashSet<string>(user.PermittedSiteIds);
|
|
sites = sites.Where(s => permittedIds.Contains(s.Id.ToString())).ToList();
|
|
}
|
|
return sites;
|
|
}
|
|
|
|
private static async Task<object?> HandleGetSite(IServiceProvider sp, GetSiteCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
return await repo.GetSiteByIdAsync(cmd.SiteId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateSite(IServiceProvider sp, CreateSiteCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var site = new Site(cmd.Name, cmd.SiteIdentifier)
|
|
{
|
|
Description = cmd.Description,
|
|
NodeAAddress = cmd.NodeAAddress,
|
|
NodeBAddress = cmd.NodeBAddress,
|
|
GrpcNodeAAddress = cmd.GrpcNodeAAddress,
|
|
GrpcNodeBAddress = cmd.GrpcNodeBAddress
|
|
};
|
|
await repo.AddSiteAsync(site);
|
|
await repo.SaveChangesAsync();
|
|
var commService = sp.GetService<CommunicationService>();
|
|
commService?.RefreshSiteAddresses();
|
|
await AuditAsync(sp, user, "Create", "Site", site.Id.ToString(), site.Name, site);
|
|
return site;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateSite(IServiceProvider sp, UpdateSiteCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var site = await repo.GetSiteByIdAsync(cmd.SiteId)
|
|
?? throw new InvalidOperationException($"Site with ID {cmd.SiteId} not found.");
|
|
site.Name = cmd.Name;
|
|
site.Description = cmd.Description;
|
|
site.NodeAAddress = cmd.NodeAAddress;
|
|
site.NodeBAddress = cmd.NodeBAddress;
|
|
site.GrpcNodeAAddress = cmd.GrpcNodeAAddress;
|
|
site.GrpcNodeBAddress = cmd.GrpcNodeBAddress;
|
|
await repo.UpdateSiteAsync(site);
|
|
await repo.SaveChangesAsync();
|
|
var commService = sp.GetService<CommunicationService>();
|
|
commService?.RefreshSiteAddresses();
|
|
await AuditAsync(sp, user, "Update", "Site", site.Id.ToString(), site.Name, site);
|
|
return site;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteSite(IServiceProvider sp, DeleteSiteCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var site = await repo.GetSiteByIdAsync(cmd.SiteId);
|
|
var instances = await repo.GetInstancesBySiteIdAsync(cmd.SiteId);
|
|
if (instances.Count > 0)
|
|
throw new InvalidOperationException(
|
|
$"Cannot delete site {cmd.SiteId}: it has {instances.Count} instance(s).");
|
|
await repo.DeleteSiteAsync(cmd.SiteId);
|
|
await repo.SaveChangesAsync();
|
|
var commService = sp.GetService<CommunicationService>();
|
|
commService?.RefreshSiteAddresses();
|
|
await AuditAsync(sp, user, "Delete", "Site", cmd.SiteId.ToString(), site?.Name ?? cmd.SiteId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
private static async Task<object?> HandleListAreas(IServiceProvider sp, ListAreasCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ICentralUiRepository>();
|
|
return await repo.GetAreaTreeBySiteIdAsync(cmd.SiteId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateArea(IServiceProvider sp, CreateAreaCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
var area = new Area(cmd.Name)
|
|
{
|
|
SiteId = cmd.SiteId,
|
|
ParentAreaId = cmd.ParentAreaId
|
|
};
|
|
await repo.AddAreaAsync(area);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "Area", area.Id.ToString(), area.Name, area);
|
|
return area;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteArea(IServiceProvider sp, DeleteAreaCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
await repo.DeleteAreaAsync(cmd.AreaId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "Area", cmd.AreaId.ToString(), cmd.AreaId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Data Connection handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListDataConnections(IServiceProvider sp, ListDataConnectionsCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
if (cmd.SiteId.HasValue)
|
|
return await repo.GetDataConnectionsBySiteIdAsync(cmd.SiteId.Value);
|
|
return await repo.GetAllDataConnectionsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetDataConnection(IServiceProvider sp, GetDataConnectionCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
return await repo.GetDataConnectionByIdAsync(cmd.DataConnectionId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateDataConnection(IServiceProvider sp, CreateDataConnectionCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var conn = new DataConnection(cmd.Name, cmd.Protocol, cmd.SiteId)
|
|
{
|
|
PrimaryConfiguration = cmd.PrimaryConfiguration,
|
|
BackupConfiguration = cmd.BackupConfiguration,
|
|
FailoverRetryCount = cmd.FailoverRetryCount
|
|
};
|
|
await repo.AddDataConnectionAsync(conn);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "DataConnection", conn.Id.ToString(), conn.Name, conn);
|
|
return conn;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateDataConnection(IServiceProvider sp, UpdateDataConnectionCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
var conn = await repo.GetDataConnectionByIdAsync(cmd.DataConnectionId)
|
|
?? throw new InvalidOperationException($"DataConnection with ID {cmd.DataConnectionId} not found.");
|
|
conn.Name = cmd.Name;
|
|
conn.Protocol = cmd.Protocol;
|
|
conn.PrimaryConfiguration = cmd.PrimaryConfiguration;
|
|
conn.BackupConfiguration = cmd.BackupConfiguration;
|
|
conn.FailoverRetryCount = cmd.FailoverRetryCount;
|
|
await repo.UpdateDataConnectionAsync(conn);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "DataConnection", conn.Id.ToString(), conn.Name, conn);
|
|
return conn;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteDataConnection(IServiceProvider sp, DeleteDataConnectionCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISiteRepository>();
|
|
await repo.DeleteDataConnectionAsync(cmd.DataConnectionId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "DataConnection", cmd.DataConnectionId.ToString(), cmd.DataConnectionId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
|
|
// ========================================================================
|
|
// External System handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListExternalSystems(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetAllExternalSystemsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetExternalSystem(IServiceProvider sp, GetExternalSystemCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateExternalSystem(IServiceProvider sp, CreateExternalSystemCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var def = new ExternalSystemDefinition(cmd.Name, cmd.EndpointUrl, cmd.AuthType)
|
|
{
|
|
AuthConfiguration = cmd.AuthConfiguration
|
|
};
|
|
await repo.AddExternalSystemAsync(def);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "ExternalSystem", def.Id.ToString(), def.Name, def);
|
|
return def;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateExternalSystem(IServiceProvider sp, UpdateExternalSystemCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var def = await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId)
|
|
?? throw new InvalidOperationException($"ExternalSystem with ID {cmd.ExternalSystemId} not found.");
|
|
def.Name = cmd.Name;
|
|
def.EndpointUrl = cmd.EndpointUrl;
|
|
def.AuthType = cmd.AuthType;
|
|
def.AuthConfiguration = cmd.AuthConfiguration;
|
|
await repo.UpdateExternalSystemAsync(def);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "ExternalSystem", def.Id.ToString(), def.Name, def);
|
|
return def;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteExternalSystem(IServiceProvider sp, DeleteExternalSystemCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
await repo.DeleteExternalSystemAsync(cmd.ExternalSystemId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "ExternalSystem", cmd.ExternalSystemId.ToString(), cmd.ExternalSystemId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
private static async Task<object?> HandleListExternalSystemMethods(IServiceProvider sp, ListExternalSystemMethodsCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetMethodsByExternalSystemIdAsync(cmd.ExternalSystemId);
|
|
}
|
|
|
|
private static async Task<object?> HandleGetExternalSystemMethod(IServiceProvider sp, GetExternalSystemMethodCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateExternalSystemMethod(IServiceProvider sp, CreateExternalSystemMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var method = new ExternalSystemMethod(cmd.Name, cmd.HttpMethod, cmd.Path)
|
|
{
|
|
ExternalSystemDefinitionId = cmd.ExternalSystemId,
|
|
ParameterDefinitions = cmd.ParameterDefinitions,
|
|
ReturnDefinition = cmd.ReturnDefinition
|
|
};
|
|
await repo.AddExternalSystemMethodAsync(method);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "ExternalSystemMethod", method.Id.ToString(), method.Name, method);
|
|
return method;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateExternalSystemMethod(IServiceProvider sp, UpdateExternalSystemMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var method = await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId)
|
|
?? throw new InvalidOperationException($"ExternalSystemMethod with ID {cmd.MethodId} not found.");
|
|
if (cmd.Name != null) method.Name = cmd.Name;
|
|
if (cmd.HttpMethod != null) method.HttpMethod = cmd.HttpMethod;
|
|
if (cmd.Path != null) method.Path = cmd.Path;
|
|
if (cmd.ParameterDefinitions != null) method.ParameterDefinitions = cmd.ParameterDefinitions;
|
|
if (cmd.ReturnDefinition != null) method.ReturnDefinition = cmd.ReturnDefinition;
|
|
await repo.UpdateExternalSystemMethodAsync(method);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "ExternalSystemMethod", method.Id.ToString(), method.Name, method);
|
|
return method;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteExternalSystemMethod(IServiceProvider sp, DeleteExternalSystemMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
await repo.DeleteExternalSystemMethodAsync(cmd.MethodId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "ExternalSystemMethod", cmd.MethodId.ToString(), cmd.MethodId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Notification handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListNotificationLists(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
return await repo.GetAllNotificationListsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetNotificationList(IServiceProvider sp, GetNotificationListCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
return await repo.GetNotificationListByIdAsync(cmd.NotificationListId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateNotificationList(IServiceProvider sp, CreateNotificationListCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
var list = new NotificationList(cmd.Name);
|
|
foreach (var email in cmd.RecipientEmails)
|
|
{
|
|
list.Recipients.Add(new NotificationRecipient(email, email));
|
|
}
|
|
await repo.AddNotificationListAsync(list);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "NotificationList", list.Id.ToString(), list.Name, list);
|
|
return list;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateNotificationList(IServiceProvider sp, UpdateNotificationListCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
var list = await repo.GetNotificationListByIdAsync(cmd.NotificationListId)
|
|
?? throw new InvalidOperationException($"NotificationList with ID {cmd.NotificationListId} not found.");
|
|
list.Name = cmd.Name;
|
|
|
|
var existingRecipients = await repo.GetRecipientsByListIdAsync(cmd.NotificationListId);
|
|
foreach (var r in existingRecipients)
|
|
{
|
|
await repo.DeleteRecipientAsync(r.Id);
|
|
}
|
|
|
|
foreach (var email in cmd.RecipientEmails)
|
|
{
|
|
await repo.AddRecipientAsync(new NotificationRecipient(email, email)
|
|
{
|
|
NotificationListId = cmd.NotificationListId
|
|
});
|
|
}
|
|
|
|
await repo.UpdateNotificationListAsync(list);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "NotificationList", list.Id.ToString(), list.Name, list);
|
|
return list;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteNotificationList(IServiceProvider sp, DeleteNotificationListCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
await repo.DeleteNotificationListAsync(cmd.NotificationListId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "NotificationList", cmd.NotificationListId.ToString(), cmd.NotificationListId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
private static async Task<object?> HandleListSmtpConfigs(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
return await repo.GetAllSmtpConfigurationsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateSmtpConfig(IServiceProvider sp, UpdateSmtpConfigCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<INotificationRepository>();
|
|
var config = await repo.GetSmtpConfigurationByIdAsync(cmd.SmtpConfigId)
|
|
?? throw new InvalidOperationException($"SmtpConfiguration with ID {cmd.SmtpConfigId} not found.");
|
|
config.Host = cmd.Server;
|
|
config.Port = cmd.Port;
|
|
config.AuthType = cmd.AuthMode;
|
|
config.FromAddress = cmd.FromAddress;
|
|
await repo.UpdateSmtpConfigurationAsync(config);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "SmtpConfiguration", config.Id.ToString(), config.Host, config);
|
|
return config;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Security handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListRoleMappings(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
return await repo.GetAllMappingsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateRoleMapping(IServiceProvider sp, CreateRoleMappingCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
var mapping = new LdapGroupMapping(cmd.LdapGroupName, cmd.Role);
|
|
await repo.AddMappingAsync(mapping);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
|
|
return mapping;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateRoleMapping(IServiceProvider sp, UpdateRoleMappingCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
var mapping = await repo.GetMappingByIdAsync(cmd.MappingId)
|
|
?? throw new InvalidOperationException($"RoleMapping with ID {cmd.MappingId} not found.");
|
|
mapping.LdapGroupName = cmd.LdapGroupName;
|
|
mapping.Role = cmd.Role;
|
|
await repo.UpdateMappingAsync(mapping);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
|
|
return mapping;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteRoleMapping(IServiceProvider sp, DeleteRoleMappingCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
await repo.DeleteMappingAsync(cmd.MappingId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "RoleMapping", cmd.MappingId.ToString(), cmd.MappingId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
private static async Task<object?> HandleListApiKeys(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
return await repo.GetAllApiKeysAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateApiKey(IServiceProvider sp, CreateApiKeyCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
var keyValue = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
|
var apiKey = new ApiKey(cmd.Name, keyValue) { IsEnabled = true };
|
|
await repo.AddApiKeyAsync(apiKey);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "ApiKey", apiKey.Id.ToString(), apiKey.Name, new { apiKey.Id, apiKey.Name, apiKey.IsEnabled });
|
|
return apiKey;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteApiKey(IServiceProvider sp, DeleteApiKeyCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
await repo.DeleteApiKeyAsync(cmd.ApiKeyId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "ApiKey", cmd.ApiKeyId.ToString(), cmd.ApiKeyId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Deployment handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleDeployArtifacts(IServiceProvider sp, MgmtDeployArtifactsCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<ArtifactDeploymentService>();
|
|
var result = await svc.DeployToAllSitesAsync(user);
|
|
return result.IsSuccess
|
|
? result.Value
|
|
: throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleQueryDeployments(IServiceProvider sp, QueryDeploymentsCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IDeploymentManagerRepository>();
|
|
if (cmd.InstanceId.HasValue)
|
|
return await repo.GetDeploymentsByInstanceIdAsync(cmd.InstanceId.Value);
|
|
return await repo.GetAllDeploymentRecordsAsync();
|
|
}
|
|
|
|
// ========================================================================
|
|
// Audit Log handler
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleQueryAuditLog(IServiceProvider sp, QueryAuditLogCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ICentralUiRepository>();
|
|
return await repo.GetAuditLogEntriesAsync(
|
|
cmd.User, cmd.EntityType, cmd.Action, cmd.From, cmd.To,
|
|
page: cmd.Page, pageSize: cmd.PageSize);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Health handlers
|
|
// ========================================================================
|
|
|
|
private static object? HandleGetHealthSummary(IServiceProvider sp)
|
|
{
|
|
var aggregator = sp.GetRequiredService<ICentralHealthAggregator>();
|
|
return aggregator.GetAllSiteStates();
|
|
}
|
|
|
|
private static object? HandleGetSiteHealth(IServiceProvider sp, GetSiteHealthCommand cmd)
|
|
{
|
|
var aggregator = sp.GetRequiredService<ICentralHealthAggregator>();
|
|
return aggregator.GetSiteState(cmd.SiteIdentifier);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Template member handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleAddAttribute(IServiceProvider sp, AddTemplateAttributeCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var attr = new TemplateAttribute(cmd.Name)
|
|
{
|
|
DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
|
|
Value = cmd.Value,
|
|
Description = cmd.Description,
|
|
DataSourceReference = cmd.DataSourceReference,
|
|
IsLocked = cmd.IsLocked
|
|
};
|
|
var result = await svc.AddAttributeAsync(cmd.TemplateId, attr, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateAttribute(IServiceProvider sp, UpdateTemplateAttributeCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var attr = new TemplateAttribute(cmd.Name)
|
|
{
|
|
DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
|
|
Value = cmd.Value,
|
|
Description = cmd.Description,
|
|
DataSourceReference = cmd.DataSourceReference,
|
|
IsLocked = cmd.IsLocked
|
|
};
|
|
var result = await svc.UpdateAttributeAsync(cmd.AttributeId, attr, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteAttribute(IServiceProvider sp, DeleteTemplateAttributeCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.DeleteAttributeAsync(cmd.AttributeId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleAddAlarm(IServiceProvider sp, AddTemplateAlarmCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var alarm = new TemplateAlarm(cmd.Name)
|
|
{
|
|
TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
|
|
PriorityLevel = cmd.PriorityLevel,
|
|
Description = cmd.Description,
|
|
TriggerConfiguration = cmd.TriggerConfiguration,
|
|
IsLocked = cmd.IsLocked
|
|
};
|
|
var result = await svc.AddAlarmAsync(cmd.TemplateId, alarm, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateAlarm(IServiceProvider sp, UpdateTemplateAlarmCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var alarm = new TemplateAlarm(cmd.Name)
|
|
{
|
|
TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
|
|
PriorityLevel = cmd.PriorityLevel,
|
|
Description = cmd.Description,
|
|
TriggerConfiguration = cmd.TriggerConfiguration,
|
|
IsLocked = cmd.IsLocked
|
|
};
|
|
var result = await svc.UpdateAlarmAsync(cmd.AlarmId, alarm, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteAlarm(IServiceProvider sp, DeleteTemplateAlarmCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.DeleteAlarmAsync(cmd.AlarmId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleAddScript(IServiceProvider sp, AddTemplateScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var script = new TemplateScript(cmd.Name, cmd.Code)
|
|
{
|
|
TriggerType = cmd.TriggerType,
|
|
TriggerConfiguration = cmd.TriggerConfiguration,
|
|
IsLocked = cmd.IsLocked,
|
|
ParameterDefinitions = cmd.ParameterDefinitions,
|
|
ReturnDefinition = cmd.ReturnDefinition
|
|
};
|
|
var result = await svc.AddScriptAsync(cmd.TemplateId, script, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateScript(IServiceProvider sp, UpdateTemplateScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var script = new TemplateScript(cmd.Name, cmd.Code)
|
|
{
|
|
TriggerType = cmd.TriggerType,
|
|
TriggerConfiguration = cmd.TriggerConfiguration,
|
|
IsLocked = cmd.IsLocked,
|
|
ParameterDefinitions = cmd.ParameterDefinitions,
|
|
ReturnDefinition = cmd.ReturnDefinition
|
|
};
|
|
var result = await svc.UpdateScriptAsync(cmd.ScriptId, script, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteScript(IServiceProvider sp, DeleteTemplateScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.DeleteScriptAsync(cmd.ScriptId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleAddComposition(IServiceProvider sp, AddTemplateCompositionCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.AddCompositionAsync(cmd.TemplateId, cmd.ComposedTemplateId, cmd.InstanceName, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteComposition(IServiceProvider sp, DeleteTemplateCompositionCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<TemplateService>();
|
|
var result = await svc.DeleteCompositionAsync(cmd.CompositionId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Shared Script handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListSharedScripts(IServiceProvider sp)
|
|
{
|
|
var svc = sp.GetRequiredService<SharedScriptService>();
|
|
return await svc.GetAllSharedScriptsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetSharedScript(IServiceProvider sp, GetSharedScriptCommand cmd)
|
|
{
|
|
var svc = sp.GetRequiredService<SharedScriptService>();
|
|
return await svc.GetSharedScriptByIdAsync(cmd.SharedScriptId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateSharedScript(IServiceProvider sp, CreateSharedScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<SharedScriptService>();
|
|
var result = await svc.CreateSharedScriptAsync(cmd.Name, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateSharedScript(IServiceProvider sp, UpdateSharedScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<SharedScriptService>();
|
|
var result = await svc.UpdateSharedScriptAsync(cmd.SharedScriptId, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteSharedScript(IServiceProvider sp, DeleteSharedScriptCommand cmd, string user)
|
|
{
|
|
var svc = sp.GetRequiredService<SharedScriptService>();
|
|
var result = await svc.DeleteSharedScriptAsync(cmd.SharedScriptId, user);
|
|
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Database Connection Definition handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListDatabaseConnections(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetAllDatabaseConnectionsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetDatabaseConnection(IServiceProvider sp, GetDatabaseConnectionCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
return await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateDatabaseConnection(IServiceProvider sp, CreateDatabaseConnectionDefCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var def = new DatabaseConnectionDefinition(cmd.Name, cmd.ConnectionString);
|
|
await repo.AddDatabaseConnectionAsync(def);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "DatabaseConnection", def.Id.ToString(), def.Name, new { def.Id, def.Name });
|
|
return def;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateDatabaseConnection(IServiceProvider sp, UpdateDatabaseConnectionDefCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
var def = await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId)
|
|
?? throw new InvalidOperationException($"DatabaseConnection with ID {cmd.DatabaseConnectionId} not found.");
|
|
def.Name = cmd.Name;
|
|
def.ConnectionString = cmd.ConnectionString;
|
|
await repo.UpdateDatabaseConnectionAsync(def);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "DatabaseConnection", def.Id.ToString(), def.Name, new { def.Id, def.Name });
|
|
return def;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteDatabaseConnection(IServiceProvider sp, DeleteDatabaseConnectionDefCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
|
await repo.DeleteDatabaseConnectionAsync(cmd.DatabaseConnectionId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "DatabaseConnection", cmd.DatabaseConnectionId.ToString(), cmd.DatabaseConnectionId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Inbound API Method handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleListApiMethods(IServiceProvider sp)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
return await repo.GetAllApiMethodsAsync();
|
|
}
|
|
|
|
private static async Task<object?> HandleGetApiMethod(IServiceProvider sp, GetApiMethodCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
return await repo.GetApiMethodByIdAsync(cmd.ApiMethodId);
|
|
}
|
|
|
|
private static async Task<object?> HandleCreateApiMethod(IServiceProvider sp, CreateApiMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
var method = new ApiMethod(cmd.Name, cmd.Script)
|
|
{
|
|
TimeoutSeconds = cmd.TimeoutSeconds,
|
|
ParameterDefinitions = cmd.ParameterDefinitions,
|
|
ReturnDefinition = cmd.ReturnDefinition
|
|
};
|
|
await repo.AddApiMethodAsync(method);
|
|
await repo.SaveChangesAsync();
|
|
sp.GetService<InboundAPI.InboundScriptExecutor>()?.CompileAndRegister(method);
|
|
await AuditAsync(sp, user, "Create", "ApiMethod", method.Id.ToString(), method.Name, method);
|
|
return method;
|
|
}
|
|
|
|
private static async Task<object?> HandleUpdateApiMethod(IServiceProvider sp, UpdateApiMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
var method = await repo.GetApiMethodByIdAsync(cmd.ApiMethodId)
|
|
?? throw new InvalidOperationException($"ApiMethod with ID {cmd.ApiMethodId} not found.");
|
|
method.Script = cmd.Script;
|
|
method.TimeoutSeconds = cmd.TimeoutSeconds;
|
|
method.ParameterDefinitions = cmd.ParameterDefinitions;
|
|
method.ReturnDefinition = cmd.ReturnDefinition;
|
|
await repo.UpdateApiMethodAsync(method);
|
|
await repo.SaveChangesAsync();
|
|
sp.GetService<InboundAPI.InboundScriptExecutor>()?.CompileAndRegister(method);
|
|
await AuditAsync(sp, user, "Update", "ApiMethod", method.Id.ToString(), method.Name, method);
|
|
return method;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteApiMethod(IServiceProvider sp, DeleteApiMethodCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
var method = await repo.GetApiMethodByIdAsync(cmd.ApiMethodId);
|
|
await repo.DeleteApiMethodAsync(cmd.ApiMethodId);
|
|
await repo.SaveChangesAsync();
|
|
if (method != null)
|
|
sp.GetService<InboundAPI.InboundScriptExecutor>()?.RemoveHandler(method.Name);
|
|
await AuditAsync(sp, user, "Delete", "ApiMethod", cmd.ApiMethodId.ToString(), method?.Name ?? cmd.ApiMethodId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Additional Security handlers (API key update, scope rules)
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleUpdateApiKey(IServiceProvider sp, UpdateApiKeyCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
|
var key = await repo.GetApiKeyByIdAsync(cmd.ApiKeyId)
|
|
?? throw new InvalidOperationException($"ApiKey with ID {cmd.ApiKeyId} not found.");
|
|
key.IsEnabled = cmd.IsEnabled;
|
|
await repo.UpdateApiKeyAsync(key);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "ApiKey", key.Id.ToString(), key.Name, new { key.Id, key.Name, key.IsEnabled });
|
|
return key;
|
|
}
|
|
|
|
private static async Task<object?> HandleListScopeRules(IServiceProvider sp, ListScopeRulesCommand cmd)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
return await repo.GetScopeRulesForMappingAsync(cmd.MappingId);
|
|
}
|
|
|
|
private static async Task<object?> HandleAddScopeRule(IServiceProvider sp, AddScopeRuleCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
var rule = new SiteScopeRule { LdapGroupMappingId = cmd.MappingId, SiteId = cmd.SiteId };
|
|
await repo.AddScopeRuleAsync(rule);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Create", "ScopeRule", rule.Id.ToString(), $"Mapping:{cmd.MappingId}/Site:{cmd.SiteId}", rule);
|
|
return rule;
|
|
}
|
|
|
|
private static async Task<object?> HandleDeleteScopeRule(IServiceProvider sp, DeleteScopeRuleCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ISecurityRepository>();
|
|
await repo.DeleteScopeRuleAsync(cmd.ScopeRuleId);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Delete", "ScopeRule", cmd.ScopeRuleId.ToString(), cmd.ScopeRuleId.ToString(), null);
|
|
return true;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Area update handler
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleUpdateArea(IServiceProvider sp, UpdateAreaCommand cmd, string user)
|
|
{
|
|
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
var area = await repo.GetAreaByIdAsync(cmd.AreaId)
|
|
?? throw new InvalidOperationException($"Area with ID {cmd.AreaId} not found.");
|
|
area.Name = cmd.Name;
|
|
await repo.UpdateAreaAsync(area);
|
|
await repo.SaveChangesAsync();
|
|
await AuditAsync(sp, user, "Update", "Area", area.Id.ToString(), area.Name, area);
|
|
return area;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Remote Query handlers
|
|
// ========================================================================
|
|
|
|
private static async Task<object?> HandleQueryEventLogs(IServiceProvider sp, QueryEventLogsCommand cmd)
|
|
{
|
|
var commService = sp.GetRequiredService<CommunicationService>();
|
|
var request = new EventLogQueryRequest(
|
|
Guid.NewGuid().ToString("N"),
|
|
cmd.SiteIdentifier,
|
|
cmd.From, cmd.To,
|
|
cmd.EventType, cmd.Severity,
|
|
cmd.InstanceName, // InstanceId filter
|
|
cmd.Keyword,
|
|
null, // ContinuationToken
|
|
cmd.PageSize,
|
|
DateTimeOffset.UtcNow);
|
|
return await commService.QueryEventLogsAsync(cmd.SiteIdentifier, request);
|
|
}
|
|
|
|
private static async Task<object?> HandleQueryParkedMessages(IServiceProvider sp, QueryParkedMessagesCommand cmd)
|
|
{
|
|
var commService = sp.GetRequiredService<CommunicationService>();
|
|
var request = new ParkedMessageQueryRequest(
|
|
Guid.NewGuid().ToString("N"),
|
|
cmd.SiteIdentifier,
|
|
cmd.Page,
|
|
cmd.PageSize,
|
|
DateTimeOffset.UtcNow);
|
|
return await commService.QueryParkedMessagesAsync(cmd.SiteIdentifier, request);
|
|
}
|
|
|
|
private static async Task<object?> HandleDebugSnapshot(IServiceProvider sp, DebugSnapshotCommand cmd)
|
|
{
|
|
var instanceRepo = sp.GetRequiredService<ITemplateEngineRepository>();
|
|
var instance = await instanceRepo.GetInstanceByIdAsync(cmd.InstanceId)
|
|
?? throw new InvalidOperationException($"Instance {cmd.InstanceId} not found.");
|
|
|
|
var siteRepo = sp.GetRequiredService<ISiteRepository>();
|
|
var site = await siteRepo.GetSiteByIdAsync(instance.SiteId)
|
|
?? throw new InvalidOperationException($"Site {instance.SiteId} not found.");
|
|
|
|
var commService = sp.GetRequiredService<CommunicationService>();
|
|
var request = new DebugSnapshotRequest(instance.UniqueName, Guid.NewGuid().ToString("N"));
|
|
return await commService.RequestDebugSnapshotAsync(site.SiteIdentifier, request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thrown when a site-scoped user attempts an operation on a site they don't have access to.
|
|
/// </summary>
|
|
public class SiteScopeViolationException : Exception
|
|
{
|
|
public SiteScopeViolationException(string message) : base(message) { }
|
|
}
|