using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services; /// /// Outcome of a single secured-write command dispatch. Wraps either the resulting /// (on success) or a human-readable error, so the page /// can render an inline result/toast rather than reasoning about transport exceptions /// or the three management response shapes. /// /// Whether the command succeeded. /// The resulting secured-write row when is true; otherwise null. /// A human-readable error message when is false; otherwise null. public record SecuredWriteActionResult(bool Success, SecuredWriteDto? Dto, string? Error) { /// Creates a successful result wrapping . /// The secured-write row the command produced. /// A successful . public static SecuredWriteActionResult Ok(SecuredWriteDto dto) => new(true, dto, null); /// Creates a failed result carrying . /// The human-readable failure message. /// A failed . public static SecuredWriteActionResult Fail(string error) => new(false, null, error); } /// /// CentralUI facade over the two-person ("secured") write management commands. /// It dispatches the strongly-typed /// SubmitSecuredWriteCommand / ApproveSecuredWriteCommand / /// RejectSecuredWriteCommand / ListSecuredWritesCommand to the central /// ManagementActor through the in-process ManagementActorHolder seam — /// the SAME path the HTTP /management endpoint uses — so the server remains the /// single enforcer of role gating, separation-of-duties (no self-approval), the actual /// MxGateway device relay on approve, and the append-only audit trail. /// /// /// The page itself performs NO device I/O and re-implements NO security logic: every /// mutation is a command submitted to the server. The current user's claims are mapped /// to an so the actor's role check and no-self-approval /// guard run against the real principal. ManagementUnauthorized / /// ManagementError / transport failures collapse into a typed /// so the page renders an inline outcome rather /// than throwing. /// public interface ISecuredWriteService { /// /// Submits a new pending secured write (Operator action). The target connection /// must exist within and use the MxGateway protocol — /// both validated server-side. /// /// Site identifier the write targets. /// MxGateway data connection name within the site. /// Fully-qualified tag path the value is written to. /// JSON-serialised value to write. /// Target data type name (e.g. Boolean, Double). /// Optional free-text comment supplied by the operator. /// Cancellation token. /// A task resolving to the submit outcome. Task SubmitAsync( string siteId, string connectionName, string tagPath, string valueJson, string valueType, string? comment, CancellationToken cancellationToken = default); /// /// Approves (and triggers execution of) a pending secured write (Verifier action). /// The server enforces that the approver differs from the submitter and runs the /// device relay. /// /// Identity of the pending secured write. /// Optional free-text comment supplied by the verifier. /// Cancellation token. /// A task resolving to the approve outcome. Task ApproveAsync( long id, string? comment, CancellationToken cancellationToken = default); /// /// Rejects a pending secured write (Verifier action). The server enforces that the /// rejecter differs from the submitter. /// /// Identity of the pending secured write. /// Optional free-text comment supplied by the verifier. /// Cancellation token. /// A task resolving to the reject outcome. Task RejectAsync( long id, string? comment, CancellationToken cancellationToken = default); /// /// Lists secured writes, optionally filtered by and /// with offset paging (read-only — any authenticated user). /// On any failure an empty result (TotalCount = 0) is returned so the page /// renders gracefully. The returned is /// the unpaged match count, so the page can drive a pager independent of page size. /// /// Status filter; null matches every status. /// Site id filter; null matches every site. /// Rows to skip (offset paging); clamped to >= 0 server-side. /// Maximum rows to return; clamped to 1..500 server-side. /// Cancellation token. /// A task resolving to the matching page + total match count, newest submission first. Task ListAsync( string? status, string? siteId, int skip = 0, int take = 200, CancellationToken cancellationToken = default); }