feat: unified gateway route table (static · proxy · remote)
The gateway does two things — reverse-proxy services and serve static
frontends — but the dashboard/API route table only ever showed the proxy
routes, so 'serving a frontend' and 'proxying a service' looked like unrelated
features and castle-app/power-graph-app were invisible in the route view.
Now there's one concept: a route maps an address (path '/foo' or host 'foo.lan')
to a target of one kind — static (a built dist served by file_server), proxy
(a local service port), or remote (a service on another node).
- core: caddyfile.py gains compute_routes() — the single source of truth for
the route list; generate_caddyfile_from_registry renders it (output byte-for-
byte identical, verified against the live Caddyfile). Adds a GatewayRoute
dataclass.
- api: GatewayRoute model → {address, kind, target, name, node}; GET /gateway
builds from compute_routes (incl. config for static frontends + mesh for
remote), so the table matches what Caddy actually does.
- app: Gateway panel shows Address · Kind · Target for every route (static
frontends + host routes now appear); program detail shows 'Reachable at
/foo/ · served (static)' for static frontends.
- cli: 'castle gateway status' prints the full route table.
- docs: registry.md/design.md/CLAUDE.md describe routes as one concept with
three target kinds.
core 94 / cli 24 / api 52 green; ruff + app build clean; Caddyfile unchanged.
This commit is contained in:
@@ -252,20 +252,19 @@ class TestGateway:
|
||||
assert data["managed_count"] == 1
|
||||
|
||||
def test_gateway_routes(self, client: TestClient) -> None:
|
||||
"""Returns proxy routes from deployed components."""
|
||||
"""Returns the full route table, tagged with kind + target."""
|
||||
response = client.get("/gateway")
|
||||
data = response.json()
|
||||
routes = data["routes"]
|
||||
assert len(routes) == 1
|
||||
route = routes[0]
|
||||
assert route["path"] == "/test-svc"
|
||||
assert route["target_port"] == 19000
|
||||
assert route["program"] == "test-svc"
|
||||
route = next(r for r in data["routes"] if r["address"] == "/test-svc")
|
||||
assert route["kind"] == "proxy"
|
||||
assert route["target"] == "localhost:19000"
|
||||
assert route["name"] == "test-svc"
|
||||
assert route["node"] == "test-node"
|
||||
|
||||
def test_gateway_routes_sorted(self, client: TestClient) -> None:
|
||||
"""Routes are sorted by path."""
|
||||
def test_gateway_route_kinds_valid(self, client: TestClient) -> None:
|
||||
"""Every route declares a known kind."""
|
||||
response = client.get("/gateway")
|
||||
data = response.json()
|
||||
paths = [r["path"] for r in data["routes"]]
|
||||
assert paths == sorted(paths)
|
||||
assert data["routes"]
|
||||
for r in data["routes"]:
|
||||
assert r["kind"] in ("static", "proxy", "remote")
|
||||
|
||||
Reference in New Issue
Block a user