feat(cli): native-alarm-source commands (template add/list/remove + instance set/clear)
This commit is contained in:
@@ -24,6 +24,7 @@ public static class InstanceCommands
|
|||||||
command.Add(BuildSetBindings(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildSetBindings(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildSetOverrides(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildSetOverrides(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildAlarmOverride(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildAlarmOverride(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
|
command.Add(BuildNativeAlarmSourceOverride(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildSetArea(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildSetArea(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildDiff(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildDiff(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildDeploy(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildDeploy(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
@@ -348,6 +349,53 @@ public static class InstanceCommands
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Command BuildNativeAlarmSourceOverride(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||||
|
{
|
||||||
|
var group = new Command("native-alarm-source")
|
||||||
|
{
|
||||||
|
Description = "Manage per-instance native alarm source overrides (retarget an inherited binding; blank = inherited)"
|
||||||
|
};
|
||||||
|
|
||||||
|
// set
|
||||||
|
var setIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||||
|
var setSourceOption = new Option<string>("--source") { Description = "Source binding canonical name (e.g. 'Pressure' or 'Module.Pressure')", Required = true };
|
||||||
|
var setConnectionOption = new Option<string?>("--connection") { Description = "Connection name override (blank = inherited)" };
|
||||||
|
var setSourceRefOption = new Option<string?>("--source-ref") { Description = "Source reference override (blank = inherited)" };
|
||||||
|
var setFilterOption = new Option<string?>("--filter") { Description = "Condition filter override (blank = inherited)" };
|
||||||
|
var setCmd = new Command("set") { Description = "Set (upsert) a native alarm source override on an instance" };
|
||||||
|
setCmd.Add(setIdOption); setCmd.Add(setSourceOption); setCmd.Add(setConnectionOption);
|
||||||
|
setCmd.Add(setSourceRefOption); setCmd.Add(setFilterOption);
|
||||||
|
setCmd.SetAction(async (ParseResult result) =>
|
||||||
|
{
|
||||||
|
return await CommandHelpers.ExecuteCommandAsync(
|
||||||
|
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||||
|
new SetInstanceNativeAlarmSourceOverrideCommand(
|
||||||
|
result.GetValue(setIdOption),
|
||||||
|
result.GetValue(setSourceOption)!,
|
||||||
|
result.GetValue(setConnectionOption),
|
||||||
|
result.GetValue(setSourceRefOption),
|
||||||
|
result.GetValue(setFilterOption)));
|
||||||
|
});
|
||||||
|
group.Add(setCmd);
|
||||||
|
|
||||||
|
// clear
|
||||||
|
var clearIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||||
|
var clearSourceOption = new Option<string>("--source") { Description = "Source binding canonical name", Required = true };
|
||||||
|
var clearCmd = new Command("clear") { Description = "Clear a native alarm source override on an instance (revert to inherited)" };
|
||||||
|
clearCmd.Add(clearIdOption); clearCmd.Add(clearSourceOption);
|
||||||
|
clearCmd.SetAction(async (ParseResult result) =>
|
||||||
|
{
|
||||||
|
return await CommandHelpers.ExecuteCommandAsync(
|
||||||
|
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||||
|
new DeleteInstanceNativeAlarmSourceOverrideCommand(
|
||||||
|
result.GetValue(clearIdOption),
|
||||||
|
result.GetValue(clearSourceOption)!));
|
||||||
|
});
|
||||||
|
group.Add(clearCmd);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
private static Command BuildSetArea(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
private static Command BuildSetArea(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||||
{
|
{
|
||||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public static class TemplateCommands
|
|||||||
command.Add(BuildDelete(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildDelete(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildAttribute(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildAttribute(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildAlarm(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildAlarm(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
|
command.Add(BuildNativeAlarmSource(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildScript(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildScript(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
command.Add(BuildComposition(urlOption, formatOption, usernameOption, passwordOption));
|
command.Add(BuildComposition(urlOption, formatOption, usernameOption, passwordOption));
|
||||||
|
|
||||||
@@ -293,6 +294,73 @@ public static class TemplateCommands
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Command BuildNativeAlarmSource(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||||
|
{
|
||||||
|
var group = new Command("native-alarm-source")
|
||||||
|
{
|
||||||
|
Description = "Manage template native alarm source bindings (read-only mirror of OPC UA A&C / MxGateway alarms)"
|
||||||
|
};
|
||||||
|
|
||||||
|
// add
|
||||||
|
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||||
|
var nameOption = new Option<string>("--name") { Description = "Source binding name", Required = true };
|
||||||
|
var connectionOption = new Option<string>("--connection") { Description = "Alarm-capable data connection name", Required = true };
|
||||||
|
var sourceRefOption = new Option<string>("--source-ref") { Description = "Source reference (OPC UA SourceNode nodeId, or MxAccess object/area)", Required = true };
|
||||||
|
var filterOption = new Option<string?>("--filter") { Description = "Optional condition filter (null = mirror all conditions under the source)" };
|
||||||
|
var descOption = new Option<string?>("--description") { Description = "Description" };
|
||||||
|
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||||
|
lockedOption.DefaultValueFactory = _ => false;
|
||||||
|
|
||||||
|
var addCmd = new Command("add") { Description = "Add a native alarm source binding to a template" };
|
||||||
|
addCmd.Add(templateIdOption);
|
||||||
|
addCmd.Add(nameOption);
|
||||||
|
addCmd.Add(connectionOption);
|
||||||
|
addCmd.Add(sourceRefOption);
|
||||||
|
addCmd.Add(filterOption);
|
||||||
|
addCmd.Add(descOption);
|
||||||
|
addCmd.Add(lockedOption);
|
||||||
|
addCmd.SetAction(async (ParseResult result) =>
|
||||||
|
{
|
||||||
|
return await CommandHelpers.ExecuteCommandAsync(
|
||||||
|
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||||
|
new AddTemplateNativeAlarmSourceCommand(
|
||||||
|
result.GetValue(templateIdOption),
|
||||||
|
result.GetValue(nameOption)!,
|
||||||
|
result.GetValue(connectionOption)!,
|
||||||
|
result.GetValue(sourceRefOption)!,
|
||||||
|
result.GetValue(filterOption),
|
||||||
|
result.GetValue(descOption),
|
||||||
|
result.GetValue(lockedOption)));
|
||||||
|
});
|
||||||
|
group.Add(addCmd);
|
||||||
|
|
||||||
|
// list
|
||||||
|
var listIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||||
|
var listCmd = new Command("list") { Description = "List native alarm source bindings on a template" };
|
||||||
|
listCmd.Add(listIdOption);
|
||||||
|
listCmd.SetAction(async (ParseResult result) =>
|
||||||
|
{
|
||||||
|
return await CommandHelpers.ExecuteCommandAsync(
|
||||||
|
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||||
|
new ListTemplateNativeAlarmSourcesCommand(result.GetValue(listIdOption)));
|
||||||
|
});
|
||||||
|
group.Add(listCmd);
|
||||||
|
|
||||||
|
// remove
|
||||||
|
var removeIdOption = new Option<int>("--id") { Description = "Native alarm source ID", Required = true };
|
||||||
|
var removeCmd = new Command("remove") { Description = "Remove a native alarm source binding from a template" };
|
||||||
|
removeCmd.Add(removeIdOption);
|
||||||
|
removeCmd.SetAction(async (ParseResult result) =>
|
||||||
|
{
|
||||||
|
return await CommandHelpers.ExecuteCommandAsync(
|
||||||
|
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||||
|
new DeleteTemplateNativeAlarmSourceCommand(result.GetValue(removeIdOption)));
|
||||||
|
});
|
||||||
|
group.Add(removeCmd);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
private static Command BuildScript(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
private static Command BuildScript(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||||
{
|
{
|
||||||
var group = new Command("script") { Description = "Manage template scripts" };
|
var group = new Command("script") { Description = "Manage template scripts" };
|
||||||
|
|||||||
@@ -252,6 +252,41 @@ scadabridge --url <url> template alarm delete --template-id <int> --name <string
|
|||||||
| `--template-id` | yes | Template ID |
|
| `--template-id` | yes | Template ID |
|
||||||
| `--name` | yes | Alarm name to delete |
|
| `--name` | yes | Alarm name to delete |
|
||||||
|
|
||||||
|
#### `template native-alarm-source add`
|
||||||
|
|
||||||
|
Bind a native alarm source to a template — a read-only mirror of an OPC UA Alarms &
|
||||||
|
Conditions server's or the MxAccess Gateway's alarms (no ack-back).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scadabridge --url <url> template native-alarm-source add --template-id <int> --name <string> --connection <string> --source-ref <string> [--filter <string>] [--description <string>] [--locked]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Required | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| `--template-id` | yes | Template ID |
|
||||||
|
| `--name` | yes | Source binding name |
|
||||||
|
| `--connection` | yes | Alarm-capable data connection name (OPC UA or MxGateway) |
|
||||||
|
| `--source-ref` | yes | Source reference (OPC UA SourceNode nodeId, or MxAccess object/area) |
|
||||||
|
| `--filter` | no | Condition filter (omit to mirror all conditions under the source) |
|
||||||
|
| `--description` | no | Description |
|
||||||
|
| `--locked` | no | Lock the binding in derived templates |
|
||||||
|
|
||||||
|
#### `template native-alarm-source list`
|
||||||
|
|
||||||
|
List the native alarm source bindings on a template.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scadabridge --url <url> template native-alarm-source list --template-id <int>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `template native-alarm-source remove`
|
||||||
|
|
||||||
|
Remove a native alarm source binding from a template (by its ID).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scadabridge --url <url> template native-alarm-source remove --id <int>
|
||||||
|
```
|
||||||
|
|
||||||
#### `template script add`
|
#### `template script add`
|
||||||
|
|
||||||
Add a script to a template.
|
Add a script to a template.
|
||||||
@@ -370,6 +405,31 @@ scadabridge --url <url> instance create --name <string> --template-id <int> --si
|
|||||||
| `--site-id` | yes | Site where the instance will run |
|
| `--site-id` | yes | Site where the instance will run |
|
||||||
| `--area-id` | no | Area within the site |
|
| `--area-id` | no | Area within the site |
|
||||||
|
|
||||||
|
#### `instance native-alarm-source set`
|
||||||
|
|
||||||
|
Override an inherited native alarm source binding for a single instance (upsert). Blank
|
||||||
|
options keep the inherited value.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scadabridge --url <url> instance native-alarm-source set --instance-id <int> --source <string> [--connection <string>] [--source-ref <string>] [--filter <string>]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Required | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| `--instance-id` | yes | Instance ID |
|
||||||
|
| `--source` | yes | Source binding canonical name (e.g. `Pressure` or `Module.Pressure`) |
|
||||||
|
| `--connection` | no | Connection name override (blank = inherited) |
|
||||||
|
| `--source-ref` | no | Source reference override (blank = inherited) |
|
||||||
|
| `--filter` | no | Condition filter override (blank = inherited) |
|
||||||
|
|
||||||
|
#### `instance native-alarm-source clear`
|
||||||
|
|
||||||
|
Clear an instance's native alarm source override, reverting to the inherited binding.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scadabridge --url <url> instance native-alarm-source clear --instance-id <int> --source <string>
|
||||||
|
```
|
||||||
|
|
||||||
#### `instance deploy`
|
#### `instance deploy`
|
||||||
|
|
||||||
Deploy an instance to its site. Acquires the per-instance operation lock.
|
Deploy an instance to its site. Acquires the per-instance operation lock.
|
||||||
|
|||||||
@@ -129,6 +129,27 @@ public class CommandTreeTests
|
|||||||
Assert.DoesNotContain("--instance-name", optionNames);
|
Assert.DoesNotContain("--instance-name", optionNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TemplateNativeAlarmSource_HasAddListRemove()
|
||||||
|
{
|
||||||
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
||||||
|
var group = template.Subcommands.Single(c => c.Name == "native-alarm-source");
|
||||||
|
var subNames = group.Subcommands.Select(c => c.Name).ToHashSet();
|
||||||
|
Assert.Contains("add", subNames);
|
||||||
|
Assert.Contains("list", subNames);
|
||||||
|
Assert.Contains("remove", subNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InstanceNativeAlarmSource_HasSetAndClear()
|
||||||
|
{
|
||||||
|
var instance = InstanceCommands.Build(Url, Format, Username, Password);
|
||||||
|
var group = instance.Subcommands.Single(c => c.Name == "native-alarm-source");
|
||||||
|
var subNames = group.Subcommands.Select(c => c.Name).ToHashSet();
|
||||||
|
Assert.Contains("set", subNames);
|
||||||
|
Assert.Contains("clear", subNames);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(typeof(GetInstanceCommand))]
|
[InlineData(typeof(GetInstanceCommand))]
|
||||||
[InlineData(typeof(ListSitesCommand))]
|
[InlineData(typeof(ListSitesCommand))]
|
||||||
@@ -141,6 +162,8 @@ public class CommandTreeTests
|
|||||||
[InlineData(typeof(ExportBundleCommand))]
|
[InlineData(typeof(ExportBundleCommand))]
|
||||||
[InlineData(typeof(PreviewBundleCommand))]
|
[InlineData(typeof(PreviewBundleCommand))]
|
||||||
[InlineData(typeof(ImportBundleCommand))]
|
[InlineData(typeof(ImportBundleCommand))]
|
||||||
|
[InlineData(typeof(AddTemplateNativeAlarmSourceCommand))]
|
||||||
|
[InlineData(typeof(SetInstanceNativeAlarmSourceOverrideCommand))]
|
||||||
public void CommandPayloadTypes_ResolveViaRegistry(Type commandType)
|
public void CommandPayloadTypes_ResolveViaRegistry(Type commandType)
|
||||||
{
|
{
|
||||||
// GetCommandName throws ArgumentException for an unregistered type — the CLI
|
// GetCommandName throws ArgumentException for an unregistered type — the CLI
|
||||||
|
|||||||
Reference in New Issue
Block a user