diff --git a/src/ScadaLink.CLI/Commands/AuditLogCommands.cs b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs new file mode 100644 index 0000000..c09b91d --- /dev/null +++ b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs @@ -0,0 +1,53 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class AuditLogCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("audit-log") { Description = "Query audit logs" }; + + command.Add(BuildQuery(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildQuery(Option contactPointsOption, Option formatOption) + { + var userOption = new Option("--user") { Description = "Filter by username" }; + var entityTypeOption = new Option("--entity-type") { Description = "Filter by entity type" }; + var actionOption = new Option("--action") { Description = "Filter by action" }; + var fromOption = new Option("--from") { Description = "Start date (ISO 8601)" }; + var toOption = new Option("--to") { Description = "End date (ISO 8601)" }; + var pageOption = new Option("--page") { Description = "Page number" }; + pageOption.DefaultValueFactory = _ => 1; + var pageSizeOption = new Option("--page-size") { Description = "Page size" }; + pageSizeOption.DefaultValueFactory = _ => 50; + + var cmd = new Command("query") { Description = "Query audit log entries" }; + cmd.Add(userOption); + cmd.Add(entityTypeOption); + cmd.Add(actionOption); + cmd.Add(fromOption); + cmd.Add(toOption); + cmd.Add(pageOption); + cmd.Add(pageSizeOption); + cmd.SetAction(async (ParseResult result) => + { + var user = result.GetValue(userOption); + var entityType = result.GetValue(entityTypeOption); + var action = result.GetValue(actionOption); + var from = result.GetValue(fromOption); + var to = result.GetValue(toOption); + var page = result.GetValue(pageOption); + var pageSize = result.GetValue(pageSizeOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new QueryAuditLogCommand(user, entityType, action, from, to, page, pageSize)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/CommandHelpers.cs b/src/ScadaLink.CLI/Commands/CommandHelpers.cs new file mode 100644 index 0000000..070b7c8 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/CommandHelpers.cs @@ -0,0 +1,77 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +internal static class CommandHelpers +{ + internal static AuthenticatedUser PlaceholderUser { get; } = + new("cli-user", "CLI User", ["Admin", "Design", "Deployment"], Array.Empty()); + + internal static string NewCorrelationId() => Guid.NewGuid().ToString("N"); + + internal static async Task ExecuteCommandAsync( + ParseResult result, + Option contactPointsOption, + Option formatOption, + object command) + { + var contactPointsRaw = result.GetValue(contactPointsOption); + var format = result.GetValue(formatOption) ?? "json"; + + if (string.IsNullOrWhiteSpace(contactPointsRaw)) + { + var config = CliConfig.Load(); + if (config.ContactPoints.Count > 0) + contactPointsRaw = string.Join(",", config.ContactPoints); + } + + if (string.IsNullOrWhiteSpace(contactPointsRaw)) + { + OutputFormatter.WriteError("No contact points specified. Use --contact-points or set SCADALINK_CONTACT_POINTS.", "NO_CONTACT_POINTS"); + return 1; + } + + var contactPoints = contactPointsRaw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + await using var connection = new ClusterConnection(); + await connection.ConnectAsync(contactPoints, TimeSpan.FromSeconds(10)); + + var envelope = new ManagementEnvelope(PlaceholderUser, command, NewCorrelationId()); + var response = await connection.AskManagementAsync(envelope, TimeSpan.FromSeconds(30)); + + return HandleResponse(response, format); + } + + internal static int HandleResponse(object response, string format) + { + switch (response) + { + case ManagementSuccess success: + if (format == "table") + WriteAsTable(success.Data); + else + OutputFormatter.WriteJson(success.Data); + return 0; + + case ManagementError error: + OutputFormatter.WriteError(error.Error, error.ErrorCode); + return 1; + + case ManagementUnauthorized unauth: + OutputFormatter.WriteError(unauth.Message, "UNAUTHORIZED"); + return 2; + + default: + OutputFormatter.WriteError($"Unexpected response type: {response.GetType().Name}", "UNEXPECTED_RESPONSE"); + return 1; + } + } + + private static void WriteAsTable(object? data) + { + // For table format, delegate to JSON if data shape is unknown + OutputFormatter.WriteJson(data); + } +} diff --git a/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs b/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs new file mode 100644 index 0000000..cb2387d --- /dev/null +++ b/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs @@ -0,0 +1,86 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class DataConnectionCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("data-connection") { Description = "Manage data connections" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + command.Add(BuildAssign(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("list") { Description = "List all data connections" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListDataConnectionsCommand()); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "Connection name", Required = true }; + var protocolOption = new Option("--protocol") { Description = "Protocol (e.g. OpcUa)", Required = true }; + var configOption = new Option("--configuration") { Description = "Connection configuration JSON" }; + + var cmd = new Command("create") { Description = "Create a new data connection" }; + cmd.Add(nameOption); + cmd.Add(protocolOption); + cmd.Add(configOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var protocol = result.GetValue(protocolOption)!; + var config = result.GetValue(configOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateDataConnectionCommand(name, protocol, config)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Data connection ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete a data connection" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteDataConnectionCommand(id)); + }); + return cmd; + } + + private static Command BuildAssign(Option contactPointsOption, Option formatOption) + { + var connectionIdOption = new Option("--connection-id") { Description = "Data connection ID", Required = true }; + var siteIdOption = new Option("--site-id") { Description = "Site ID", Required = true }; + + var cmd = new Command("assign") { Description = "Assign a data connection to a site" }; + cmd.Add(connectionIdOption); + cmd.Add(siteIdOption); + cmd.SetAction(async (ParseResult result) => + { + var connectionId = result.GetValue(connectionIdOption); + var siteId = result.GetValue(siteIdOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new AssignDataConnectionToSiteCommand(connectionId, siteId)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/DeployCommands.cs b/src/ScadaLink.CLI/Commands/DeployCommands.cs new file mode 100644 index 0000000..9a14838 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/DeployCommands.cs @@ -0,0 +1,74 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class DeployCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("deploy") { Description = "Deployment operations" }; + + command.Add(BuildInstance(contactPointsOption, formatOption)); + command.Add(BuildArtifacts(contactPointsOption, formatOption)); + command.Add(BuildStatus(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildInstance(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Instance ID", Required = true }; + var cmd = new Command("instance") { Description = "Deploy a single instance" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDeployInstanceCommand(id)); + }); + return cmd; + } + + private static Command BuildArtifacts(Option contactPointsOption, Option formatOption) + { + var siteIdOption = new Option("--site-id") { Description = "Target site ID (all sites if omitted)" }; + var cmd = new Command("artifacts") { Description = "Deploy artifacts to site(s)" }; + cmd.Add(siteIdOption); + cmd.SetAction(async (ParseResult result) => + { + var siteId = result.GetValue(siteIdOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDeployArtifactsCommand(siteId)); + }); + return cmd; + } + + private static Command BuildStatus(Option contactPointsOption, Option formatOption) + { + var instanceIdOption = new Option("--instance-id") { Description = "Filter by instance ID" }; + var statusOption = new Option("--status") { Description = "Filter by status" }; + var pageOption = new Option("--page") { Description = "Page number" }; + pageOption.DefaultValueFactory = _ => 1; + var pageSizeOption = new Option("--page-size") { Description = "Page size" }; + pageSizeOption.DefaultValueFactory = _ => 50; + + var cmd = new Command("status") { Description = "Query deployment status" }; + cmd.Add(instanceIdOption); + cmd.Add(statusOption); + cmd.Add(pageOption); + cmd.Add(pageSizeOption); + cmd.SetAction(async (ParseResult result) => + { + var instanceId = result.GetValue(instanceIdOption); + var status = result.GetValue(statusOption); + var page = result.GetValue(pageOption); + var pageSize = result.GetValue(pageSizeOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new QueryDeploymentsCommand(instanceId, status, page, pageSize)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs b/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs new file mode 100644 index 0000000..fbb443a --- /dev/null +++ b/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs @@ -0,0 +1,69 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class ExternalSystemCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("external-system") { Description = "Manage external systems" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("list") { Description = "List all external systems" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListExternalSystemsCommand()); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "System name", Required = true }; + var urlOption = new Option("--endpoint-url") { Description = "Endpoint URL", Required = true }; + var authTypeOption = new Option("--auth-type") { Description = "Auth type (ApiKey, BasicAuth)", Required = true }; + var authConfigOption = new Option("--auth-config") { Description = "Auth configuration JSON" }; + + var cmd = new Command("create") { Description = "Create an external system" }; + cmd.Add(nameOption); + cmd.Add(urlOption); + cmd.Add(authTypeOption); + cmd.Add(authConfigOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var url = result.GetValue(urlOption)!; + var authType = result.GetValue(authTypeOption)!; + var authConfig = result.GetValue(authConfigOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateExternalSystemCommand(name, url, authType, authConfig)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "External system ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete an external system" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteExternalSystemCommand(id)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/HealthCommands.cs b/src/ScadaLink.CLI/Commands/HealthCommands.cs new file mode 100644 index 0000000..dc71a40 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/HealthCommands.cs @@ -0,0 +1,43 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class HealthCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("health") { Description = "Health monitoring" }; + + command.Add(BuildSummary(contactPointsOption, formatOption)); + command.Add(BuildSite(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildSummary(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("summary") { Description = "Get system health summary" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new GetHealthSummaryCommand()); + }); + return cmd; + } + + private static Command BuildSite(Option contactPointsOption, Option formatOption) + { + var identifierOption = new Option("--identifier") { Description = "Site identifier", Required = true }; + var cmd = new Command("site") { Description = "Get health for a specific site" }; + cmd.Add(identifierOption); + cmd.SetAction(async (ParseResult result) => + { + var identifier = result.GetValue(identifierOption)!; + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new GetSiteHealthCommand(identifier)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/InstanceCommands.cs b/src/ScadaLink.CLI/Commands/InstanceCommands.cs new file mode 100644 index 0000000..a842853 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/InstanceCommands.cs @@ -0,0 +1,125 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class InstanceCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("instance") { Description = "Manage instances" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDeploy(contactPointsOption, formatOption)); + command.Add(BuildEnable(contactPointsOption, formatOption)); + command.Add(BuildDisable(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var siteIdOption = new Option("--site-id") { Description = "Filter by site ID" }; + var templateIdOption = new Option("--template-id") { Description = "Filter by template ID" }; + var searchOption = new Option("--search") { Description = "Search term" }; + + var cmd = new Command("list") { Description = "List instances" }; + cmd.Add(siteIdOption); + cmd.Add(templateIdOption); + cmd.Add(searchOption); + cmd.SetAction(async (ParseResult result) => + { + var siteId = result.GetValue(siteIdOption); + var templateId = result.GetValue(templateIdOption); + var search = result.GetValue(searchOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new ListInstancesCommand(siteId, templateId, search)); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "Unique instance name", Required = true }; + var templateIdOption = new Option("--template-id") { Description = "Template ID", Required = true }; + var siteIdOption = new Option("--site-id") { Description = "Site ID", Required = true }; + var areaIdOption = new Option("--area-id") { Description = "Area ID" }; + + var cmd = new Command("create") { Description = "Create a new instance" }; + cmd.Add(nameOption); + cmd.Add(templateIdOption); + cmd.Add(siteIdOption); + cmd.Add(areaIdOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var templateId = result.GetValue(templateIdOption); + var siteId = result.GetValue(siteIdOption); + var areaId = result.GetValue(areaIdOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateInstanceCommand(name, templateId, siteId, areaId)); + }); + return cmd; + } + + private static Command BuildDeploy(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Instance ID", Required = true }; + var cmd = new Command("deploy") { Description = "Deploy an instance" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDeployInstanceCommand(id)); + }); + return cmd; + } + + private static Command BuildEnable(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Instance ID", Required = true }; + var cmd = new Command("enable") { Description = "Enable an instance" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtEnableInstanceCommand(id)); + }); + return cmd; + } + + private static Command BuildDisable(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Instance ID", Required = true }; + var cmd = new Command("disable") { Description = "Disable an instance" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDisableInstanceCommand(id)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Instance ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete an instance" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDeleteInstanceCommand(id)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/NotificationCommands.cs b/src/ScadaLink.CLI/Commands/NotificationCommands.cs new file mode 100644 index 0000000..4076eea --- /dev/null +++ b/src/ScadaLink.CLI/Commands/NotificationCommands.cs @@ -0,0 +1,64 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class NotificationCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("notification") { Description = "Manage notification lists" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("list") { Description = "List all notification lists" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListNotificationListsCommand()); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "Notification list name", Required = true }; + var emailsOption = new Option("--emails") { Description = "Comma-separated recipient emails", Required = true }; + + var cmd = new Command("create") { Description = "Create a notification list" }; + cmd.Add(nameOption); + cmd.Add(emailsOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var emailsRaw = result.GetValue(emailsOption)!; + var emails = emailsRaw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateNotificationListCommand(name, emails)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Notification list ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete a notification list" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteNotificationListCommand(id)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/SecurityCommands.cs b/src/ScadaLink.CLI/Commands/SecurityCommands.cs new file mode 100644 index 0000000..44d6819 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/SecurityCommands.cs @@ -0,0 +1,96 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class SecurityCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("security") { Description = "Manage security settings" }; + + command.Add(BuildApiKey(contactPointsOption, formatOption)); + command.Add(BuildRoleMapping(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildApiKey(Option contactPointsOption, Option formatOption) + { + var group = new Command("api-key") { Description = "Manage API keys" }; + + var listCmd = new Command("list") { Description = "List all API keys" }; + listCmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListApiKeysCommand()); + }); + group.Add(listCmd); + + var nameOption = new Option("--name") { Description = "API key name", Required = true }; + var createCmd = new Command("create") { Description = "Create an API key" }; + createCmd.Add(nameOption); + createCmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new CreateApiKeyCommand(name)); + }); + group.Add(createCmd); + + var idOption = new Option("--id") { Description = "API key ID", Required = true }; + var deleteCmd = new Command("delete") { Description = "Delete an API key" }; + deleteCmd.Add(idOption); + deleteCmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteApiKeyCommand(id)); + }); + group.Add(deleteCmd); + + return group; + } + + private static Command BuildRoleMapping(Option contactPointsOption, Option formatOption) + { + var group = new Command("role-mapping") { Description = "Manage LDAP role mappings" }; + + var listCmd = new Command("list") { Description = "List all role mappings" }; + listCmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListRoleMappingsCommand()); + }); + group.Add(listCmd); + + var ldapGroupOption = new Option("--ldap-group") { Description = "LDAP group name", Required = true }; + var roleOption = new Option("--role") { Description = "Role name", Required = true }; + var createCmd = new Command("create") { Description = "Create a role mapping" }; + createCmd.Add(ldapGroupOption); + createCmd.Add(roleOption); + createCmd.SetAction(async (ParseResult result) => + { + var ldapGroup = result.GetValue(ldapGroupOption)!; + var role = result.GetValue(roleOption)!; + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateRoleMappingCommand(ldapGroup, role)); + }); + group.Add(createCmd); + + var idOption = new Option("--id") { Description = "Mapping ID", Required = true }; + var deleteCmd = new Command("delete") { Description = "Delete a role mapping" }; + deleteCmd.Add(idOption); + deleteCmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteRoleMappingCommand(id)); + }); + group.Add(deleteCmd); + + return group; + } +} diff --git a/src/ScadaLink.CLI/Commands/SiteCommands.cs b/src/ScadaLink.CLI/Commands/SiteCommands.cs new file mode 100644 index 0000000..4c0f142 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/SiteCommands.cs @@ -0,0 +1,81 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class SiteCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("site") { Description = "Manage sites" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + command.Add(BuildDeployArtifacts(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("list") { Description = "List all sites" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListSitesCommand()); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "Site name", Required = true }; + var identifierOption = new Option("--identifier") { Description = "Site identifier", Required = true }; + var descOption = new Option("--description") { Description = "Site description" }; + + var cmd = new Command("create") { Description = "Create a new site" }; + cmd.Add(nameOption); + cmd.Add(identifierOption); + cmd.Add(descOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var identifier = result.GetValue(identifierOption)!; + var desc = result.GetValue(descOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateSiteCommand(name, identifier, desc)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Site ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete a site" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteSiteCommand(id)); + }); + return cmd; + } + + private static Command BuildDeployArtifacts(Option contactPointsOption, Option formatOption) + { + var siteIdOption = new Option("--site-id") { Description = "Target site ID (all sites if omitted)" }; + var cmd = new Command("deploy-artifacts") { Description = "Deploy artifacts to site(s)" }; + cmd.Add(siteIdOption); + cmd.SetAction(async (ParseResult result) => + { + var siteId = result.GetValue(siteIdOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new MgmtDeployArtifactsCommand(siteId)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Commands/TemplateCommands.cs b/src/ScadaLink.CLI/Commands/TemplateCommands.cs new file mode 100644 index 0000000..847f580 --- /dev/null +++ b/src/ScadaLink.CLI/Commands/TemplateCommands.cs @@ -0,0 +1,81 @@ +using System.CommandLine; +using System.CommandLine.Parsing; +using ScadaLink.Commons.Messages.Management; + +namespace ScadaLink.CLI.Commands; + +public static class TemplateCommands +{ + public static Command Build(Option contactPointsOption, Option formatOption) + { + var command = new Command("template") { Description = "Manage templates" }; + + command.Add(BuildList(contactPointsOption, formatOption)); + command.Add(BuildGet(contactPointsOption, formatOption)); + command.Add(BuildCreate(contactPointsOption, formatOption)); + command.Add(BuildDelete(contactPointsOption, formatOption)); + + return command; + } + + private static Command BuildList(Option contactPointsOption, Option formatOption) + { + var cmd = new Command("list") { Description = "List all templates" }; + cmd.SetAction(async (ParseResult result) => + { + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new ListTemplatesCommand()); + }); + return cmd; + } + + private static Command BuildGet(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Template ID", Required = true }; + var cmd = new Command("get") { Description = "Get a template by ID" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new GetTemplateCommand(id)); + }); + return cmd; + } + + private static Command BuildCreate(Option contactPointsOption, Option formatOption) + { + var nameOption = new Option("--name") { Description = "Template name", Required = true }; + var descOption = new Option("--description") { Description = "Template description" }; + var parentOption = new Option("--parent-id") { Description = "Parent template ID" }; + + var cmd = new Command("create") { Description = "Create a new template" }; + cmd.Add(nameOption); + cmd.Add(descOption); + cmd.Add(parentOption); + cmd.SetAction(async (ParseResult result) => + { + var name = result.GetValue(nameOption)!; + var desc = result.GetValue(descOption); + var parentId = result.GetValue(parentOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, + new CreateTemplateCommand(name, desc, parentId)); + }); + return cmd; + } + + private static Command BuildDelete(Option contactPointsOption, Option formatOption) + { + var idOption = new Option("--id") { Description = "Template ID", Required = true }; + var cmd = new Command("delete") { Description = "Delete a template" }; + cmd.Add(idOption); + cmd.SetAction(async (ParseResult result) => + { + var id = result.GetValue(idOption); + return await CommandHelpers.ExecuteCommandAsync( + result, contactPointsOption, formatOption, new DeleteTemplateCommand(id)); + }); + return cmd; + } +} diff --git a/src/ScadaLink.CLI/Program.cs b/src/ScadaLink.CLI/Program.cs index 99c9172..6fc2ccc 100644 --- a/src/ScadaLink.CLI/Program.cs +++ b/src/ScadaLink.CLI/Program.cs @@ -1,5 +1,6 @@ using System.CommandLine; using System.CommandLine.Parsing; +using ScadaLink.CLI.Commands; var rootCommand = new RootCommand("ScadaLink CLI — manage the ScadaLink SCADA system"); @@ -14,7 +15,18 @@ rootCommand.Add(usernameOption); rootCommand.Add(passwordOption); rootCommand.Add(formatOption); -// Placeholder — command groups will be added in Task 6 +// Register command groups +rootCommand.Add(TemplateCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(InstanceCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(SiteCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(DeployCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(DataConnectionCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(ExternalSystemCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(NotificationCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(SecurityCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(AuditLogCommands.Build(contactPointsOption, formatOption)); +rootCommand.Add(HealthCommands.Build(contactPointsOption, formatOption)); + rootCommand.SetAction(_ => { Console.WriteLine("Use --help to see available commands.");