feat(scanner): Complete PoE implementation with Windows compatibility fix
- Fix namespace conflicts (Subgraph → PoESubgraph) - Add hash sanitization for Windows filesystem (colon → underscore) - Update all test mocks to use It.IsAny<>() - Add direct orchestrator unit tests - All 8 PoE tests now passing (100% success rate) - Complete SPRINT_3500_0001_0001 documentation Fixes compilation errors and Windows filesystem compatibility issues. Tests: 8/8 passing Files: 8 modified, 1 new test, 1 completion report 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
334
src/Cli/StellaOps.Cli/Commands/Admin/AdminCommandGroup.cs
Normal file
334
src/Cli/StellaOps.Cli/Commands/Admin/AdminCommandGroup.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_4100_0006_0005 - Admin Utility Integration
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace StellaOps.Cli.Commands.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Administrative command group for platform management operations.
|
||||
/// Provides policy, users, feeds, and system management commands.
|
||||
/// </summary>
|
||||
internal static class AdminCommandGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Build the admin command group with policy/users/feeds/system subcommands.
|
||||
/// </summary>
|
||||
public static Command BuildAdminCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var admin = new Command("admin", "Administrative operations for platform management");
|
||||
|
||||
// Add subcommand groups
|
||||
admin.Add(BuildPolicyCommand(services, verboseOption, cancellationToken));
|
||||
admin.Add(BuildUsersCommand(services, verboseOption, cancellationToken));
|
||||
admin.Add(BuildFeedsCommand(services, verboseOption, cancellationToken));
|
||||
admin.Add(BuildSystemCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
private static Command BuildPolicyCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var policy = new Command("policy", "Policy management commands");
|
||||
|
||||
// policy export
|
||||
var export = new Command("export", "Export active policy snapshot");
|
||||
var exportOutputOption = new Option<string?>("--output", "-o")
|
||||
{
|
||||
Description = "Output file path (stdout if omitted)"
|
||||
};
|
||||
export.Add(exportOutputOption);
|
||||
export.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var output = parseResult.GetValue(exportOutputOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandlePolicyExportAsync(services, output, verbose, ct);
|
||||
});
|
||||
policy.Add(export);
|
||||
|
||||
// policy import
|
||||
var import = new Command("import", "Import policy from file");
|
||||
var importFileOption = new Option<string>("--file", "-f")
|
||||
{
|
||||
Description = "Policy file to import (YAML or JSON)",
|
||||
Required = true
|
||||
};
|
||||
var validateOnlyOption = new Option<bool>("--validate-only")
|
||||
{
|
||||
Description = "Validate without importing"
|
||||
};
|
||||
import.Add(importFileOption);
|
||||
import.Add(validateOnlyOption);
|
||||
import.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var file = parseResult.GetValue(importFileOption)!;
|
||||
var validateOnly = parseResult.GetValue(validateOnlyOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandlePolicyImportAsync(services, file, validateOnly, verbose, ct);
|
||||
});
|
||||
policy.Add(import);
|
||||
|
||||
// policy validate
|
||||
var validate = new Command("validate", "Validate policy file without importing");
|
||||
var validateFileOption = new Option<string>("--file", "-f")
|
||||
{
|
||||
Description = "Policy file to validate",
|
||||
Required = true
|
||||
};
|
||||
validate.Add(validateFileOption);
|
||||
validate.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var file = parseResult.GetValue(validateFileOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandlePolicyValidateAsync(services, file, verbose, ct);
|
||||
});
|
||||
policy.Add(validate);
|
||||
|
||||
// policy list
|
||||
var list = new Command("list", "List policy revisions");
|
||||
var listFormatOption = new Option<string>("--format")
|
||||
{
|
||||
Description = "Output format: table, json"
|
||||
};
|
||||
listFormatOption.SetDefaultValue("table");
|
||||
list.Add(listFormatOption);
|
||||
list.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var format = parseResult.GetValue(listFormatOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandlePolicyListAsync(services, format, verbose, ct);
|
||||
});
|
||||
policy.Add(list);
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
private static Command BuildUsersCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var users = new Command("users", "User management commands");
|
||||
|
||||
// users list
|
||||
var list = new Command("list", "List users");
|
||||
var roleFilterOption = new Option<string?>("--role")
|
||||
{
|
||||
Description = "Filter by role"
|
||||
};
|
||||
var formatOption = new Option<string>("--format")
|
||||
{
|
||||
Description = "Output format: table, json"
|
||||
};
|
||||
formatOption.SetDefaultValue("table");
|
||||
list.Add(roleFilterOption);
|
||||
list.Add(formatOption);
|
||||
list.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var role = parseResult.GetValue(roleFilterOption);
|
||||
var format = parseResult.GetValue(formatOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleUsersListAsync(services, role, format, verbose, ct);
|
||||
});
|
||||
users.Add(list);
|
||||
|
||||
// users add
|
||||
var add = new Command("add", "Add new user");
|
||||
var emailArg = new Argument<string>("email")
|
||||
{
|
||||
Description = "User email address"
|
||||
};
|
||||
var roleOption = new Option<string>("--role", "-r")
|
||||
{
|
||||
Description = "User role",
|
||||
Required = true
|
||||
};
|
||||
var tenantOption = new Option<string?>("--tenant", "-t")
|
||||
{
|
||||
Description = "Tenant ID (default if omitted)"
|
||||
};
|
||||
add.Add(emailArg);
|
||||
add.Add(roleOption);
|
||||
add.Add(tenantOption);
|
||||
add.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var email = parseResult.GetValue(emailArg)!;
|
||||
var role = parseResult.GetValue(roleOption)!;
|
||||
var tenant = parseResult.GetValue(tenantOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleUsersAddAsync(services, email, role, tenant, verbose, ct);
|
||||
});
|
||||
users.Add(add);
|
||||
|
||||
// users revoke
|
||||
var revoke = new Command("revoke", "Revoke user access");
|
||||
var revokeEmailArg = new Argument<string>("email")
|
||||
{
|
||||
Description = "User email address"
|
||||
};
|
||||
var confirmOption = new Option<bool>("--confirm")
|
||||
{
|
||||
Description = "Confirm revocation (required for safety)"
|
||||
};
|
||||
revoke.Add(revokeEmailArg);
|
||||
revoke.Add(confirmOption);
|
||||
revoke.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var email = parseResult.GetValue(revokeEmailArg)!;
|
||||
var confirm = parseResult.GetValue(confirmOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleUsersRevokeAsync(services, email, confirm, verbose, ct);
|
||||
});
|
||||
users.Add(revoke);
|
||||
|
||||
// users update
|
||||
var update = new Command("update", "Update user role");
|
||||
var updateEmailArg = new Argument<string>("email")
|
||||
{
|
||||
Description = "User email address"
|
||||
};
|
||||
var newRoleOption = new Option<string>("--role", "-r")
|
||||
{
|
||||
Description = "New user role",
|
||||
Required = true
|
||||
};
|
||||
update.Add(updateEmailArg);
|
||||
update.Add(newRoleOption);
|
||||
update.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var email = parseResult.GetValue(updateEmailArg)!;
|
||||
var newRole = parseResult.GetValue(newRoleOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleUsersUpdateAsync(services, email, newRole, verbose, ct);
|
||||
});
|
||||
users.Add(update);
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
private static Command BuildFeedsCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var feeds = new Command("feeds", "Advisory feed management commands");
|
||||
|
||||
// feeds list
|
||||
var list = new Command("list", "List configured feeds");
|
||||
var listFormatOption = new Option<string>("--format")
|
||||
{
|
||||
Description = "Output format: table, json"
|
||||
};
|
||||
listFormatOption.SetDefaultValue("table");
|
||||
list.Add(listFormatOption);
|
||||
list.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var format = parseResult.GetValue(listFormatOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleFeedsListAsync(services, format, verbose, ct);
|
||||
});
|
||||
feeds.Add(list);
|
||||
|
||||
// feeds status
|
||||
var status = new Command("status", "Show feed sync status");
|
||||
var statusSourceOption = new Option<string?>("--source", "-s")
|
||||
{
|
||||
Description = "Filter by source ID"
|
||||
};
|
||||
status.Add(statusSourceOption);
|
||||
status.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var source = parseResult.GetValue(statusSourceOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleFeedsStatusAsync(services, source, verbose, ct);
|
||||
});
|
||||
feeds.Add(status);
|
||||
|
||||
// feeds refresh
|
||||
var refresh = new Command("refresh", "Trigger feed refresh");
|
||||
var refreshSourceOption = new Option<string?>("--source", "-s")
|
||||
{
|
||||
Description = "Refresh specific source (all if omitted)"
|
||||
};
|
||||
var forceOption = new Option<bool>("--force")
|
||||
{
|
||||
Description = "Force refresh (ignore cache)"
|
||||
};
|
||||
refresh.Add(refreshSourceOption);
|
||||
refresh.Add(forceOption);
|
||||
refresh.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var source = parseResult.GetValue(refreshSourceOption);
|
||||
var force = parseResult.GetValue(forceOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleFeedsRefreshAsync(services, source, force, verbose, ct);
|
||||
});
|
||||
feeds.Add(refresh);
|
||||
|
||||
// feeds history
|
||||
var history = new Command("history", "Show sync history");
|
||||
var historySourceOption = new Option<string>("--source", "-s")
|
||||
{
|
||||
Description = "Source ID",
|
||||
Required = true
|
||||
};
|
||||
var limitOption = new Option<int>("--limit", "-n")
|
||||
{
|
||||
Description = "Limit number of results"
|
||||
};
|
||||
limitOption.SetDefaultValue(10);
|
||||
history.Add(historySourceOption);
|
||||
history.Add(limitOption);
|
||||
history.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var source = parseResult.GetValue(historySourceOption)!;
|
||||
var limit = parseResult.GetValue(limitOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleFeedsHistoryAsync(services, source, limit, verbose, ct);
|
||||
});
|
||||
feeds.Add(history);
|
||||
|
||||
return feeds;
|
||||
}
|
||||
|
||||
private static Command BuildSystemCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var system = new Command("system", "System management commands");
|
||||
|
||||
// system status
|
||||
var status = new Command("status", "Show system health");
|
||||
var statusFormatOption = new Option<string>("--format")
|
||||
{
|
||||
Description = "Output format: table, json"
|
||||
};
|
||||
statusFormatOption.SetDefaultValue("table");
|
||||
status.Add(statusFormatOption);
|
||||
status.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var format = parseResult.GetValue(statusFormatOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleSystemStatusAsync(services, format, verbose, ct);
|
||||
});
|
||||
system.Add(status);
|
||||
|
||||
// system info
|
||||
var info = new Command("info", "Show version, build, and configuration information");
|
||||
info.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return await AdminCommandHandlers.HandleSystemInfoAsync(services, verbose, ct);
|
||||
});
|
||||
system.Add(info);
|
||||
|
||||
return system;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user