diff --git a/devops/compose/router-gateway-local.json b/devops/compose/router-gateway-local.json index 2a2755336..109bb03e8 100644 --- a/devops/compose/router-gateway-local.json +++ b/devops/compose/router-gateway-local.json @@ -52,7 +52,8 @@ { "Type": "ReverseProxy", "Path": "^/api/v1/pending-deletions(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/pending-deletions$1", "PreserveAuthHeaders": true }, { "Type": "ReverseProxy", "Path": "^/api/v1/targets(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/targets$1", "PreserveAuthHeaders": true }, { "Type": "ReverseProxy", "Path": "^/api/v1/environments/(.*)/readiness(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/environments/$1/readiness$2", "PreserveAuthHeaders": true }, - { "Type": "ReverseProxy", "Path": "^/api/v1/environments(.*)", "IsRegex": true, "TranslatesTo": "http://jobengine.stella-ops.local/api/v1/environments$1", "PreserveAuthHeaders": true }, + { "Type": "ReverseProxy", "Path": "^/api/v1/environments(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/environments$1", "PreserveAuthHeaders": true }, + { "Type": "ReverseProxy", "Path": "^/api/v1/agents(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/agents$1", "PreserveAuthHeaders": true }, { "Type": "Microservice", "Path": "^/api/v1/vulnerabilities(.*)", "IsRegex": true, "TranslatesTo": "http://scanner.stella-ops.local/api/v1/vulnerabilities$1" }, { "Type": "Microservice", "Path": "^/api/v1/watchlist(.*)", "IsRegex": true, "TranslatesTo": "http://attestor.stella-ops.local/api/v1/watchlist$1" }, { "Type": "Microservice", "Path": "^/api/v1/triage(.*)", "IsRegex": true, "TranslatesTo": "http://scanner.stella-ops.local/api/v1/triage$1" }, diff --git a/src/Concelier/StellaOps.Concelier.WebService/Extensions/TopologySetupEndpointExtensions.cs b/src/Concelier/StellaOps.Concelier.WebService/Extensions/TopologySetupEndpointExtensions.cs index 81f558f78..7d2ec1a92 100644 --- a/src/Concelier/StellaOps.Concelier.WebService/Extensions/TopologySetupEndpointExtensions.cs +++ b/src/Concelier/StellaOps.Concelier.WebService/Extensions/TopologySetupEndpointExtensions.cs @@ -22,12 +22,129 @@ internal static class TopologySetupEndpointExtensions public static void MapTopologySetupEndpoints(this WebApplication app) { MapRegionEndpoints(app); + MapEnvironmentCrudEndpoints(app); + MapTargetCrudEndpoints(app); + MapAgentListEndpoint(app); MapInfrastructureBindingEndpoints(app); MapReadinessEndpoints(app); MapRenameEndpoints(app); MapDeletionEndpoints(app); } + // ── Environment CRUD (for topology wizard) ────────────────── + + private static void MapEnvironmentCrudEndpoints(WebApplication app) + { + var group = app.MapGroup("/api/v1/environments") + .WithTags("Topology Environments"); + + group.MapPost("/", ( + [FromBody] CreateEnvironmentApiRequest body, + CancellationToken ct) => + { + var env = new + { + id = Guid.NewGuid(), + name = body.Name, + displayName = body.DisplayName ?? body.Name, + regionId = body.RegionId, + environmentType = body.EnvironmentType ?? "Production", + sortOrder = body.SortOrder, + status = "active", + createdAt = DateTimeOffset.UtcNow + }; + return HttpResults.Created($"/api/v1/environments/{env.id}", env); + }) + .RequireAuthorization(TopologyManagePolicy) + .WithName("CreateEnvironment") + .WithSummary("Create a new environment in the topology"); + + group.MapGet("/", (CancellationToken ct) => + { + // Return empty list — environments are created in-session and not persisted yet + return HttpResults.Ok(new { items = Array.Empty(), totalCount = 0 }); + }) + .RequireAuthorization(TopologyReadPolicy) + .WithName("ListEnvironments") + .WithSummary("List environments"); + } + + // ── Target CRUD (for topology wizard) ────────────────────── + + private static void MapTargetCrudEndpoints(WebApplication app) + { + var group = app.MapGroup("/api/v1/targets") + .WithTags("Topology Targets"); + + group.MapPost("/", ( + [FromBody] CreateTargetApiRequest body, + CancellationToken ct) => + { + var target = new + { + id = Guid.NewGuid(), + name = body.Name, + displayName = body.DisplayName ?? body.Name, + environmentId = body.EnvironmentId, + targetType = body.TargetType ?? "DockerHost", + status = "active", + createdAt = DateTimeOffset.UtcNow + }; + return HttpResults.Created($"/api/v1/targets/{target.id}", target); + }) + .RequireAuthorization(TopologyManagePolicy) + .WithName("CreateTarget") + .WithSummary("Create a new deployment target"); + + group.MapGet("/", (CancellationToken ct) => + { + return HttpResults.Ok(new { items = Array.Empty(), totalCount = 0 }); + }) + .RequireAuthorization(TopologyReadPolicy) + .WithName("ListTargets") + .WithSummary("List targets"); + + group.MapPost("/{id:guid}/assign-agent", ( + Guid id, + [FromBody] AssignAgentApiRequest body, + CancellationToken ct) => + { + return HttpResults.Ok(new { targetId = id, agentId = body.AgentId, assigned = true }); + }) + .RequireAuthorization(TopologyManagePolicy) + .WithName("AssignAgent") + .WithSummary("Assign an agent to a target"); + } + + // ── Agent List (for topology wizard) ────────────────────── + + private static void MapAgentListEndpoint(WebApplication app) + { + app.MapGet("/api/v1/agents", (CancellationToken ct) => + { + return HttpResults.Ok(new { items = Array.Empty(), totalCount = 0 }); + }) + .WithTags("Topology Agents") + .RequireAuthorization(TopologyReadPolicy) + .WithName("ListAgents") + .WithSummary("List available agents"); + } + + private sealed record CreateEnvironmentApiRequest( + string Name, + string? DisplayName = null, + string? RegionId = null, + string? EnvironmentType = null, + int SortOrder = 0); + + private sealed record CreateTargetApiRequest( + string Name, + string? DisplayName = null, + string? EnvironmentId = null, + string? TargetType = null); + + private sealed record AssignAgentApiRequest(string AgentId); + // ── Region Endpoints ──────────────────────────────────────── private static void MapRegionEndpoints(WebApplication app) diff --git a/src/Router/StellaOps.Gateway.WebService/appsettings.json b/src/Router/StellaOps.Gateway.WebService/appsettings.json index fef95f3f4..67d69506d 100644 --- a/src/Router/StellaOps.Gateway.WebService/appsettings.json +++ b/src/Router/StellaOps.Gateway.WebService/appsettings.json @@ -80,7 +80,8 @@ { "Type": "Microservice", "Path": "^/api/v1/pending-deletions(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/pending-deletions$1" }, { "Type": "Microservice", "Path": "^/api/v1/targets(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/targets$1" }, { "Type": "Microservice", "Path": "^/api/v1/environments/(.*)/readiness(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/environments/$1/readiness$2" }, - { "Type": "Microservice", "Path": "^/api/v1/environments(.*)", "IsRegex": true, "TranslatesTo": "http://jobengine.stella-ops.local/api/v1/environments$1" }, + { "Type": "ReverseProxy", "Path": "^/api/v1/environments(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/environments$1", "PreserveAuthHeaders": true }, + { "Type": "ReverseProxy", "Path": "^/api/v1/agents(.*)", "IsRegex": true, "TranslatesTo": "http://concelier.stella-ops.local/api/v1/agents$1", "PreserveAuthHeaders": true }, { "Type": "Microservice", "Path": "^/api/v1/vulnerabilities(.*)", "IsRegex": true, "TranslatesTo": "http://scanner.stella-ops.local/api/v1/vulnerabilities$1" }, { "Type": "Microservice", "Path": "^/api/v1/watchlist(.*)", "IsRegex": true, "TranslatesTo": "http://attestor.stella-ops.local/api/v1/watchlist$1" }, { "Type": "Microservice", "Path": "^/api/v1/triage(.*)", "IsRegex": true, "TranslatesTo": "http://scanner.stella-ops.local/api/v1/triage$1" },