docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
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 41s
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>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
/// Parameter object carrying the operator-editable fields for an equipment create or update,
|
||||
/// so <see cref="IUnsTreeService.CreateEquipmentAsync"/> and
|
||||
/// <see cref="IUnsTreeService.UpdateEquipmentAsync"/> avoid an unwieldy positional signature.
|
||||
/// The <c>EquipmentId</c> and <c>EquipmentUuid</c> are system-generated (decision #125) and are
|
||||
/// The <c>EquipmentId</c> and <c>EquipmentUuid</c> are system-generated and are
|
||||
/// therefore not part of this input. Optional string fields that arrive whitespace-only are
|
||||
/// collapsed to <c>null</c> by the service.
|
||||
/// </summary>
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed record LineEditDto(string UnsLineId, string UnsAreaId, string Name
|
||||
/// An equipment projected for editing: its system-generated id, the operator-editable identity and
|
||||
/// OPC 40010 identification fields, plus the concurrency token the edit modal must echo back on save.
|
||||
/// </summary>
|
||||
/// <param name="EquipmentId">The system-generated stable id (read-only — never operator-edited, decision #125).</param>
|
||||
/// <param name="EquipmentId">The system-generated stable id (read-only — never operator-edited).</param>
|
||||
/// <param name="Name">UNS level-5 segment name.</param>
|
||||
/// <param name="MachineCode">Operator colloquial id; unique fleet-wide.</param>
|
||||
/// <param name="UnsLineId">The owning line id (the UNS-line selection).</param>
|
||||
@@ -54,7 +54,7 @@ public sealed record EquipmentEditDto(string EquipmentId, string Name, string Ma
|
||||
/// <summary>
|
||||
/// An equipment-bound tag projected for editing: its operator-editable fields, the owning equipment
|
||||
/// (so the host can scope the candidate-driver list and refresh the right node), plus the concurrency
|
||||
/// token the edit modal must echo back on save. Tree tags are always equipment-bound (decision #110),
|
||||
/// token the edit modal must echo back on save. Tree tags are always equipment-bound,
|
||||
/// so <c>FolderPath</c> never surfaces here.
|
||||
/// </summary>
|
||||
/// <param name="TagId">The tag's stable id (read-only on edit).</param>
|
||||
@@ -73,7 +73,7 @@ public sealed record TagEditDto(string TagId, string EquipmentId, string Name, s
|
||||
/// <summary>
|
||||
/// An equipment-bound virtual tag projected for editing: its operator-editable fields, the owning
|
||||
/// equipment (so the host can refresh the right node), plus the concurrency token the edit modal must
|
||||
/// echo back on save. Virtual tags are always scoped to an equipment (plan decision #2), so the modal
|
||||
/// echo back on save. Virtual tags are always scoped to an equipment, so the modal
|
||||
/// never offers an equipment-change control.
|
||||
/// </summary>
|
||||
/// <param name="VirtualTagId">The virtual tag's stable id (read-only on edit).</param>
|
||||
@@ -92,7 +92,7 @@ public sealed record VirtualTagEditDto(string VirtualTagId, string EquipmentId,
|
||||
/// <summary>
|
||||
/// The outcome of a bulk equipment CSV import: how many rows were inserted, how many were skipped
|
||||
/// (existing MachineCode — the importer is additive-only, never an update), and a per-row error list
|
||||
/// for rows that could not be inserted (unknown line, unknown driver, or a decision-#122 cluster
|
||||
/// for rows that could not be inserted (unknown line, unknown driver, or a cluster
|
||||
/// mismatch). Skipped rows never appear in <see cref="Errors"/>.
|
||||
/// </summary>
|
||||
/// <param name="Inserted">The count of new Equipment rows added.</param>
|
||||
@@ -221,7 +221,7 @@ public interface IUnsTreeService
|
||||
|
||||
/// <summary>
|
||||
/// Updates a UNS area's name, notes, and owning cluster. When the cluster changes, the
|
||||
/// decision-#122 reassignment guard blocks the move if any driver-bound equipment under the
|
||||
/// reassignment guard blocks the move if any driver-bound equipment under the
|
||||
/// area is bound to a driver in a different cluster than the target. Uses last-write-wins
|
||||
/// optimistic concurrency on <see cref="Configuration.Entities.UnsArea.RowVersion"/>.
|
||||
/// </summary>
|
||||
@@ -231,7 +231,7 @@ public interface IUnsTreeService
|
||||
/// <param name="newClusterId">The target cluster (may equal the current one).</param>
|
||||
/// <param name="rowVersion">The concurrency token the caller last read.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>Success, a missing-row failure, a #122 guard failure, or a concurrency failure.</returns>
|
||||
/// <returns>Success, a missing-row failure, a cluster-guard failure, or a concurrency failure.</returns>
|
||||
Task<UnsMutationResult> UpdateAreaAsync(string unsAreaId, string name, string? notes, string newClusterId, byte[] rowVersion, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -283,21 +283,21 @@ public interface IUnsTreeService
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new equipment under a UNS line. The <c>EquipmentId</c> is system-generated
|
||||
/// (decision #125: <c>EQ-</c> + the first 12 hex chars of a fresh <c>EquipmentUuid</c>).
|
||||
/// (<c>EQ-</c> + the first 12 hex chars of a fresh <c>EquipmentUuid</c>).
|
||||
/// Fails if the line is unset, if the MachineCode is already used fleet-wide, or if the
|
||||
/// decision-#122 driver-cluster guard trips. Whitespace-only DriverInstanceId/ZTag/SAPID
|
||||
/// driver-cluster guard trips. Whitespace-only DriverInstanceId/ZTag/SAPID
|
||||
/// collapse to <c>null</c>.
|
||||
/// </summary>
|
||||
/// <param name="input">The operator-editable equipment fields.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>Success, a missing-line failure, a duplicate-MachineCode failure, or a #122 guard failure.</returns>
|
||||
/// <returns>Success, a missing-line failure, a duplicate-MachineCode failure, or a cluster-guard failure.</returns>
|
||||
Task<UnsMutationResult> CreateEquipmentAsync(EquipmentInput input, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk-imports equipment from a parsed set of <see cref="EquipmentInput"/> rows in a single
|
||||
/// context, applying the same rules as the single-add path: a row whose <c>UnsLineId</c> does not
|
||||
/// exist is an error; a row whose <c>DriverInstanceId</c> is set but does not resolve is an error;
|
||||
/// a driver-bound row whose driver is in a different cluster than its line fails the decision-#122
|
||||
/// a driver-bound row whose driver is in a different cluster than its line fails the cluster
|
||||
/// guard; and a row whose <c>MachineCode</c> already exists in the DB <em>or</em> earlier in the
|
||||
/// same batch is silently skipped (additive-only — never an update). Inserted rows get a
|
||||
/// system-generated <c>EQ-</c> id and a fresh <c>EquipmentUuid</c>. All inserts are saved once at
|
||||
@@ -310,7 +310,7 @@ public interface IUnsTreeService
|
||||
|
||||
/// <summary>
|
||||
/// Updates an equipment's mutable fields (driver binding, line, name, MachineCode, external
|
||||
/// ids, and the OPC 40010 identification fields). The decision-#122 driver-cluster guard blocks
|
||||
/// ids, and the OPC 40010 identification fields). The driver-cluster guard blocks
|
||||
/// binding to a driver in a different cluster than the equipment's line. Uses last-write-wins
|
||||
/// optimistic concurrency on <see cref="Configuration.Entities.Equipment.RowVersion"/>.
|
||||
/// </summary>
|
||||
@@ -318,7 +318,7 @@ public interface IUnsTreeService
|
||||
/// <param name="input">The new operator-editable equipment fields.</param>
|
||||
/// <param name="rowVersion">The concurrency token the caller last read.</param>
|
||||
/// <param name="ct">A token to cancel the operation.</param>
|
||||
/// <returns>Success, a missing-row failure, a #122 guard failure, or a concurrency failure.</returns>
|
||||
/// <returns>Success, a missing-row failure, a cluster-guard failure, or a concurrency failure.</returns>
|
||||
Task<UnsMutationResult> UpdateEquipmentAsync(string equipmentId, EquipmentInput input, byte[] rowVersion, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -361,7 +361,7 @@ public interface IUnsTreeService
|
||||
/// <summary>
|
||||
/// Loads the drivers eligible to back a tag on the given equipment: drivers in the equipment's
|
||||
/// cluster (<c>Equipment.UnsLine → UnsArea.ClusterId</c>) whose namespace is Equipment-kind
|
||||
/// (decision #110 — tree tags are equipment-bound). Ordered by <c>DriverInstanceId</c>. Returns
|
||||
/// (tree tags are equipment-bound). Ordered by <c>DriverInstanceId</c>. Returns
|
||||
/// an empty list when the equipment cannot be resolved to a cluster.
|
||||
/// </summary>
|
||||
/// <param name="equipmentId">The equipment whose candidate drivers to load.</param>
|
||||
@@ -373,10 +373,10 @@ public interface IUnsTreeService
|
||||
Task<IReadOnlyList<(string DriverInstanceId, string Display, string DriverType, string DriverConfig)>> LoadTagDriversForEquipmentAsync(string equipmentId, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new equipment-bound tag. <c>FolderPath</c> is always <c>null</c> (decision #110 —
|
||||
/// the tree only edits equipment-bound tags). Fails on a duplicate <c>TagId</c>, invalid
|
||||
/// Creates a new equipment-bound tag. <c>FolderPath</c> is always <c>null</c> (the tree
|
||||
/// only edits equipment-bound tags). Fails on a duplicate <c>TagId</c>, invalid
|
||||
/// <c>TagConfig</c> JSON, an unknown driver, a driver whose namespace is not Equipment-kind, a
|
||||
/// driver in a different cluster than the equipment (decision #122), or a name already used on
|
||||
/// driver in a different cluster than the equipment, or a name already used on
|
||||
/// the equipment. Whitespace-only <c>PollGroupId</c> collapses to <c>null</c>.
|
||||
/// </summary>
|
||||
/// <param name="equipmentId">The owning equipment.</param>
|
||||
@@ -388,8 +388,8 @@ public interface IUnsTreeService
|
||||
/// <summary>
|
||||
/// Updates an equipment-bound tag's driver binding, name, data type, access level, write-retry
|
||||
/// flag, poll group, and config. The owning <c>EquipmentId</c> and the <c>null</c>
|
||||
/// <c>FolderPath</c> are preserved. Re-runs the JSON-validity, namespace-kind, and decision-#122
|
||||
/// cluster guards against the tag's existing equipment, and enforces name uniqueness on that
|
||||
/// <c>FolderPath</c> are preserved. Re-runs the JSON-validity, namespace-kind, and cluster
|
||||
/// guards against the tag's existing equipment, and enforces name uniqueness on that
|
||||
/// equipment excluding this tag. Uses last-write-wins optimistic concurrency on
|
||||
/// <see cref="Configuration.Entities.Tag.RowVersion"/>.
|
||||
/// </summary>
|
||||
@@ -432,7 +432,7 @@ public interface IUnsTreeService
|
||||
Task<UnsMutationResult> CreateScriptAsync(string name, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new equipment-bound virtual tag (plan decision #2 — virtual tags are always scoped
|
||||
/// Creates a new equipment-bound virtual tag (virtual tags are always scoped
|
||||
/// to an equipment). Fails if the equipment does not exist, if no script is chosen, if neither a
|
||||
/// change trigger nor a timer is set, if the timer is below the 50 ms minimum, on a duplicate
|
||||
/// <c>VirtualTagId</c>, or on a name already used on the equipment.
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class AbCipTagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The TagConfig JSON string to parse, or <c>null</c>/empty for a new tag.</param>
|
||||
/// <returns>The populated model.</returns>
|
||||
public static AbCipTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -34,6 +36,7 @@ public sealed class AbCipTagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
|
||||
/// (enum as its name string; the optional host address omitted when blank) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "deviceHostAddress", string.IsNullOrWhiteSpace(DeviceHostAddress) ? null : DeviceHostAddress.Trim());
|
||||
@@ -43,6 +46,7 @@ public sealed class AbCipTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or <c>null</c> when valid.</returns>
|
||||
public string? Validate()
|
||||
=> string.IsNullOrWhiteSpace(TagPath) ? "Tag path is required." : null;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class AbLegacyTagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The TagConfig JSON string to parse, or <c>null</c>/empty for a new tag.</param>
|
||||
/// <returns>The populated model.</returns>
|
||||
public static AbLegacyTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -34,6 +36,7 @@ public sealed class AbLegacyTagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
|
||||
/// (enum as its name string; the optional host address omitted when blank) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "deviceHostAddress", string.IsNullOrWhiteSpace(DeviceHostAddress) ? null : DeviceHostAddress.Trim());
|
||||
@@ -43,6 +46,7 @@ public sealed class AbLegacyTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or <c>null</c> when valid.</returns>
|
||||
public string? Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Address))
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class FocasTagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The raw TagConfig JSON string, or null/empty for a fresh model.</param>
|
||||
/// <returns>The populated model, defaulted where fields are absent from <paramref name="json"/>.</returns>
|
||||
public static FocasTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -34,6 +36,7 @@ public sealed class FocasTagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
|
||||
/// (enum as its name string; the optional host address omitted when blank) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "deviceHostAddress", string.IsNullOrWhiteSpace(DeviceHostAddress) ? null : DeviceHostAddress.Trim());
|
||||
@@ -43,6 +46,7 @@ public sealed class FocasTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or null when the model is valid.</returns>
|
||||
public string? Validate()
|
||||
=> string.IsNullOrWhiteSpace(Address) ? "Address is required." : null;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ public sealed class ModbusTagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The raw TagConfig JSON string, or <c>null</c> for a new/empty config.</param>
|
||||
/// <returns>The populated <see cref="ModbusTagConfigModel"/>.</returns>
|
||||
public static ModbusTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -46,6 +48,7 @@ public sealed class ModbusTagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the six exposed fields
|
||||
/// (enums as their name strings) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "region", Region);
|
||||
@@ -58,5 +61,6 @@ public sealed class ModbusTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or <c>null</c> when the model is valid.</returns>
|
||||
public string? Validate() => null;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ public sealed class NativeAlarmModel
|
||||
/// <summary>Loads a model from a TagConfig JSON string. When no <c>alarm</c> object is present the
|
||||
/// model is flagged non-alarm and the alarm fields keep their defaults. Retains every original key
|
||||
/// at the root and inside <c>alarm</c> so a load→save preserves fields this editor doesn't expose.</summary>
|
||||
/// <param name="json">The tag's raw <c>TagConfig</c> JSON, or null/empty for a fresh config.</param>
|
||||
/// <returns>The populated model.</returns>
|
||||
public static NativeAlarmModel FromJson(string? json)
|
||||
{
|
||||
var root = TagConfigJson.ParseOrNew(json);
|
||||
@@ -63,6 +65,7 @@ public sealed class NativeAlarmModel
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string. When <see cref="IsAlarm"/> is set,
|
||||
/// writes the alarm fields over the preserved <c>alarm</c> sub-object (creating it if absent); a
|
||||
/// <c>null</c> <see cref="HistorizeToAveva"/> REMOVES the key so the absent ⇒ historize default holds.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
if (IsAlarm)
|
||||
@@ -92,6 +95,7 @@ public sealed class NativeAlarmModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or null when the model is valid.</returns>
|
||||
public string? Validate() => null;
|
||||
|
||||
/// <summary>Default <c>alarmType</c> seeded when an alarm-bearing attribute is picked but the
|
||||
@@ -111,6 +115,8 @@ public sealed class NativeAlarmModel
|
||||
/// preserved: when an <c>alarm</c> object already exists this returns the input unchanged (never
|
||||
/// overwrites an authored alarm). Returns the (possibly unchanged) TagConfig JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">The tag's raw <c>TagConfig</c> JSON, or null/empty for a fresh config.</param>
|
||||
/// <returns>The (possibly unchanged) TagConfig JSON string with the default alarm seeded when applicable.</returns>
|
||||
public static string SeedDefaultAlarm(string? json)
|
||||
{
|
||||
var root = TagConfigJson.ParseOrNew(json);
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed class OpcUaClientTagConfigModel
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The tag's TagConfig JSON (null/blank/malformed ⇒ defaults).</param>
|
||||
/// <returns>The populated model.</returns>
|
||||
public static OpcUaClientTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -38,6 +39,7 @@ public sealed class OpcUaClientTagConfigModel
|
||||
/// <c>FullName</c> is written PascalCase (the composer/walker contract key); any history keys merged
|
||||
/// by the TagModal (<c>isHistorized</c> / <c>historianTagname</c>) are carried through untouched as
|
||||
/// preserved unknown keys.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "FullName", FullName.Trim());
|
||||
@@ -45,6 +47,7 @@ public sealed class OpcUaClientTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or <c>null</c> when valid.</returns>
|
||||
public string? Validate()
|
||||
=> string.IsNullOrWhiteSpace(FullName) ? "An upstream node reference (FullName) is required." : null;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class S7TagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The raw TagConfig JSON string, or null/empty for a fresh model.</param>
|
||||
/// <returns>The populated model, defaulted where fields are absent from <paramref name="json"/>.</returns>
|
||||
public static S7TagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -34,6 +36,7 @@ public sealed class S7TagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
|
||||
/// (enum as its name string) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "address", Address.Trim());
|
||||
@@ -43,6 +46,7 @@ public sealed class S7TagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or null when the model is valid.</returns>
|
||||
public string? Validate()
|
||||
=> string.IsNullOrWhiteSpace(Address) ? "Address is required." : null;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public static class TagArrayConfig
|
||||
/// <summary>Reads the array-shape keys from a TagConfig JSON, defaulting any absent key
|
||||
/// (null/blank/malformed input ⇒ <c>(false, null)</c>). A negative or non-numeric <c>arrayLength</c>
|
||||
/// reads as <c>null</c>.</summary>
|
||||
/// <param name="json">The TagConfig JSON to read from, or <c>null</c>.</param>
|
||||
/// <returns>The parsed array-shape state.</returns>
|
||||
public static ArrayState Read(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -39,6 +41,10 @@ public static class TagArrayConfig
|
||||
/// (driver-specific or unknown) key. <c>isArray</c> is dropped when false (absent ⇒ false at the
|
||||
/// materialiser); <c>arrayLength</c> is dropped when <paramref name="isArray"/> is false or
|
||||
/// <paramref name="arrayLength"/> is null (never leaves an orphan length behind a cleared isArray).</summary>
|
||||
/// <param name="json">The existing TagConfig JSON to merge into, or <c>null</c> for a new config.</param>
|
||||
/// <param name="isArray">Whether the tag should be materialised as a 1-D array.</param>
|
||||
/// <param name="arrayLength">The array length to store; ignored when <paramref name="isArray"/> is false.</param>
|
||||
/// <returns>The merged TagConfig JSON string.</returns>
|
||||
public static string Set(string? json, bool isArray, uint? arrayLength)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -52,6 +58,9 @@ public static class TagArrayConfig
|
||||
/// <summary>Validates the array-shape intent: when <paramref name="isArray"/> is set, a positive
|
||||
/// <paramref name="arrayLength"/> is required. Returns an error string when invalid, or <c>null</c>
|
||||
/// when valid (a non-array is always valid regardless of length).</summary>
|
||||
/// <param name="isArray">Whether the tag is configured as a 1-D array.</param>
|
||||
/// <param name="arrayLength">The configured array length, or <c>null</c> when unset.</param>
|
||||
/// <returns>An error message when the combination is invalid; otherwise <c>null</c>.</returns>
|
||||
public static string? Validate(bool isArray, uint? arrayLength)
|
||||
=> isArray && (arrayLength is null or 0)
|
||||
? "Array length must be a positive number when 'This tag is an array' is checked."
|
||||
|
||||
@@ -20,6 +20,8 @@ public static class TagConfigEditorMap
|
||||
};
|
||||
|
||||
/// <summary>Returns the editor component type for a driver type, or null if none is registered.</summary>
|
||||
/// <param name="driverType">The driver type name to look up an editor component for.</param>
|
||||
/// <returns>The editor component <see cref="Type"/>, or null if no typed editor is registered.</returns>
|
||||
public static Type? Resolve(string? driverType)
|
||||
=> driverType is not null && Map.TryGetValue(driverType, out var t) ? t : null;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
public static class TagConfigJson
|
||||
{
|
||||
/// <summary>Parses <paramref name="json"/> into a mutable object; returns a fresh empty object on null/blank/malformed/non-object input.</summary>
|
||||
/// <param name="json">The JSON string to parse.</param>
|
||||
/// <returns>The parsed <see cref="JsonObject"/>, or a fresh empty object on invalid input.</returns>
|
||||
public static JsonObject ParseOrNew(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) { return new JsonObject(); }
|
||||
@@ -19,25 +21,46 @@ public static class TagConfigJson
|
||||
}
|
||||
|
||||
/// <summary>Serialises the object to compact JSON (JsonNode.ToJsonString() defaults to non-indented).</summary>
|
||||
/// <param name="obj">The JSON object to serialise.</param>
|
||||
/// <returns>The compact JSON string.</returns>
|
||||
public static string Serialize(JsonObject obj) => obj.ToJsonString();
|
||||
|
||||
/// <summary>Reads a string value, or null if absent/null/non-string.</summary>
|
||||
/// <param name="o">The JSON object to read from.</param>
|
||||
/// <param name="name">The property name to read.</param>
|
||||
/// <returns>The string value, or null if absent, null, or non-string.</returns>
|
||||
public static string? GetString(JsonObject o, string name)
|
||||
=> o.TryGetPropertyValue(name, out var n) && n is JsonValue v && v.TryGetValue<string>(out var s) ? s : null;
|
||||
|
||||
/// <summary>Reads an int value, or <paramref name="fallback"/> if absent/null/non-numeric (incl. object/array nodes).</summary>
|
||||
/// <param name="o">The JSON object to read from.</param>
|
||||
/// <param name="name">The property name to read.</param>
|
||||
/// <param name="fallback">The value to return if the property is absent, null, or non-numeric.</param>
|
||||
/// <returns>The int value, or <paramref name="fallback"/>.</returns>
|
||||
public static int GetInt(JsonObject o, string name, int fallback = 0)
|
||||
=> o.TryGetPropertyValue(name, out var n) && n is JsonValue v && v.TryGetValue<int>(out var i) ? i : fallback;
|
||||
|
||||
/// <summary>Reads a bool value, or <paramref name="fallback"/> if absent/null/non-boolean (incl. object/array/number/string nodes).</summary>
|
||||
/// <param name="o">The JSON object to read from.</param>
|
||||
/// <param name="name">The property name to read.</param>
|
||||
/// <param name="fallback">The value to return if the property is absent, null, or non-boolean.</param>
|
||||
/// <returns>The bool value, or <paramref name="fallback"/>.</returns>
|
||||
public static bool GetBool(JsonObject o, string name, bool fallback = false)
|
||||
=> o.TryGetPropertyValue(name, out var n) && n is JsonValue v && v.TryGetValue<bool>(out var b) ? b : fallback;
|
||||
|
||||
/// <summary>Reads an enum by its serialised name, or <paramref name="fallback"/> if absent/unparseable.</summary>
|
||||
/// <typeparam name="TEnum">The enum type to parse.</typeparam>
|
||||
/// <param name="o">The JSON object to read from.</param>
|
||||
/// <param name="name">The property name to read.</param>
|
||||
/// <param name="fallback">The value to return if the property is absent or unparseable.</param>
|
||||
/// <returns>The parsed enum value, or <paramref name="fallback"/>.</returns>
|
||||
public static TEnum GetEnum<TEnum>(JsonObject o, string name, TEnum fallback) where TEnum : struct, Enum
|
||||
=> GetString(o, name) is { } s && Enum.TryParse<TEnum>(s, ignoreCase: true, out var v) ? v : fallback;
|
||||
|
||||
/// <summary>Sets a string/number/enum-name value (enums via ToString()). A null value REMOVES the key, so it is omitted from the serialised JSON.</summary>
|
||||
/// <param name="o">The JSON object to mutate.</param>
|
||||
/// <param name="name">The property name to set.</param>
|
||||
/// <param name="value">The value to set, or null to remove the property.</param>
|
||||
public static void Set(JsonObject o, string name, object? value)
|
||||
{
|
||||
if (value is null) { o.Remove(name); return; }
|
||||
@@ -52,6 +75,9 @@ public static class TagConfigJson
|
||||
/// <c>alarm</c> object (and root history/array intent, scaling, etc.). Null/blank/malformed input starts
|
||||
/// from a bare <c>{"FullName":...}</c> object.
|
||||
/// </summary>
|
||||
/// <param name="json">The existing TagConfig JSON string, or null/blank/malformed to start fresh.</param>
|
||||
/// <param name="fullName">The newly-picked Galaxy <c>tag_name.AttributeName</c> reference.</param>
|
||||
/// <returns>The updated TagConfig serialised back to a JSON string.</returns>
|
||||
public static string SetFullName(string? json, string fullName)
|
||||
{
|
||||
// A blank reference is never a valid Galaxy bind — surface it loudly rather than persisting
|
||||
|
||||
@@ -25,6 +25,9 @@ public static class TagConfigValidator
|
||||
/// Validates a tag's <paramref name="configJson"/> for the given <paramref name="driverType"/>.
|
||||
/// Returns an error string to block save, or null when valid / no typed validator is registered.
|
||||
/// </summary>
|
||||
/// <param name="driverType">The driver type name to look up a typed validator for.</param>
|
||||
/// <param name="configJson">The tag's config JSON to validate.</param>
|
||||
/// <returns>An error string to block save, or null when the config is valid or unmapped.</returns>
|
||||
public static string? Validate(string? driverType, string? configJson)
|
||||
=> driverType is not null && Validators.TryGetValue(driverType, out var v) ? v(configJson) : null;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public static class TagHistorizeConfig
|
||||
|
||||
/// <summary>Reads the history-intent keys from a TagConfig JSON, defaulting any absent key
|
||||
/// (null/blank/malformed input ⇒ <c>(false, "")</c>).</summary>
|
||||
/// <param name="json">The tag's TagConfig JSON, or null/empty/malformed input.</param>
|
||||
/// <returns>The parsed history-intent state, defaulted to <c>(false, "")</c> when keys are absent.</returns>
|
||||
public static HistorizeState Read(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -36,6 +38,10 @@ public static class TagHistorizeConfig
|
||||
/// (driver-specific or unknown) key. <c>isHistorized</c> is dropped when false (absent ⇒ false at the
|
||||
/// composer); <c>historianTagname</c> is dropped when null/blank (absent ⇒ defaults to FullName) and
|
||||
/// trimmed otherwise.</summary>
|
||||
/// <param name="json">The tag's existing TagConfig JSON, or null/empty to start from a new object.</param>
|
||||
/// <param name="isHistorized">Whether the server should expose OPC UA HistoryRead over this tag's node.</param>
|
||||
/// <param name="historianTagname">Optional historian tagname override; null/blank omits the key.</param>
|
||||
/// <returns>The merged TagConfig JSON with the two history-intent keys applied.</returns>
|
||||
public static string Set(string? json, bool isHistorized, string? historianTagname)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class TwinCATTagConfigModel
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The TagConfig JSON string to parse, or null/empty for a fresh model.</param>
|
||||
/// <returns>A populated <see cref="TwinCATTagConfigModel"/>.</returns>
|
||||
public static TwinCATTagConfigModel FromJson(string? json)
|
||||
{
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
@@ -34,6 +36,7 @@ public sealed class TwinCATTagConfigModel
|
||||
|
||||
/// <summary>Serialises this model back to a TagConfig JSON string, writing the exposed fields
|
||||
/// (enum as its name string; the optional host address omitted when blank) over the preserved key bag.</summary>
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
TagConfigJson.Set(_bag, "deviceHostAddress", string.IsNullOrWhiteSpace(DeviceHostAddress) ? null : DeviceHostAddress.Trim());
|
||||
@@ -43,6 +46,7 @@ public sealed class TwinCATTagConfigModel
|
||||
}
|
||||
|
||||
/// <summary>Validation hook; returns an error message or null when the model is valid.</summary>
|
||||
/// <returns>An error message describing the validation failure, or null when the model is valid.</returns>
|
||||
public string? Validate()
|
||||
=> string.IsNullOrWhiteSpace(SymbolPath) ? "Symbol path is required." : null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
/// <param name="DriverInstanceId">The bound driver; must resolve to an Equipment-kind namespace in the equipment's cluster.</param>
|
||||
/// <param name="DataType">OPC UA built-in type name (Boolean / Int32 / Float / etc.).</param>
|
||||
/// <param name="AccessLevel">Tag-level OPC UA access baseline.</param>
|
||||
/// <param name="WriteIdempotent">Whether writes are safe to retry (decisions #44–45).</param>
|
||||
/// <param name="WriteIdempotent">Whether writes are safe to retry.</param>
|
||||
/// <param name="PollGroupId">Optional poll-group batching key; whitespace/empty collapses to <c>null</c>.</param>
|
||||
/// <param name="TagConfig">Schemaless per-driver-type JSON config; validated for JSON well-formedness.</param>
|
||||
public sealed record TagInput(
|
||||
|
||||
@@ -96,6 +96,11 @@ public static class UnsTreeAssembly
|
||||
/// (and enterprises whose clusters have no areas) are retained. Ordering is
|
||||
/// deterministic and ordinal at every level.
|
||||
/// </summary>
|
||||
/// <param name="clusters">The flat set of cluster rows to nest under the enterprise root.</param>
|
||||
/// <param name="areas">The flat set of area rows to nest under their owning clusters.</param>
|
||||
/// <param name="lines">The flat set of line rows to nest under their owning areas.</param>
|
||||
/// <param name="equipment">The flat set of equipment rows to nest under their owning lines.</param>
|
||||
/// <returns>The assembled read-only list of top-level UNS tree nodes.</returns>
|
||||
public static IReadOnlyList<UnsNode> Build(
|
||||
IReadOnlyList<ClusterRow> clusters,
|
||||
IReadOnlyList<AreaRow> areas,
|
||||
|
||||
@@ -293,7 +293,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
|
||||
db.Entry(entity).Property(e => e.RowVersion).OriginalValue = rowVersion;
|
||||
|
||||
// Decision #122: a cluster move must not orphan driver-bound equipment from its driver's
|
||||
// A cluster move must not orphan driver-bound equipment from its driver's
|
||||
// cluster. Any equipment under this area that is bound to a driver in a different cluster
|
||||
// than the target blocks the move.
|
||||
if (newClusterId != entity.ClusterId)
|
||||
@@ -416,7 +416,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
|
||||
db.Entry(entity).Property(e => e.RowVersion).OriginalValue = rowVersion;
|
||||
|
||||
// Decision #122: a reparent to a different area must not orphan driver-bound equipment
|
||||
// A reparent to a different area must not orphan driver-bound equipment
|
||||
// from its driver's cluster. Resolve the new area's cluster and check every bound
|
||||
// equipment item under this line against it.
|
||||
if (newUnsAreaId != entity.UnsAreaId)
|
||||
@@ -580,8 +580,8 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reuse the #122 guard: it reports an unknown DriverInstanceId and a driver/line cluster
|
||||
// mismatch, and is a no-op for driver-less rows.
|
||||
// Reuse the driver/line cluster guard: it reports an unknown DriverInstanceId and a
|
||||
// driver/line cluster mismatch, and is a no-op for driver-less rows.
|
||||
var guard = await CheckDriverClusterGuardAsync(db, row, ct);
|
||||
if (guard is not null)
|
||||
{
|
||||
@@ -838,7 +838,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
return Array.Empty<(string, string, string, string)>();
|
||||
}
|
||||
|
||||
// Drivers in the equipment's cluster whose namespace is Equipment-kind (decision #110).
|
||||
// Drivers in the equipment's cluster whose namespace is Equipment-kind.
|
||||
// GalaxyMxGateway is an ordinary Equipment-kind driver post-de-split, so it surfaces here
|
||||
// alongside the PLC drivers; its DriverConfig feeds the Galaxy live-browse address picker.
|
||||
var equipmentNamespaceIds = await db.Namespaces
|
||||
@@ -1142,7 +1142,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
var equipGuard = await ValidateEquipTokenAsync(db, entity.EquipmentId, input.ScriptId, ct);
|
||||
if (equipGuard is not null) return equipGuard.Value;
|
||||
|
||||
// EquipmentId is preserved — virtual tags are always equipment-bound (plan decision #2).
|
||||
// EquipmentId is preserved — virtual tags are always equipment-bound.
|
||||
entity.Name = input.Name;
|
||||
entity.DataType = input.DataType;
|
||||
entity.ScriptId = input.ScriptId;
|
||||
@@ -1283,7 +1283,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
/// <summary>Returns <c>true</c> if <paramref name="json"/> parses as a well-formed JSON document.
|
||||
/// Null/blank input is treated as invalid (not well-formed JSON) so every caller gets the friendly
|
||||
/// "not valid JSON" result rather than an unhandled <see cref="ArgumentNullException"/> from
|
||||
/// <c>JsonDocument.Parse(null)</c> (AdminUI-002).</summary>
|
||||
/// <c>JsonDocument.Parse(null)</c>.</summary>
|
||||
private static bool IsValidJson(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
@@ -1335,7 +1335,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
|
||||
/// <summary>
|
||||
/// Validates a tree tag's driver binding: the driver must exist, live in an Equipment-kind
|
||||
/// namespace (decision #110), and be in the same cluster as the owning equipment (decision #122).
|
||||
/// namespace, and be in the same cluster as the owning equipment.
|
||||
/// Returns <c>null</c> when the binding is allowed, or a populated failure otherwise.
|
||||
/// </summary>
|
||||
private static async Task<UnsMutationResult?> CheckTagDriverGuardAsync(
|
||||
@@ -1367,7 +1367,7 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decision #122: an equipment may only bind to a driver in the same cluster as its line.
|
||||
/// An equipment may only bind to a driver in the same cluster as its line.
|
||||
/// Policy:
|
||||
/// <list type="bullet">
|
||||
/// <item>Driver-less (<c>DriverInstanceId</c> empty) → always allowed (returns <c>null</c>).</item>
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
|
||||
/// <summary>
|
||||
/// Parameter object carrying the operator-editable fields for an equipment-bound VirtualTag create
|
||||
/// or update via the UNS tree. Virtual tags are always scoped to an equipment (plan decision #2), so
|
||||
/// or update via the UNS tree. Virtual tags are always scoped to an equipment, so
|
||||
/// the owning <c>EquipmentId</c> is supplied separately and never changes on update. A virtual tag
|
||||
/// must have at least one evaluation trigger: <see cref="ChangeTriggered"/> or a non-null
|
||||
/// <see cref="TimerIntervalMs"/> (which, when set, must be at least 50 ms).
|
||||
|
||||
Reference in New Issue
Block a user