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:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,137 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
/// <summary>
/// Single source of truth for AuditLog (#23) rows. Central rows leave ForwardState null;
/// site rows leave IngestedAtUtc null until ingest. Append-only.
/// </summary>
/// <remarks>
/// All <c>*Utc</c>-suffixed <see cref="DateTime"/> properties on this record are
/// invariantly UTC ("All timestamps are UTC throughout the system." — CLAUDE.md).
/// Their init-setters call <see cref="DateTime.SpecifyKind(DateTime, DateTimeKind)"/>
/// to force <see cref="DateTimeKind.Utc"/> on assignment, so a value built from a
/// <c>DateTime</c> literal or re-hydrated from a SQL Server <c>datetime2</c> column
/// (which strips the <c>Kind</c> flag on the wire) cannot leak downstream as
/// <see cref="DateTimeKind.Unspecified"/> or be silently re-interpreted as local
/// time. The unrelated <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications"/>
/// surface uses <see cref="DateTimeOffset"/> for the same UTC guarantee; this
/// entity stays on <see cref="DateTime"/> to match the partitioned SQL Server
/// <c>datetime2</c> column shape required by the AuditLog table.
/// </remarks>
public sealed record AuditEvent
{
/// <summary>Idempotency key; uniquely identifies one audit lifecycle event.</summary>
public Guid EventId { get; init; }
/// <summary>
/// UTC timestamp when the audited action occurred at its source. The value
/// MUST be in UTC ("All timestamps are UTC throughout the system." — CLAUDE.md).
/// The init-setter forces <see cref="DateTimeKind.Utc"/> on assignment via
/// <see cref="DateTime.SpecifyKind(DateTime, DateTimeKind)"/>, so any
/// construction path that supplies a value with <see cref="DateTimeKind.Unspecified"/>
/// (e.g. a <c>DateTime</c> literal, JSON deserialisation, or a SQL Server
/// <c>datetime2</c> read where the value bypassed the EF converter) is
/// re-tagged as UTC rather than treated as local time downstream. Producers
/// are still expected to supply values that ARE genuinely UTC — the setter
/// only fixes the <c>Kind</c> flag, it cannot re-interpret a local-time value.
/// </summary>
public DateTime OccurredAtUtc
{
get => _occurredAtUtc;
init => _occurredAtUtc = DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
private readonly DateTime _occurredAtUtc;
/// <summary>
/// UTC timestamp when the row was ingested at central; null on the site hot-path.
/// The value MUST be in UTC when non-null; the init-setter forces
/// <see cref="DateTimeKind.Utc"/> on assignment, matching
/// <see cref="OccurredAtUtc"/>'s contract.
/// </summary>
public DateTime? IngestedAtUtc
{
get => _ingestedAtUtc;
init => _ingestedAtUtc = value.HasValue
? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc)
: null;
}
private readonly DateTime? _ingestedAtUtc;
/// <summary>Trust-boundary channel the audited action crossed.</summary>
public AuditChannel Channel { get; init; }
/// <summary>Specific event kind within the channel (see alog.md §4).</summary>
public AuditKind Kind { get; init; }
/// <summary>Correlation id linking related audit rows (e.g. the cached-op lifecycle).</summary>
public Guid? CorrelationId { get; init; }
/// <summary>
/// Id of the originating script execution / inbound request — the universal
/// per-run correlation value, distinct from <see cref="CorrelationId"/> (which
/// is the per-operation lifecycle id).
/// </summary>
public Guid? ExecutionId { get; init; }
/// <summary>
/// <see cref="ExecutionId"/> of the execution that spawned this run, when this
/// run was spawned by another; null for top-level runs. Lets a spawned
/// execution point back at its spawner for cross-run correlation.
/// </summary>
public Guid? ParentExecutionId { get; init; }
/// <summary>Site id where the action originated; null for central-direct events.</summary>
public string? SourceSiteId { get; init; }
/// <summary>
/// The cluster node on which the event was emitted — `node-a` / `node-b` for
/// site rows (qualified by <see cref="SourceSiteId"/>), `central-a` / `central-b`
/// for central-originated rows. Stamped by the writing node from
/// <c>INodeIdentityProvider</c>; nullable so reconciled rows from a node that
/// has since been retired don't block ingest.
/// </summary>
public string? SourceNode { get; init; }
/// <summary>Instance id where the action originated, when applicable.</summary>
public string? SourceInstanceId { get; init; }
/// <summary>Script that initiated the action (script trust boundary), when applicable.</summary>
public string? SourceScript { get; init; }
/// <summary>Authenticated actor for inbound paths (API key name, user, etc.).</summary>
public string? Actor { get; init; }
/// <summary>Target of the action: external system name, db connection name, list name, or inbound method.</summary>
public string? Target { get; init; }
/// <summary>Lifecycle status of this row.</summary>
public AuditStatus Status { get; init; }
/// <summary>HTTP status code where applicable (outbound API + inbound API).</summary>
public int? HttpStatus { get; init; }
/// <summary>Duration of the audited action in milliseconds, when measurable.</summary>
public int? DurationMs { get; init; }
/// <summary>Human-readable error summary on failure rows.</summary>
public string? ErrorMessage { get; init; }
/// <summary>Verbose error detail (stack/exception) on failure rows.</summary>
public string? ErrorDetail { get; init; }
/// <summary>Truncated/redacted request summary; capped per AuditLogOptions.</summary>
public string? RequestSummary { get; init; }
/// <summary>Truncated/redacted response summary; capped per AuditLogOptions.</summary>
public string? ResponseSummary { get; init; }
/// <summary>True when Request/Response summaries were truncated to the payload cap.</summary>
public bool PayloadTruncated { get; init; }
/// <summary>Free-form JSON extension column for channel-specific extras.</summary>
public string? Extra { get; init; }
/// <summary>Site-local forwarding state; null on central rows.</summary>
public AuditForwardState? ForwardState { get; init; }
}
@@ -0,0 +1,40 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
public class AuditLogEntry
{
/// <summary>Auto-incremented primary key.</summary>
public int Id { get; set; }
/// <summary>Username of the actor who performed the action.</summary>
public string User { get; set; }
/// <summary>Action performed (e.g. Created, Updated, Deleted).</summary>
public string Action { get; set; }
/// <summary>Entity type name (e.g. Template, ExternalSystem).</summary>
public string EntityType { get; set; }
/// <summary>String representation of the entity's primary key.</summary>
public string EntityId { get; set; }
/// <summary>Human-readable name of the affected entity.</summary>
public string EntityName { get; set; }
/// <summary>JSON snapshot of the entity's state after the action; null for deletes.</summary>
public string? AfterStateJson { get; set; }
/// <summary>UTC timestamp when the audit entry was recorded.</summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>Bundle import session id when this entry was created during a bundle import; otherwise null.</summary>
public Guid? BundleImportId { get; set; }
/// <summary>
/// Creates an audit log entry for the specified user action on a named entity.
/// </summary>
/// <param name="user">Username of the actor performing the action.</param>
/// <param name="action">Action name (e.g. Created, Updated, Deleted).</param>
/// <param name="entityType">Entity type name.</param>
/// <param name="entityId">String primary key of the affected entity.</param>
/// <param name="entityName">Human-readable name of the affected entity.</param>
public AuditLogEntry(string user, string action, string entityType, string entityId, string entityName)
{
User = user ?? throw new ArgumentNullException(nameof(user));
Action = action ?? throw new ArgumentNullException(nameof(action));
EntityType = entityType ?? throw new ArgumentNullException(nameof(entityType));
EntityId = entityId ?? throw new ArgumentNullException(nameof(entityId));
EntityName = entityName ?? throw new ArgumentNullException(nameof(entityName));
}
}
@@ -0,0 +1,69 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
/// <summary>
/// Central operational state row for a cached call (Site Call Audit #22, Audit Log #23 M3).
/// One row per <see cref="TrackedOperationId"/> in the <c>SiteCalls</c> table — append-once
/// then monotonic status update. Status transitions are forward-only
/// (<c>Submitted → Forwarded → Attempted → Delivered|Failed|Parked|Discarded</c>); an
/// out-of-order or duplicate upsert is a silent no-op so duplicate gRPC packets and
/// reconciliation pulls can both feed the same writer without rolling state back.
/// </summary>
/// <remarks>
/// Sites remain the source of truth — this row is the eventually-consistent mirror the
/// Central UI's Site Calls page reads. Unlike the partitioned <c>AuditLog</c> table this
/// entity backs operational (mutable) state on a standard non-partitioned table on
/// <c>[PRIMARY]</c>; no DB-role restriction applies.
/// </remarks>
public sealed record SiteCall
{
/// <summary>Strong-typed idempotency key shared with every audit row for the operation.</summary>
public required TrackedOperationId TrackedOperationId { get; init; }
/// <summary>Trust-boundary channel — <c>"ApiOutbound"</c> or <c>"DbOutbound"</c>.</summary>
public required string Channel { get; init; }
/// <summary>Human-readable target (e.g. <c>"ERP.GetOrder"</c>).</summary>
public required string Target { get; init; }
/// <summary>Site id that submitted the cached call.</summary>
public required string SourceSite { get; init; }
/// <summary>
/// The cluster node on which the cached call was emitted — <c>node-a</c> /
/// <c>node-b</c> for site rows (qualified by <see cref="SourceSite"/>),
/// <c>central-a</c> / <c>central-b</c> for central-originated rows. Stamped
/// by the emitting node from <c>INodeIdentityProvider</c>; nullable so
/// reconciled rows from a node that has since been retired don't block ingest.
/// </summary>
public string? SourceNode { get; init; }
/// <summary>
/// Lifecycle status — string form of
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AuditStatus"/>. Monotonic: later rank
/// wins, earlier rank is a no-op.
/// </summary>
public required string Status { get; init; }
/// <summary>Number of dispatch attempts so far; 0 prior to first attempt.</summary>
public required int RetryCount { get; init; }
/// <summary>Most recent error message; null when no failures have occurred.</summary>
public string? LastError { get; init; }
/// <summary>Most recent HTTP status code (API calls only); null otherwise.</summary>
public int? HttpStatus { get; init; }
/// <summary>UTC timestamp the cached call was first submitted at the site.</summary>
public required DateTime CreatedAtUtc { get; init; }
/// <summary>UTC timestamp of the latest status mutation at the site.</summary>
public required DateTime UpdatedAtUtc { get; init; }
/// <summary>UTC timestamp the row reached a terminal status; null while still active.</summary>
public DateTime? TerminalAtUtc { get; init; }
/// <summary>UTC timestamp central ingested (or last refreshed) this row.</summary>
public required DateTime IngestedAtUtc { get; init; }
}
@@ -0,0 +1,36 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
/// <summary>
/// WP-8: Stores the deployed configuration snapshot for an instance.
/// Captured at deploy time; compared against template-derived (live flattened) config for staleness detection.
/// </summary>
public class DeployedConfigSnapshot
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the owning <c>Instance</c> entity.</summary>
public int InstanceId { get; set; }
/// <summary>Unique deployment identifier assigned at deploy time for idempotency.</summary>
public string DeploymentId { get; set; }
/// <summary>Revision hash of the flattened configuration at deploy time, used for staleness detection.</summary>
public string RevisionHash { get; set; }
/// <summary>
/// JSON-serialized FlattenedConfiguration captured at deploy time.
/// </summary>
public string ConfigurationJson { get; set; }
/// <summary>UTC timestamp when this snapshot was persisted.</summary>
public DateTimeOffset DeployedAt { get; set; }
/// <summary>Initializes a new snapshot with the deployment identity, revision hash, and serialized configuration.</summary>
/// <param name="deploymentId">Unique deployment identifier.</param>
/// <param name="revisionHash">Revision hash of the flattened configuration.</param>
/// <param name="configurationJson">JSON-serialized flattened configuration.</param>
public DeployedConfigSnapshot(string deploymentId, string revisionHash, string configurationJson)
{
DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId));
RevisionHash = revisionHash ?? throw new ArgumentNullException(nameof(revisionHash));
ConfigurationJson = configurationJson ?? throw new ArgumentNullException(nameof(configurationJson));
}
}
@@ -0,0 +1,67 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
public class DeploymentRecord
{
/// <summary>
/// The deployment record identifier.
/// </summary>
public int Id { get; set; }
/// <summary>
/// The instance identifier being deployed.
/// </summary>
public int InstanceId { get; set; }
/// <summary>
/// The current deployment status.
/// </summary>
public DeploymentStatus Status { get; set; }
/// <summary>
/// The deployment identifier.
/// </summary>
public string DeploymentId { get; set; }
/// <summary>
/// The revision hash of the deployed configuration, or null.
/// </summary>
public string? RevisionHash { get; set; }
/// <summary>
/// The user who initiated the deployment.
/// </summary>
public string DeployedBy { get; set; }
/// <summary>
/// The time when the deployment was initiated.
/// </summary>
public DateTimeOffset DeployedAt { get; set; }
/// <summary>
/// The time when the deployment completed, or null if still in progress.
/// </summary>
public DateTimeOffset? CompletedAt { get; set; }
/// <summary>
/// Error message if the deployment failed, or null.
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// WP-4: Optimistic concurrency token for deployment status updates.
/// </summary>
public byte[] RowVersion { get; set; } = [];
/// <summary>
/// Initializes a new instance of the DeploymentRecord.
/// </summary>
/// <param name="deploymentId">The deployment identifier.</param>
/// <param name="deployedBy">The user initiating the deployment.</param>
public DeploymentRecord(string deploymentId, string deployedBy)
{
DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId));
DeployedBy = deployedBy ?? throw new ArgumentNullException(nameof(deployedBy));
}
}
@@ -0,0 +1,27 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
/// <summary>
/// Records a system-wide artifact deployment operation, tracking status per site.
/// </summary>
public class SystemArtifactDeploymentRecord
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Type identifier for the deployed artifact (e.g. the artifact category name).</summary>
public string ArtifactType { get; set; }
/// <summary>Username of the operator who initiated the deployment.</summary>
public string DeployedBy { get; set; }
/// <summary>UTC timestamp when the deployment was initiated.</summary>
public DateTimeOffset DeployedAt { get; set; }
/// <summary>JSON-serialized per-site deployment status map, or null if not yet computed.</summary>
public string? PerSiteStatus { get; set; }
/// <summary>Initializes a new <see cref="SystemArtifactDeploymentRecord"/> with required fields.</summary>
/// <param name="artifactType">The artifact type being deployed.</param>
/// <param name="deployedBy">The username of the initiating operator.</param>
public SystemArtifactDeploymentRecord(string artifactType, string deployedBy)
{
ArtifactType = artifactType ?? throw new ArgumentNullException(nameof(artifactType));
DeployedBy = deployedBy ?? throw new ArgumentNullException(nameof(deployedBy));
}
}
@@ -0,0 +1,24 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
public class DatabaseConnectionDefinition
{
/// <summary>Gets or sets the primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the human-readable connection name.</summary>
public string Name { get; set; }
/// <summary>Gets or sets the ADO.NET connection string for this database.</summary>
public string ConnectionString { get; set; }
/// <summary>Gets or sets the maximum number of retry attempts for transient failures.</summary>
public int MaxRetries { get; set; }
/// <summary>Gets or sets the delay between retry attempts.</summary>
public TimeSpan RetryDelay { get; set; }
/// <summary>Initializes a new <see cref="DatabaseConnectionDefinition"/> with the required name and connection string.</summary>
/// <param name="name">The human-readable connection name.</param>
/// <param name="connectionString">The ADO.NET connection string.</param>
public DatabaseConnectionDefinition(string name, string connectionString)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
}
@@ -0,0 +1,32 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
public class ExternalSystemDefinition
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Display name for the external system.</summary>
public string Name { get; set; }
/// <summary>Base URL of the external system's HTTP endpoint.</summary>
public string EndpointUrl { get; set; }
/// <summary>Authentication type identifier (e.g., "ApiKey", "Basic").</summary>
public string AuthType { get; set; }
/// <summary>JSON-serialized authentication configuration for the selected <see cref="AuthType"/>.</summary>
public string? AuthConfiguration { get; set; }
/// <summary>Maximum number of retry attempts for transient failures.</summary>
public int MaxRetries { get; set; }
/// <summary>Fixed delay between retry attempts.</summary>
public TimeSpan RetryDelay { get; set; }
/// <summary>
/// Initializes a new <see cref="ExternalSystemDefinition"/>.
/// </summary>
/// <param name="name">Display name for the external system.</param>
/// <param name="endpointUrl">Base URL of the external system's HTTP endpoint.</param>
/// <param name="authType">Authentication type identifier.</param>
public ExternalSystemDefinition(string name, string endpointUrl, string authType)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
EndpointUrl = endpointUrl ?? throw new ArgumentNullException(nameof(endpointUrl));
AuthType = authType ?? throw new ArgumentNullException(nameof(authType));
}
}
@@ -0,0 +1,33 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
/// <summary>
/// Defines a callable HTTP method on an external system definition.
/// </summary>
public class ExternalSystemMethod
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key referencing the owning <c>ExternalSystemDefinition</c>.</summary>
public int ExternalSystemDefinitionId { get; set; }
/// <summary>Name of the method as referenced in scripts.</summary>
public string Name { get; set; }
/// <summary>HTTP method (GET, POST, PUT, DELETE, etc.).</summary>
public string HttpMethod { get; set; }
/// <summary>URL path relative to the external system's base URL.</summary>
public string Path { get; set; }
/// <summary>JSON-serialized parameter definitions for this method, or null if there are none.</summary>
public string? ParameterDefinitions { get; set; }
/// <summary>JSON-serialized return type definition for this method, or null if void.</summary>
public string? ReturnDefinition { get; set; }
/// <summary>Initializes a new instance of <see cref="ExternalSystemMethod"/> with the required fields.</summary>
/// <param name="name">The method name.</param>
/// <param name="httpMethod">The HTTP method verb.</param>
/// <param name="path">The URL path.</param>
public ExternalSystemMethod(string name, string httpMethod, string path)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
HttpMethod = httpMethod ?? throw new ArgumentNullException(nameof(httpMethod));
Path = path ?? throw new ArgumentNullException(nameof(path));
}
}
@@ -0,0 +1,68 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.InboundApi;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
/// <summary>
/// An inbound-API bearer credential. Per ConfigurationDatabase-012 the plaintext key
/// is never persisted: the entity stores only <see cref="KeyHash"/>, a deterministic
/// keyed hash of the key (HMAC-SHA256 with a server-side pepper). The plaintext is
/// generated at creation, shown to the operator exactly once, and then discarded.
/// </summary>
public class ApiKey
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Display name for the API key.</summary>
public string Name { get; set; }
/// <summary>
/// Deterministic keyed hash of the API key value. This is the only form of the
/// credential persisted; the plaintext key is never stored. Authentication hashes
/// the presented candidate with the same scheme and compares against this value.
/// </summary>
public string KeyHash { get; set; }
/// <summary>When false, the key is rejected even if the hash matches.</summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Creates an API key from a plaintext value, immediately hashing it with the
/// unpeppered default hasher (<see cref="ApiKeyHasher.Default"/>) so the entity
/// never holds the plaintext. Production code paths that have a configured pepper
/// should use <see cref="FromHash(string, string)"/> with a peppered hash instead.
/// </summary>
/// <param name="name">Display name for the API key.</param>
/// <param name="keyValue">Plaintext key value; hashed immediately and never stored.</param>
public ApiKey(string name, string keyValue)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
if (keyValue is null) throw new ArgumentNullException(nameof(keyValue));
KeyHash = ApiKeyHasher.Default.Hash(keyValue);
}
/// <summary>
/// Parameterless constructor for the EF Core materializer. Application code uses
/// <see cref="ApiKey(string, string)"/> or <see cref="FromHash(string, string)"/>.
/// </summary>
private ApiKey()
{
Name = string.Empty;
KeyHash = string.Empty;
}
/// <summary>
/// Creates an API key from an already-computed key hash. Used by the creation
/// path, which generates a random key, hashes it with the configured (peppered)
/// <see cref="IApiKeyHasher"/>, and stores only the resulting hash.
/// </summary>
/// <param name="name">Display name for the API key.</param>
/// <param name="keyHash">Pre-computed keyed hash of the API key value.</param>
public static ApiKey FromHash(string name, string keyHash)
{
return new ApiKey
{
Name = name ?? throw new ArgumentNullException(nameof(name)),
KeyHash = keyHash ?? throw new ArgumentNullException(nameof(keyHash)),
};
}
}
@@ -0,0 +1,28 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
public class ApiMethod
{
/// <summary>Gets or sets the primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the method name used in the route (<c>/api/{Name}</c>).</summary>
public string Name { get; set; }
/// <summary>Gets or sets the C# script body executed when the method is invoked.</summary>
public string Script { get; set; }
/// <summary>Gets or sets the JSON-serialised list of API key IDs approved for this method, or <c>null</c> for unrestricted.</summary>
public string? ApprovedApiKeyIds { get; set; }
/// <summary>Gets or sets the JSON Schema describing the accepted parameters, or <c>null</c> if the method takes no parameters.</summary>
public string? ParameterDefinitions { get; set; }
/// <summary>Gets or sets the JSON Schema describing the return type, or <c>null</c> if the method returns nothing.</summary>
public string? ReturnDefinition { get; set; }
/// <summary>Gets or sets the script execution timeout in seconds.</summary>
public int TimeoutSeconds { get; set; }
/// <summary>Initializes a new <see cref="ApiMethod"/> with the required name and script.</summary>
/// <param name="name">The method name (used as the route segment).</param>
/// <param name="script">The C# script body to execute.</param>
public ApiMethod(string name, string script)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Script = script ?? throw new ArgumentNullException(nameof(script));
}
}
@@ -0,0 +1,24 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
public class Area
{
/// <summary>Gets or sets the database primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the id of the site this area belongs to.</summary>
public int SiteId { get; set; }
/// <summary>Gets or sets the display name of the area.</summary>
public string Name { get; set; }
/// <summary>Gets or sets the id of the parent area, or null for a root area.</summary>
public int? ParentAreaId { get; set; }
/// <summary>Gets or sets the child areas nested under this area.</summary>
public ICollection<Area> Children { get; set; } = new List<Area>();
/// <summary>
/// Initializes a new <see cref="Area"/> with the given name.
/// </summary>
/// <param name="name">Display name for the area.</param>
public Area(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,34 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
public class Instance
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the template this instance is based on.</summary>
public int TemplateId { get; set; }
/// <summary>Foreign key to the site where this instance is deployed.</summary>
public int SiteId { get; set; }
/// <summary>Optional foreign key to the organisational area this instance belongs to.</summary>
public int? AreaId { get; set; }
/// <summary>System-wide unique name that identifies this instance.</summary>
public string UniqueName { get; set; }
/// <summary>Current lifecycle state of the instance.</summary>
public InstanceState State { get; set; }
/// <summary>Per-attribute value overrides applied on top of the template defaults.</summary>
public ICollection<InstanceAttributeOverride> AttributeOverrides { get; set; } = new List<InstanceAttributeOverride>();
/// <summary>Per-alarm configuration overrides applied on top of the template defaults.</summary>
public ICollection<InstanceAlarmOverride> AlarmOverrides { get; set; } = new List<InstanceAlarmOverride>();
/// <summary>Data-connection bindings that map template tags to site data sources.</summary>
public ICollection<InstanceConnectionBinding> ConnectionBindings { get; set; } = new List<InstanceConnectionBinding>();
/// <summary>
/// Initializes a new instance with the required unique name.
/// </summary>
/// <param name="uniqueName">System-wide unique name for this instance.</param>
public Instance(string uniqueName)
{
UniqueName = uniqueName ?? throw new ArgumentNullException(nameof(uniqueName));
}
}
@@ -0,0 +1,54 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
/// <summary>
/// Per-instance override for a template-defined alarm. Lets a deployed
/// instance tweak setpoints, priority, or per-band messages without forking
/// the template. Locked alarms (TemplateAlarm.IsLocked) cannot be overridden
/// — LockEnforcer rejects the change at write time.
///
/// Merge semantics (applied during flattening, after template inheritance and
/// composition):
/// • <see cref="TriggerConfigurationOverride"/> with a HiLo trigger merges
/// into the inherited JSON setpoint-by-setpoint (derived keys win,
/// inherited keys survive for unset derived keys). Same logic as
/// template-to-template HiLo override, just one layer deeper.
/// • For ValueMatch / RangeViolation / RateOfChange, the override replaces
/// the whole TriggerConfiguration JSON (existing whole-replace semantics).
/// • <see cref="PriorityLevelOverride"/> replaces the alarm's PriorityLevel
/// when set.
/// </summary>
public class InstanceAlarmOverride
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the instance this override belongs to.</summary>
public int InstanceId { get; set; }
/// <summary>
/// Canonical name of the alarm being overridden — matches
/// <c>ResolvedAlarm.CanonicalName</c> after flattening, so composed-member
/// alarms are referenced as <c>[CompositionInstance].[AlarmName]</c>.
/// </summary>
public string AlarmCanonicalName { get; set; }
/// <summary>
/// Partial JSON (for HiLo) or full JSON (for binary trigger types) to
/// override the inherited TriggerConfiguration. <c>null</c> means
/// "leave inherited as-is".
/// </summary>
public string? TriggerConfigurationOverride { get; set; }
/// <summary>
/// Replaces the alarm's PriorityLevel when set. <c>null</c> = keep inherited.
/// </summary>
public int? PriorityLevelOverride { get; set; }
/// <summary>
/// Initializes a new alarm override for the specified alarm.
/// </summary>
/// <param name="alarmCanonicalName">Canonical name of the alarm to override.</param>
public InstanceAlarmOverride(string alarmCanonicalName)
{
AlarmCanonicalName = alarmCanonicalName ?? throw new ArgumentNullException(nameof(alarmCanonicalName));
}
}
@@ -0,0 +1,20 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
public class InstanceAttributeOverride
{
/// <summary>Gets or sets the primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the foreign key of the owning instance.</summary>
public int InstanceId { get; set; }
/// <summary>Gets or sets the attribute name this override targets.</summary>
public string AttributeName { get; set; }
/// <summary>Gets or sets the override value, or <c>null</c> to clear a previous override.</summary>
public string? OverrideValue { get; set; }
/// <summary>Initializes a new <see cref="InstanceAttributeOverride"/> for the given attribute name.</summary>
/// <param name="attributeName">The name of the attribute to override.</param>
public InstanceAttributeOverride(string attributeName)
{
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
}
}
@@ -0,0 +1,22 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
public class InstanceConnectionBinding
{
/// <summary>Auto-incremented primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the owning instance.</summary>
public int InstanceId { get; set; }
/// <summary>Name of the attribute on the instance that this binding maps to a data connection tag.</summary>
public string AttributeName { get; set; }
/// <summary>Foreign key to the data connection that provides values for this attribute.</summary>
public int DataConnectionId { get; set; }
/// <summary>
/// Creates a binding for the specified attribute name.
/// </summary>
/// <param name="attributeName">Name of the attribute being bound to a data connection.</param>
public InstanceConnectionBinding(string attributeName)
{
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
}
}
@@ -0,0 +1,98 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
/// <summary>
/// A single notification queued in the central outbox. Created at a site (where the
/// <see cref="NotificationId"/> GUID is generated) and forwarded to the central cluster
/// for delivery, retry, and audit. The lifecycle is tracked by <see cref="Status"/>.
/// </summary>
public class Notification
{
/// <summary>GUID primary key, generated at the originating site.</summary>
public string NotificationId { get; set; }
/// <summary>Gets or sets the notification type.</summary>
public NotificationType Type { get; set; }
/// <summary>Gets or sets the notification list name.</summary>
public string ListName { get; set; }
/// <summary>Gets or sets the notification subject.</summary>
public string Subject { get; set; }
/// <summary>Gets or sets the notification body.</summary>
public string Body { get; set; }
/// <summary>JSON extensibility hook for channel-specific payload data.</summary>
public string? TypeData { get; set; }
/// <summary>Gets or sets the notification delivery status.</summary>
public NotificationStatus Status { get; set; } = NotificationStatus.Pending;
/// <summary>Gets or sets the delivery retry count.</summary>
public int RetryCount { get; set; }
/// <summary>Gets or sets the last error message, if any.</summary>
public string? LastError { get; set; }
/// <summary>Resolved delivery targets snapshotted at delivery time, for audit.</summary>
public string? ResolvedTargets { get; set; }
/// <summary>Gets or sets the originating site ID.</summary>
public string SourceSiteId { get; set; }
/// <summary>
/// The cluster node on which the notification was emitted — `node-a` / `node-b`
/// for site rows (qualified by <see cref="SourceSiteId"/>), `central-a` / `central-b`
/// for central-originated rows. Carried from the site on the
/// <see cref="Commons.Messages.Notification.NotificationSubmit"/> and persisted at
/// central; nullable so rows submitted before the column existed don't block ingest.
/// </summary>
public string? SourceNode { get; set; }
/// <summary>Gets or sets the originating instance ID, if any.</summary>
public string? SourceInstanceId { get; set; }
/// <summary>Gets or sets the originating script name, if any.</summary>
public string? SourceScript { get; set; }
/// <summary>
/// The originating script execution's <c>ExecutionId</c> (Audit Log #23). Carried from
/// the site on the <see cref="Commons.Messages.Notification.NotificationSubmit"/> so the
/// central dispatcher can stamp the same id onto its <c>NotifyDeliver</c> audit rows,
/// correlating them with the site-emitted <c>NotifySend</c> row. Null for notifications
/// submitted before the column existed, or raised outside a script-execution context.
/// </summary>
public Guid? OriginExecutionId { get; set; }
/// <summary>
/// The originating routed script execution's <c>ParentExecutionId</c> (Audit Log #23).
/// Carried from the site on the <see cref="Commons.Messages.Notification.NotificationSubmit"/>
/// so the central dispatcher can stamp the same parent id onto its <c>NotifyDeliver</c>
/// audit rows, correlating them with the site-emitted <c>NotifySend</c> row. Null for
/// non-routed runs, or for notifications submitted before the column existed.
/// </summary>
public Guid? OriginParentExecutionId { get; set; }
/// <summary>Gets or sets the time when the notification was enqueued at the site.</summary>
public DateTimeOffset SiteEnqueuedAt { get; set; }
/// <summary>Central ingest time.</summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>Gets or sets the time of the last delivery attempt, if any.</summary>
public DateTimeOffset? LastAttemptAt { get; set; }
/// <summary>Gets or sets the time of the next scheduled delivery attempt, if any.</summary>
public DateTimeOffset? NextAttemptAt { get; set; }
/// <summary>Gets or sets the time the notification was delivered, if any.</summary>
public DateTimeOffset? DeliveredAt { get; set; }
/// <summary>
/// Initializes a new instance of the Notification class.
/// </summary>
/// <param name="notificationId">The notification ID (GUID).</param>
/// <param name="type">The notification type.</param>
/// <param name="listName">The notification list name.</param>
/// <param name="subject">The notification subject.</param>
/// <param name="body">The notification body text.</param>
/// <param name="sourceSiteId">The originating site ID.</param>
public Notification(string notificationId, NotificationType type, string listName,
string subject, string body, string sourceSiteId)
{
NotificationId = notificationId ?? throw new ArgumentNullException(nameof(notificationId));
Type = type;
ListName = listName ?? throw new ArgumentNullException(nameof(listName));
Subject = subject ?? throw new ArgumentNullException(nameof(subject));
Body = body ?? throw new ArgumentNullException(nameof(body));
SourceSiteId = sourceSiteId ?? throw new ArgumentNullException(nameof(sourceSiteId));
}
}
@@ -0,0 +1,22 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
public class NotificationList
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Display name of the notification list.</summary>
public string Name { get; set; }
/// <summary>Delivery type discriminator (e.g., Email).</summary>
public NotificationType Type { get; set; } = NotificationType.Email;
/// <summary>Recipients belonging to this list.</summary>
public ICollection<NotificationRecipient> Recipients { get; set; } = new List<NotificationRecipient>();
/// <summary>Initializes the notification list with the given name.</summary>
/// <param name="name">Display name of the notification list.</param>
public NotificationList(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,24 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
public class NotificationRecipient
{
/// <summary>Gets or sets the database primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the id of the parent notification list.</summary>
public int NotificationListId { get; set; }
/// <summary>Gets or sets the display name of the recipient.</summary>
public string Name { get; set; }
/// <summary>Gets or sets the recipient's email address.</summary>
public string EmailAddress { get; set; }
/// <summary>
/// Initializes a new <see cref="NotificationRecipient"/> with the required fields.
/// </summary>
/// <param name="name">Display name of the recipient.</param>
/// <param name="emailAddress">Email address of the recipient.</param>
public NotificationRecipient(string name, string emailAddress)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
EmailAddress = emailAddress ?? throw new ArgumentNullException(nameof(emailAddress));
}
}
@@ -0,0 +1,40 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
public class SmtpConfiguration
{
/// <summary>Gets or sets the primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the SMTP server hostname or IP address.</summary>
public string Host { get; set; }
/// <summary>Gets or sets the SMTP server port number.</summary>
public int Port { get; set; }
/// <summary>Gets or sets the authentication type (e.g. Basic, OAuth2ClientCredentials).</summary>
public string AuthType { get; set; }
/// <summary>Gets or sets the serialized credentials (password or OAuth2 client secret), or null when not applicable.</summary>
public string? Credentials { get; set; }
/// <summary>Gets or sets the TLS mode (None, StartTLS, or SSL), or null to use the provider default.</summary>
public string? TlsMode { get; set; }
/// <summary>Gets or sets the sender address placed in the From header.</summary>
public string FromAddress { get; set; }
/// <summary>Gets or sets the connection timeout in seconds.</summary>
public int ConnectionTimeoutSeconds { get; set; }
/// <summary>Gets or sets the maximum number of concurrent SMTP connections.</summary>
public int MaxConcurrentConnections { get; set; }
/// <summary>Gets or sets the maximum number of delivery retries before parking.</summary>
public int MaxRetries { get; set; }
/// <summary>Gets or sets the delay between retry attempts.</summary>
public TimeSpan RetryDelay { get; set; }
/// <summary>
/// Initializes a new <see cref="SmtpConfiguration"/> with required fields.
/// </summary>
/// <param name="host">SMTP server hostname or IP address.</param>
/// <param name="authType">Authentication type string (e.g. Basic, OAuth2ClientCredentials).</param>
/// <param name="fromAddress">Sender address for the From header.</param>
public SmtpConfiguration(string host, string authType, string fromAddress)
{
Host = host ?? throw new ArgumentNullException(nameof(host));
AuthType = authType ?? throw new ArgumentNullException(nameof(authType));
FromAddress = fromAddress ?? throw new ArgumentNullException(nameof(fromAddress));
}
}
@@ -0,0 +1,26 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
public class SharedScript
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Unique script name used to reference this script from templates.</summary>
public string Name { get; set; }
/// <summary>C# script source code.</summary>
public string Code { get; set; }
/// <summary>JSON-serialized parameter definitions, or null when the script takes no parameters.</summary>
public string? ParameterDefinitions { get; set; }
/// <summary>JSON-serialized return type definition, or null when the script has no return value.</summary>
public string? ReturnDefinition { get; set; }
/// <summary>
/// Initializes a new shared script with the required name and code.
/// </summary>
/// <param name="name">Unique script name.</param>
/// <param name="code">C# script source code.</param>
public SharedScript(string name, string code)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Code = code ?? throw new ArgumentNullException(nameof(code));
}
}
@@ -0,0 +1,23 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Security;
public class LdapGroupMapping
{
/// <summary>Gets or sets the primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the LDAP/AD group CN that this mapping targets.</summary>
public string LdapGroupName { get; set; }
/// <summary>Gets or sets the ScadaBridge role name this group maps to.</summary>
public string Role { get; set; }
// Parameterless constructor for EF Core seed data
private LdapGroupMapping() { LdapGroupName = null!; Role = null!; }
/// <summary>Initializes a new <see cref="LdapGroupMapping"/> linking an LDAP group to a ScadaBridge role.</summary>
/// <param name="ldapGroupName">The LDAP group name (CN).</param>
/// <param name="role">The ScadaBridge role name to assign.</param>
public LdapGroupMapping(string ldapGroupName, string role)
{
LdapGroupName = ldapGroupName ?? throw new ArgumentNullException(nameof(ldapGroupName));
Role = role ?? throw new ArgumentNullException(nameof(role));
}
}
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Security;
public class SiteScopeRule
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the <see cref="LdapGroupMapping"/> this rule restricts.</summary>
public int LdapGroupMappingId { get; set; }
/// <summary>Foreign key to the site this rule limits the mapping to.</summary>
public int SiteId { get; set; }
}
@@ -0,0 +1,32 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
public class DataConnection
{
/// <summary>Gets or sets the database primary key.</summary>
public int Id { get; set; }
/// <summary>Gets or sets the owning site's id.</summary>
public int SiteId { get; set; }
/// <summary>Gets or sets the unique name of this data connection within the site.</summary>
public string Name { get; set; }
/// <summary>Gets or sets the protocol type string (e.g. "OpcUa").</summary>
public string Protocol { get; set; }
/// <summary>Gets or sets the primary protocol-specific configuration JSON.</summary>
public string? PrimaryConfiguration { get; set; }
/// <summary>Gets or sets the backup protocol-specific configuration JSON used on failover.</summary>
public string? BackupConfiguration { get; set; }
/// <summary>Gets or sets the number of failover retry attempts before the connection is marked failed.</summary>
public int FailoverRetryCount { get; set; } = 3;
/// <summary>
/// Initializes a new <see cref="DataConnection"/> with the required fields.
/// </summary>
/// <param name="name">Unique name of the connection within the site.</param>
/// <param name="protocol">Protocol type string (e.g. "OpcUa").</param>
/// <param name="siteId">Id of the owning site.</param>
public DataConnection(string name, string protocol, int siteId)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Protocol = protocol ?? throw new ArgumentNullException(nameof(protocol));
SiteId = siteId;
}
}
@@ -0,0 +1,32 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
public class Site
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Human-readable display name for the site.</summary>
public string Name { get; set; }
/// <summary>Machine-readable identifier used in Akka addresses and API routing.</summary>
public string SiteIdentifier { get; set; }
/// <summary>Optional description of the site.</summary>
public string? Description { get; set; }
/// <summary>Akka remote address for site node A (ClusterClient contact point).</summary>
public string? NodeAAddress { get; set; }
/// <summary>Akka remote address for site node B (ClusterClient contact point).</summary>
public string? NodeBAddress { get; set; }
/// <summary>gRPC endpoint for site node A used by the central SiteStreamGrpcClient.</summary>
public string? GrpcNodeAAddress { get; set; }
/// <summary>gRPC endpoint for site node B used by the central SiteStreamGrpcClient.</summary>
public string? GrpcNodeBAddress { get; set; }
/// <summary>
/// Initializes a new site with the required name and identifier.
/// </summary>
/// <param name="name">Human-readable display name.</param>
/// <param name="siteIdentifier">Machine-readable identifier used in Akka addresses.</param>
public Site(string name, string siteIdentifier)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
SiteIdentifier = siteIdentifier ?? throw new ArgumentNullException(nameof(siteIdentifier));
}
}
@@ -0,0 +1,65 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class Template
{
/// <summary>
/// The unique identifier for the template.
/// </summary>
public int Id { get; set; }
/// <summary>
/// The name of the template.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Optional description of the template.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// The identifier of the parent template, if this template inherits from another.
/// </summary>
public int? ParentTemplateId { get; set; }
/// <summary>
/// The identifier of the folder containing this template.
/// </summary>
public int? FolderId { get; set; }
/// <summary>
/// Collection of attributes defined in this template.
/// </summary>
public ICollection<TemplateAttribute> Attributes { get; set; } = new List<TemplateAttribute>();
/// <summary>
/// Collection of alarms defined in this template.
/// </summary>
public ICollection<TemplateAlarm> Alarms { get; set; } = new List<TemplateAlarm>();
/// <summary>
/// Collection of scripts defined in this template.
/// </summary>
public ICollection<TemplateScript> Scripts { get; set; } = new List<TemplateScript>();
/// <summary>
/// Collection of compositions defined in this template.
/// </summary>
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
/// <summary>
/// True when this template was auto-derived to back a TemplateComposition
/// slot. Derived templates inherit from a base (see <see cref="ParentTemplateId"/>),
/// are owned by their composition row (see <see cref="OwnerCompositionId"/>),
/// and are hidden from the main template tree by default.
/// </summary>
public bool IsDerived { get; set; }
/// <summary>
/// Back-reference to the <see cref="TemplateComposition"/> that owns this
/// derived template. Non-null only when <see cref="IsDerived"/>; cascade-
/// delete when the composition is removed. Always null on base templates.
/// </summary>
public int? OwnerCompositionId { get; set; }
/// <summary>
/// Initializes a new instance of the Template with the specified name.
/// </summary>
/// <param name="name">The name of the template.</param>
public Template(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,49 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class TemplateAlarm
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the owning <see cref="Template"/>.</summary>
public int TemplateId { get; set; }
/// <summary>Unique alarm name within the template.</summary>
public string Name { get; set; }
/// <summary>Optional human-readable description of the alarm.</summary>
public string? Description { get; set; }
/// <summary>Alarm priority level; lower values indicate higher priority.</summary>
public int PriorityLevel { get; set; }
/// <summary>When true, this alarm is locked and cannot be overridden in derived templates.</summary>
public bool IsLocked { get; set; }
/// <summary>Type of trigger condition that activates this alarm.</summary>
public AlarmTriggerType TriggerType { get; set; }
/// <summary>JSON-serialized trigger configuration specific to the <see cref="TriggerType"/>.</summary>
public string? TriggerConfiguration { get; set; }
/// <summary>Optional ID of the script to execute when the alarm triggers.</summary>
public int? OnTriggerScriptId { get; set; }
/// <summary>
/// True when this row was copied from the base template and has not been
/// overridden on the derived template. Changes to the base flow downward
/// for inherited rows; an explicit override flips this to false.
/// Always false on base (non-derived) templates.
/// </summary>
public bool IsInherited { get; set; }
/// <summary>
/// Set on a base alarm. When true, derived templates may not override the
/// alarm — the row is rendered readonly with a 🔒 in the derived UI, and
/// any attempt to update it through the API is rejected.
/// </summary>
public bool LockedInDerived { get; set; }
/// <summary>
/// Initializes a new <see cref="TemplateAlarm"/> with the specified name.
/// </summary>
/// <param name="name">The unique alarm name within the template.</param>
public TemplateAlarm(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,63 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class TemplateAttribute
{
/// <summary>
/// Gets or sets the attribute ID.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the template ID that owns this attribute.
/// </summary>
public int TemplateId { get; set; }
/// <summary>
/// Gets or sets the attribute name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the attribute value.
/// </summary>
public string? Value { get; set; }
/// <summary>
/// Gets or sets the data type of the attribute.
/// </summary>
public DataType DataType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the attribute is locked from override.
/// </summary>
public bool IsLocked { get; set; }
/// <summary>
/// Gets or sets the attribute description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the data source reference for this attribute.
/// </summary>
public string? DataSourceReference { get; set; }
/// <summary>
/// True when this row was copied from the base template and has not been
/// overridden on the derived template. Changes to the base flow downward
/// for inherited rows; an explicit override flips this to false.
/// Always false on base (non-derived) templates.
/// </summary>
public bool IsInherited { get; set; }
/// <summary>
/// Set on a base attribute. When true, derived templates may not override
/// the value — the row is rendered readonly with a 🔒 in the derived UI,
/// and any attempt to update it through the API is rejected.
/// </summary>
public bool LockedInDerived { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TemplateAttribute"/> class with the specified name.
/// </summary>
/// <param name="name">The attribute name.</param>
public TemplateAttribute(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,22 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class TemplateComposition
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the parent template that includes this composition.</summary>
public int TemplateId { get; set; }
/// <summary>Foreign key to the template being composed into the parent.</summary>
public int ComposedTemplateId { get; set; }
/// <summary>Name of the composition instance within the parent template's namespace.</summary>
public string InstanceName { get; set; }
/// <summary>
/// Initializes a new template composition with the required instance name.
/// </summary>
/// <param name="instanceName">Name of this composition slot within the parent template.</param>
public TemplateComposition(string instanceName)
{
InstanceName = instanceName ?? throw new ArgumentNullException(nameof(instanceName));
}
}
@@ -0,0 +1,22 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class TemplateFolder
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Display name for the folder.</summary>
public string Name { get; set; }
/// <summary>ID of the parent folder, or null for root-level folders.</summary>
public int? ParentFolderId { get; set; }
/// <summary>Display ordering position within the parent folder.</summary>
public int SortOrder { get; set; }
/// <summary>
/// Initializes a new <see cref="TemplateFolder"/> with the specified name.
/// </summary>
/// <param name="name">The display name for the folder.</param>
public TemplateFolder(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
@@ -0,0 +1,80 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
public class TemplateScript
{
/// <summary>
/// The script identifier.
/// </summary>
public int Id { get; set; }
/// <summary>
/// The template identifier this script belongs to.
/// </summary>
public int TemplateId { get; set; }
/// <summary>
/// The script name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Whether the script is locked for editing.
/// </summary>
public bool IsLocked { get; set; }
/// <summary>
/// The script code.
/// </summary>
public string Code { get; set; }
/// <summary>
/// The trigger type for the script, or null.
/// </summary>
public string? TriggerType { get; set; }
/// <summary>
/// The trigger configuration, or null.
/// </summary>
public string? TriggerConfiguration { get; set; }
/// <summary>
/// The parameter definitions in JSON format, or null.
/// </summary>
public string? ParameterDefinitions { get; set; }
/// <summary>
/// The return type definition in JSON format, or null.
/// </summary>
public string? ReturnDefinition { get; set; }
/// <summary>
/// The minimum time between script runs, or null.
/// </summary>
public TimeSpan? MinTimeBetweenRuns { get; set; }
/// <summary>
/// True when this row was copied from the base template and has not been
/// overridden on the derived template. Changes to the base flow downward
/// for inherited rows; an explicit override flips this to false.
/// Always false on base (non-derived) templates.
/// </summary>
public bool IsInherited { get; set; }
/// <summary>
/// Set on a base script. When true, derived templates may not override
/// the script body — the row is rendered readonly in the derived
/// UI, and any attempt to update it through the API is rejected.
/// </summary>
public bool LockedInDerived { get; set; }
/// <summary>
/// Initializes a new instance of the TemplateScript.
/// </summary>
/// <param name="name">The script name.</param>
/// <param name="code">The script code.</param>
public TemplateScript(string name, string code)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Code = code ?? throw new ArgumentNullException(nameof(code));
}
}