feat: achieve CLI parity with Central UI
Add 33 new management message records, ManagementActor handlers, and CLI commands to close all functionality gaps between the Central UI and the Management CLI. New capabilities include: - Template member CRUD (attributes, alarms, scripts, compositions) - Shared script CRUD - Database connection definition CRUD - Inbound API method CRUD - LDAP scope rule management - API key enable/disable - Area update - Remote event log and parked message queries - Missing get/update commands for templates, sites, instances, data connections, external systems, notifications, and SMTP config Includes 12 new ManagementActor unit tests covering authorization, happy-path queries, and error handling. Updates CLI README and component design documents (Component-CLI.md, Component-ManagementService.md).
This commit is contained in:
@@ -61,6 +61,17 @@ scadalink template update <name> --file <path>
|
||||
scadalink template delete <name>
|
||||
scadalink template validate <name>
|
||||
scadalink template diff <instance-code>
|
||||
scadalink template attribute add --template-id <id> --name <name> --data-type <type> [--default-value <value>] [--tag-path <path>]
|
||||
scadalink template attribute update --template-id <id> --name <name> [--data-type <type>] [--default-value <value>] [--tag-path <path>]
|
||||
scadalink template attribute delete --template-id <id> --name <name>
|
||||
scadalink template alarm add --template-id <id> --name <name> --trigger-attribute <attr> --condition <cond> --setpoint <value> [--severity <level>] [--notification-list <name>]
|
||||
scadalink template alarm update --template-id <id> --name <name> [--condition <cond>] [--setpoint <value>] [--severity <level>] [--notification-list <name>]
|
||||
scadalink template alarm delete --template-id <id> --name <name>
|
||||
scadalink template script add --template-id <id> --name <name> --trigger-type <type> [--trigger-attribute <attr>] [--interval <ms>] --code <code>
|
||||
scadalink template script update --template-id <id> --name <name> [--trigger-type <type>] [--trigger-attribute <attr>] [--interval <ms>] [--code <code>]
|
||||
scadalink template script delete --template-id <id> --name <name>
|
||||
scadalink template composition add --template-id <id> --module-template-id <id> --instance-name <name>
|
||||
scadalink template composition delete --template-id <id> --instance-name <name>
|
||||
```
|
||||
|
||||
### Instance Commands
|
||||
@@ -69,6 +80,7 @@ scadalink instance list [--site <site>] [--area <area>] [--format json|table]
|
||||
scadalink instance get <code> [--format json|table]
|
||||
scadalink instance create --template <name> --site <site> --code <code> [--area <area>]
|
||||
scadalink instance set-overrides <code> --file <path>
|
||||
scadalink instance set-bindings <code> --bindings <json>
|
||||
scadalink instance bind-connections <code> --file <path>
|
||||
scadalink instance assign-area <code> --area <area>
|
||||
scadalink instance enable <code>
|
||||
@@ -85,6 +97,7 @@ scadalink site update <site-id> --file <path>
|
||||
scadalink site delete <site-id>
|
||||
scadalink site area list <site-id>
|
||||
scadalink site area create <site-id> --name <name> [--parent <parent-area>]
|
||||
scadalink site area update <site-id> --name <name> [--new-name <name>] [--parent <parent-area>]
|
||||
scadalink site area delete <site-id> --name <name>
|
||||
```
|
||||
|
||||
@@ -122,7 +135,7 @@ scadalink notification get <name> [--format json|table]
|
||||
scadalink notification create --file <path>
|
||||
scadalink notification update <name> --file <path>
|
||||
scadalink notification delete <name>
|
||||
scadalink notification smtp get [--format json|table]
|
||||
scadalink notification smtp list [--format json|table]
|
||||
scadalink notification smtp update --file <path>
|
||||
```
|
||||
|
||||
@@ -130,12 +143,17 @@ scadalink notification smtp update --file <path>
|
||||
```
|
||||
scadalink security api-key list [--format json|table]
|
||||
scadalink security api-key create --name <name>
|
||||
scadalink security api-key update <name> [--name <new-name>] [--enabled <bool>]
|
||||
scadalink security api-key enable <name>
|
||||
scadalink security api-key disable <name>
|
||||
scadalink security api-key delete <name>
|
||||
scadalink security role-mapping list [--format json|table]
|
||||
scadalink security role-mapping create --group <ldap-group> --role <role> [--site <site>]
|
||||
scadalink security role-mapping update --id <id> [--group <ldap-group>] [--role <role>]
|
||||
scadalink security role-mapping delete --group <ldap-group> --role <role>
|
||||
scadalink security scope-rule list [--role-mapping-id <id>] [--format json|table]
|
||||
scadalink security scope-rule add --role-mapping-id <id> --site-id <site-id>
|
||||
scadalink security scope-rule delete --id <id>
|
||||
```
|
||||
|
||||
### Audit Log Commands
|
||||
@@ -147,6 +165,35 @@ scadalink audit-log query [--user <username>] [--entity-type <type>] [--from <da
|
||||
```
|
||||
scadalink health summary [--format json|table]
|
||||
scadalink health site <site-id> [--format json|table]
|
||||
scadalink health event-log --site-identifier <site-id> [--from <date>] [--to <date>] [--search <term>] [--page <n>] [--page-size <n>] [--format json|table]
|
||||
scadalink health parked-messages --site-identifier <site-id> [--page <n>] [--page-size <n>] [--format json|table]
|
||||
```
|
||||
|
||||
### Shared Script Commands
|
||||
```
|
||||
scadalink shared-script list [--format json|table]
|
||||
scadalink shared-script get --id <id> [--format json|table]
|
||||
scadalink shared-script create --name <name> --code <code>
|
||||
scadalink shared-script update --id <id> [--name <name>] [--code <code>]
|
||||
scadalink shared-script delete --id <id>
|
||||
```
|
||||
|
||||
### Database Connection Commands
|
||||
```
|
||||
scadalink db-connection list [--format json|table]
|
||||
scadalink db-connection get --id <id> [--format json|table]
|
||||
scadalink db-connection create --name <name> --connection-string <string> [--provider <provider>]
|
||||
scadalink db-connection update --id <id> [--name <name>] [--connection-string <string>] [--provider <provider>]
|
||||
scadalink db-connection delete --id <id>
|
||||
```
|
||||
|
||||
### Inbound API Method Commands
|
||||
```
|
||||
scadalink api-method list [--format json|table]
|
||||
scadalink api-method get --id <id> [--format json|table]
|
||||
scadalink api-method create --name <name> --code <code> [--description <desc>]
|
||||
scadalink api-method update --id <id> [--name <name>] [--code <code>] [--description <desc>]
|
||||
scadalink api-method delete --id <id>
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -45,12 +45,19 @@ The ManagementActor registers itself with `ClusterClientReceptionist` at startup
|
||||
- **ValidateTemplate**: Run on-demand pre-deployment validation (flattening, naming collisions, script compilation).
|
||||
- **GetTemplateDiff**: Compare deployed vs. template-derived configuration for an instance.
|
||||
|
||||
### Template Members
|
||||
|
||||
- **AddTemplateAttribute** / **UpdateTemplateAttribute** / **DeleteTemplateAttribute**: Manage attributes on a template.
|
||||
- **AddTemplateAlarm** / **UpdateTemplateAlarm** / **DeleteTemplateAlarm**: Manage alarm definitions on a template.
|
||||
- **AddTemplateScript** / **UpdateTemplateScript** / **DeleteTemplateScript**: Manage scripts on a template.
|
||||
- **AddTemplateComposition** / **DeleteTemplateComposition**: Manage feature module compositions on a template.
|
||||
|
||||
### Instances
|
||||
|
||||
- **ListInstances** / **GetInstance**: Query instances, with filtering by site and area.
|
||||
- **CreateInstance**: Create a new instance from a template.
|
||||
- **UpdateInstanceOverrides**: Set attribute overrides on an instance.
|
||||
- **BindDataConnections**: Bind data connections to instance attributes.
|
||||
- **SetInstanceBindings** / **BindDataConnections**: Bind data connections to instance attributes.
|
||||
- **AssignArea**: Assign an instance to an area.
|
||||
- **EnableInstance** / **DisableInstance** / **DeleteInstance**: Instance lifecycle commands.
|
||||
|
||||
@@ -85,25 +92,47 @@ The ManagementActor registers itself with `ClusterClientReceptionist` at startup
|
||||
|
||||
### Security (LDAP & API Keys)
|
||||
|
||||
- **ListApiKeys** / **CreateApiKey** / **EnableApiKey** / **DisableApiKey** / **DeleteApiKey**: Manage API keys.
|
||||
- **ListApiKeys** / **CreateApiKey** / **UpdateApiKey** / **EnableApiKey** / **DisableApiKey** / **DeleteApiKey**: Manage API keys.
|
||||
- **ListRoleMappings** / **CreateRoleMapping** / **UpdateRoleMapping** / **DeleteRoleMapping**: Manage LDAP group-to-role mappings.
|
||||
- **ListScopeRules** / **AddScopeRule** / **DeleteScopeRule**: Manage site scope rules on role mappings.
|
||||
|
||||
### Audit Log
|
||||
|
||||
- **QueryAuditLog**: Query audit log entries with filtering by entity type, user, date range, etc.
|
||||
|
||||
### Shared Scripts
|
||||
|
||||
- **ListSharedScripts** / **GetSharedScript**: Query shared script definitions.
|
||||
- **CreateSharedScript** / **UpdateSharedScript** / **DeleteSharedScript**: Manage shared scripts.
|
||||
|
||||
### Database Connections
|
||||
|
||||
- **ListDatabaseConnections** / **GetDatabaseConnection**: Query database connection definitions.
|
||||
- **CreateDatabaseConnection** / **UpdateDatabaseConnection** / **DeleteDatabaseConnection**: Manage database connections.
|
||||
|
||||
### Inbound API Methods
|
||||
|
||||
- **ListApiMethods** / **GetApiMethod**: Query inbound API method definitions.
|
||||
- **CreateApiMethod** / **UpdateApiMethod** / **DeleteApiMethod**: Manage inbound API methods.
|
||||
|
||||
### Health
|
||||
|
||||
- **GetHealthSummary**: Query current health status of all sites.
|
||||
- **GetSiteHealth**: Query detailed health for a specific site.
|
||||
|
||||
### Remote Queries
|
||||
|
||||
- **QuerySiteEventLog**: Query site event log entries from a remote site (routed via communication layer). Supports date range, keyword search, and pagination.
|
||||
- **QueryParkedMessages**: Query parked (dead-letter) messages at a remote site (routed via communication layer). Supports pagination.
|
||||
|
||||
## Authorization
|
||||
|
||||
Every incoming message carries the authenticated user's identity and roles. The ManagementActor enforces the same role-based authorization rules as the Central UI:
|
||||
|
||||
- **Admin** role required for: site management, area management, API key management, role mapping management, system configuration.
|
||||
- **Design** role required for: template authoring, shared scripts, external system definitions, database connection definitions, notification lists, inbound API method definitions.
|
||||
- **Deployment** role required for: instance management, deployments, debug view, parked message management, site event log viewing. Site scoping is enforced for site-scoped Deployment users.
|
||||
- **Admin** role required for: site management, area management, API key management, role mapping management, scope rule management, system configuration.
|
||||
- **Design** role required for: template authoring (including template member management: attributes, alarms, scripts, compositions), shared scripts, external system definitions, database connection definitions, notification lists, inbound API method definitions.
|
||||
- **Deployment** role required for: instance management, deployments, debug view, parked message queries, site event log queries. Site scoping is enforced for site-scoped Deployment users.
|
||||
- **Read-only access** (any authenticated role): health summary, health site, site event log queries, parked message queries.
|
||||
|
||||
Unauthorized commands receive an `Unauthorized` response message. Failed authorization attempts are not audit logged (consistent with existing behavior).
|
||||
|
||||
@@ -120,7 +149,8 @@ The ManagementActor receives the following services and repositories via DI (inj
|
||||
- `INotificationRepository` — Notification lists and SMTP config.
|
||||
- `ISecurityRepository` — API keys and LDAP role mappings.
|
||||
- `IInboundApiRepository` — Inbound API method definitions.
|
||||
- `ICentralUiRepository` — UI-related queries (shared scripts, database connections).
|
||||
- `ISharedScriptRepository` / `SharedScriptService` — Shared script definitions.
|
||||
- `IDatabaseConnectionRepository` — Database connection definitions.
|
||||
- `ICentralHealthAggregator` — Health status aggregation.
|
||||
- `CommunicationService` — Central-site communication for deployment and remote queries.
|
||||
|
||||
|
||||
118
src/ScadaLink.CLI/Commands/ApiMethodCommands.cs
Normal file
118
src/ScadaLink.CLI/Commands/ApiMethodCommands.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
|
||||
namespace ScadaLink.CLI.Commands;
|
||||
|
||||
public static class ApiMethodCommands
|
||||
{
|
||||
public static Command Build(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var command = new Command("api-method") { Description = "Manage inbound API methods" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all API methods" };
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListApiMethodsCommand());
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "API method ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get an API method by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetApiMethodCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildCreate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var nameOption = new Option<string>("--name") { Description = "Method name", Required = true };
|
||||
var scriptOption = new Option<string>("--script") { Description = "Script code", Required = true };
|
||||
var timeoutOption = new Option<int>("--timeout") { Description = "Timeout in seconds" };
|
||||
timeoutOption.DefaultValueFactory = _ => 30;
|
||||
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
|
||||
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
|
||||
|
||||
var cmd = new Command("create") { Description = "Create an API method" };
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(scriptOption);
|
||||
cmd.Add(timeoutOption);
|
||||
cmd.Add(parametersOption);
|
||||
cmd.Add(returnDefOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var script = result.GetValue(scriptOption)!;
|
||||
var timeout = result.GetValue(timeoutOption);
|
||||
var parameters = result.GetValue(parametersOption);
|
||||
var returnDef = result.GetValue(returnDefOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new CreateApiMethodCommand(name, script, timeout, parameters, returnDef));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "API method ID", Required = true };
|
||||
var scriptOption = new Option<string>("--script") { Description = "Script code", Required = true };
|
||||
var timeoutOption = new Option<int>("--timeout") { Description = "Timeout in seconds" };
|
||||
timeoutOption.DefaultValueFactory = _ => 30;
|
||||
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
|
||||
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update an API method" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(scriptOption);
|
||||
cmd.Add(timeoutOption);
|
||||
cmd.Add(parametersOption);
|
||||
cmd.Add(returnDefOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var script = result.GetValue(scriptOption)!;
|
||||
var timeout = result.GetValue(timeoutOption);
|
||||
var parameters = result.GetValue(parametersOption);
|
||||
var returnDef = result.GetValue(returnDefOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateApiMethodCommand(id, script, timeout, parameters, returnDef));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDelete(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "API method ID", Required = true };
|
||||
var cmd = new Command("delete") { Description = "Delete an API method" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new DeleteApiMethodCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,69 @@ public static class DataConnectionCommands
|
||||
var command = new Command("data-connection") { Description = "Manage data connections" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
command.Add(BuildAssign(contactPointsOption, formatOption));
|
||||
command.Add(BuildUnassign(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Data connection ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get a data connection by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetDataConnectionCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Data connection ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Connection name", Required = true };
|
||||
var protocolOption = new Option<string>("--protocol") { Description = "Protocol", Required = true };
|
||||
var configOption = new Option<string?>("--configuration") { Description = "Configuration JSON" };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update a data connection" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(protocolOption);
|
||||
cmd.Add(configOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var protocol = result.GetValue(protocolOption)!;
|
||||
var config = result.GetValue(configOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateDataConnectionCommand(id, name, protocol, config));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUnassign(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--assignment-id") { Description = "Assignment ID", Required = true };
|
||||
var cmd = new Command("unassign") { Description = "Unassign a data connection from a site" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new UnassignDataConnectionFromSiteCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all data connections" };
|
||||
|
||||
101
src/ScadaLink.CLI/Commands/DbConnectionCommands.cs
Normal file
101
src/ScadaLink.CLI/Commands/DbConnectionCommands.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
|
||||
namespace ScadaLink.CLI.Commands;
|
||||
|
||||
public static class DbConnectionCommands
|
||||
{
|
||||
public static Command Build(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var command = new Command("db-connection") { Description = "Manage database connections" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all database connections" };
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListDatabaseConnectionsCommand());
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Database connection ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get a database connection by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetDatabaseConnectionCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildCreate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var nameOption = new Option<string>("--name") { Description = "Connection name", Required = true };
|
||||
var connStrOption = new Option<string>("--connection-string") { Description = "Connection string", Required = true };
|
||||
|
||||
var cmd = new Command("create") { Description = "Create a database connection" };
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(connStrOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var connStr = result.GetValue(connStrOption)!;
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new CreateDatabaseConnectionDefCommand(name, connStr));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Database connection ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Connection name", Required = true };
|
||||
var connStrOption = new Option<string>("--connection-string") { Description = "Connection string", Required = true };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update a database connection" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(connStrOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var connStr = result.GetValue(connStrOption)!;
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateDatabaseConnectionDefCommand(id, name, connStr));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDelete(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Database connection ID", Required = true };
|
||||
var cmd = new Command("delete") { Description = "Delete a database connection" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new DeleteDatabaseConnectionDefCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,56 @@ public static class ExternalSystemCommands
|
||||
var command = new Command("external-system") { Description = "Manage external systems" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "External system ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get an external system by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetExternalSystemCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "External system ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "System name", Required = true };
|
||||
var urlOption = new Option<string>("--endpoint-url") { Description = "Endpoint URL", Required = true };
|
||||
var authTypeOption = new Option<string>("--auth-type") { Description = "Auth type", Required = true };
|
||||
var authConfigOption = new Option<string?>("--auth-config") { Description = "Auth configuration JSON" };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update an external system" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(urlOption);
|
||||
cmd.Add(authTypeOption);
|
||||
cmd.Add(authConfigOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
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 UpdateExternalSystemCommand(id, name, url, authType, authConfig));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all external systems" };
|
||||
|
||||
@@ -12,6 +12,8 @@ public static class HealthCommands
|
||||
|
||||
command.Add(BuildSummary(contactPointsOption, formatOption));
|
||||
command.Add(BuildSite(contactPointsOption, formatOption));
|
||||
command.Add(BuildEventLog(contactPointsOption, formatOption));
|
||||
command.Add(BuildParkedMessages(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -40,4 +42,67 @@ public static class HealthCommands
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildEventLog(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var siteOption = new Option<string>("--site") { Description = "Site identifier", Required = true };
|
||||
var eventTypeOption = new Option<string?>("--event-type") { Description = "Filter by event type" };
|
||||
var severityOption = new Option<string?>("--severity") { Description = "Filter by severity" };
|
||||
var keywordOption = new Option<string?>("--keyword") { Description = "Keyword search" };
|
||||
var fromOption = new Option<DateTimeOffset?>("--from") { Description = "Start date (ISO 8601)" };
|
||||
var toOption = new Option<DateTimeOffset?>("--to") { Description = "End date (ISO 8601)" };
|
||||
var pageOption = new Option<int>("--page") { Description = "Page number" };
|
||||
pageOption.DefaultValueFactory = _ => 1;
|
||||
var pageSizeOption = new Option<int>("--page-size") { Description = "Page size" };
|
||||
pageSizeOption.DefaultValueFactory = _ => 50;
|
||||
|
||||
var cmd = new Command("event-log") { Description = "Query site event logs" };
|
||||
cmd.Add(siteOption);
|
||||
cmd.Add(eventTypeOption);
|
||||
cmd.Add(severityOption);
|
||||
cmd.Add(keywordOption);
|
||||
cmd.Add(fromOption);
|
||||
cmd.Add(toOption);
|
||||
cmd.Add(pageOption);
|
||||
cmd.Add(pageSizeOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new QueryEventLogsCommand(
|
||||
result.GetValue(siteOption)!,
|
||||
result.GetValue(eventTypeOption),
|
||||
result.GetValue(severityOption),
|
||||
result.GetValue(keywordOption),
|
||||
result.GetValue(fromOption),
|
||||
result.GetValue(toOption),
|
||||
result.GetValue(pageOption),
|
||||
result.GetValue(pageSizeOption)));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildParkedMessages(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var siteOption = new Option<string>("--site") { Description = "Site identifier", Required = true };
|
||||
var pageOption = new Option<int>("--page") { Description = "Page number" };
|
||||
pageOption.DefaultValueFactory = _ => 1;
|
||||
var pageSizeOption = new Option<int>("--page-size") { Description = "Page size" };
|
||||
pageSizeOption.DefaultValueFactory = _ => 50;
|
||||
|
||||
var cmd = new Command("parked-messages") { Description = "Query parked messages at a site" };
|
||||
cmd.Add(siteOption);
|
||||
cmd.Add(pageOption);
|
||||
cmd.Add(pageSizeOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new QueryParkedMessagesCommand(
|
||||
result.GetValue(siteOption)!,
|
||||
result.GetValue(pageOption),
|
||||
result.GetValue(pageSizeOption)));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ public static class InstanceCommands
|
||||
var command = new Command("instance") { Description = "Manage instances" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildSetBindings(contactPointsOption, formatOption));
|
||||
command.Add(BuildDeploy(contactPointsOption, formatOption));
|
||||
command.Add(BuildEnable(contactPointsOption, formatOption));
|
||||
command.Add(BuildDisable(contactPointsOption, formatOption));
|
||||
@@ -20,6 +22,43 @@ public static class InstanceCommands
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get an instance by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildSetBindings(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var bindingsOption = new Option<string>("--bindings") { Description = "JSON array of [attributeName, dataConnectionId] pairs", Required = true };
|
||||
|
||||
var cmd = new Command("set-bindings") { Description = "Set data connection bindings for an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(bindingsOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var bindingsJson = result.GetValue(bindingsOption)!;
|
||||
var pairs = System.Text.Json.JsonSerializer.Deserialize<List<List<object>>>(bindingsJson)
|
||||
?? throw new InvalidOperationException("Invalid bindings JSON");
|
||||
var bindings = pairs.Select(p =>
|
||||
(p[0].ToString()!, int.Parse(p[1].ToString()!))).ToList();
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new SetConnectionBindingsCommand(id, bindings));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var siteIdOption = new Option<int?>("--site-id") { Description = "Filter by site ID" };
|
||||
|
||||
@@ -11,12 +11,91 @@ public static class NotificationCommands
|
||||
var command = new Command("notification") { Description = "Manage notification lists" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
command.Add(BuildSmtp(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Notification list ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get a notification list by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetNotificationListCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Notification list ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "List name", Required = true };
|
||||
var emailsOption = new Option<string>("--emails") { Description = "Comma-separated recipient emails", Required = true };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update a notification list" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(emailsOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
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 UpdateNotificationListCommand(id, name, emails));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildSmtp(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("smtp") { Description = "Manage SMTP configuration" };
|
||||
|
||||
var listCmd = new Command("list") { Description = "List SMTP configurations" };
|
||||
listCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListSmtpConfigsCommand());
|
||||
});
|
||||
group.Add(listCmd);
|
||||
|
||||
var idOption = new Option<int>("--id") { Description = "SMTP config ID", Required = true };
|
||||
var serverOption = new Option<string>("--server") { Description = "SMTP server", Required = true };
|
||||
var portOption = new Option<int>("--port") { Description = "SMTP port", Required = true };
|
||||
var authModeOption = new Option<string>("--auth-mode") { Description = "Auth mode", Required = true };
|
||||
var fromOption = new Option<string>("--from-address") { Description = "From email address", Required = true };
|
||||
var updateCmd = new Command("update") { Description = "Update SMTP configuration" };
|
||||
updateCmd.Add(idOption);
|
||||
updateCmd.Add(serverOption);
|
||||
updateCmd.Add(portOption);
|
||||
updateCmd.Add(authModeOption);
|
||||
updateCmd.Add(fromOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var server = result.GetValue(serverOption)!;
|
||||
var port = result.GetValue(portOption);
|
||||
var authMode = result.GetValue(authModeOption)!;
|
||||
var from = result.GetValue(fromOption)!;
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateSmtpConfigCommand(id, server, port, authMode, from));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all notification lists" };
|
||||
|
||||
@@ -12,6 +12,7 @@ public static class SecurityCommands
|
||||
|
||||
command.Add(BuildApiKey(contactPointsOption, formatOption));
|
||||
command.Add(BuildRoleMapping(contactPointsOption, formatOption));
|
||||
command.Add(BuildScopeRule(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -50,6 +51,20 @@ public static class SecurityCommands
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "API key ID", Required = true };
|
||||
var enabledOption = new Option<bool>("--enabled") { Description = "Enable or disable", Required = true };
|
||||
var updateCmd = new Command("update") { Description = "Enable or disable an API key" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(enabledOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(updateIdOption);
|
||||
var enabled = result.GetValue(enabledOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new UpdateApiKeyCommand(id, enabled));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -91,6 +106,67 @@ public static class SecurityCommands
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Mapping ID", Required = true };
|
||||
var updateLdapGroupOption = new Option<string>("--ldap-group") { Description = "LDAP group name", Required = true };
|
||||
var updateRoleOption = new Option<string>("--role") { Description = "Role name", Required = true };
|
||||
var updateCmd = new Command("update") { Description = "Update a role mapping" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(updateLdapGroupOption);
|
||||
updateCmd.Add(updateRoleOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(updateIdOption);
|
||||
var ldapGroup = result.GetValue(updateLdapGroupOption)!;
|
||||
var role = result.GetValue(updateRoleOption)!;
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateRoleMappingCommand(id, ldapGroup, role));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildScopeRule(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("scope-rule") { Description = "Manage LDAP scope rules" };
|
||||
|
||||
var mappingIdOption = new Option<int>("--mapping-id") { Description = "Role mapping ID", Required = true };
|
||||
var listCmd = new Command("list") { Description = "List scope rules for a mapping" };
|
||||
listCmd.Add(mappingIdOption);
|
||||
listCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var mappingId = result.GetValue(mappingIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListScopeRulesCommand(mappingId));
|
||||
});
|
||||
group.Add(listCmd);
|
||||
|
||||
var addMappingIdOption = new Option<int>("--mapping-id") { Description = "Role mapping ID", Required = true };
|
||||
var siteIdOption = new Option<int>("--site-id") { Description = "Site ID", Required = true };
|
||||
var addCmd = new Command("add") { Description = "Add a scope rule" };
|
||||
addCmd.Add(addMappingIdOption);
|
||||
addCmd.Add(siteIdOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var mappingId = result.GetValue(addMappingIdOption);
|
||||
var siteId = result.GetValue(siteIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new AddScopeRuleCommand(mappingId, siteId));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Scope rule ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete a scope rule" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(deleteIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new DeleteScopeRuleCommand(id));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
113
src/ScadaLink.CLI/Commands/SharedScriptCommands.cs
Normal file
113
src/ScadaLink.CLI/Commands/SharedScriptCommands.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
|
||||
namespace ScadaLink.CLI.Commands;
|
||||
|
||||
public static class SharedScriptCommands
|
||||
{
|
||||
public static Command Build(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var command = new Command("shared-script") { Description = "Manage shared scripts" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all shared scripts" };
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListSharedScriptsCommand());
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Shared script ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get a shared script by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetSharedScriptCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildCreate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var nameOption = new Option<string>("--name") { Description = "Script name", Required = true };
|
||||
var codeOption = new Option<string>("--code") { Description = "Script code", Required = true };
|
||||
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
|
||||
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
|
||||
|
||||
var cmd = new Command("create") { Description = "Create a shared script" };
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(codeOption);
|
||||
cmd.Add(parametersOption);
|
||||
cmd.Add(returnDefOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var code = result.GetValue(codeOption)!;
|
||||
var parameters = result.GetValue(parametersOption);
|
||||
var returnDef = result.GetValue(returnDefOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new CreateSharedScriptCommand(name, code, parameters, returnDef));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Shared script ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Script name", Required = true };
|
||||
var codeOption = new Option<string>("--code") { Description = "Script code", Required = true };
|
||||
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
|
||||
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update a shared script" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(codeOption);
|
||||
cmd.Add(parametersOption);
|
||||
cmd.Add(returnDefOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var code = result.GetValue(codeOption)!;
|
||||
var parameters = result.GetValue(parametersOption);
|
||||
var returnDef = result.GetValue(returnDefOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateSharedScriptCommand(id, name, code, parameters, returnDef));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDelete(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Shared script ID", Required = true };
|
||||
var cmd = new Command("delete") { Description = "Delete a shared script" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new DeleteSharedScriptCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,30 @@ public static class SiteCommands
|
||||
var command = new Command("site") { Description = "Manage sites" };
|
||||
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
command.Add(BuildDeployArtifacts(contactPointsOption, formatOption));
|
||||
command.Add(BuildArea(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Site ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get a site by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new GetSiteCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var cmd = new Command("list") { Description = "List all sites" };
|
||||
@@ -100,6 +116,67 @@ public static class SiteCommands
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildArea(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("area") { Description = "Manage areas" };
|
||||
|
||||
var siteIdOption = new Option<int>("--site-id") { Description = "Site ID", Required = true };
|
||||
var listCmd = new Command("list") { Description = "List areas for a site" };
|
||||
listCmd.Add(siteIdOption);
|
||||
listCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var siteId = result.GetValue(siteIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ListAreasCommand(siteId));
|
||||
});
|
||||
group.Add(listCmd);
|
||||
|
||||
var createSiteIdOption = new Option<int>("--site-id") { Description = "Site ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Area name", Required = true };
|
||||
var parentOption = new Option<int?>("--parent-id") { Description = "Parent area ID" };
|
||||
var createCmd = new Command("create") { Description = "Create an area" };
|
||||
createCmd.Add(createSiteIdOption);
|
||||
createCmd.Add(nameOption);
|
||||
createCmd.Add(parentOption);
|
||||
createCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var siteId = result.GetValue(createSiteIdOption);
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var parentId = result.GetValue(parentOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new CreateAreaCommand(siteId, name, parentId));
|
||||
});
|
||||
group.Add(createCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Area ID", Required = true };
|
||||
var updateNameOption = new Option<string>("--name") { Description = "New area name", Required = true };
|
||||
var updateCmd = new Command("update") { Description = "Update an area" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(updateNameOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(updateIdOption);
|
||||
var name = result.GetValue(updateNameOption)!;
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new UpdateAreaCommand(id, name));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Area ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete an area" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(deleteIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new DeleteAreaCommand(id));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildDeployArtifacts(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var siteIdOption = new Option<int?>("--site-id") { Description = "Target site ID (all sites if omitted)" };
|
||||
|
||||
@@ -13,7 +13,13 @@ public static class TemplateCommands
|
||||
command.Add(BuildList(contactPointsOption, formatOption));
|
||||
command.Add(BuildGet(contactPointsOption, formatOption));
|
||||
command.Add(BuildCreate(contactPointsOption, formatOption));
|
||||
command.Add(BuildUpdate(contactPointsOption, formatOption));
|
||||
command.Add(BuildValidate(contactPointsOption, formatOption));
|
||||
command.Add(BuildDelete(contactPointsOption, formatOption));
|
||||
command.Add(BuildAttribute(contactPointsOption, formatOption));
|
||||
command.Add(BuildAlarm(contactPointsOption, formatOption));
|
||||
command.Add(BuildScript(contactPointsOption, formatOption));
|
||||
command.Add(BuildComposition(contactPointsOption, formatOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -65,6 +71,45 @@ public static class TemplateCommands
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildUpdate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Template ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Template name", Required = true };
|
||||
var descOption = new Option<string?>("--description") { Description = "Template description" };
|
||||
var parentOption = new Option<int?>("--parent-id") { Description = "Parent template ID" };
|
||||
|
||||
var cmd = new Command("update") { Description = "Update a template" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(descOption);
|
||||
cmd.Add(parentOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var desc = result.GetValue(descOption);
|
||||
var parentId = result.GetValue(parentOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateTemplateCommand(id, name, desc, parentId));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildValidate(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Template ID", Required = true };
|
||||
var cmd = new Command("validate") { Description = "Validate a template" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption, new ValidateTemplateCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDelete(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Template ID", Required = true };
|
||||
@@ -78,4 +123,281 @@ public static class TemplateCommands
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildAttribute(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("attribute") { Description = "Manage template attributes" };
|
||||
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Attribute name", Required = true };
|
||||
var dataTypeOption = new Option<string>("--data-type") { Description = "Data type", Required = true };
|
||||
var valueOption = new Option<string?>("--value") { Description = "Default value" };
|
||||
var descOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var sourceOption = new Option<string?>("--data-source") { Description = "Data source reference" };
|
||||
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
lockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var addCmd = new Command("add") { Description = "Add an attribute to a template" };
|
||||
addCmd.Add(templateIdOption);
|
||||
addCmd.Add(nameOption);
|
||||
addCmd.Add(dataTypeOption);
|
||||
addCmd.Add(valueOption);
|
||||
addCmd.Add(descOption);
|
||||
addCmd.Add(sourceOption);
|
||||
addCmd.Add(lockedOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new AddTemplateAttributeCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(nameOption)!,
|
||||
result.GetValue(dataTypeOption)!,
|
||||
result.GetValue(valueOption),
|
||||
result.GetValue(descOption),
|
||||
result.GetValue(sourceOption),
|
||||
result.GetValue(lockedOption)));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Attribute ID", Required = true };
|
||||
var updateNameOption = new Option<string>("--name") { Description = "Attribute name", Required = true };
|
||||
var updateDataTypeOption = new Option<string>("--data-type") { Description = "Data type", Required = true };
|
||||
var updateValueOption = new Option<string?>("--value") { Description = "Default value" };
|
||||
var updateDescOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var updateSourceOption = new Option<string?>("--data-source") { Description = "Data source reference" };
|
||||
var updateLockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
updateLockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var updateCmd = new Command("update") { Description = "Update a template attribute" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(updateNameOption);
|
||||
updateCmd.Add(updateDataTypeOption);
|
||||
updateCmd.Add(updateValueOption);
|
||||
updateCmd.Add(updateDescOption);
|
||||
updateCmd.Add(updateSourceOption);
|
||||
updateCmd.Add(updateLockedOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateTemplateAttributeCommand(
|
||||
result.GetValue(updateIdOption),
|
||||
result.GetValue(updateNameOption)!,
|
||||
result.GetValue(updateDataTypeOption)!,
|
||||
result.GetValue(updateValueOption),
|
||||
result.GetValue(updateDescOption),
|
||||
result.GetValue(updateSourceOption),
|
||||
result.GetValue(updateLockedOption)));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Attribute ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete a template attribute" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new DeleteTemplateAttributeCommand(result.GetValue(deleteIdOption)));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildAlarm(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("alarm") { Description = "Manage template alarms" };
|
||||
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Alarm name", Required = true };
|
||||
var triggerTypeOption = new Option<string>("--trigger-type") { Description = "Trigger type", Required = true };
|
||||
var priorityOption = new Option<int>("--priority") { Description = "Alarm priority", Required = true };
|
||||
var descOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var triggerConfigOption = new Option<string?>("--trigger-config") { Description = "Trigger configuration JSON" };
|
||||
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
lockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var addCmd = new Command("add") { Description = "Add an alarm to a template" };
|
||||
addCmd.Add(templateIdOption);
|
||||
addCmd.Add(nameOption);
|
||||
addCmd.Add(triggerTypeOption);
|
||||
addCmd.Add(priorityOption);
|
||||
addCmd.Add(descOption);
|
||||
addCmd.Add(triggerConfigOption);
|
||||
addCmd.Add(lockedOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new AddTemplateAlarmCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(nameOption)!,
|
||||
result.GetValue(triggerTypeOption)!,
|
||||
result.GetValue(priorityOption)!,
|
||||
result.GetValue(descOption),
|
||||
result.GetValue(triggerConfigOption),
|
||||
result.GetValue(lockedOption)));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Alarm ID", Required = true };
|
||||
var updateNameOption = new Option<string>("--name") { Description = "Alarm name", Required = true };
|
||||
var updateTriggerTypeOption = new Option<string>("--trigger-type") { Description = "Trigger type", Required = true };
|
||||
var updatePriorityOption = new Option<int>("--priority") { Description = "Alarm priority", Required = true };
|
||||
var updateDescOption = new Option<string?>("--description") { Description = "Description" };
|
||||
var updateTriggerConfigOption = new Option<string?>("--trigger-config") { Description = "Trigger configuration JSON" };
|
||||
var updateLockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
updateLockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var updateCmd = new Command("update") { Description = "Update a template alarm" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(updateNameOption);
|
||||
updateCmd.Add(updateTriggerTypeOption);
|
||||
updateCmd.Add(updatePriorityOption);
|
||||
updateCmd.Add(updateDescOption);
|
||||
updateCmd.Add(updateTriggerConfigOption);
|
||||
updateCmd.Add(updateLockedOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateTemplateAlarmCommand(
|
||||
result.GetValue(updateIdOption),
|
||||
result.GetValue(updateNameOption)!,
|
||||
result.GetValue(updateTriggerTypeOption)!,
|
||||
result.GetValue(updatePriorityOption)!,
|
||||
result.GetValue(updateDescOption),
|
||||
result.GetValue(updateTriggerConfigOption),
|
||||
result.GetValue(updateLockedOption)));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Alarm ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete a template alarm" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new DeleteTemplateAlarmCommand(result.GetValue(deleteIdOption)));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildScript(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("script") { Description = "Manage template scripts" };
|
||||
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var nameOption = new Option<string>("--name") { Description = "Script name", Required = true };
|
||||
var codeOption = new Option<string>("--code") { Description = "Script code", Required = true };
|
||||
var triggerTypeOption = new Option<string>("--trigger-type") { Description = "Trigger type", Required = true };
|
||||
var triggerConfigOption = new Option<string?>("--trigger-config") { Description = "Trigger configuration JSON" };
|
||||
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
lockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var addCmd = new Command("add") { Description = "Add a script to a template" };
|
||||
addCmd.Add(templateIdOption);
|
||||
addCmd.Add(nameOption);
|
||||
addCmd.Add(codeOption);
|
||||
addCmd.Add(triggerTypeOption);
|
||||
addCmd.Add(triggerConfigOption);
|
||||
addCmd.Add(lockedOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new AddTemplateScriptCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(nameOption)!,
|
||||
result.GetValue(codeOption)!,
|
||||
result.GetValue(triggerTypeOption)!,
|
||||
result.GetValue(triggerConfigOption),
|
||||
result.GetValue(lockedOption)));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var updateIdOption = new Option<int>("--id") { Description = "Script ID", Required = true };
|
||||
var updateNameOption = new Option<string>("--name") { Description = "Script name", Required = true };
|
||||
var updateCodeOption = new Option<string>("--code") { Description = "Script code", Required = true };
|
||||
var updateTriggerTypeOption = new Option<string>("--trigger-type") { Description = "Trigger type", Required = true };
|
||||
var updateTriggerConfigOption = new Option<string?>("--trigger-config") { Description = "Trigger configuration JSON" };
|
||||
var updateLockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
updateLockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
var updateCmd = new Command("update") { Description = "Update a template script" };
|
||||
updateCmd.Add(updateIdOption);
|
||||
updateCmd.Add(updateNameOption);
|
||||
updateCmd.Add(updateCodeOption);
|
||||
updateCmd.Add(updateTriggerTypeOption);
|
||||
updateCmd.Add(updateTriggerConfigOption);
|
||||
updateCmd.Add(updateLockedOption);
|
||||
updateCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new UpdateTemplateScriptCommand(
|
||||
result.GetValue(updateIdOption),
|
||||
result.GetValue(updateNameOption)!,
|
||||
result.GetValue(updateCodeOption)!,
|
||||
result.GetValue(updateTriggerTypeOption)!,
|
||||
result.GetValue(updateTriggerConfigOption),
|
||||
result.GetValue(updateLockedOption)));
|
||||
});
|
||||
group.Add(updateCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Script ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete a template script" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new DeleteTemplateScriptCommand(result.GetValue(deleteIdOption)));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildComposition(Option<string> contactPointsOption, Option<string> formatOption)
|
||||
{
|
||||
var group = new Command("composition") { Description = "Manage template compositions" };
|
||||
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var instanceNameOption = new Option<string>("--instance-name") { Description = "Composed instance name", Required = true };
|
||||
var composedTemplateIdOption = new Option<int>("--composed-template-id") { Description = "Composed template ID", Required = true };
|
||||
|
||||
var addCmd = new Command("add") { Description = "Add a composition to a template" };
|
||||
addCmd.Add(templateIdOption);
|
||||
addCmd.Add(instanceNameOption);
|
||||
addCmd.Add(composedTemplateIdOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new AddTemplateCompositionCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(instanceNameOption)!,
|
||||
result.GetValue(composedTemplateIdOption)));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
var deleteIdOption = new Option<int>("--id") { Description = "Composition ID", Required = true };
|
||||
var deleteCmd = new Command("delete") { Description = "Delete a template composition" };
|
||||
deleteCmd.Add(deleteIdOption);
|
||||
deleteCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, contactPointsOption, formatOption,
|
||||
new DeleteTemplateCompositionCommand(result.GetValue(deleteIdOption)));
|
||||
});
|
||||
group.Add(deleteCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ 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.Add(SharedScriptCommands.Build(contactPointsOption, formatOption));
|
||||
rootCommand.Add(DbConnectionCommands.Build(contactPointsOption, formatOption));
|
||||
rootCommand.Add(ApiMethodCommands.Build(contactPointsOption, formatOption));
|
||||
|
||||
rootCommand.SetAction(_ =>
|
||||
{
|
||||
|
||||
@@ -119,6 +119,21 @@ scadalink --contact-points <uri> template create --name <string> [--description
|
||||
| `--description` | no | Template description |
|
||||
| `--parent-id` | no | Parent template ID for inheritance |
|
||||
|
||||
#### `template update`
|
||||
|
||||
Update an existing template's name, description, or parent.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template update --id <int> [--name <string>] [--description <string>] [--parent-id <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Template ID |
|
||||
| `--name` | no | Updated template name |
|
||||
| `--description` | no | Updated description |
|
||||
| `--parent-id` | no | Updated parent template ID |
|
||||
|
||||
#### `template delete`
|
||||
|
||||
Delete a template by ID.
|
||||
@@ -131,10 +146,201 @@ scadalink --contact-points <uri> template delete --id <int>
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Template ID |
|
||||
|
||||
#### `template validate`
|
||||
|
||||
Run pre-deployment validation on a template (flattening, naming collisions, script compilation).
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template validate --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Template ID |
|
||||
|
||||
#### `template attribute add`
|
||||
|
||||
Add an attribute to a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template attribute add --template-id <int> --name <string> --data-type <string> [--default-value <string>] [--tag-path <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Attribute name |
|
||||
| `--data-type` | yes | Attribute data type (e.g. `Float`, `Int`, `String`, `Bool`) |
|
||||
| `--default-value` | no | Default value |
|
||||
| `--tag-path` | no | Data connection tag path |
|
||||
|
||||
#### `template attribute update`
|
||||
|
||||
Update an attribute on a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template attribute update --template-id <int> --name <string> [--data-type <string>] [--default-value <string>] [--tag-path <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Attribute name to update |
|
||||
| `--data-type` | no | Updated data type |
|
||||
| `--default-value` | no | Updated default value |
|
||||
| `--tag-path` | no | Updated tag path |
|
||||
|
||||
#### `template attribute delete`
|
||||
|
||||
Remove an attribute from a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template attribute delete --template-id <int> --name <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Attribute name to delete |
|
||||
|
||||
#### `template alarm add`
|
||||
|
||||
Add an alarm definition to a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template alarm add --template-id <int> --name <string> --trigger-attribute <string> --condition <string> --setpoint <string> [--severity <string>] [--notification-list <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Alarm name |
|
||||
| `--trigger-attribute` | yes | Attribute that triggers the alarm |
|
||||
| `--condition` | yes | Trigger condition (e.g. `GreaterThan`, `LessThan`, `Equal`) |
|
||||
| `--setpoint` | yes | Setpoint value |
|
||||
| `--severity` | no | Alarm severity (default: `Warning`) |
|
||||
| `--notification-list` | no | Notification list name to notify on alarm |
|
||||
|
||||
#### `template alarm update`
|
||||
|
||||
Update an alarm definition on a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template alarm update --template-id <int> --name <string> [--condition <string>] [--setpoint <string>] [--severity <string>] [--notification-list <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Alarm name to update |
|
||||
| `--condition` | no | Updated trigger condition |
|
||||
| `--setpoint` | no | Updated setpoint value |
|
||||
| `--severity` | no | Updated severity |
|
||||
| `--notification-list` | no | Updated notification list name |
|
||||
|
||||
#### `template alarm delete`
|
||||
|
||||
Remove an alarm definition from a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template alarm delete --template-id <int> --name <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Alarm name to delete |
|
||||
|
||||
#### `template script add`
|
||||
|
||||
Add a script to a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template script add --template-id <int> --name <string> --trigger-type <string> [--trigger-attribute <string>] [--interval <int>] --code <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Script name |
|
||||
| `--trigger-type` | yes | Trigger type: `OnChange`, `Periodic`, `OnAlarm` |
|
||||
| `--trigger-attribute` | no | Attribute name for `OnChange` trigger |
|
||||
| `--interval` | no | Interval in milliseconds for `Periodic` trigger |
|
||||
| `--code` | yes | Script source code (or `@filepath` to read from file) |
|
||||
|
||||
#### `template script update`
|
||||
|
||||
Update a script on a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template script update --template-id <int> --name <string> [--trigger-type <string>] [--trigger-attribute <string>] [--interval <int>] [--code <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Script name to update |
|
||||
| `--trigger-type` | no | Updated trigger type |
|
||||
| `--trigger-attribute` | no | Updated trigger attribute |
|
||||
| `--interval` | no | Updated interval |
|
||||
| `--code` | no | Updated script source code (or `@filepath`) |
|
||||
|
||||
#### `template script delete`
|
||||
|
||||
Remove a script from a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template script delete --template-id <int> --name <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Template ID |
|
||||
| `--name` | yes | Script name to delete |
|
||||
|
||||
#### `template composition add`
|
||||
|
||||
Add a feature module composition to a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template composition add --template-id <int> --module-template-id <int> --instance-name <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Target template ID |
|
||||
| `--module-template-id` | yes | Feature module template ID to compose |
|
||||
| `--instance-name` | yes | Instance name for the composed module (used in path-qualified addressing) |
|
||||
|
||||
#### `template composition delete`
|
||||
|
||||
Remove a feature module composition from a template.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> template composition delete --template-id <int> --instance-name <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--template-id` | yes | Target template ID |
|
||||
| `--instance-name` | yes | Instance name of the composed module to remove |
|
||||
|
||||
---
|
||||
|
||||
### `instance` — Manage instances
|
||||
|
||||
#### `instance get`
|
||||
|
||||
Get a single instance by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> instance get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Instance ID |
|
||||
|
||||
#### `instance list`
|
||||
|
||||
List instances, with optional filters.
|
||||
@@ -212,10 +418,35 @@ scadalink --contact-points <uri> instance delete --id <int>
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Instance ID |
|
||||
|
||||
#### `instance set-bindings`
|
||||
|
||||
Set data connection bindings for an instance's attributes.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> instance set-bindings --id <int> --bindings <json>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Instance ID |
|
||||
| `--bindings` | yes | JSON string mapping attribute names to data connection IDs (e.g. `{"attr1": 1, "attr2": 2}`) |
|
||||
|
||||
---
|
||||
|
||||
### `site` — Manage sites
|
||||
|
||||
#### `site get`
|
||||
|
||||
Get a single site by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> site get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Site ID |
|
||||
|
||||
#### `site list`
|
||||
|
||||
List all registered sites.
|
||||
@@ -262,6 +493,58 @@ scadalink --contact-points <uri> site deploy-artifacts [--site-id <int>]
|
||||
|--------|----------|-------------|
|
||||
| `--site-id` | no | Target site ID; omit to deploy to all sites |
|
||||
|
||||
#### `site area list`
|
||||
|
||||
List all areas for a site.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> site area list --site-id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--site-id` | yes | Site ID |
|
||||
|
||||
#### `site area create`
|
||||
|
||||
Create an area within a site.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> site area create --site-id <int> --name <string> [--parent-area-id <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--site-id` | yes | Site ID |
|
||||
| `--name` | yes | Area name |
|
||||
| `--parent-area-id` | no | Parent area ID for nested areas |
|
||||
|
||||
#### `site area update`
|
||||
|
||||
Update an area's name or parent.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> site area update --id <int> [--name <string>] [--parent-area-id <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Area ID |
|
||||
| `--name` | no | Updated area name |
|
||||
| `--parent-area-id` | no | Updated parent area ID |
|
||||
|
||||
#### `site area delete`
|
||||
|
||||
Delete an area. Fails if any instances are assigned to it.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> site area delete --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Area ID |
|
||||
|
||||
---
|
||||
|
||||
### `deploy` — Deployment operations
|
||||
@@ -309,6 +592,18 @@ scadalink --contact-points <uri> deploy status [--instance-id <int>] [--status <
|
||||
|
||||
### `data-connection` — Manage data connections
|
||||
|
||||
#### `data-connection get`
|
||||
|
||||
Get a single data connection by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> data-connection get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Data connection ID |
|
||||
|
||||
#### `data-connection list`
|
||||
|
||||
List all configured data connections.
|
||||
@@ -331,6 +626,21 @@ scadalink --contact-points <uri> data-connection create --name <string> --protoc
|
||||
| `--protocol` | yes | Protocol identifier (e.g. `OpcUa`) |
|
||||
| `--configuration` | no | Protocol-specific configuration as a JSON string |
|
||||
|
||||
#### `data-connection update`
|
||||
|
||||
Update a data connection definition.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> data-connection update --id <int> [--name <string>] [--protocol <string>] [--configuration <json>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Data connection ID |
|
||||
| `--name` | no | Updated connection name |
|
||||
| `--protocol` | no | Updated protocol identifier |
|
||||
| `--configuration` | no | Updated protocol-specific configuration as a JSON string |
|
||||
|
||||
#### `data-connection delete`
|
||||
|
||||
Delete a data connection.
|
||||
@@ -356,10 +666,35 @@ scadalink --contact-points <uri> data-connection assign --connection-id <int> --
|
||||
| `--connection-id` | yes | Data connection ID |
|
||||
| `--site-id` | yes | Site ID |
|
||||
|
||||
#### `data-connection unassign`
|
||||
|
||||
Remove a data connection assignment from a site.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> data-connection unassign --connection-id <int> --site-id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--connection-id` | yes | Data connection ID |
|
||||
| `--site-id` | yes | Site ID |
|
||||
|
||||
---
|
||||
|
||||
### `external-system` — Manage external HTTP systems
|
||||
|
||||
#### `external-system get`
|
||||
|
||||
Get a single external system definition by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> external-system get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | External system ID |
|
||||
|
||||
#### `external-system list`
|
||||
|
||||
List all external system definitions.
|
||||
@@ -383,6 +718,22 @@ scadalink --contact-points <uri> external-system create --name <string> --endpoi
|
||||
| `--auth-type` | yes | Authentication type: `ApiKey` or `BasicAuth` |
|
||||
| `--auth-config` | no | Auth credentials as a JSON string |
|
||||
|
||||
#### `external-system update`
|
||||
|
||||
Update an external system definition.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> external-system update --id <int> [--name <string>] [--endpoint-url <url>] [--auth-type <string>] [--auth-config <json>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | External system ID |
|
||||
| `--name` | no | Updated display name |
|
||||
| `--endpoint-url` | no | Updated base URL |
|
||||
| `--auth-type` | no | Updated authentication type |
|
||||
| `--auth-config` | no | Updated auth credentials as a JSON string |
|
||||
|
||||
#### `external-system delete`
|
||||
|
||||
Delete an external system definition.
|
||||
@@ -399,6 +750,18 @@ scadalink --contact-points <uri> external-system delete --id <int>
|
||||
|
||||
### `notification` — Manage notification lists
|
||||
|
||||
#### `notification get`
|
||||
|
||||
Get a single notification list by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> notification get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Notification list ID |
|
||||
|
||||
#### `notification list`
|
||||
|
||||
List all notification lists.
|
||||
@@ -420,6 +783,20 @@ scadalink --contact-points <uri> notification create --name <string> --emails <e
|
||||
| `--name` | yes | Notification list name |
|
||||
| `--emails` | yes | Comma-separated list of recipient email addresses |
|
||||
|
||||
#### `notification update`
|
||||
|
||||
Update a notification list's name or recipients.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> notification update --id <int> [--name <string>] [--emails <email1,email2,...>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Notification list ID |
|
||||
| `--name` | no | Updated list name |
|
||||
| `--emails` | no | Updated comma-separated list of recipient email addresses |
|
||||
|
||||
#### `notification delete`
|
||||
|
||||
Delete a notification list.
|
||||
@@ -432,6 +809,31 @@ scadalink --contact-points <uri> notification delete --id <int>
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Notification list ID |
|
||||
|
||||
#### `notification smtp list`
|
||||
|
||||
Show the current SMTP configuration.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> notification smtp list
|
||||
```
|
||||
|
||||
#### `notification smtp update`
|
||||
|
||||
Update the SMTP configuration.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> notification smtp update --host <string> --port <int> --auth-type <string> [--username <string>] [--password <string>] [--from-address <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--host` | yes | SMTP server hostname |
|
||||
| `--port` | yes | SMTP server port |
|
||||
| `--auth-type` | yes | Authentication type: `OAuth2` or `Basic` |
|
||||
| `--username` | no | SMTP username (for Basic auth) |
|
||||
| `--password` | no | SMTP password (for Basic auth) |
|
||||
| `--from-address` | no | Sender email address |
|
||||
|
||||
---
|
||||
|
||||
### `security` — Security settings
|
||||
@@ -456,6 +858,20 @@ scadalink --contact-points <uri> security api-key create --name <string>
|
||||
|--------|----------|-------------|
|
||||
| `--name` | yes | Descriptive label for the key |
|
||||
|
||||
#### `security api-key update`
|
||||
|
||||
Update an API key's name or enabled status.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> security api-key update --id <int> [--name <string>] [--enabled <bool>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | API key ID |
|
||||
| `--name` | no | Updated label |
|
||||
| `--enabled` | no | Enable or disable the key (`true` or `false`) |
|
||||
|
||||
#### `security api-key delete`
|
||||
|
||||
Revoke and delete an API key.
|
||||
@@ -489,6 +905,20 @@ scadalink --contact-points <uri> security role-mapping create --ldap-group <stri
|
||||
| `--ldap-group` | yes | LDAP group distinguished name or CN |
|
||||
| `--role` | yes | ScadaLink role: `Admin`, `Design`, or `Deployment` |
|
||||
|
||||
#### `security role-mapping update`
|
||||
|
||||
Update an LDAP role mapping.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> security role-mapping update --id <int> [--ldap-group <string>] [--role <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Mapping ID |
|
||||
| `--ldap-group` | no | Updated LDAP group distinguished name or CN |
|
||||
| `--role` | no | Updated ScadaLink role |
|
||||
|
||||
#### `security role-mapping delete`
|
||||
|
||||
Remove an LDAP role mapping.
|
||||
@@ -501,6 +931,43 @@ scadalink --contact-points <uri> security role-mapping delete --id <int>
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Mapping ID |
|
||||
|
||||
#### `security scope-rule list`
|
||||
|
||||
List all site scope rules for role mappings.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> security scope-rule list [--role-mapping-id <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--role-mapping-id` | no | Filter by role mapping ID |
|
||||
|
||||
#### `security scope-rule add`
|
||||
|
||||
Add a site scope rule to a role mapping, restricting it to a specific site.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> security scope-rule add --role-mapping-id <int> --site-id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--role-mapping-id` | yes | Role mapping ID |
|
||||
| `--site-id` | yes | Site ID to scope the mapping to |
|
||||
|
||||
#### `security scope-rule delete`
|
||||
|
||||
Remove a site scope rule from a role mapping.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> security scope-rule delete --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Scope rule ID |
|
||||
|
||||
---
|
||||
|
||||
### `health` — Health monitoring
|
||||
@@ -525,6 +992,37 @@ scadalink --contact-points <uri> health site --identifier <string>
|
||||
|--------|----------|-------------|
|
||||
| `--identifier` | yes | Site identifier (e.g. `site-a`) |
|
||||
|
||||
#### `health event-log`
|
||||
|
||||
Query the site event log for a specific site. Events are fetched remotely from the site's local SQLite store.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> health event-log --site-identifier <string> [--from <datetime>] [--to <datetime>] [--search <string>] [--page <int>] [--page-size <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Default | Description |
|
||||
|--------|----------|---------|-------------|
|
||||
| `--site-identifier` | yes | — | Site identifier |
|
||||
| `--from` | no | — | Start timestamp in ISO 8601 format |
|
||||
| `--to` | no | — | End timestamp in ISO 8601 format |
|
||||
| `--search` | no | — | Keyword search term |
|
||||
| `--page` | no | `1` | Page number |
|
||||
| `--page-size` | no | `50` | Results per page |
|
||||
|
||||
#### `health parked-messages`
|
||||
|
||||
Query parked (dead-letter) messages at a specific site.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> health parked-messages --site-identifier <string> [--page <int>] [--page-size <int>]
|
||||
```
|
||||
|
||||
| Option | Required | Default | Description |
|
||||
|--------|----------|---------|-------------|
|
||||
| `--site-identifier` | yes | — | Site identifier |
|
||||
| `--page` | no | `1` | Page number |
|
||||
| `--page-size` | no | `50` | Results per page |
|
||||
|
||||
---
|
||||
|
||||
### `audit-log` — Audit log queries
|
||||
@@ -549,6 +1047,199 @@ scadalink --contact-points <uri> audit-log query [options]
|
||||
|
||||
---
|
||||
|
||||
### `shared-script` — Manage shared scripts
|
||||
|
||||
#### `shared-script list`
|
||||
|
||||
List all shared script definitions.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> shared-script list
|
||||
```
|
||||
|
||||
#### `shared-script get`
|
||||
|
||||
Get a single shared script by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> shared-script get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Shared script ID |
|
||||
|
||||
#### `shared-script create`
|
||||
|
||||
Create a new shared script.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> shared-script create --name <string> --code <string>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--name` | yes | Shared script name |
|
||||
| `--code` | yes | Script source code (or `@filepath` to read from file) |
|
||||
|
||||
#### `shared-script update`
|
||||
|
||||
Update a shared script's name or code.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> shared-script update --id <int> [--name <string>] [--code <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Shared script ID |
|
||||
| `--name` | no | Updated script name |
|
||||
| `--code` | no | Updated script source code (or `@filepath`) |
|
||||
|
||||
#### `shared-script delete`
|
||||
|
||||
Delete a shared script.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> shared-script delete --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Shared script ID |
|
||||
|
||||
---
|
||||
|
||||
### `db-connection` — Manage database connections
|
||||
|
||||
#### `db-connection list`
|
||||
|
||||
List all database connection definitions.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> db-connection list
|
||||
```
|
||||
|
||||
#### `db-connection get`
|
||||
|
||||
Get a single database connection by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> db-connection get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Database connection ID |
|
||||
|
||||
#### `db-connection create`
|
||||
|
||||
Create a new database connection definition.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> db-connection create --name <string> --connection-string <string> [--provider <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--name` | yes | Connection name |
|
||||
| `--connection-string` | yes | Database connection string |
|
||||
| `--provider` | no | Database provider (default: `SqlServer`) |
|
||||
|
||||
#### `db-connection update`
|
||||
|
||||
Update a database connection definition.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> db-connection update --id <int> [--name <string>] [--connection-string <string>] [--provider <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Database connection ID |
|
||||
| `--name` | no | Updated connection name |
|
||||
| `--connection-string` | no | Updated connection string |
|
||||
| `--provider` | no | Updated database provider |
|
||||
|
||||
#### `db-connection delete`
|
||||
|
||||
Delete a database connection definition.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> db-connection delete --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | Database connection ID |
|
||||
|
||||
---
|
||||
|
||||
### `api-method` — Manage inbound API methods
|
||||
|
||||
#### `api-method list`
|
||||
|
||||
List all inbound API method definitions.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> api-method list
|
||||
```
|
||||
|
||||
#### `api-method get`
|
||||
|
||||
Get a single inbound API method by ID.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> api-method get --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | API method ID |
|
||||
|
||||
#### `api-method create`
|
||||
|
||||
Create a new inbound API method.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> api-method create --name <string> --code <string> [--description <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--name` | yes | Method name (used as the URL path segment in `POST /api/{methodName}`) |
|
||||
| `--code` | yes | Script source code implementing the method (or `@filepath` to read from file) |
|
||||
| `--description` | no | Method description |
|
||||
|
||||
#### `api-method update`
|
||||
|
||||
Update an inbound API method.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> api-method update --id <int> [--name <string>] [--code <string>] [--description <string>]
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | API method ID |
|
||||
| `--name` | no | Updated method name |
|
||||
| `--code` | no | Updated script source code (or `@filepath`) |
|
||||
| `--description` | no | Updated description |
|
||||
|
||||
#### `api-method delete`
|
||||
|
||||
Delete an inbound API method.
|
||||
|
||||
```sh
|
||||
scadalink --contact-points <uri> api-method delete --id <int>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--id` | yes | API method ID |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
The CLI connects to the Central cluster using Akka.NET's `ClusterClient`. It does not join the cluster — it contacts the `ClusterClientReceptionist` on one of the configured Central nodes and sends commands to the `ManagementActor` at path `/user/management`.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ScadaLink.Commons.Messages.Management;
|
||||
|
||||
public record ListDatabaseConnectionsCommand;
|
||||
public record GetDatabaseConnectionCommand(int DatabaseConnectionId);
|
||||
public record CreateDatabaseConnectionDefCommand(string Name, string ConnectionString);
|
||||
public record UpdateDatabaseConnectionDefCommand(int DatabaseConnectionId, string Name, string ConnectionString);
|
||||
public record DeleteDatabaseConnectionDefCommand(int DatabaseConnectionId);
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ScadaLink.Commons.Messages.Management;
|
||||
|
||||
public record ListApiMethodsCommand;
|
||||
public record GetApiMethodCommand(int ApiMethodId);
|
||||
public record CreateApiMethodCommand(string Name, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition);
|
||||
public record UpdateApiMethodCommand(int ApiMethodId, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition);
|
||||
public record DeleteApiMethodCommand(int ApiMethodId);
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ScadaLink.Commons.Messages.Management;
|
||||
|
||||
public record QueryEventLogsCommand(string SiteIdentifier, string? EventType = null, string? Severity = null, string? Keyword = null, DateTimeOffset? From = null, DateTimeOffset? To = null, int Page = 1, int PageSize = 50);
|
||||
public record QueryParkedMessagesCommand(string SiteIdentifier, int Page = 1, int PageSize = 50);
|
||||
@@ -7,3 +7,7 @@ public record ListRoleMappingsCommand;
|
||||
public record CreateRoleMappingCommand(string LdapGroupName, string Role);
|
||||
public record UpdateRoleMappingCommand(int MappingId, string LdapGroupName, string Role);
|
||||
public record DeleteRoleMappingCommand(int MappingId);
|
||||
public record UpdateApiKeyCommand(int ApiKeyId, bool IsEnabled);
|
||||
public record ListScopeRulesCommand(int MappingId);
|
||||
public record AddScopeRuleCommand(int MappingId, int SiteId);
|
||||
public record DeleteScopeRuleCommand(int ScopeRuleId);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ScadaLink.Commons.Messages.Management;
|
||||
|
||||
public record ListSharedScriptsCommand;
|
||||
public record GetSharedScriptCommand(int SharedScriptId);
|
||||
public record CreateSharedScriptCommand(string Name, string Code, string? ParameterDefinitions, string? ReturnDefinition);
|
||||
public record UpdateSharedScriptCommand(int SharedScriptId, string Name, string Code, string? ParameterDefinitions, string? ReturnDefinition);
|
||||
public record DeleteSharedScriptCommand(int SharedScriptId);
|
||||
@@ -8,3 +8,4 @@ public record DeleteSiteCommand(int SiteId);
|
||||
public record ListAreasCommand(int SiteId);
|
||||
public record CreateAreaCommand(int SiteId, string Name, int? ParentAreaId);
|
||||
public record DeleteAreaCommand(int AreaId);
|
||||
public record UpdateAreaCommand(int AreaId, string Name);
|
||||
|
||||
@@ -6,3 +6,16 @@ public record CreateTemplateCommand(string Name, string? Description, int? Paren
|
||||
public record UpdateTemplateCommand(int TemplateId, string Name, string? Description, int? ParentTemplateId);
|
||||
public record DeleteTemplateCommand(int TemplateId);
|
||||
public record ValidateTemplateCommand(int TemplateId);
|
||||
|
||||
// Template member operations
|
||||
public record AddTemplateAttributeCommand(int TemplateId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked);
|
||||
public record UpdateTemplateAttributeCommand(int AttributeId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked);
|
||||
public record DeleteTemplateAttributeCommand(int AttributeId);
|
||||
public record AddTemplateAlarmCommand(int TemplateId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
|
||||
public record UpdateTemplateAlarmCommand(int AlarmId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
|
||||
public record DeleteTemplateAlarmCommand(int AlarmId);
|
||||
public record AddTemplateScriptCommand(int TemplateId, string Name, string Code, string? TriggerType, string? TriggerConfiguration, bool IsLocked);
|
||||
public record UpdateTemplateScriptCommand(int ScriptId, string Name, string Code, string? TriggerType, string? TriggerConfiguration, bool IsLocked);
|
||||
public record DeleteTemplateScriptCommand(int ScriptId);
|
||||
public record AddTemplateCompositionCommand(int TemplateId, string InstanceName, int ComposedTemplateId);
|
||||
public record DeleteTemplateCompositionCommand(int CompositionId);
|
||||
|
||||
@@ -6,11 +6,14 @@ using Microsoft.Extensions.Logging;
|
||||
using ScadaLink.Commons.Entities.ExternalSystems;
|
||||
using ScadaLink.Commons.Entities.InboundApi;
|
||||
using ScadaLink.Commons.Entities.Instances;
|
||||
using ScadaLink.Commons.Entities.Scripts;
|
||||
using ScadaLink.Commons.Entities.Templates;
|
||||
using ScadaLink.Commons.Entities.Notifications;
|
||||
using ScadaLink.Commons.Entities.Security;
|
||||
using ScadaLink.Commons.Entities.Sites;
|
||||
using ScadaLink.Commons.Interfaces.Repositories;
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
using ScadaLink.Commons.Messages.RemoteQuery;
|
||||
using ScadaLink.DeploymentManager;
|
||||
using ScadaLink.HealthMonitoring;
|
||||
using ScadaLink.Communication;
|
||||
@@ -76,7 +79,9 @@ public class ManagementActor : ReceiveActor
|
||||
CreateSiteCommand or UpdateSiteCommand or DeleteSiteCommand
|
||||
or ListRoleMappingsCommand or CreateRoleMappingCommand
|
||||
or UpdateRoleMappingCommand or DeleteRoleMappingCommand
|
||||
or ListApiKeysCommand or CreateApiKeyCommand or DeleteApiKeyCommand => "Admin",
|
||||
or ListApiKeysCommand or CreateApiKeyCommand or DeleteApiKeyCommand
|
||||
or UpdateApiKeyCommand
|
||||
or ListScopeRulesCommand or AddScopeRuleCommand or DeleteScopeRuleCommand => "Admin",
|
||||
|
||||
// Design operations
|
||||
CreateAreaCommand or DeleteAreaCommand
|
||||
@@ -90,7 +95,15 @@ public class ManagementActor : ReceiveActor
|
||||
or CreateDataConnectionCommand or UpdateDataConnectionCommand
|
||||
or DeleteDataConnectionCommand
|
||||
or AssignDataConnectionToSiteCommand
|
||||
or UnassignDataConnectionFromSiteCommand => "Design",
|
||||
or UnassignDataConnectionFromSiteCommand
|
||||
or AddTemplateAttributeCommand or UpdateTemplateAttributeCommand or DeleteTemplateAttributeCommand
|
||||
or AddTemplateAlarmCommand or UpdateTemplateAlarmCommand or DeleteTemplateAlarmCommand
|
||||
or AddTemplateScriptCommand or UpdateTemplateScriptCommand or DeleteTemplateScriptCommand
|
||||
or AddTemplateCompositionCommand or DeleteTemplateCompositionCommand
|
||||
or CreateSharedScriptCommand or UpdateSharedScriptCommand or DeleteSharedScriptCommand
|
||||
or CreateDatabaseConnectionDefCommand or UpdateDatabaseConnectionDefCommand or DeleteDatabaseConnectionDefCommand
|
||||
or CreateApiMethodCommand or UpdateApiMethodCommand or DeleteApiMethodCommand
|
||||
or UpdateAreaCommand => "Design",
|
||||
|
||||
// Deployment operations
|
||||
CreateInstanceCommand or MgmtDeployInstanceCommand or MgmtEnableInstanceCommand
|
||||
@@ -114,6 +127,19 @@ public class ManagementActor : ReceiveActor
|
||||
DeleteTemplateCommand cmd => await HandleDeleteTemplate(sp, cmd, user),
|
||||
ValidateTemplateCommand cmd => await HandleValidateTemplate(sp, cmd),
|
||||
|
||||
// Template members
|
||||
AddTemplateAttributeCommand cmd => await HandleAddAttribute(sp, cmd, user),
|
||||
UpdateTemplateAttributeCommand cmd => await HandleUpdateAttribute(sp, cmd, user),
|
||||
DeleteTemplateAttributeCommand cmd => await HandleDeleteAttribute(sp, cmd, user),
|
||||
AddTemplateAlarmCommand cmd => await HandleAddAlarm(sp, cmd, user),
|
||||
UpdateTemplateAlarmCommand cmd => await HandleUpdateAlarm(sp, cmd, user),
|
||||
DeleteTemplateAlarmCommand cmd => await HandleDeleteAlarm(sp, cmd, user),
|
||||
AddTemplateScriptCommand cmd => await HandleAddScript(sp, cmd, user),
|
||||
UpdateTemplateScriptCommand cmd => await HandleUpdateScript(sp, cmd, user),
|
||||
DeleteTemplateScriptCommand cmd => await HandleDeleteScript(sp, cmd, user),
|
||||
AddTemplateCompositionCommand cmd => await HandleAddComposition(sp, cmd, user),
|
||||
DeleteTemplateCompositionCommand cmd => await HandleDeleteComposition(sp, cmd, user),
|
||||
|
||||
// Instances
|
||||
ListInstancesCommand cmd => await HandleListInstances(sp, cmd),
|
||||
GetInstanceCommand cmd => await HandleGetInstance(sp, cmd),
|
||||
@@ -133,6 +159,7 @@ public class ManagementActor : ReceiveActor
|
||||
ListAreasCommand cmd => await HandleListAreas(sp, cmd),
|
||||
CreateAreaCommand cmd => await HandleCreateArea(sp, cmd),
|
||||
DeleteAreaCommand cmd => await HandleDeleteArea(sp, cmd),
|
||||
UpdateAreaCommand cmd => await HandleUpdateArea(sp, cmd),
|
||||
|
||||
// Data Connections
|
||||
ListDataConnectionsCommand => await HandleListDataConnections(sp),
|
||||
@@ -159,6 +186,27 @@ public class ManagementActor : ReceiveActor
|
||||
ListSmtpConfigsCommand => await HandleListSmtpConfigs(sp),
|
||||
UpdateSmtpConfigCommand cmd => await HandleUpdateSmtpConfig(sp, cmd),
|
||||
|
||||
// Shared Scripts
|
||||
ListSharedScriptsCommand => await HandleListSharedScripts(sp),
|
||||
GetSharedScriptCommand cmd => await HandleGetSharedScript(sp, cmd),
|
||||
CreateSharedScriptCommand cmd => await HandleCreateSharedScript(sp, cmd, user),
|
||||
UpdateSharedScriptCommand cmd => await HandleUpdateSharedScript(sp, cmd, user),
|
||||
DeleteSharedScriptCommand cmd => await HandleDeleteSharedScript(sp, cmd, user),
|
||||
|
||||
// Database Connections (External System)
|
||||
ListDatabaseConnectionsCommand => await HandleListDatabaseConnections(sp),
|
||||
GetDatabaseConnectionCommand cmd => await HandleGetDatabaseConnection(sp, cmd),
|
||||
CreateDatabaseConnectionDefCommand cmd => await HandleCreateDatabaseConnection(sp, cmd),
|
||||
UpdateDatabaseConnectionDefCommand cmd => await HandleUpdateDatabaseConnection(sp, cmd),
|
||||
DeleteDatabaseConnectionDefCommand cmd => await HandleDeleteDatabaseConnection(sp, cmd),
|
||||
|
||||
// Inbound API Methods
|
||||
ListApiMethodsCommand => await HandleListApiMethods(sp),
|
||||
GetApiMethodCommand cmd => await HandleGetApiMethod(sp, cmd),
|
||||
CreateApiMethodCommand cmd => await HandleCreateApiMethod(sp, cmd),
|
||||
UpdateApiMethodCommand cmd => await HandleUpdateApiMethod(sp, cmd),
|
||||
DeleteApiMethodCommand cmd => await HandleDeleteApiMethod(sp, cmd),
|
||||
|
||||
// Security
|
||||
ListRoleMappingsCommand => await HandleListRoleMappings(sp),
|
||||
CreateRoleMappingCommand cmd => await HandleCreateRoleMapping(sp, cmd),
|
||||
@@ -167,6 +215,10 @@ public class ManagementActor : ReceiveActor
|
||||
ListApiKeysCommand => await HandleListApiKeys(sp),
|
||||
CreateApiKeyCommand cmd => await HandleCreateApiKey(sp, cmd),
|
||||
DeleteApiKeyCommand cmd => await HandleDeleteApiKey(sp, cmd),
|
||||
UpdateApiKeyCommand cmd => await HandleUpdateApiKey(sp, cmd),
|
||||
ListScopeRulesCommand cmd => await HandleListScopeRules(sp, cmd),
|
||||
AddScopeRuleCommand cmd => await HandleAddScopeRule(sp, cmd),
|
||||
DeleteScopeRuleCommand cmd => await HandleDeleteScopeRule(sp, cmd),
|
||||
|
||||
// Deployments
|
||||
MgmtDeployArtifactsCommand cmd => await HandleDeployArtifacts(sp, cmd, user),
|
||||
@@ -179,6 +231,10 @@ public class ManagementActor : ReceiveActor
|
||||
GetHealthSummaryCommand => HandleGetHealthSummary(sp),
|
||||
GetSiteHealthCommand cmd => HandleGetSiteHealth(sp, cmd),
|
||||
|
||||
// Remote Queries
|
||||
QueryEventLogsCommand cmd => await HandleQueryEventLogs(sp, cmd),
|
||||
QueryParkedMessagesCommand cmd => await HandleQueryParkedMessages(sp, cmd),
|
||||
|
||||
_ => throw new NotSupportedException($"Unknown command type: {command.GetType().Name}")
|
||||
};
|
||||
}
|
||||
@@ -705,4 +761,348 @@ public class ManagementActor : ReceiveActor
|
||||
var aggregator = sp.GetRequiredService<ICentralHealthAggregator>();
|
||||
return aggregator.GetSiteState(cmd.SiteIdentifier);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Template member handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleAddAttribute(IServiceProvider sp, AddTemplateAttributeCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var attr = new TemplateAttribute(cmd.Name)
|
||||
{
|
||||
DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
|
||||
Value = cmd.Value,
|
||||
Description = cmd.Description,
|
||||
DataSourceReference = cmd.DataSourceReference,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.AddAttributeAsync(cmd.TemplateId, attr, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateAttribute(IServiceProvider sp, UpdateTemplateAttributeCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var attr = new TemplateAttribute(cmd.Name)
|
||||
{
|
||||
DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
|
||||
Value = cmd.Value,
|
||||
Description = cmd.Description,
|
||||
DataSourceReference = cmd.DataSourceReference,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.UpdateAttributeAsync(cmd.AttributeId, attr, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteAttribute(IServiceProvider sp, DeleteTemplateAttributeCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var result = await svc.DeleteAttributeAsync(cmd.AttributeId, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleAddAlarm(IServiceProvider sp, AddTemplateAlarmCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var alarm = new TemplateAlarm(cmd.Name)
|
||||
{
|
||||
TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
|
||||
PriorityLevel = cmd.PriorityLevel,
|
||||
Description = cmd.Description,
|
||||
TriggerConfiguration = cmd.TriggerConfiguration,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.AddAlarmAsync(cmd.TemplateId, alarm, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateAlarm(IServiceProvider sp, UpdateTemplateAlarmCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var alarm = new TemplateAlarm(cmd.Name)
|
||||
{
|
||||
TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
|
||||
PriorityLevel = cmd.PriorityLevel,
|
||||
Description = cmd.Description,
|
||||
TriggerConfiguration = cmd.TriggerConfiguration,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.UpdateAlarmAsync(cmd.AlarmId, alarm, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteAlarm(IServiceProvider sp, DeleteTemplateAlarmCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var result = await svc.DeleteAlarmAsync(cmd.AlarmId, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleAddScript(IServiceProvider sp, AddTemplateScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var script = new TemplateScript(cmd.Name, cmd.Code)
|
||||
{
|
||||
TriggerType = cmd.TriggerType,
|
||||
TriggerConfiguration = cmd.TriggerConfiguration,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.AddScriptAsync(cmd.TemplateId, script, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateScript(IServiceProvider sp, UpdateTemplateScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var script = new TemplateScript(cmd.Name, cmd.Code)
|
||||
{
|
||||
TriggerType = cmd.TriggerType,
|
||||
TriggerConfiguration = cmd.TriggerConfiguration,
|
||||
IsLocked = cmd.IsLocked
|
||||
};
|
||||
var result = await svc.UpdateScriptAsync(cmd.ScriptId, script, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteScript(IServiceProvider sp, DeleteTemplateScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var result = await svc.DeleteScriptAsync(cmd.ScriptId, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleAddComposition(IServiceProvider sp, AddTemplateCompositionCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var result = await svc.AddCompositionAsync(cmd.TemplateId, cmd.ComposedTemplateId, cmd.InstanceName, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteComposition(IServiceProvider sp, DeleteTemplateCompositionCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<TemplateService>();
|
||||
var result = await svc.DeleteCompositionAsync(cmd.CompositionId, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Shared Script handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleListSharedScripts(IServiceProvider sp)
|
||||
{
|
||||
var svc = sp.GetRequiredService<SharedScriptService>();
|
||||
return await svc.GetAllSharedScriptsAsync();
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleGetSharedScript(IServiceProvider sp, GetSharedScriptCommand cmd)
|
||||
{
|
||||
var svc = sp.GetRequiredService<SharedScriptService>();
|
||||
return await svc.GetSharedScriptByIdAsync(cmd.SharedScriptId);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleCreateSharedScript(IServiceProvider sp, CreateSharedScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<SharedScriptService>();
|
||||
var result = await svc.CreateSharedScriptAsync(cmd.Name, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateSharedScript(IServiceProvider sp, UpdateSharedScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<SharedScriptService>();
|
||||
var result = await svc.UpdateSharedScriptAsync(cmd.SharedScriptId, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteSharedScript(IServiceProvider sp, DeleteSharedScriptCommand cmd, string user)
|
||||
{
|
||||
var svc = sp.GetRequiredService<SharedScriptService>();
|
||||
var result = await svc.DeleteSharedScriptAsync(cmd.SharedScriptId, user);
|
||||
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Database Connection Definition handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleListDatabaseConnections(IServiceProvider sp)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
return await repo.GetAllDatabaseConnectionsAsync();
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleGetDatabaseConnection(IServiceProvider sp, GetDatabaseConnectionCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
return await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleCreateDatabaseConnection(IServiceProvider sp, CreateDatabaseConnectionDefCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
var def = new DatabaseConnectionDefinition(cmd.Name, cmd.ConnectionString);
|
||||
await repo.AddDatabaseConnectionAsync(def);
|
||||
await repo.SaveChangesAsync();
|
||||
return def;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateDatabaseConnection(IServiceProvider sp, UpdateDatabaseConnectionDefCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
var def = await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId)
|
||||
?? throw new InvalidOperationException($"DatabaseConnection with ID {cmd.DatabaseConnectionId} not found.");
|
||||
def.Name = cmd.Name;
|
||||
def.ConnectionString = cmd.ConnectionString;
|
||||
await repo.UpdateDatabaseConnectionAsync(def);
|
||||
await repo.SaveChangesAsync();
|
||||
return def;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteDatabaseConnection(IServiceProvider sp, DeleteDatabaseConnectionDefCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
await repo.DeleteDatabaseConnectionAsync(cmd.DatabaseConnectionId);
|
||||
await repo.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Inbound API Method handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleListApiMethods(IServiceProvider sp)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
return await repo.GetAllApiMethodsAsync();
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleGetApiMethod(IServiceProvider sp, GetApiMethodCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
return await repo.GetApiMethodByIdAsync(cmd.ApiMethodId);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleCreateApiMethod(IServiceProvider sp, CreateApiMethodCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
var method = new ApiMethod(cmd.Name, cmd.Script)
|
||||
{
|
||||
TimeoutSeconds = cmd.TimeoutSeconds,
|
||||
ParameterDefinitions = cmd.ParameterDefinitions,
|
||||
ReturnDefinition = cmd.ReturnDefinition
|
||||
};
|
||||
await repo.AddApiMethodAsync(method);
|
||||
await repo.SaveChangesAsync();
|
||||
return method;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateApiMethod(IServiceProvider sp, UpdateApiMethodCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
var method = await repo.GetApiMethodByIdAsync(cmd.ApiMethodId)
|
||||
?? throw new InvalidOperationException($"ApiMethod with ID {cmd.ApiMethodId} not found.");
|
||||
method.Script = cmd.Script;
|
||||
method.TimeoutSeconds = cmd.TimeoutSeconds;
|
||||
method.ParameterDefinitions = cmd.ParameterDefinitions;
|
||||
method.ReturnDefinition = cmd.ReturnDefinition;
|
||||
await repo.UpdateApiMethodAsync(method);
|
||||
await repo.SaveChangesAsync();
|
||||
return method;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteApiMethod(IServiceProvider sp, DeleteApiMethodCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
await repo.DeleteApiMethodAsync(cmd.ApiMethodId);
|
||||
await repo.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Additional Security handlers (API key update, scope rules)
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleUpdateApiKey(IServiceProvider sp, UpdateApiKeyCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IInboundApiRepository>();
|
||||
var key = await repo.GetApiKeyByIdAsync(cmd.ApiKeyId)
|
||||
?? throw new InvalidOperationException($"ApiKey with ID {cmd.ApiKeyId} not found.");
|
||||
key.IsEnabled = cmd.IsEnabled;
|
||||
await repo.UpdateApiKeyAsync(key);
|
||||
await repo.SaveChangesAsync();
|
||||
return key;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleListScopeRules(IServiceProvider sp, ListScopeRulesCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<ISecurityRepository>();
|
||||
return await repo.GetScopeRulesForMappingAsync(cmd.MappingId);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleAddScopeRule(IServiceProvider sp, AddScopeRuleCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<ISecurityRepository>();
|
||||
var rule = new SiteScopeRule { LdapGroupMappingId = cmd.MappingId, SiteId = cmd.SiteId };
|
||||
await repo.AddScopeRuleAsync(rule);
|
||||
await repo.SaveChangesAsync();
|
||||
return rule;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteScopeRule(IServiceProvider sp, DeleteScopeRuleCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<ISecurityRepository>();
|
||||
await repo.DeleteScopeRuleAsync(cmd.ScopeRuleId);
|
||||
await repo.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Area update handler
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleUpdateArea(IServiceProvider sp, UpdateAreaCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
|
||||
var area = await repo.GetAreaByIdAsync(cmd.AreaId)
|
||||
?? throw new InvalidOperationException($"Area with ID {cmd.AreaId} not found.");
|
||||
area.Name = cmd.Name;
|
||||
await repo.UpdateAreaAsync(area);
|
||||
await repo.SaveChangesAsync();
|
||||
return area;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Remote Query handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<object?> HandleQueryEventLogs(IServiceProvider sp, QueryEventLogsCommand cmd)
|
||||
{
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
var request = new EventLogQueryRequest(
|
||||
Guid.NewGuid().ToString("N"),
|
||||
cmd.SiteIdentifier,
|
||||
cmd.From, cmd.To,
|
||||
cmd.EventType, cmd.Severity,
|
||||
null, // InstanceId
|
||||
cmd.Keyword,
|
||||
null, // ContinuationToken
|
||||
cmd.PageSize,
|
||||
DateTimeOffset.UtcNow);
|
||||
return await commService.QueryEventLogsAsync(cmd.SiteIdentifier, request);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleQueryParkedMessages(IServiceProvider sp, QueryParkedMessagesCommand cmd)
|
||||
{
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
var request = new ParkedMessageQueryRequest(
|
||||
Guid.NewGuid().ToString("N"),
|
||||
cmd.SiteIdentifier,
|
||||
cmd.Page,
|
||||
cmd.PageSize,
|
||||
DateTimeOffset.UtcNow);
|
||||
return await commService.QueryParkedMessagesAsync(cmd.SiteIdentifier, request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
using ScadaLink.Commons.Types;
|
||||
using ScadaLink.ManagementService;
|
||||
using ScadaLink.TemplateEngine;
|
||||
using ScadaLink.TemplateEngine.Services;
|
||||
|
||||
namespace ScadaLink.ManagementService.Tests;
|
||||
@@ -148,10 +149,9 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
var data = Assert.IsAssignableFrom<IReadOnlyList<Template>>(response.Data);
|
||||
Assert.Equal(2, data.Count);
|
||||
Assert.Equal("PumpTemplate", data[0].Name);
|
||||
Assert.Equal("ValveTemplate", data[1].Name);
|
||||
Assert.NotNull(response.JsonData);
|
||||
Assert.Contains("PumpTemplate", response.JsonData);
|
||||
Assert.Contains("ValveTemplate", response.JsonData);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
@@ -192,8 +192,8 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
var instance = Assert.IsType<Instance>(response.Data);
|
||||
Assert.Equal("Pump1", instance.UniqueName);
|
||||
Assert.NotNull(response.JsonData);
|
||||
Assert.Contains("Pump1", response.JsonData);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
@@ -297,4 +297,185 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// New command authorization tests
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void SharedScriptCreate_WithAdminRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new CreateSharedScriptCommand("Script1", "code", null, null), "Admin");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Design", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatabaseConnectionCreate_WithDeploymentRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new CreateDatabaseConnectionDefCommand("DB1", "Server=test"), "Deployment");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Design", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiMethodCreate_WithAdminRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new CreateApiMethodCommand("Method1", "code", 30, null, null), "Admin");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Design", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTemplateAttribute_WithDeploymentRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new AddTemplateAttributeCommand(1, "Attr1", "Float", null, null, null, false), "Deployment");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Design", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateApiKey_WithDesignRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new UpdateApiKeyCommand(1, true), "Design");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Admin", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddScopeRule_WithDesignRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new AddScopeRuleCommand(1, 1), "Design");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Admin", response.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateArea_WithAdminRole_ReturnsUnauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new UpdateAreaCommand(1, "NewName"), "Admin");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("Design", response.Message);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// New command read-only query tests (no role required)
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void ListSharedScripts_WithNoRoles_ReturnsSuccess()
|
||||
{
|
||||
_templateRepo.GetAllSharedScriptsAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Commons.Entities.Scripts.SharedScript>());
|
||||
_services.AddScoped<SharedScriptService>();
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new ListSharedScriptsCommand());
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListDatabaseConnections_WithNoRoles_ReturnsSuccess()
|
||||
{
|
||||
var extRepo = Substitute.For<IExternalSystemRepository>();
|
||||
extRepo.GetAllDatabaseConnectionsAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Commons.Entities.ExternalSystems.DatabaseConnectionDefinition>());
|
||||
_services.AddScoped(_ => extRepo);
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new ListDatabaseConnectionsCommand());
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListApiMethods_WithNoRoles_ReturnsSuccess()
|
||||
{
|
||||
var apiRepo = Substitute.For<IInboundApiRepository>();
|
||||
apiRepo.GetAllApiMethodsAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Commons.Entities.InboundApi.ApiMethod>());
|
||||
_services.AddScoped(_ => apiRepo);
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new ListApiMethodsCommand());
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListScopeRules_WithAdminRole_ReturnsSuccess()
|
||||
{
|
||||
var secRepo = Substitute.For<ISecurityRepository>();
|
||||
secRepo.GetScopeRulesForMappingAsync(1, Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Commons.Entities.Security.SiteScopeRule>());
|
||||
_services.AddScoped(_ => secRepo);
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new ListScopeRulesCommand(1), "Admin");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// New command error handling tests
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void ListDatabaseConnections_WhenRepoThrows_ReturnsError()
|
||||
{
|
||||
var extRepo = Substitute.For<IExternalSystemRepository>();
|
||||
extRepo.GetAllDatabaseConnectionsAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new InvalidOperationException("Connection refused"));
|
||||
_services.AddScoped(_ => extRepo);
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(new ListDatabaseConnectionsCommand());
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("Connection refused", response.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user