From 550e2d0ebec8819d1f29e0a946b128bad7c2ba53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bir=C3=B3=2C=20Csaba=20Attila?= Date: Sat, 14 Feb 2026 13:54:12 +0100 Subject: [PATCH 1/3] feat(config): add shared DTO library with models, enums, and MQTT topics Create FlowForge.Shared class library containing DTOs for Flow, Build, Deploy, Project, Target, Auth, and Monitor domains. Add enums for Permission and ProjectRole. Add type-safe MQTT topic builders. Co-Authored-By: Claude Opus 4.6 --- .../FlowForge.Shared/Enums/Permission.cs | 18 +++++++++++++ .../FlowForge.Shared/Enums/ProjectRole.cs | 13 +++++++++ .../FlowForge.Shared/FlowForge.Shared.csproj | 10 +++++++ .../Models/Auth/UserInfoDto.cs | 13 +++++++++ .../Models/Build/BuildJobDto.cs | 19 +++++++++++++ .../Models/Build/BuildProgressDto.cs | 12 +++++++++ .../Models/Build/BuildResultDto.cs | 13 +++++++++ .../Models/Build/BuildStatus.cs | 13 +++++++++ .../Models/Deploy/DeployRequestDto.cs | 11 ++++++++ .../Models/Deploy/DeployResultDto.cs | 12 +++++++++ .../Models/Deploy/DeployStatus.cs | 15 +++++++++++ .../Models/Flow/FlowConnection.cs | 10 +++++++ .../Models/Flow/FlowDocument.cs | 13 +++++++++ .../FlowForge.Shared/Models/Flow/FlowNode.cs | 12 +++++++++ .../FlowForge.Shared/Models/Flow/FlowPort.cs | 10 +++++++ .../Models/Flow/NodePosition.cs | 10 +++++++ .../Models/Monitor/MonitorSessionDto.cs | 12 +++++++++ .../Models/Monitor/PlcVariableValueDto.cs | 12 +++++++++ .../Models/Project/ProjectDetailDto.cs | 19 +++++++++++++ .../Models/Project/ProjectSummaryDto.cs | 15 +++++++++++ .../Models/Target/PlcTargetDto.cs | 16 +++++++++++ .../Models/Target/TargetGroupDto.cs | 12 +++++++++ .../FlowForge.Shared/Mqtt/MqttTopics.cs | 27 +++++++++++++++++++ 23 files changed, 317 insertions(+) create mode 100644 src/shared/FlowForge.Shared/Enums/Permission.cs create mode 100644 src/shared/FlowForge.Shared/Enums/ProjectRole.cs create mode 100644 src/shared/FlowForge.Shared/FlowForge.Shared.csproj create mode 100644 src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs create mode 100644 src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs create mode 100644 src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs create mode 100644 src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs create mode 100644 src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs create mode 100644 src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs create mode 100644 src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs create mode 100644 src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs create mode 100644 src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs create mode 100644 src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs diff --git a/src/shared/FlowForge.Shared/Enums/Permission.cs b/src/shared/FlowForge.Shared/Enums/Permission.cs new file mode 100644 index 0000000..97b8519 --- /dev/null +++ b/src/shared/FlowForge.Shared/Enums/Permission.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Enums; + +public enum Permission +{ + ProjectView, + ProjectCreate, + ProjectEdit, + ProjectDelete, + Build, + Deploy, + TargetManage, + Monitor, + AdminUsers, + AdminSystem +} diff --git a/src/shared/FlowForge.Shared/Enums/ProjectRole.cs b/src/shared/FlowForge.Shared/Enums/ProjectRole.cs new file mode 100644 index 0000000..d22c91e --- /dev/null +++ b/src/shared/FlowForge.Shared/Enums/ProjectRole.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Enums; + +public enum ProjectRole +{ + Viewer, + Editor, + Builder, + Deployer, + Owner +} diff --git a/src/shared/FlowForge.Shared/FlowForge.Shared.csproj b/src/shared/FlowForge.Shared/FlowForge.Shared.csproj new file mode 100644 index 0000000..4900ca5 --- /dev/null +++ b/src/shared/FlowForge.Shared/FlowForge.Shared.csproj @@ -0,0 +1,10 @@ + + + + net9.0 + enable + enable + FlowForge.Shared + + + diff --git a/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs b/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs new file mode 100644 index 0000000..436ad4a --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Auth; + +public record UserInfoDto +{ + public string Id { get; init; } = string.Empty; + public string UserName { get; init; } = string.Empty; + public string Email { get; init; } = string.Empty; + public string DisplayName { get; init; } = string.Empty; + public IReadOnlyList Roles { get; init; } = []; +} diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs new file mode 100644 index 0000000..18ad350 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Build; + +public record BuildJobDto +{ + public Guid Id { get; init; } + public Guid ProjectId { get; init; } + public string ProjectName { get; init; } = string.Empty; + public string RepoUrl { get; init; } = string.Empty; + public string Branch { get; init; } = "main"; + public string TwinCatVersion { get; init; } = string.Empty; + public string RequestedBy { get; init; } = string.Empty; + public BuildStatus Status { get; init; } + public bool IncludeDeploy { get; init; } + public string? TargetAmsNetId { get; init; } + public DateTimeOffset CreatedAt { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs new file mode 100644 index 0000000..dd1c237 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Build; + +public record BuildProgressDto +{ + public Guid BuildId { get; init; } + public string Stage { get; init; } = string.Empty; + public int Percentage { get; init; } + public string Message { get; init; } = string.Empty; +} diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs new file mode 100644 index 0000000..ba6d3d5 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Build; + +public record BuildResultDto +{ + public Guid BuildId { get; init; } + public bool Success { get; init; } + public IReadOnlyList Errors { get; init; } = []; + public string? CommitSha { get; init; } + public DateTimeOffset CompletedAt { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs b/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs new file mode 100644 index 0000000..1e6b0d5 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Build; + +public enum BuildStatus +{ + Pending, + Claimed, + InProgress, + Completed, + Failed +} diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs new file mode 100644 index 0000000..af1ca3d --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Deploy; + +public record DeployRequestDto +{ + public Guid ProjectId { get; init; } + public string TargetAmsNetId { get; init; } = string.Empty; + public string? ApproverId { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs new file mode 100644 index 0000000..ca0ba20 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Deploy; + +public record DeployResultDto +{ + public Guid DeployId { get; init; } + public bool Success { get; init; } + public string? Error { get; init; } + public DateTimeOffset CompletedAt { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs new file mode 100644 index 0000000..8a7fc26 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Deploy; + +public enum DeployStatus +{ + Pending, + AwaitingApproval, + Approved, + InProgress, + Completed, + Failed, + Rejected +} diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs new file mode 100644 index 0000000..7ae7094 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Flow; + +public record FlowConnection +{ + public FlowPort From { get; init; } = new(); + public FlowPort To { get; init; } = new(); +} diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs new file mode 100644 index 0000000..264b30c --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Flow; + +public record FlowDocument +{ + public string Name { get; init; } = string.Empty; + public string Version { get; init; } = string.Empty; + public IReadOnlyList Nodes { get; init; } = []; + public IReadOnlyList Connections { get; init; } = []; + public Dictionary Metadata { get; init; } = []; +} diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs new file mode 100644 index 0000000..fb98898 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Flow; + +public record FlowNode +{ + public string Id { get; init; } = string.Empty; + public string Type { get; init; } = string.Empty; + public NodePosition Position { get; init; } = new(); + public Dictionary Parameters { get; init; } = []; +} diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs new file mode 100644 index 0000000..1a62aec --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Flow; + +public record FlowPort +{ + public string NodeId { get; init; } = string.Empty; + public string PortName { get; init; } = string.Empty; +} diff --git a/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs b/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs new file mode 100644 index 0000000..cd867cd --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Flow; + +public record NodePosition +{ + public double X { get; init; } + public double Y { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs b/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs new file mode 100644 index 0000000..e0e48d5 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Monitor; + +public record MonitorSessionDto +{ + public string SessionId { get; init; } = string.Empty; + public string SignalREndpoint { get; init; } = string.Empty; + public string AuthToken { get; init; } = string.Empty; + public string TargetAmsNetId { get; init; } = string.Empty; +} diff --git a/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs b/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs new file mode 100644 index 0000000..0bafc5e --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Monitor; + +public record PlcVariableValueDto +{ + public string Path { get; init; } = string.Empty; + public object? Value { get; init; } + public string DataType { get; init; } = string.Empty; + public DateTimeOffset Timestamp { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs b/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs new file mode 100644 index 0000000..fdd5a0e --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +using FlowForge.Shared.Models.Flow; + +namespace FlowForge.Shared.Models.Project; + +public record ProjectDetailDto +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string RepoUrl { get; init; } = string.Empty; + public string Branch { get; init; } = "main"; + public string? LastCommitSha { get; init; } + public FlowDocument? Flow { get; init; } + public DateTimeOffset CreatedAt { get; init; } + public DateTimeOffset UpdatedAt { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs b/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs new file mode 100644 index 0000000..0f8e399 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Project; + +public record ProjectSummaryDto +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string RepoUrl { get; init; } = string.Empty; + public string? LastCommitSha { get; init; } + public DateTimeOffset CreatedAt { get; init; } + public DateTimeOffset UpdatedAt { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs b/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs new file mode 100644 index 0000000..8bb3d03 --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Target; + +public record PlcTargetDto +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public string AmsNetId { get; init; } = string.Empty; + public string TwinCatVersion { get; init; } = string.Empty; + public IReadOnlyList Labels { get; init; } = []; + public Guid? GroupId { get; init; } + public bool IsProductionTarget { get; init; } + public bool DeployLocked { get; init; } +} diff --git a/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs b/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs new file mode 100644 index 0000000..7850dfc --- /dev/null +++ b/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Models.Target; + +public record TargetGroupDto +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public IReadOnlyList Targets { get; init; } = []; +} diff --git a/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs b/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs new file mode 100644 index 0000000..a89f4bc --- /dev/null +++ b/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.Shared.Mqtt; + +public static class MqttTopics +{ + private const string Prefix = "flowforge"; + + public static string BuildNotify(string twinCatVersion) => + $"{Prefix}/build/notify/{twinCatVersion}"; + + public static string BuildProgress(Guid buildId) => + $"{Prefix}/build/progress/{buildId}"; + + public static string DeployRequest(string targetId) => + $"{Prefix}/deploy/request/{targetId}"; + + public static string AdsRead(string amsNetId) => + $"{Prefix}/ads/read/{amsNetId}"; + + public static string AdsWrite(string amsNetId) => + $"{Prefix}/ads/write/{amsNetId}"; + + public static string AdsNotification(string amsNetId) => + $"{Prefix}/ads/notification/{amsNetId}"; +} From 304a0c5e7909e89476ad29d37f831b534c2bb12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bir=C3=B3=2C=20Csaba=20Attila?= Date: Sat, 14 Feb 2026 14:04:47 +0100 Subject: [PATCH 2/3] feat(monitor-server): add typed hub interface, auth, and service layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add monitor server architecture: - Hubs/IPlcDataHubClient — typed SignalR client interface - Auth/TokenValidator — short-lived HMAC token validation - Services/IMqttAdsClient + MqttAdsClient — ADS over MQTT interface - Services/SubscriptionManager — per-connection subscription tracking Add Shared project reference. Update solution to include Shared. Co-Authored-By: Claude Opus 4.6 --- .../FlowForge.MonitorServer.sln | 6 +++ .../Auth/TokenValidator.cs | 21 ++++++++++ .../FlowForge.MonitorServer.csproj | 4 ++ .../Hubs/IPlcDataHubClient.cs | 13 +++++++ .../Services/IMqttAdsClient.cs | 15 +++++++ .../Services/MqttAdsClient.cs | 39 +++++++++++++++++++ .../Services/SubscriptionManager.cs | 39 +++++++++++++++++++ 7 files changed, 137 insertions(+) create mode 100644 src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs create mode 100644 src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs create mode 100644 src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs create mode 100644 src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs create mode 100644 src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs diff --git a/src/monitor-server/FlowForge.MonitorServer.sln b/src/monitor-server/FlowForge.MonitorServer.sln index 1d502dd..bd4c559 100644 --- a/src/monitor-server/FlowForge.MonitorServer.sln +++ b/src/monitor-server/FlowForge.MonitorServer.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowForge.MonitorServer", "src\FlowForge.MonitorServer\FlowForge.MonitorServer.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowForge.Shared", "..\shared\FlowForge.Shared\FlowForge.Shared.csproj", "{F3A4B5C6-D7E8-9012-CDEF-123456789013}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,5 +17,9 @@ Global {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU + {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs b/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs new file mode 100644 index 0000000..b09ed59 --- /dev/null +++ b/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace FlowForge.MonitorServer.Auth; + +public class TokenValidator +{ + private readonly string _expectedToken; + + public TokenValidator(string expectedToken) + { + _expectedToken = expectedToken; + } + + public bool Validate(string? token) + { + // TODO: Implement HMAC-based short-lived token validation + // For now, simple string comparison with the token injected at container creation + return !string.IsNullOrEmpty(token) && token == _expectedToken; + } +} diff --git a/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj b/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj index 6718a44..2470c34 100644 --- a/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj +++ b/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs new file mode 100644 index 0000000..f584f8b --- /dev/null +++ b/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +using FlowForge.Shared.Models.Monitor; + +namespace FlowForge.MonitorServer.Hubs; + +public interface IPlcDataHubClient +{ + Task ReceiveVariableValues(IReadOnlyList values); + Task ReceiveConnectionStatus(string status); + Task ReceiveError(string error); +} diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs new file mode 100644 index 0000000..fba2c9d --- /dev/null +++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +using FlowForge.Shared.Models.Monitor; + +namespace FlowForge.MonitorServer.Services; + +public interface IMqttAdsClient +{ + Task ConnectAsync(CancellationToken ct = default); + Task ReadVariableAsync(string variablePath, CancellationToken ct = default); + Task SubscribeAsync(string variablePath, Action callback, CancellationToken ct = default); + Task UnsubscribeAsync(string variablePath, CancellationToken ct = default); + Task DisconnectAsync(CancellationToken ct = default); +} diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs new file mode 100644 index 0000000..945c54d --- /dev/null +++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +using FlowForge.Shared.Models.Monitor; + +namespace FlowForge.MonitorServer.Services; + +public class MqttAdsClient : IMqttAdsClient +{ + public Task ConnectAsync(CancellationToken ct) + { + // TODO: Connect to MQTT broker using MQTTnet + throw new NotImplementedException(); + } + + public Task ReadVariableAsync(string variablePath, CancellationToken ct) + { + // TODO: Publish ADS read request via MQTT, await response + throw new NotImplementedException(); + } + + public Task SubscribeAsync(string variablePath, Action callback, CancellationToken ct) + { + // TODO: Register ADS subscription via MQTT + throw new NotImplementedException(); + } + + public Task UnsubscribeAsync(string variablePath, CancellationToken ct) + { + // TODO: Unregister ADS subscription + throw new NotImplementedException(); + } + + public Task DisconnectAsync(CancellationToken ct) + { + // TODO: Disconnect from MQTT broker + throw new NotImplementedException(); + } +} diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs new file mode 100644 index 0000000..6abba5b --- /dev/null +++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila) +// SPDX-License-Identifier: AGPL-3.0-or-later + +using System.Collections.Concurrent; + +namespace FlowForge.MonitorServer.Services; + +public class SubscriptionManager +{ + private readonly ConcurrentDictionary> _subscriptionsByConnection = new(); + + public void AddSubscription(string connectionId, string variablePath) + { + var paths = _subscriptionsByConnection.GetOrAdd(connectionId, _ => []); + lock (paths) { paths.Add(variablePath); } + } + + public void RemoveSubscription(string connectionId, string variablePath) + { + if (_subscriptionsByConnection.TryGetValue(connectionId, out var paths)) + { + lock (paths) { paths.Remove(variablePath); } + } + } + + public IReadOnlySet GetSubscriptions(string connectionId) + { + if (_subscriptionsByConnection.TryGetValue(connectionId, out var paths)) + { + lock (paths) { return paths.ToHashSet(); } + } + return new HashSet(); + } + + public void RemoveAllSubscriptions(string connectionId) + { + _subscriptionsByConnection.TryRemove(connectionId, out _); + } +} From 24d1132d4ebc503c446cb0113f68b86b432f9cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bir=C3=B3=2C=20Csaba=20Attila?= Date: Sat, 14 Feb 2026 15:33:46 +0100 Subject: [PATCH 3/3] ci: trigger CLA re-check