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 _); + } +} 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}"; +}