docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -17,6 +17,9 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
/// </remarks>
|
||||
public sealed class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<OtOpcUaConfigDbContext>
|
||||
{
|
||||
/// <summary>Creates a new DbContext instance for design-time operations.</summary>
|
||||
/// <param name="args">Command-line arguments (unused).</param>
|
||||
/// <returns>The configured DbContext instance.</returns>
|
||||
public OtOpcUaConfigDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var connection = Environment.GetEnvironmentVariable("OTOPCUA_CONFIG_CONNECTION");
|
||||
|
||||
@@ -6,13 +6,16 @@ public sealed class ClusterNode
|
||||
/// <summary>Stable per-machine logical ID, e.g. "LINE3-OPCUA-A".</summary>
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
/// <summary>The unique identifier of the cluster this node belongs to.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Machine hostname / IP.</summary>
|
||||
public required string Host { get; set; }
|
||||
|
||||
/// <summary>The OPC UA server port (default 4840).</summary>
|
||||
public int OpcUaPort { get; set; } = 4840;
|
||||
|
||||
/// <summary>The dashboard HTTP port (default 8081).</summary>
|
||||
public int DashboardPort { get; set; } = 8081;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,15 +35,21 @@ public sealed class ClusterNode
|
||||
/// </summary>
|
||||
public string? DriverConfigOverridesJson { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this node is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets the timestamp when this node was last seen.</summary>
|
||||
public DateTime? LastSeenAt { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the timestamp when this node was created.</summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the username of who created this node.</summary>
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
// Navigation
|
||||
/// <summary>Gets or sets the cluster this node belongs to.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
/// <summary>Gets or sets the credentials associated with this node.</summary>
|
||||
public ICollection<ClusterNodeCredential> Credentials { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -8,22 +8,30 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class ClusterNodeCredential
|
||||
{
|
||||
/// <summary>Gets or sets the credential identifier.</summary>
|
||||
public Guid CredentialId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the node identifier this credential binds to.</summary>
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the credential kind (login, certificate, etc.).</summary>
|
||||
public required CredentialKind Kind { get; set; }
|
||||
|
||||
/// <summary>Login name / cert thumbprint / SID / gMSA name.</summary>
|
||||
/// <summary>Gets or sets the credential value (login name / cert thumbprint / SID / gMSA name).</summary>
|
||||
public required string Value { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the credential is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets the date/time when the credential was last rotated.</summary>
|
||||
public DateTime? RotatedAt { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the date/time when the credential was created.</summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the user who created the credential.</summary>
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the related cluster node.</summary>
|
||||
public ClusterNode? Node { get; set; }
|
||||
}
|
||||
|
||||
@@ -6,21 +6,28 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class ConfigAuditLog
|
||||
{
|
||||
/// <summary>Gets or sets the unique audit log identifier.</summary>
|
||||
public long AuditId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the timestamp of the audit event.</summary>
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the principal (user or service) that initiated the event.</summary>
|
||||
public required string Principal { get; set; }
|
||||
|
||||
/// <summary>DraftCreated | DraftEdited | Published | RolledBack | NodeApplied | CredentialAdded | CredentialDisabled | ClusterCreated | NodeAdded | ExternalIdReleased | CrossClusterNamespaceAttempt | OpcUaAccessDenied | …</summary>
|
||||
public required string EventType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier associated with the event, if applicable.</summary>
|
||||
public string? ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the node identifier associated with the event, if applicable.</summary>
|
||||
public string? NodeId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the generation identifier associated with the event, if applicable.</summary>
|
||||
public long? GenerationId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets additional event details in JSON format.</summary>
|
||||
public string? DetailsJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,21 +7,27 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class ConfigEdit
|
||||
{
|
||||
/// <summary>Gets the unique identifier for this edit.</summary>
|
||||
public Guid EditId { get; init; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>Gets the type of entity that was edited.</summary>
|
||||
public required string EntityType { get; init; }
|
||||
|
||||
/// <summary>Gets the identifier of the entity that was edited.</summary>
|
||||
public Guid EntityId { get; init; }
|
||||
|
||||
/// <summary>JSON payload of the column-name → new-value pairs touched by this edit.</summary>
|
||||
/// <summary>Gets the JSON payload of the column-name → new-value pairs touched by this edit.</summary>
|
||||
public required string FieldsJson { get; init; }
|
||||
|
||||
/// <summary>Optional correlation across edits inside a single admin operation.</summary>
|
||||
/// <summary>Gets the optional correlation identifier across edits inside a single admin operation.</summary>
|
||||
public Guid? ExecutionId { get; init; }
|
||||
|
||||
/// <summary>Gets the username of the user who performed the edit.</summary>
|
||||
public required string EditedBy { get; init; }
|
||||
|
||||
/// <summary>Gets the UTC timestamp when the edit was performed.</summary>
|
||||
public DateTime EditedAtUtc { get; init; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets the node identifier of the admin instance that performed the edit.</summary>
|
||||
public required string SourceNode { get; init; }
|
||||
}
|
||||
|
||||
@@ -10,21 +10,30 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class Deployment
|
||||
{
|
||||
/// <summary>Gets or sets the unique deployment identifier.</summary>
|
||||
public Guid DeploymentId { get; init; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>Gets or sets the revision hash of the deployment artifact.</summary>
|
||||
public required string RevisionHash { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the deployment status.</summary>
|
||||
public DeploymentStatus Status { get; set; } = DeploymentStatus.Dispatching;
|
||||
|
||||
/// <summary>Gets or sets the name of the user who created the deployment.</summary>
|
||||
public required string CreatedBy { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the deployment was created.</summary>
|
||||
public DateTime CreatedAtUtc { get; init; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the serialized artifact blob containing the configuration.</summary>
|
||||
public byte[] ArtifactBlob { get; init; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the row version for optimistic concurrency control.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the failure reason if the deployment failed.</summary>
|
||||
public string? FailureReason { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the deployment was sealed.</summary>
|
||||
public DateTime? SealedAtUtc { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,15 +3,27 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// <summary>Per-device row for multi-device drivers (Modbus, AB CIP). Optional for single-device drivers.</summary>
|
||||
public sealed class Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique database row identifier for the device.
|
||||
/// </summary>
|
||||
public Guid DeviceRowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device identifier.
|
||||
/// </summary>
|
||||
public required string DeviceId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="DriverInstance.DriverInstanceId"/>.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device name.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the device is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Schemaless per-driver-type device config (host, port, unit ID, slot, etc.).</summary>
|
||||
|
||||
@@ -39,6 +39,7 @@ public sealed class DriverHostStatus
|
||||
/// </summary>
|
||||
public required string HostName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the current connectivity state of the host.</summary>
|
||||
public DriverHostState State { get; set; } = DriverHostState.Unknown;
|
||||
|
||||
/// <summary>Timestamp of the last state transition (not of the most recent heartbeat).</summary>
|
||||
|
||||
@@ -3,10 +3,13 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// <summary>One driver instance in a cluster's generation. JSON config is schemaless per-driver-type.</summary>
|
||||
public sealed class DriverInstance
|
||||
{
|
||||
/// <summary>Gets or sets the row ID for this driver instance.</summary>
|
||||
public Guid DriverInstanceRowId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the unique driver instance identifier.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster ID this driver instance belongs to.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -15,11 +18,13 @@ public sealed class DriverInstance
|
||||
/// </summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the friendly name of this driver instance.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Galaxy | ModbusTcp | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient</summary>
|
||||
public required string DriverType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this driver instance is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Schemaless per-driver-type JSON config. Validated against registered JSON schema at draft-publish time (decision #91).</summary>
|
||||
@@ -46,5 +51,6 @@ public sealed class DriverInstance
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the related server cluster for navigation.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </remarks>
|
||||
public sealed class DriverInstanceResilienceStatus
|
||||
{
|
||||
/// <summary>Gets or sets the driver instance identifier.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
/// <summary>Gets or sets the host name.</summary>
|
||||
public required string HostName { get; set; }
|
||||
|
||||
/// <summary>Most recent time the circuit breaker for this (instance, host) opened; null if never.</summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class Equipment
|
||||
{
|
||||
/// <summary>Gets or sets the row identifier for this equipment.</summary>
|
||||
public Guid EquipmentRowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -43,19 +44,29 @@ public sealed class Equipment
|
||||
|
||||
// OPC UA Companion Spec OPC 40010 Machinery Identification fields (decision #139).
|
||||
// All nullable so equipment can be added before identity is fully captured.
|
||||
/// <summary>Gets or sets the manufacturer name for this equipment.</summary>
|
||||
public string? Manufacturer { get; set; }
|
||||
/// <summary>Gets or sets the model number or designation for this equipment.</summary>
|
||||
public string? Model { get; set; }
|
||||
/// <summary>Gets or sets the serial number for this equipment.</summary>
|
||||
public string? SerialNumber { get; set; }
|
||||
/// <summary>Gets or sets the hardware revision level for this equipment.</summary>
|
||||
public string? HardwareRevision { get; set; }
|
||||
/// <summary>Gets or sets the software revision level for this equipment.</summary>
|
||||
public string? SoftwareRevision { get; set; }
|
||||
/// <summary>Gets or sets the year of construction for this equipment.</summary>
|
||||
public short? YearOfConstruction { get; set; }
|
||||
/// <summary>Gets or sets the asset location information for this equipment.</summary>
|
||||
public string? AssetLocation { get; set; }
|
||||
/// <summary>Gets or sets the manufacturer URI for this equipment.</summary>
|
||||
public string? ManufacturerUri { get; set; }
|
||||
/// <summary>Gets or sets the device manual URI for this equipment.</summary>
|
||||
public string? DeviceManualUri { get; set; }
|
||||
|
||||
/// <summary>Nullable hook for future schemas-repo template ID (decision #112).</summary>
|
||||
public string? EquipmentClassRef { get; set; }
|
||||
|
||||
/// <summary>Gets or sets whether this equipment is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -17,15 +17,31 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </remarks>
|
||||
public sealed class EquipmentImportBatch
|
||||
{
|
||||
/// <summary>Gets or sets the unique identifier for this batch.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the user name who created this batch.</summary>
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when this batch was created.</summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the total number of rows staged in this batch.</summary>
|
||||
public int RowsStaged { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of rows accepted in this batch.</summary>
|
||||
public int RowsAccepted { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of rows rejected in this batch.</summary>
|
||||
public int RowsRejected { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when this batch was finalised, or null if still in staging.</summary>
|
||||
public DateTime? FinalisedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the collection of staged rows in this batch.</summary>
|
||||
public ICollection<EquipmentImportRow> Rows { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -37,32 +53,74 @@ public sealed class EquipmentImportBatch
|
||||
/// </summary>
|
||||
public sealed class EquipmentImportRow
|
||||
{
|
||||
/// <summary>Gets or sets the unique identifier for this row.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the parent batch identifier.</summary>
|
||||
public Guid BatchId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the line number in the source file.</summary>
|
||||
public int LineNumberInFile { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this row was accepted.</summary>
|
||||
public bool IsAccepted { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the reason this row was rejected, if applicable.</summary>
|
||||
public string? RejectReason { get; set; }
|
||||
|
||||
// Required (decision #117)
|
||||
/// <summary>Gets or sets the Z tag identifier.</summary>
|
||||
public required string ZTag { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the machine code.</summary>
|
||||
public required string MachineCode { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the SAP identifier.</summary>
|
||||
public required string SAPID { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment identifier.</summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment UUID.</summary>
|
||||
public required string EquipmentUuid { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment name.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UNS area name.</summary>
|
||||
public required string UnsAreaName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UNS line name.</summary>
|
||||
public required string UnsLineName { get; set; }
|
||||
|
||||
// Optional (decision #139 — OPC 40010 Identification)
|
||||
/// <summary>Gets or sets the manufacturer name.</summary>
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment model.</summary>
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the serial number.</summary>
|
||||
public string? SerialNumber { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the hardware revision.</summary>
|
||||
public string? HardwareRevision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the software revision.</summary>
|
||||
public string? SoftwareRevision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the year of construction.</summary>
|
||||
public string? YearOfConstruction { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the asset location.</summary>
|
||||
public string? AssetLocation { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the manufacturer URI.</summary>
|
||||
public string? ManufacturerUri { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the device manual URI.</summary>
|
||||
public string? DeviceManualUri { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the parent batch.</summary>
|
||||
public EquipmentImportBatch? Batch { get; set; }
|
||||
}
|
||||
|
||||
@@ -9,10 +9,13 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class ExternalIdReservation
|
||||
{
|
||||
/// <summary>Gets or sets the unique reservation identifier.</summary>
|
||||
public Guid ReservationId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the kind of reservation (ZTag or SAPID).</summary>
|
||||
public required ReservationKind Kind { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the reserved external ID value.</summary>
|
||||
public required string Value { get; set; }
|
||||
|
||||
/// <summary>The equipment that owns this reservation. Stays bound even when equipment is disabled.</summary>
|
||||
@@ -21,16 +24,21 @@ public sealed class ExternalIdReservation
|
||||
/// <summary>First cluster to publish this reservation.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the timestamp when the reservation was first published.</summary>
|
||||
public DateTime FirstPublishedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the identifier of the user or system that first published the reservation.</summary>
|
||||
public required string FirstPublishedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the timestamp of the most recent publication.</summary>
|
||||
public DateTime LastPublishedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Non-null when explicitly released by FleetAdmin (audit-logged, requires reason).</summary>
|
||||
public DateTime? ReleasedAt { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the identifier of the user or system that released the reservation.</summary>
|
||||
public string? ReleasedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the reason for releasing the reservation.</summary>
|
||||
public string? ReleaseReason { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,24 +8,30 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class Namespace
|
||||
{
|
||||
/// <summary>Gets or sets the row identifier for this namespace.</summary>
|
||||
public Guid NamespaceRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID, e.g. "LINE3-OPCUA-equipment". Globally unique in v2.</summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the namespace kind.</summary>
|
||||
public required NamespaceKind Kind { get; set; }
|
||||
|
||||
/// <summary>E.g. "urn:zb:warsaw-west:equipment". Unique fleet-wide per generation.</summary>
|
||||
public required string NamespaceUri { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the namespace is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets optional notes about the namespace.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the associated server cluster.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,14 +8,19 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class NodeAcl
|
||||
{
|
||||
/// <summary>Gets or sets the database row ID for this ACL entry.</summary>
|
||||
public Guid NodeAclRowId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the logical ID of this ACL entry.</summary>
|
||||
public required string NodeAclId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster ID for this ACL entry.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the LDAP group for this ACL entry.</summary>
|
||||
public required string LdapGroup { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the scope kind for this ACL entry.</summary>
|
||||
public required NodeAclScopeKind ScopeKind { get; set; }
|
||||
|
||||
/// <summary>NULL when <see cref="ScopeKind"/> = <see cref="NodeAclScopeKind.Cluster"/>; otherwise the scoped entity's logical ID.</summary>
|
||||
@@ -24,6 +29,7 @@ public sealed class NodeAcl
|
||||
/// <summary>Bitmask of <see cref="NodePermissions"/>. Stored as int in SQL.</summary>
|
||||
public required NodePermissions PermissionFlags { get; set; }
|
||||
|
||||
/// <summary>Gets or sets optional notes for this ACL entry.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -10,20 +10,29 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class NodeDeploymentState
|
||||
{
|
||||
/// <summary>Gets or sets the cluster node identifier.</summary>
|
||||
public required string NodeId { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the deployment identifier.</summary>
|
||||
public Guid DeploymentId { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the deployment status on this node.</summary>
|
||||
public NodeDeploymentStatus Status { get; set; } = NodeDeploymentStatus.Applying;
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the deployment application started.</summary>
|
||||
public DateTime StartedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the deployment was successfully applied, or null if not yet applied.</summary>
|
||||
public DateTime? AppliedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the failure reason if the deployment failed, or null if successful.</summary>
|
||||
public string? FailureReason { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the row version for optimistic concurrency control.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the cluster node entity reference.</summary>
|
||||
public ClusterNode? Node { get; set; }
|
||||
/// <summary>Gets or sets the deployment entity reference.</summary>
|
||||
public Deployment? Deployment { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,14 +3,19 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// <summary>Driver-scoped polling group. Tags reference it via <see cref="Tag.PollGroupId"/>.</summary>
|
||||
public sealed class PollGroup
|
||||
{
|
||||
/// <summary>Gets or sets the database row identifier for the polling group.</summary>
|
||||
public Guid PollGroupRowId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the unique identifier for the polling group.</summary>
|
||||
public required string PollGroupId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the driver instance that owns this polling group.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the display name of the polling group.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the poll interval in milliseconds.</summary>
|
||||
public int IntervalMs { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </remarks>
|
||||
public sealed class Script
|
||||
{
|
||||
/// <summary>Gets or sets the script row identifier.</summary>
|
||||
public Guid ScriptRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id. Globally unique in v2.</summary>
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </remarks>
|
||||
public sealed class ScriptedAlarm
|
||||
{
|
||||
/// <summary>Gets or sets the database row identifier for this scripted alarm.</summary>
|
||||
public Guid ScriptedAlarmRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id — drives <c>AlarmConditionType.ConditionName</c>. Globally unique in v2.</summary>
|
||||
@@ -52,6 +53,7 @@ public sealed class ScriptedAlarm
|
||||
/// </summary>
|
||||
public bool Retain { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this alarm is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -45,13 +45,16 @@ public sealed class ScriptedAlarmState
|
||||
/// <summary>Operator-supplied ack comment. Null if no comment or never acked.</summary>
|
||||
public string? LastAckComment { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp of the last acknowledgment.</summary>
|
||||
public DateTime? LastAckUtc { get; set; }
|
||||
|
||||
/// <summary>User who last confirmed.</summary>
|
||||
public string? LastConfirmUser { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the operator-supplied confirm comment. Null if no comment or never confirmed.</summary>
|
||||
public string? LastConfirmComment { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp of the last confirmation.</summary>
|
||||
public DateTime? LastConfirmUtc { get; set; }
|
||||
|
||||
/// <summary>JSON array of operator comments, append-only (GxP audit).</summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed class ServerCluster
|
||||
/// <summary>Stable logical ID, e.g. "LINE3-OPCUA".</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the display name for the server cluster.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>UNS level 1. Canonical org value: "zb" per decision #140.</summary>
|
||||
@@ -19,23 +20,33 @@ public sealed class ServerCluster
|
||||
/// <summary>UNS level 2, e.g. "warsaw-west".</summary>
|
||||
public required string Site { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of nodes in the cluster.</summary>
|
||||
public byte NodeCount { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the redundancy mode for the cluster.</summary>
|
||||
public required RedundancyMode RedundancyMode { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the cluster is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets optional notes about the cluster.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the cluster was created.</summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Gets or sets the user who created the cluster.</summary>
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when the cluster was last modified.</summary>
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the user who last modified the cluster.</summary>
|
||||
public string? ModifiedBy { get; set; }
|
||||
|
||||
// Navigation
|
||||
/// <summary>Gets or sets the collection of cluster nodes.</summary>
|
||||
public ICollection<ClusterNode> Nodes { get; set; } = [];
|
||||
/// <summary>Gets or sets the collection of namespaces in the cluster.</summary>
|
||||
public ICollection<Namespace> Namespaces { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -9,12 +9,24 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </summary>
|
||||
public sealed class Tag
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique database row identifier for the tag.
|
||||
/// </summary>
|
||||
public Guid TagRowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag identifier.
|
||||
/// </summary>
|
||||
public required string TagId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the driver instance identifier for this tag.
|
||||
/// </summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device identifier.
|
||||
/// </summary>
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -23,6 +35,9 @@ public sealed class Tag
|
||||
/// </summary>
|
||||
public string? EquipmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag name.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Only used when <see cref="EquipmentId"/> is NULL (SystemPlatform namespace).</summary>
|
||||
@@ -31,11 +46,17 @@ public sealed class Tag
|
||||
/// <summary>OPC UA built-in type name (Boolean / Int32 / Float / etc.).</summary>
|
||||
public required string DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access level for this tag.
|
||||
/// </summary>
|
||||
public required TagAccessLevel AccessLevel { get; set; }
|
||||
|
||||
/// <summary>Per decisions #44–45 — opt-in for write retry eligibility.</summary>
|
||||
public bool WriteIdempotent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the poll group identifier for batching read/write operations.
|
||||
/// </summary>
|
||||
public string? PollGroupId { get; set; }
|
||||
|
||||
/// <summary>Register address / scaling / poll group / byte-order / etc. — schemaless per driver type.</summary>
|
||||
|
||||
@@ -3,19 +3,24 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// <summary>UNS level-3 segment. Generation-versioned per decision #115.</summary>
|
||||
public sealed class UnsArea
|
||||
{
|
||||
/// <summary>Gets or sets the unique row identifier.</summary>
|
||||
public Guid UnsAreaRowId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UNS area identifier.</summary>
|
||||
public required string UnsAreaId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>UNS level 3 segment: matches <c>^[a-z0-9-]{1,32}$</c> OR equals literal <c>_default</c>.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets optional notes for the area.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the associated server cluster.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// <summary>UNS level-4 segment. Generation-versioned per decision #115.</summary>
|
||||
public sealed class UnsLine
|
||||
{
|
||||
/// <summary>Gets or sets the unique row identifier for this UNS line.</summary>
|
||||
public Guid UnsLineRowId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the unique identifier for this UNS line.</summary>
|
||||
public required string UnsLineId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="UnsArea.UnsAreaId"/>.</summary>
|
||||
@@ -13,6 +15,7 @@ public sealed class UnsLine
|
||||
/// <summary>UNS level 4 segment: matches <c>^[a-z0-9-]{1,32}$</c> OR equals literal <c>_default</c>.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets optional notes describing this UNS line.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -20,9 +20,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
/// </remarks>
|
||||
public sealed class VirtualTag
|
||||
{
|
||||
/// <summary>Gets or sets the database row ID (primary key).</summary>
|
||||
public Guid VirtualTagRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id. Globally unique in v2.</summary>
|
||||
/// <summary>Gets or sets the stable logical identifier, globally unique in v2.</summary>
|
||||
public required string VirtualTagId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Equipment.EquipmentId"/> — owner of this virtual tag.</summary>
|
||||
@@ -43,9 +44,10 @@ public sealed class VirtualTag
|
||||
/// <summary>Timer re-evaluation cadence in milliseconds. <c>null</c> = no timer.</summary>
|
||||
public int? TimerIntervalMs { get; set; }
|
||||
|
||||
/// <summary>Per plan decision #10 — checkbox to route this tag's values through <c>IHistoryWriter</c>.</summary>
|
||||
/// <summary>Gets or sets a value indicating whether this tag's values should be historized.</summary>
|
||||
public bool Historize { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this virtual tag is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
|
||||
@@ -31,6 +31,8 @@ public sealed class GenerationSealedCache
|
||||
/// <summary>Root directory for all clusters' sealed caches.</summary>
|
||||
public string CacheRoot => _cacheRoot;
|
||||
|
||||
/// <summary>Initializes a new instance of the GenerationSealedCache class.</summary>
|
||||
/// <param name="cacheRoot">The root directory for the cache.</param>
|
||||
public GenerationSealedCache(string cacheRoot)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(cacheRoot);
|
||||
@@ -43,6 +45,9 @@ public sealed class GenerationSealedCache
|
||||
/// mark the file read-only, then atomically publish the <c>CURRENT</c> pointer. Existing
|
||||
/// sealed files for prior generations are preserved (prune separately).
|
||||
/// </summary>
|
||||
/// <param name="snapshot">The generation snapshot to seal.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task SealAsync(GenerationSnapshot snapshot, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
@@ -88,6 +93,9 @@ public sealed class GenerationSealedCache
|
||||
/// (first-boot-no-snapshot case) or when the sealed file is corrupt. Never silently
|
||||
/// falls back to a prior generation.
|
||||
/// </summary>
|
||||
/// <param name="clusterId">The cluster ID to read the snapshot for.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous operation containing the generation snapshot.</returns>
|
||||
public Task<GenerationSnapshot> ReadCurrentAsync(string clusterId, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(clusterId);
|
||||
@@ -135,6 +143,8 @@ public sealed class GenerationSealedCache
|
||||
}
|
||||
|
||||
/// <summary>Return the generation id the <c>CURRENT</c> pointer points at, or null if no pointer exists.</summary>
|
||||
/// <param name="clusterId">The cluster ID to get the current generation ID for.</param>
|
||||
/// <returns>The generation ID, or null if no pointer exists.</returns>
|
||||
public long? TryGetCurrentGenerationId(string clusterId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(clusterId);
|
||||
@@ -165,6 +175,11 @@ public sealed class GenerationSealedCache
|
||||
/// <summary>Sealed cache is unreachable — caller must fail closed.</summary>
|
||||
public sealed class GenerationCacheUnavailableException : Exception
|
||||
{
|
||||
/// <summary>Initializes a new instance of the GenerationCacheUnavailableException class.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
public GenerationCacheUnavailableException(string message) : base(message) { }
|
||||
/// <summary>Initializes a new instance of the GenerationCacheUnavailableException class with an inner exception.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="inner">The inner exception.</param>
|
||||
public GenerationCacheUnavailableException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
|
||||
/// </summary>
|
||||
public sealed class GenerationSnapshot
|
||||
{
|
||||
/// <summary>Gets or sets the auto-generated LiteDB ID.</summary>
|
||||
public int Id { get; set; } // LiteDB auto-ID
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
/// <summary>Gets or sets the generation identifier.</summary>
|
||||
public required long GenerationId { get; set; }
|
||||
/// <summary>Gets or sets the time this snapshot was cached.</summary>
|
||||
public required DateTime CachedAt { get; set; }
|
||||
/// <summary>Gets or sets the JSON-serialized payload content.</summary>
|
||||
public required string PayloadJson { get; set; }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,18 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
|
||||
/// </remarks>
|
||||
public interface ILocalConfigCache
|
||||
{
|
||||
/// <summary>Retrieves the most recent generation snapshot for the specified cluster.</summary>
|
||||
/// <param name="clusterId">The cluster identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The most recent generation snapshot, or null if none exists.</returns>
|
||||
Task<GenerationSnapshot?> GetMostRecentAsync(string clusterId, CancellationToken ct = default);
|
||||
/// <summary>Stores a generation snapshot in the local cache.</summary>
|
||||
/// <param name="snapshot">The generation snapshot to store.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
Task PutAsync(GenerationSnapshot snapshot, CancellationToken ct = default);
|
||||
/// <summary>Removes old generations, keeping only the most recent N.</summary>
|
||||
/// <param name="clusterId">The cluster identifier.</param>
|
||||
/// <param name="keepLatest">The number of latest generations to keep.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
Task PruneOldGenerationsAsync(string clusterId, int keepLatest = 10, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class LiteDbConfigCache : ILocalConfigCache, IDisposable
|
||||
// page-level write, not the find-then-insert window.
|
||||
private readonly SemaphoreSlim _writeGate = new(initialCount: 1, maxCount: 1);
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="LiteDbConfigCache"/> class.</summary>
|
||||
/// <param name="dbPath">Path to the LiteDB database file.</param>
|
||||
public LiteDbConfigCache(string dbPath)
|
||||
{
|
||||
// LiteDB can be tolerant of header-only corruption at construction time (it may overwrite
|
||||
@@ -43,6 +45,9 @@ public sealed class LiteDbConfigCache : ILocalConfigCache, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the most recent snapshot for the specified cluster.</summary>
|
||||
/// <param name="clusterId">The cluster ID.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
public Task<GenerationSnapshot?> GetMostRecentAsync(string clusterId, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
@@ -53,6 +58,9 @@ public sealed class LiteDbConfigCache : ILocalConfigCache, IDisposable
|
||||
return Task.FromResult<GenerationSnapshot?>(snapshot);
|
||||
}
|
||||
|
||||
/// <summary>Stores a snapshot in the cache.</summary>
|
||||
/// <param name="snapshot">The snapshot to store.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
public async Task PutAsync(GenerationSnapshot snapshot, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
@@ -81,6 +89,10 @@ public sealed class LiteDbConfigCache : ILocalConfigCache, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes old generation snapshots, keeping only the latest ones.</summary>
|
||||
/// <param name="clusterId">The cluster ID.</param>
|
||||
/// <param name="keepLatest">Number of latest generations to keep.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
public Task PruneOldGenerationsAsync(string clusterId, int keepLatest = 10, CancellationToken ct = default)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
@@ -97,6 +109,7 @@ public sealed class LiteDbConfigCache : ILocalConfigCache, IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Releases all resources used by the cache.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_writeGate.Dispose();
|
||||
|
||||
@@ -27,6 +27,12 @@ public sealed class ResilientConfigReader
|
||||
private readonly ResiliencePipeline _pipeline;
|
||||
private readonly ILogger<ResilientConfigReader> _logger;
|
||||
|
||||
/// <summary>Initializes a resilient config reader with the given cache and options.</summary>
|
||||
/// <param name="cache">The sealed cache for fallback.</param>
|
||||
/// <param name="staleFlag">The stale config flag to manage.</param>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
/// <param name="timeout">The timeout for central fetch (default 2s).</param>
|
||||
/// <param name="retryCount">The number of retries (default 3).</param>
|
||||
public ResilientConfigReader(
|
||||
GenerationSealedCache cache,
|
||||
StaleConfigFlag staleFlag,
|
||||
@@ -71,6 +77,9 @@ public sealed class ResilientConfigReader
|
||||
@"(?ix)\b(Password|Pwd|User\s*Id|Uid|AccessToken|Authorization|Api[-_]?Key)\s*=\s*[^;,)\s]*",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
/// <summary>Redacts sensitive credential information from a message.</summary>
|
||||
/// <param name="message">The message to scrub.</param>
|
||||
/// <returns>The message with redacted credentials.</returns>
|
||||
internal static string ScrubSecrets(string? message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message)) return message ?? string.Empty;
|
||||
@@ -80,10 +89,15 @@ public sealed class ResilientConfigReader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute <paramref name="centralFetch"/> through the resilience pipeline. On full failure
|
||||
/// (post-retry), reads the sealed cache for <paramref name="clusterId"/> and passes the
|
||||
/// snapshot to <paramref name="fromSnapshot"/> to extract the requested shape.
|
||||
/// Executes a central fetch through the resilience pipeline. On full failure
|
||||
/// (post-retry), reads the sealed cache and extracts the requested shape.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of configuration to read.</typeparam>
|
||||
/// <param name="clusterId">The cluster ID to fetch for.</param>
|
||||
/// <param name="centralFetch">Function to fetch from central DB.</param>
|
||||
/// <param name="fromSnapshot">Function to extract the config from a snapshot.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The configuration of type T.</returns>
|
||||
public async ValueTask<T> ReadAsync<T>(
|
||||
string clusterId,
|
||||
Func<CancellationToken, ValueTask<T>> centralFetch,
|
||||
|
||||
@@ -12,39 +12,69 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbContext> options)
|
||||
: DbContext(options), IDataProtectionKeyContext
|
||||
{
|
||||
/// <summary>Gets the DbSet of server clusters.</summary>
|
||||
public DbSet<ServerCluster> ServerClusters => Set<ServerCluster>();
|
||||
/// <summary>Gets the DbSet of cluster nodes.</summary>
|
||||
public DbSet<ClusterNode> ClusterNodes => Set<ClusterNode>();
|
||||
/// <summary>Gets the DbSet of cluster node credentials.</summary>
|
||||
public DbSet<ClusterNodeCredential> ClusterNodeCredentials => Set<ClusterNodeCredential>();
|
||||
/// <summary>Gets the DbSet of namespaces.</summary>
|
||||
public DbSet<Namespace> Namespaces => Set<Namespace>();
|
||||
/// <summary>Gets the DbSet of UNS areas.</summary>
|
||||
public DbSet<UnsArea> UnsAreas => Set<UnsArea>();
|
||||
/// <summary>Gets the DbSet of UNS lines.</summary>
|
||||
public DbSet<UnsLine> UnsLines => Set<UnsLine>();
|
||||
/// <summary>Gets the DbSet of driver instances.</summary>
|
||||
public DbSet<DriverInstance> DriverInstances => Set<DriverInstance>();
|
||||
/// <summary>Gets the DbSet of devices.</summary>
|
||||
public DbSet<Device> Devices => Set<Device>();
|
||||
/// <summary>Gets the DbSet of equipment.</summary>
|
||||
public DbSet<Equipment> Equipment => Set<Equipment>();
|
||||
/// <summary>Gets the DbSet of tags.</summary>
|
||||
public DbSet<Tag> Tags => Set<Tag>();
|
||||
/// <summary>Gets the DbSet of poll groups.</summary>
|
||||
public DbSet<PollGroup> PollGroups => Set<PollGroup>();
|
||||
/// <summary>Gets the DbSet of node ACLs.</summary>
|
||||
public DbSet<NodeAcl> NodeAcls => Set<NodeAcl>();
|
||||
/// <summary>Gets the DbSet of configuration audit logs.</summary>
|
||||
public DbSet<ConfigAuditLog> ConfigAuditLogs => Set<ConfigAuditLog>();
|
||||
/// <summary>Gets the DbSet of external ID reservations.</summary>
|
||||
public DbSet<ExternalIdReservation> ExternalIdReservations => Set<ExternalIdReservation>();
|
||||
/// <summary>Gets the DbSet of driver host statuses.</summary>
|
||||
public DbSet<DriverHostStatus> DriverHostStatuses => Set<DriverHostStatus>();
|
||||
/// <summary>Gets the DbSet of driver instance resilience statuses.</summary>
|
||||
public DbSet<DriverInstanceResilienceStatus> DriverInstanceResilienceStatuses => Set<DriverInstanceResilienceStatus>();
|
||||
/// <summary>Gets the DbSet of LDAP group role mappings.</summary>
|
||||
public DbSet<LdapGroupRoleMapping> LdapGroupRoleMappings => Set<LdapGroupRoleMapping>();
|
||||
/// <summary>Gets the DbSet of equipment import batches.</summary>
|
||||
public DbSet<EquipmentImportBatch> EquipmentImportBatches => Set<EquipmentImportBatch>();
|
||||
/// <summary>Gets the DbSet of equipment import rows.</summary>
|
||||
public DbSet<EquipmentImportRow> EquipmentImportRows => Set<EquipmentImportRow>();
|
||||
/// <summary>Gets the DbSet of scripts.</summary>
|
||||
public DbSet<Script> Scripts => Set<Script>();
|
||||
/// <summary>Gets the DbSet of virtual tags.</summary>
|
||||
public DbSet<VirtualTag> VirtualTags => Set<VirtualTag>();
|
||||
/// <summary>Gets the DbSet of scripted alarms.</summary>
|
||||
public DbSet<ScriptedAlarm> ScriptedAlarms => Set<ScriptedAlarm>();
|
||||
/// <summary>Gets the DbSet of scripted alarm states.</summary>
|
||||
public DbSet<ScriptedAlarmState> ScriptedAlarmStates => Set<ScriptedAlarmState>();
|
||||
|
||||
// v2 deploy-model tables (Phase 1 of the Akka + fused-hosting alignment).
|
||||
/// <summary>Gets the DbSet of deployments.</summary>
|
||||
public DbSet<Deployment> Deployments => Set<Deployment>();
|
||||
/// <summary>Gets the DbSet of node deployment states.</summary>
|
||||
public DbSet<NodeDeploymentState> NodeDeploymentStates => Set<NodeDeploymentState>();
|
||||
/// <summary>Gets the DbSet of configuration edits.</summary>
|
||||
public DbSet<ConfigEdit> ConfigEdits => Set<ConfigEdit>();
|
||||
|
||||
// ASP.NET DataProtection key ring storage (decision: keys persisted in ConfigDb so every
|
||||
// admin-role node decrypts the same cookies without sharing a filesystem).
|
||||
/// <summary>Gets the DbSet of data protection keys.</summary>
|
||||
public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();
|
||||
|
||||
/// <summary>Configures the entity model when the context is first created.</summary>
|
||||
/// <param name="modelBuilder">The model builder used to configure the context.</param>
|
||||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
@@ -12,6 +12,9 @@ public static class ServiceCollectionExtensions
|
||||
/// Registers <see cref="IDbContextFactory{TContext}"/> for <see cref="OtOpcUaConfigDbContext"/>
|
||||
/// using the connection string named <c>ConfigDb</c> from <see cref="IConfiguration"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to configure.</param>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddOtOpcUaConfigDb(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString(ConnectionStringName)
|
||||
|
||||
@@ -22,10 +22,13 @@ public interface ILdapGroupRoleMappingService
|
||||
/// Hot path — fires on every sign-in. The default EF implementation relies on the
|
||||
/// <c>IX_LdapGroupRoleMapping_Group</c> index. Case-insensitive per LDAP conventions.
|
||||
/// </remarks>
|
||||
/// <param name="ldapGroups">The LDAP groups to search for.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(
|
||||
IEnumerable<string> ldapGroups, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Enumerate every mapping; Admin UI listing only.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Create a new grant.</summary>
|
||||
@@ -34,14 +37,20 @@ public interface ILdapGroupRoleMappingService
|
||||
/// ClusterId, duplicate (group, cluster) pair, etc.) — ValidatedLdapGroupRoleMappingService
|
||||
/// is the write surface that enforces these; the raw service here surfaces DB-level violations.
|
||||
/// </exception>
|
||||
/// <param name="row">The LDAP group role mapping to create.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping row, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Delete a mapping by its surrogate key.</summary>
|
||||
/// <param name="id">The unique identifier of the mapping to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task DeleteAsync(Guid id, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Thrown when <see cref="LdapGroupRoleMapping"/> authoring violates an invariant.</summary>
|
||||
public sealed class InvalidLdapGroupRoleMappingException : Exception
|
||||
{
|
||||
/// <summary>Initializes a new instance of the InvalidLdapGroupRoleMappingException.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
public InvalidLdapGroupRoleMappingException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Services;
|
||||
/// </summary>
|
||||
public sealed class LdapGroupRoleMappingService(OtOpcUaConfigDbContext db) : ILdapGroupRoleMappingService
|
||||
{
|
||||
/// <summary>Gets LDAP group role mappings for the specified groups.</summary>
|
||||
/// <param name="ldapGroups">The LDAP group names to query.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The matching role mappings.</returns>
|
||||
public async Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(
|
||||
IEnumerable<string> ldapGroups, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -24,6 +28,9 @@ public sealed class LdapGroupRoleMappingService(OtOpcUaConfigDbContext db) : ILd
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Lists all LDAP group role mappings.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>All role mappings ordered by group and cluster ID.</returns>
|
||||
public async Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken cancellationToken)
|
||||
=> await db.LdapGroupRoleMappings
|
||||
.AsNoTracking()
|
||||
@@ -32,6 +39,10 @@ public sealed class LdapGroupRoleMappingService(OtOpcUaConfigDbContext db) : ILd
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>Creates a new LDAP group role mapping.</summary>
|
||||
/// <param name="row">The mapping to create.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The created mapping with generated ID and timestamp.</returns>
|
||||
public async Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping row, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(row);
|
||||
@@ -45,6 +56,10 @@ public sealed class LdapGroupRoleMappingService(OtOpcUaConfigDbContext db) : ILd
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>Deletes an LDAP group role mapping.</summary>
|
||||
/// <param name="id">The mapping identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that completes when the deletion is done.</returns>
|
||||
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var existing = await db.LdapGroupRoleMappings.FindAsync([id], cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Validation;
|
||||
/// </summary>
|
||||
public sealed class DraftSnapshot
|
||||
{
|
||||
/// <summary>Gets the draft generation identifier.</summary>
|
||||
public required long GenerationId { get; init; }
|
||||
/// <summary>Gets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
@@ -23,13 +25,21 @@ public sealed class DraftSnapshot
|
||||
/// </summary>
|
||||
public string? Site { get; init; }
|
||||
|
||||
/// <summary>Gets the list of OPC UA namespaces.</summary>
|
||||
public IReadOnlyList<Namespace> Namespaces { get; init; } = [];
|
||||
/// <summary>Gets the list of driver instances.</summary>
|
||||
public IReadOnlyList<DriverInstance> DriverInstances { get; init; } = [];
|
||||
/// <summary>Gets the list of devices.</summary>
|
||||
public IReadOnlyList<Device> Devices { get; init; } = [];
|
||||
/// <summary>Gets the list of UNS areas.</summary>
|
||||
public IReadOnlyList<UnsArea> UnsAreas { get; init; } = [];
|
||||
/// <summary>Gets the list of UNS lines.</summary>
|
||||
public IReadOnlyList<UnsLine> UnsLines { get; init; } = [];
|
||||
/// <summary>Gets the list of equipment.</summary>
|
||||
public IReadOnlyList<Equipment> Equipment { get; init; } = [];
|
||||
/// <summary>Gets the list of tags.</summary>
|
||||
public IReadOnlyList<Tag> Tags { get; init; } = [];
|
||||
/// <summary>Gets the list of poll groups.</summary>
|
||||
public IReadOnlyList<PollGroup> PollGroups { get; init; } = [];
|
||||
|
||||
/// <summary>Prior Equipment rows (any generation, same cluster) for stability checks.</summary>
|
||||
|
||||
@@ -17,6 +17,10 @@ public static class DraftValidator
|
||||
private const string UnsDefaultSegment = "_default";
|
||||
private const int MaxPathLength = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Validates a draft snapshot and returns all validation errors found in a single pass.
|
||||
/// </summary>
|
||||
/// <param name="draft">The draft snapshot to validate.</param>
|
||||
public static IReadOnlyList<ValidationError> Validate(DraftSnapshot draft)
|
||||
{
|
||||
var errors = new List<ValidationError>();
|
||||
@@ -142,6 +146,7 @@ public static class DraftValidator
|
||||
}
|
||||
|
||||
/// <summary>Decision #125: EquipmentId = 'EQ-' + lowercase first 12 hex chars of the UUID.</summary>
|
||||
/// <param name="uuid">The equipment UUID to derive the ID from.</param>
|
||||
public static string DeriveEquipmentId(Guid uuid) =>
|
||||
"EQ-" + uuid.ToString("N")[..12].ToLowerInvariant();
|
||||
|
||||
@@ -196,6 +201,8 @@ public static class DraftValidator
|
||||
/// <see cref="DraftSnapshot"/>. Returns every failing rule in one pass, same shape as
|
||||
/// <see cref="Validate"/>.
|
||||
/// </remarks>
|
||||
/// <param name="cluster">The server cluster to validate.</param>
|
||||
/// <param name="clusterNodes">The cluster nodes to validate against the cluster configuration.</param>
|
||||
public static IReadOnlyList<ValidationError> ValidateClusterTopology(
|
||||
ServerCluster cluster,
|
||||
IReadOnlyList<ClusterNode> clusterNodes)
|
||||
|
||||
Reference in New Issue
Block a user