refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Runtime globals for the Test Run sandbox. Mirrors the real site-runtime
|
||||
/// <c>ScriptGlobals</c> surface (ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts) member-for-member
|
||||
/// so the same user code that runs at a site also compiles and runs here.
|
||||
///
|
||||
/// Instance-context members — <c>Instance.GetAttribute/SetAttribute/CallScript</c>,
|
||||
/// <c>Attributes</c>, <c>Children</c>, <c>Parent</c> — need a live deployed
|
||||
/// instance. With no instance bound they throw <see cref="ScriptSandboxException"/>;
|
||||
/// with one bound (see <see cref="SandboxInstanceContext"/>) they route to it.
|
||||
///
|
||||
/// <c>ExternalSystem</c>, <c>Database</c>, and <c>Scripts.CallShared</c> run
|
||||
/// against central's real services and fire for real; <c>Notify</c> is a
|
||||
/// signature-faithful no-op fake. None of them depend on a bound instance.
|
||||
/// </summary>
|
||||
public class SandboxScriptHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Script parameters passed to the sandbox.
|
||||
/// </summary>
|
||||
public ScriptParameters Parameters { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation token for the sandbox execution.
|
||||
/// </summary>
|
||||
public CancellationToken CancellationToken { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Alarm context for the sandbox.
|
||||
/// </summary>
|
||||
public AlarmContext? Alarm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Script scope defining the execution context.
|
||||
/// </summary>
|
||||
public ScriptScope Scope { get; init; } = ScriptScope.Root;
|
||||
|
||||
/// <summary>
|
||||
/// Instance context providing access to deployed instance data.
|
||||
/// </summary>
|
||||
public SandboxInstanceContext Instance { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Helper for external system calls.
|
||||
/// </summary>
|
||||
public SandboxExternalHelper ExternalSystem => Instance.ExternalSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for database operations.
|
||||
/// </summary>
|
||||
public SandboxDatabaseHelper Database => Instance.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for sending notifications.
|
||||
/// </summary>
|
||||
public SandboxNotifyHelper Notify => Instance.Notify;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for calling scripts.
|
||||
/// </summary>
|
||||
public SandboxScriptCallHelper Scripts => Instance.Scripts;
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for attributes scoped to the current instance.
|
||||
/// </summary>
|
||||
public SandboxAttributeAccessor Attributes => new(Instance, Scope.SelfPath);
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for child compositions.
|
||||
/// </summary>
|
||||
public SandboxChildrenAccessor Children => new(Instance, Scope.SelfPath);
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the parent composition, or null if at root.
|
||||
/// </summary>
|
||||
public SandboxCompositionAccessor? Parent =>
|
||||
Scope.ParentPath == null ? null : new SandboxCompositionAccessor(Instance, Scope.ParentPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Backs the sandbox <c>Instance</c> when a Test Run is bound to a real
|
||||
/// deployed instance. Null when unbound. The implementation routes to the
|
||||
/// instance cross-site over the cluster transport.
|
||||
/// </summary>
|
||||
public interface ISandboxInstanceGateway
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value of an attribute with the specified canonical name.
|
||||
/// </summary>
|
||||
/// <param name="canonicalName">The canonical name of the attribute.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The attribute value, or null if not found.</returns>
|
||||
Task<object?> GetAttributeAsync(string canonicalName, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of an attribute with the specified canonical name.
|
||||
/// </summary>
|
||||
/// <param name="canonicalName">The canonical name of the attribute.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task SetAttributeAsync(string canonicalName, string value, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Calls a script with the specified canonical name.
|
||||
/// </summary>
|
||||
/// <param name="canonicalScriptName">The canonical name of the script.</param>
|
||||
/// <param name="parameters">Script parameters, or null if none.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The script result, or null if none.</returns>
|
||||
Task<object?> CallScriptAsync(
|
||||
string canonicalScriptName, IReadOnlyDictionary<string, object?>? parameters, CancellationToken ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sandbox mirror of <c>ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ScriptRuntimeContext</c> —
|
||||
/// the <c>Instance</c> global. Attribute and sibling-script access needs a real
|
||||
/// deployed instance: with no gateway wired it throws; with one (a bound
|
||||
/// instance) it routes cross-site. <c>ExternalSystem</c>/<c>Database</c>/
|
||||
/// <c>Scripts</c> run against central's real services regardless of binding;
|
||||
/// <c>Notify</c> is a signature-faithful no-op fake.
|
||||
/// </summary>
|
||||
public class SandboxInstanceContext
|
||||
{
|
||||
private readonly ISandboxInstanceGateway? _gateway;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for external system calls.
|
||||
/// </summary>
|
||||
public SandboxExternalHelper ExternalSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper for database operations.
|
||||
/// </summary>
|
||||
public SandboxDatabaseHelper Database { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper for sending notifications.
|
||||
/// </summary>
|
||||
public SandboxNotifyHelper Notify { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper for calling scripts.
|
||||
/// </summary>
|
||||
public SandboxScriptCallHelper Scripts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SandboxInstanceContext.
|
||||
/// </summary>
|
||||
/// <param name="gateway">Gateway for accessing deployed instance data, or null if unbound.</param>
|
||||
/// <param name="external">External system helper, or null to create a default.</param>
|
||||
/// <param name="database">Database helper, or null to create a default.</param>
|
||||
/// <param name="notify">Notification helper, or null to create a default.</param>
|
||||
/// <param name="scripts">Script call helper, or null to create a default.</param>
|
||||
public SandboxInstanceContext(
|
||||
ISandboxInstanceGateway? gateway = null,
|
||||
SandboxExternalHelper? external = null,
|
||||
SandboxDatabaseHelper? database = null,
|
||||
SandboxNotifyHelper? notify = null,
|
||||
SandboxScriptCallHelper? scripts = null)
|
||||
{
|
||||
_gateway = gateway;
|
||||
ExternalSystem = external ?? new SandboxExternalHelper(null, "<sandbox>");
|
||||
Database = database ?? new SandboxDatabaseHelper(null, "<sandbox>");
|
||||
Notify = notify ?? new SandboxNotifyHelper();
|
||||
Scripts = scripts ?? new SandboxScriptCallHelper(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of an attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">The name of the attribute.</param>
|
||||
/// <returns>The attribute value, or null if not found.</returns>
|
||||
public Task<object?> GetAttribute(string attributeName)
|
||||
{
|
||||
if (_gateway == null)
|
||||
throw new ScriptSandboxException(
|
||||
$"GetAttribute(\"{attributeName}\") needs a deployed instance — " +
|
||||
"bind one in Test Run to read live attribute values.");
|
||||
return _gateway.GetAttributeAsync(attributeName, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of an attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">The name of the attribute.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
public void SetAttribute(string attributeName, string value)
|
||||
{
|
||||
if (_gateway == null)
|
||||
throw new ScriptSandboxException(
|
||||
$"SetAttribute(\"{attributeName}\") needs a deployed instance — " +
|
||||
"bind one in Test Run to write attribute values.");
|
||||
_gateway.SetAttributeAsync(attributeName, value, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a sibling script.
|
||||
/// </summary>
|
||||
/// <param name="scriptName">The name of the script.</param>
|
||||
/// <param name="parameters">Script parameters, or null if none.</param>
|
||||
/// <returns>The script result, or null if none.</returns>
|
||||
public Task<object?> CallScript(string scriptName, object? parameters = null)
|
||||
{
|
||||
if (_gateway == null)
|
||||
throw new ScriptSandboxException(
|
||||
$"CallScript(\"{scriptName}\") needs a deployed instance — " +
|
||||
"bind one in Test Run to call sibling scripts.");
|
||||
return _gateway.CallScriptAsync(scriptName, ScriptArgs.Normalize(parameters), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sandbox mirror of <c>ScriptRuntimeContext.ScriptCallHelper</c> —
|
||||
/// <c>Scripts.CallShared(...)</c>. Compiles and runs the named shared script in
|
||||
/// the same sandbox via the wired delegate.
|
||||
/// </summary>
|
||||
public class SandboxScriptCallHelper
|
||||
{
|
||||
private readonly Func<string, IReadOnlyDictionary<string, object?>?, CancellationToken, Task<object?>>? _callShared;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SandboxScriptCallHelper.
|
||||
/// </summary>
|
||||
/// <param name="callShared">Delegate for calling shared scripts, or null if not available.</param>
|
||||
public SandboxScriptCallHelper(
|
||||
Func<string, IReadOnlyDictionary<string, object?>?, CancellationToken, Task<object?>>? callShared)
|
||||
{
|
||||
_callShared = callShared;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a shared script.
|
||||
/// </summary>
|
||||
/// <param name="scriptName">The name of the shared script.</param>
|
||||
/// <param name="parameters">Script parameters, or null if none.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The script result, or null if none.</returns>
|
||||
public Task<object?> CallShared(
|
||||
string scriptName,
|
||||
object? parameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_callShared == null)
|
||||
throw new ScriptSandboxException(
|
||||
$"Scripts.CallShared(\"{scriptName}\") — shared-script catalog not configured for Test Run.");
|
||||
return _callShared(scriptName, ScriptArgs.Normalize(parameters), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sandbox mirror of <c>ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.AttributeAccessor</c> —
|
||||
/// scope-aware <c>Attributes["X"]</c> access anchored at a canonical-name prefix.
|
||||
/// </summary>
|
||||
public class SandboxAttributeAccessor
|
||||
{
|
||||
private readonly SandboxInstanceContext _ctx;
|
||||
|
||||
/// <summary>
|
||||
/// The scope prefix for attribute resolution.
|
||||
/// </summary>
|
||||
public string ScopePrefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SandboxAttributeAccessor.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The sandbox instance context.</param>
|
||||
/// <param name="prefix">The scope prefix for attribute names.</param>
|
||||
public SandboxAttributeAccessor(SandboxInstanceContext ctx, string prefix)
|
||||
{
|
||||
_ctx = ctx;
|
||||
ScopePrefix = prefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a key to its fully qualified name within the current scope.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key.</param>
|
||||
/// <returns>The fully qualified attribute name.</returns>
|
||||
public string Resolve(string key) =>
|
||||
ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an attribute value by key.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key.</param>
|
||||
/// <returns>The attribute value, or null if not found.</returns>
|
||||
public object? this[string key]
|
||||
{
|
||||
get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult();
|
||||
set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an attribute value asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key.</param>
|
||||
/// <returns>The attribute value, or null if not found.</returns>
|
||||
public Task<object?> GetAsync(string key) => _ctx.GetAttribute(Resolve(key));
|
||||
|
||||
/// <summary>
|
||||
/// Sets an attribute value asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key.</param>
|
||||
/// <param name="value">The value to set, or null.</param>
|
||||
/// <returns>A task representing the operation.</returns>
|
||||
public Task SetAsync(string key, object? value)
|
||||
{
|
||||
_ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sandbox mirror of <c>ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.CompositionAccessor</c> —
|
||||
/// a view of one composition: its attributes plus an invokable <c>CallScript</c>.
|
||||
/// </summary>
|
||||
public class SandboxCompositionAccessor
|
||||
{
|
||||
private readonly SandboxInstanceContext _ctx;
|
||||
|
||||
/// <summary>
|
||||
/// The path to the composition within the instance hierarchy.
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for attributes within the composition.
|
||||
/// </summary>
|
||||
public SandboxAttributeAccessor Attributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SandboxCompositionAccessor.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The sandbox instance context.</param>
|
||||
/// <param name="path">The path to the composition within the instance hierarchy.</param>
|
||||
public SandboxCompositionAccessor(SandboxInstanceContext ctx, string path)
|
||||
{
|
||||
_ctx = ctx;
|
||||
Path = path;
|
||||
Attributes = new SandboxAttributeAccessor(ctx, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a script name to its fully qualified name within the composition.
|
||||
/// </summary>
|
||||
/// <param name="scriptName">The script name.</param>
|
||||
/// <returns>The fully qualified script name.</returns>
|
||||
public string ResolveScript(string scriptName) =>
|
||||
Path.Length == 0 ? scriptName : Path + "." + scriptName;
|
||||
|
||||
/// <summary>
|
||||
/// Calls a script within the composition.
|
||||
/// </summary>
|
||||
/// <param name="scriptName">The name of the script.</param>
|
||||
/// <param name="parameters">Script parameters, or null if none.</param>
|
||||
/// <returns>The script result, or null if none.</returns>
|
||||
public Task<object?> CallScript(string scriptName, object? parameters = null)
|
||||
=> _ctx.CallScript(ResolveScript(scriptName), parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sandbox mirror of <c>ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ChildrenAccessor</c> —
|
||||
/// dictionary-style access to child compositions.
|
||||
/// </summary>
|
||||
public class SandboxChildrenAccessor
|
||||
{
|
||||
private readonly SandboxInstanceContext _ctx;
|
||||
private readonly string _selfPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SandboxChildrenAccessor.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The sandbox instance context.</param>
|
||||
/// <param name="selfPath">The path to the parent composition.</param>
|
||||
public SandboxChildrenAccessor(SandboxInstanceContext ctx, string selfPath)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_selfPath = selfPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a child composition by name.
|
||||
/// </summary>
|
||||
/// <param name="compositionName">The name of the child composition.</param>
|
||||
/// <returns>An accessor for the child composition.</returns>
|
||||
public SandboxCompositionAccessor this[string compositionName]
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = _selfPath.Length == 0
|
||||
? compositionName
|
||||
: _selfPath + "." + compositionName;
|
||||
return new SandboxCompositionAccessor(_ctx, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distinct exception so the Test Run pipeline can label sandbox-only
|
||||
/// limitations differently from genuine runtime errors in user code.
|
||||
/// </summary>
|
||||
public class ScriptSandboxException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ScriptSandboxException.
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public ScriptSandboxException(string message) : base(message) { }
|
||||
}
|
||||
Reference in New Issue
Block a user