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,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));
|
||||
}
|
||||
}
|
||||
+27
@@ -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));
|
||||
}
|
||||
}
|
||||
+24
@@ -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));
|
||||
}
|
||||
}
|
||||
+32
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user