Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/monitor-server/FlowForge.MonitorServer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
<PackageReference Include="MQTTnet" Version="5.*" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\shared\FlowForge.Shared\FlowForge.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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<PlcVariableValueDto> values);
Task ReceiveConnectionStatus(string status);
Task ReceiveError(string error);
}
Original file line number Diff line number Diff line change
@@ -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<PlcVariableValueDto> ReadVariableAsync(string variablePath, CancellationToken ct = default);
Task SubscribeAsync(string variablePath, Action<PlcVariableValueDto> callback, CancellationToken ct = default);
Task UnsubscribeAsync(string variablePath, CancellationToken ct = default);
Task DisconnectAsync(CancellationToken ct = default);
}
Original file line number Diff line number Diff line change
@@ -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<PlcVariableValueDto> ReadVariableAsync(string variablePath, CancellationToken ct)
{
// TODO: Publish ADS read request via MQTT, await response
throw new NotImplementedException();
}

public Task SubscribeAsync(string variablePath, Action<PlcVariableValueDto> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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<string, HashSet<string>> _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<string> GetSubscriptions(string connectionId)
{
if (_subscriptionsByConnection.TryGetValue(connectionId, out var paths))
{
lock (paths) { return paths.ToHashSet(); }
}
return new HashSet<string>();
}

public void RemoveAllSubscriptions(string connectionId)
{
_subscriptionsByConnection.TryRemove(connectionId, out _);
}
}
18 changes: 18 additions & 0 deletions src/shared/FlowForge.Shared/Enums/Permission.cs
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions src/shared/FlowForge.Shared/Enums/ProjectRole.cs
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions src/shared/FlowForge.Shared/FlowForge.Shared.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>FlowForge.Shared</RootNamespace>
</PropertyGroup>

</Project>
13 changes: 13 additions & 0 deletions src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs
Original file line number Diff line number Diff line change
@@ -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<string> Roles { get; init; } = [];
}
19 changes: 19 additions & 0 deletions src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 12 additions & 0 deletions src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 13 additions & 0 deletions src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs
Original file line number Diff line number Diff line change
@@ -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<string> Errors { get; init; } = [];
public string? CommitSha { get; init; }
public DateTimeOffset CompletedAt { get; init; }
}
13 changes: 13 additions & 0 deletions src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 12 additions & 0 deletions src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
15 changes: 15 additions & 0 deletions src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs
Original file line number Diff line number Diff line change
@@ -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();
}
13 changes: 13 additions & 0 deletions src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs
Original file line number Diff line number Diff line change
@@ -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<FlowNode> Nodes { get; init; } = [];
public IReadOnlyList<FlowConnection> Connections { get; init; } = [];
public Dictionary<string, string> Metadata { get; init; } = [];
}
12 changes: 12 additions & 0 deletions src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs
Original file line number Diff line number Diff line change
@@ -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<string, object?> Parameters { get; init; } = [];
}
10 changes: 10 additions & 0 deletions src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 10 additions & 0 deletions src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 12 additions & 0 deletions src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Loading