using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; /// /// Detached, editable projection of a raw Tag — the read side of tag editing. WP1 exposes this /// loader; WP4 owns the create/update authoring surface and should align its input model with these /// fields (identity is the RawPath, formed from the tag's device + optional group + name). /// /// The tag's stable logical id. /// The owning device's logical id. /// The containing tag group's logical id, or null when directly under the device. /// The tag name (a RawPath segment). /// The OPC UA built-in type name. /// The tag's access-level baseline. /// Whether writes to this tag are retry-eligible. /// The optional poll-group id. /// The schemaless per-driver TagConfig JSON blob. /// The optimistic-concurrency token. public readonly record struct RawTagEditDto( string TagId, string DeviceId, string? TagGroupId, string Name, string DataType, TagAccessLevel AccessLevel, bool WriteIdempotent, string? PollGroupId, string TagConfig, byte[] RowVersion); /// /// Detached, editable projection of a DriverInstance — the read side of the Wave-B (WP3) /// "Configure driver" modal. DriverType is included so the modal can dispatch to the correct /// typed driver-config editor; it is immutable, so does /// not change it. /// /// The driver instance's stable logical id. /// The driver instance name (a RawPath segment). /// The immutable driver-type string (see DriverTypeNames). /// The schemaless per-driver-type DriverConfig JSON blob. /// Optional per-instance resilience-pipeline overrides JSON, or null. /// The optimistic-concurrency token. public sealed record RawDriverEditDto( string DriverInstanceId, string Name, string DriverType, string DriverConfig, string? ResilienceConfig, byte[] RowVersion); /// /// Detached, editable projection of a Device — the read side of the Wave-B (WP3) "Configure /// device" modal. DriverType is joined in from the parent DriverInstance so the modal can /// dispatch to the correct typed device-config editor (endpoint/connection settings now live in /// DeviceConfig). /// /// The device's stable logical id. /// The owning driver instance's logical id. /// The device name (a RawPath segment). /// The schemaless per-driver-type DeviceConfig JSON blob (host/endpoint + per-device settings). /// Whether the device is enabled. /// The parent driver's type string, joined in for editor dispatch. /// The optimistic-concurrency token. public sealed record RawDeviceEditDto( string DeviceId, string DriverInstanceId, string Name, string DeviceConfig, bool Enabled, string DriverType, byte[] RowVersion); /// /// Loads and mutates the v3 Raw project tree (/raw) — the cluster-rooted /// Enterprise → Cluster → Folder → Driver → Device → TagGroup → Tag hierarchy — from the config /// database. The peer of for the Raw subtree. /// /// Contract split (Batch 2 Wave A): this file's committed surface is the read contract /// the RawTree component consumes — (eager Enterprise→Cluster roots) /// and (one lazy level per expand). B2-WP1 (RawTreeService) /// extends this interface with the mutation surface — create/rename/delete per node type, /// move-into-folder, and the per-node edit projections — enforcing the integrity rules with /// user-readable errors (a delete blocked by a UnsTagReference names the referencing equipment). /// Rename methods return so the UI can show non-blocking warnings. /// Mutations reuse . WP2 must not add to this interface. /// /// public interface IRawTreeService { /// /// Loads the eager top of the Raw tree: Enterprise → Cluster roots, each cluster marked /// so its folders + drivers load on first expand. Empty /// clusters are retained so they stay visible and authorable. The returned nodes are detached /// view-models, safe to hold and mutate UI state on after the underlying context is disposed. /// /// A token to cancel the load. /// The enterprise roots, populated down to (but not through) their clusters. Task> LoadRootsAsync(CancellationToken ct = default); /// /// Loads the direct children of one container node — the next level down, keyed off /// 's and : /// a Cluster yields its root Folders + Drivers; a Folder yields sub-Folders + Drivers; a Driver /// yields its Devices; a Device yields its root TagGroups + Tags; a TagGroup yields sub-TagGroups /// + Tags. Tags are leaves and yield nothing. Ordering is deterministic and ordinal. Reads /// untracked; returns detached nodes. Per-device tag counts can be large — the implementation may /// page, but the committed shape returns the level's nodes (paging knobs, if any, are WP1's to add /// on the concrete type without changing this signature's meaning for the tree). /// /// The container node whose direct children to load. /// A token to cancel the load. /// The parent's direct child nodes; empty for a leaf or an empty container. Task> LoadChildrenAsync(RawNode parent, CancellationToken ct = default); // --- Folder mutations --- /// /// Creates a new RawFolder under a cluster ( null) or under /// another folder. The name is validated as a RawPath segment and must be unique among its siblings /// (case-sensitive ordinal). On success the result's CreatedId carries the new folder's /// logical id. /// /// The owning cluster id. /// The parent folder's logical id, or null for a cluster-root folder. /// The folder name (a RawPath segment). /// A token to cancel the operation. /// The create outcome; CreatedId is the new RawFolderId on success. Task CreateFolderAsync(string clusterId, string? parentFolderId, string name, CancellationToken ct = default); /// /// Renames a RawFolder. Validates the new name as a RawPath segment and sibling-unique. /// Because a folder rename changes the RawPath of every tag beneath it, the returned /// carries advisory warnings for historized and equipment-referenced /// tags in the folder's subtree. /// /// The folder's logical id. /// The new folder name. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The rename outcome with any downstream-impact warnings. Task RenameFolderAsync(string rawFolderId, string newName, byte[] rowVersion, CancellationToken ct = default); /// /// Deletes an empty RawFolder. Blocked (with the blocker named) while it still contains child /// folders or driver instances. /// /// The folder's logical id. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The delete outcome. Task DeleteFolderAsync(string rawFolderId, byte[] rowVersion, CancellationToken ct = default); /// /// Moves a driver instance into a folder () or to the cluster root /// (null target). The target folder must be in the driver's cluster and the driver's name must be /// unique among the target's driver siblings. /// /// The driver instance's logical id. /// The destination folder's logical id, or null for the cluster root. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The move outcome. Task MoveDriverToFolderAsync(string driverInstanceId, string? targetFolderId, byte[] rowVersion, CancellationToken ct = default); // --- Driver mutations --- /// /// Creates a minimal driver instance and its auto-created default Device so the tree has a /// child to show. Full typed driver/device config authoring is Wave B (WP3) — this create takes the /// driver config JSON verbatim and seeds an empty ({}) default device. The driver name is a /// RawPath segment, unique among its cluster/folder siblings. /// /// The owning cluster id. /// The containing folder's logical id, or null for a cluster-root driver. /// The driver instance name (a RawPath segment). /// The driver-type string (see DriverTypeNames). /// The driver config JSON blob (opaque here; validated in Wave B). /// A token to cancel the operation. /// The create outcome; CreatedId is the new DriverInstanceId on success. Task CreateDriverAsync(string clusterId, string? folderId, string name, string driverType, string driverConfigJson, CancellationToken ct = default); /// /// Renames a driver instance. Validates the new name as a sibling-unique RawPath segment and returns /// downstream-impact warnings for historized/equipment-referenced tags beneath the driver. /// /// The driver instance's logical id. /// The new driver name. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The rename outcome with any downstream-impact warnings. Task RenameDriverAsync(string driverInstanceId, string newName, byte[] rowVersion, CancellationToken ct = default); /// Enables or disables a driver instance (last-write-wins on ). /// The driver instance's logical id. /// The new enabled state. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The update outcome. Task SetDriverEnabledAsync(string driverInstanceId, bool enabled, byte[] rowVersion, CancellationToken ct = default); /// /// Deletes a driver instance, cascading its auto-created empty devices. Blocked (with the blocking /// device named) when any of its devices still contains tag groups or tags — remove those first. /// /// The driver instance's logical id. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The delete outcome. Task DeleteDriverAsync(string driverInstanceId, byte[] rowVersion, CancellationToken ct = default); // --- Device mutations --- /// /// Creates a minimal Device under a driver. Full typed device-config authoring is Wave B /// (WP3) — this create takes the device config JSON verbatim. The device name must be unique among /// the driver's devices. /// /// The owning driver instance's logical id. /// The device name (a RawPath segment). /// The device config JSON blob (opaque here; validated in Wave B). /// A token to cancel the operation. /// The create outcome; CreatedId is the new DeviceId on success. Task CreateDeviceAsync(string driverInstanceId, string name, string deviceConfigJson, CancellationToken ct = default); /// /// Renames a Device. Validates the new name as a driver-unique RawPath segment and returns /// downstream-impact warnings for historized/equipment-referenced tags beneath the device. /// /// The device's logical id. /// The new device name. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The rename outcome with any downstream-impact warnings. Task RenameDeviceAsync(string deviceId, string newName, byte[] rowVersion, CancellationToken ct = default); /// Deletes a Device. Blocked (with the blocker named) while it holds tag groups or tags. /// The device's logical id. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The delete outcome. Task DeleteDeviceAsync(string deviceId, byte[] rowVersion, CancellationToken ct = default); // --- TagGroup mutations --- /// /// Creates a new TagGroup under a device ( null) or under /// another group. The name is validated as a RawPath segment, unique among its siblings. /// /// The owning device's logical id. /// The parent group's logical id, or null for a device-root group. /// The group name (a RawPath segment). /// A token to cancel the operation. /// The create outcome; CreatedId is the new TagGroupId on success. Task CreateTagGroupAsync(string deviceId, string? parentGroupId, string name, CancellationToken ct = default); /// /// Renames a TagGroup. Validates the new name as a sibling-unique RawPath segment and returns /// downstream-impact warnings for historized/equipment-referenced tags beneath the group. /// /// The group's logical id. /// The new group name. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The rename outcome with any downstream-impact warnings. Task RenameTagGroupAsync(string tagGroupId, string newName, byte[] rowVersion, CancellationToken ct = default); /// Deletes a TagGroup. Blocked (with the blocker named) while it holds child groups or tags. /// The group's logical id. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The delete outcome. Task DeleteTagGroupAsync(string tagGroupId, byte[] rowVersion, CancellationToken ct = default); // --- Tag mutations / projections (create + update are WP4) --- /// /// Deletes a raw Tag. Blocked while any UnsTagReference points at it; the failure /// message names the referencing equipment(s) so the operator can remove the reference(s) first. /// /// The tag's logical id. /// The optimistic-concurrency token echoed from the loaded node. /// A token to cancel the operation. /// The delete outcome. Task DeleteTagAsync(string tagId, byte[] rowVersion, CancellationToken ct = default); /// /// Loads a raw tag's editable fields as a detached projection. WP1 provides the read-only projection; /// the create/update authoring surface is WP4 (which should align its input shape with /// ). /// /// The tag's logical id. /// A token to cancel the load. /// The tag's editable projection, or null when the tag does not exist. Task LoadTagForEditAsync(string tagId, CancellationToken ct = default); /// /// Creates a raw Tag under a device ( null) or a tag group. The /// name is validated as a RawPath segment and must be unique among its siblings on the same /// (DeviceId, TagGroupId); the is validated for JSON /// well-formedness. On success the result's CreatedId carries the new tag's logical id. /// /// The owning device's logical id. /// The containing tag group's logical id, or null for a device-root tag. /// The tag's editable fields (identity is the RawPath — no TagId here). /// A token to cancel the operation. /// The create outcome; CreatedId is the new TagId on success. Task CreateTagAsync(string deviceId, string? tagGroupId, RawTagInput input, CancellationToken ct = default); /// /// Updates a raw Tag's editable fields. The new name is re-validated as a RawPath segment and /// sibling-unique on the tag's existing (DeviceId, TagGroupId) excluding itself; the /// is validated for JSON well-formedness. Last-write-wins on /// . The tag's DeviceId/TagGroupId (its identity location) /// are preserved — moving a tag is not this method's job. /// /// The tag's logical id. /// The tag's edited fields. /// The optimistic-concurrency token echoed from the loaded tag. /// A token to cancel the operation. /// The update outcome. Task UpdateTagAsync(string tagId, RawTagInput input, byte[] rowVersion, CancellationToken ct = default); /// /// Commits a batch of raw tags under a device — the WP5 CSV-import path. Each row's /// TagGroupPath is a /-separated tag-group path under the device whose segments are /// auto-created as nested TagGroups (reusing existing ones). Per-row validation errors (bad /// name segment, malformed TagConfig JSON, a duplicate name within the device+group) are /// collected, not thrown. The import is all-or-nothing: if any row errors, nothing is inserted /// and the errors are returned; otherwise all tags (and any newly-needed groups) are committed in one /// SaveChanges. /// /// The target device's logical id. /// The rows to import. /// A token to cancel the operation. /// The import outcome: the inserted count on success, or the per-row errors with a 0 count. Task ImportTagsAsync(string deviceId, IReadOnlyList rows, CancellationToken ct = default); /// /// Loads a driver instance's editable fields as a detached projection for the WP3 "Configure driver" /// modal. DriverType is included for typed-editor dispatch (it is immutable). /// /// The driver instance's logical id. /// A token to cancel the load. /// The driver's editable projection, or null when it does not exist. Task LoadDriverForEditAsync(string driverInstanceId, CancellationToken ct = default); /// /// Updates a driver instance's name + config from the WP3 "Configure driver" modal. The name is /// validated as a sibling-unique RawPath segment (same scope as ); /// last-write-wins on . DriverType is immutable and is not changed. /// /// The driver instance's logical id. /// The (possibly changed) driver name. /// The new DriverConfig JSON blob. /// The new resilience-config JSON, or null/blank to clear. /// The optimistic-concurrency token echoed from the loaded projection. /// A token to cancel the operation. /// The update outcome. Task UpdateDriverAsync(string driverInstanceId, string name, string driverConfigJson, string? resilienceConfig, byte[] rowVersion, CancellationToken ct = default); /// /// Loads a device's editable fields as a detached projection for the WP3 "Configure device" modal. /// DriverType is joined in from the parent driver so the modal can dispatch to the correct typed /// device-config editor. /// /// The device's logical id. /// A token to cancel the load. /// The device's editable projection, or null when it does not exist. Task LoadDeviceForEditAsync(string deviceId, CancellationToken ct = default); /// /// Updates a device's name + config + enabled state from the WP3 "Configure device" modal. The name /// is validated as a RawPath segment unique among the driver's devices; last-write-wins on /// . /// /// The device's logical id. /// The (possibly changed) device name. /// The new DeviceConfig JSON blob (endpoint + per-device settings). /// The new enabled state. /// The optimistic-concurrency token echoed from the loaded projection. /// A token to cancel the operation. /// The update outcome. Task UpdateDeviceAsync(string deviceId, string name, string deviceConfigJson, bool enabled, byte[] rowVersion, CancellationToken ct = default); /// /// Builds the single merged config blob the driver-probe (test-connect) path binds from for one device: /// the parent driver's DriverConfig folded together with this device's DeviceConfig (via /// DriverDeviceConfigMerger, endpoint keys merging up from DeviceConfig). Used by the /// "Test connect" button inside the WP3 driver/device modals now that the endpoint lives in /// DeviceConfig. /// /// The device to probe. /// A token to cancel the load. /// The parent driver's type + the merged config JSON, or null when the device/driver cannot be resolved. Task<(string DriverType, string MergedConfigJson)?> LoadMergedProbeConfigAsync(string deviceId, CancellationToken ct = default); }