feat: add HTTP Management API, migrate CLI from Akka ClusterClient to HTTP

Replace the CLI's Akka.NET ClusterClient transport with a simple HTTP client
targeting a new POST /management endpoint on the Central Host. The endpoint
handles Basic Auth, LDAP authentication, role resolution, and ManagementActor
dispatch in a single round-trip — eliminating the CLI's Akka, LDAP, and
Security dependencies.

Also fixes DCL ReSubscribeAll losing subscriptions on repeated reconnect by
deriving the tag list from _subscriptionsByInstance instead of _subscriptionIds.
This commit is contained in:
Joseph Doherty
2026-03-20 23:55:31 -04:00
parent 7740a3bcf9
commit 1a540f4f0a
38 changed files with 863 additions and 758 deletions

View File

@@ -6,18 +6,18 @@ namespace ScadaLink.CLI.Commands;
public static class DeployCommands
{
public static Command Build(Option<string> contactPointsOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var command = new Command("deploy") { Description = "Deployment operations" };
command.Add(BuildInstance(contactPointsOption, formatOption, usernameOption, passwordOption));
command.Add(BuildArtifacts(contactPointsOption, formatOption, usernameOption, passwordOption));
command.Add(BuildStatus(contactPointsOption, formatOption, usernameOption, passwordOption));
command.Add(BuildInstance(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildArtifacts(urlOption, formatOption, usernameOption, passwordOption));
command.Add(BuildStatus(urlOption, formatOption, usernameOption, passwordOption));
return command;
}
private static Command BuildInstance(Option<string> contactPointsOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
private static Command BuildInstance(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
var cmd = new Command("instance") { Description = "Deploy a single instance" };
@@ -26,12 +26,12 @@ public static class DeployCommands
{
var id = result.GetValue(idOption);
return await CommandHelpers.ExecuteCommandAsync(
result, contactPointsOption, formatOption, usernameOption, passwordOption, new MgmtDeployInstanceCommand(id));
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtDeployInstanceCommand(id));
});
return cmd;
}
private static Command BuildArtifacts(Option<string> contactPointsOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
private static Command BuildArtifacts(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var siteIdOption = new Option<int?>("--site-id") { Description = "Target site ID (all sites if omitted)" };
var cmd = new Command("artifacts") { Description = "Deploy artifacts to site(s)" };
@@ -40,12 +40,12 @@ public static class DeployCommands
{
var siteId = result.GetValue(siteIdOption);
return await CommandHelpers.ExecuteCommandAsync(
result, contactPointsOption, formatOption, usernameOption, passwordOption, new MgmtDeployArtifactsCommand(siteId));
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtDeployArtifactsCommand(siteId));
});
return cmd;
}
private static Command BuildStatus(Option<string> contactPointsOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
private static Command BuildStatus(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var instanceIdOption = new Option<int?>("--instance-id") { Description = "Filter by instance ID" };
var statusOption = new Option<string?>("--status") { Description = "Filter by status" };
@@ -66,7 +66,7 @@ public static class DeployCommands
var page = result.GetValue(pageOption);
var pageSize = result.GetValue(pageSizeOption);
return await CommandHelpers.ExecuteCommandAsync(
result, contactPointsOption, formatOption, usernameOption, passwordOption,
result, urlOption, formatOption, usernameOption, passwordOption,
new QueryDeploymentsCommand(instanceId, status, page, pageSize));
});
return cmd;