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:
Joseph Doherty
2026-03-18 01:21:20 -04:00
parent b2385709f8
commit c63fb1c4a6
24 changed files with 2500 additions and 15 deletions
@@ -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;
}
}