diff --git a/cli_issues.md b/cli_issues.md new file mode 100644 index 0000000..82b1e23 --- /dev/null +++ b/cli_issues.md @@ -0,0 +1,17 @@ +# CLI Issues & Missing Features + +Log any bugs, unexpected behavior, or missing features in the ScadaLink CLI here. + +## Format + +``` +### [Short title] +- **Command**: `scadalink ` +- **Description**: What happened or what's missing +- **Expected**: What should happen +- **Error output** (if applicable): +``` + +--- + + diff --git a/src/ScadaLink.CLI/README.md b/src/ScadaLink.CLI/README.md index 93b1a4e..d22b2a8 100644 --- a/src/ScadaLink.CLI/README.md +++ b/src/ScadaLink.CLI/README.md @@ -1247,3 +1247,7 @@ The CLI connects to the Central cluster using Akka.NET's `ClusterClient`. It doe The connection is established per-command invocation and torn down cleanly via `CoordinatedShutdown` when the command completes. Role enforcement is applied by the ManagementActor on the server side. The current CLI placeholder user carries `Admin`, `Design`, and `Deployment` roles; production use will integrate LDAP authentication via `--username` / `--password`. + +## Issues & Missing Features + +If you encounter bugs, unexpected behavior, or missing features in the CLI, log them in [`cli_issues.md`](../../cli_issues.md) in the project root. Include a brief description, the command involved, and any relevant error output. diff --git a/src/ScadaLink.HealthMonitoring/HealthReportSender.cs b/src/ScadaLink.HealthMonitoring/HealthReportSender.cs index a2bf904..94e6cb6 100644 --- a/src/ScadaLink.HealthMonitoring/HealthReportSender.cs +++ b/src/ScadaLink.HealthMonitoring/HealthReportSender.cs @@ -49,9 +49,10 @@ public class HealthReportSender : BackgroundService { try { - // TODO: Wire S&F buffer depths when StoreAndForward service is available in DI - // e.g., var depths = await _bufferDepthProvider.GetDepthsAsync(); - // _collector.SetStoreAndForwardDepths(depths); + // Only the active node (running the DeploymentManager singleton) sends health reports. + // The standby node has no instance/connection data and would overwrite the active's report. + if (!_collector.IsActiveNode) + continue; var seq = Interlocked.Increment(ref _sequenceNumber); var report = _collector.CollectReport(_siteId); diff --git a/src/ScadaLink.HealthMonitoring/ISiteHealthCollector.cs b/src/ScadaLink.HealthMonitoring/ISiteHealthCollector.cs index c64ee97..68b122e 100644 --- a/src/ScadaLink.HealthMonitoring/ISiteHealthCollector.cs +++ b/src/ScadaLink.HealthMonitoring/ISiteHealthCollector.cs @@ -17,5 +17,7 @@ public interface ISiteHealthCollector void UpdateTagResolution(string connectionName, int totalSubscribed, int successfullyResolved); void SetStoreAndForwardDepths(IReadOnlyDictionary depths); void SetInstanceCounts(int deployed, int enabled, int disabled); + void SetActiveNode(bool isActive); + bool IsActiveNode { get; } SiteHealthReport CollectReport(string siteId); } diff --git a/src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs b/src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs index b676d20..05310d1 100644 --- a/src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs +++ b/src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs @@ -17,6 +17,7 @@ public class SiteHealthCollector : ISiteHealthCollector private readonly ConcurrentDictionary _tagResolutionCounts = new(); private IReadOnlyDictionary _sfBufferDepths = new Dictionary(); private int _deployedInstanceCount, _enabledInstanceCount, _disabledInstanceCount; + private volatile bool _isActiveNode; /// /// Increment the script error counter. Covers unhandled exceptions, @@ -90,6 +91,10 @@ public class SiteHealthCollector : ISiteHealthCollector Interlocked.Exchange(ref _disabledInstanceCount, disabled); } + public void SetActiveNode(bool isActive) => _isActiveNode = isActive; + + public bool IsActiveNode => _isActiveNode; + /// /// Collect the current health report for the site and reset interval counters. /// Connection statuses and tag resolution counts are NOT reset (they reflect current state). diff --git a/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs index ede0e4c..f25be81 100644 --- a/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs +++ b/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs @@ -83,6 +83,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers protected override void PreStart() { base.PreStart(); + _healthCollector?.SetActiveNode(true); _logger.LogInformation("DeploymentManagerActor starting — loading deployed configs from SQLite..."); // Load all configs asynchronously and pipe to self @@ -94,6 +95,12 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers }).PipeTo(Self); } + protected override void PostStop() + { + _healthCollector?.SetActiveNode(false); + base.PostStop(); + } + /// /// OneForOneStrategy: Resume on exceptions to preserve Instance Actor state, /// Stop only on ActorInitializationException (actor failed to start). @@ -160,6 +167,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers for (var i = startIdx; i < endIdx; i++) { var config = state.Configs[i]; + EnsureDclConnections(config.ConfigJson); CreateInstanceActor(config.InstanceUniqueName, config.ConfigJson); }