Files
stackpilot/backend/routers/networks.py
T
menzeljandClaude Opus 4.8 a4e26f880a Phase 9: network management + stack delete in UI (0.9.0)
- Networks: network_service (list w/ subnet/containers/in-use/owning-stack,
  create bridge/macvlan/ipvlan/overlay + optional subnet/gateway/internal,
  delete with default-network guard, prune) + routers/networks.py; real
  Networks page replaces the placeholder.
- Fix: local stacks can now be deleted from the UI — Delete button on stack
  detail (with optional keep-files-on-disk) and a trash action on stack cards,
  via a shared ConfirmDialog. (Backend DELETE existed; no UI surfaced it.)

Verified: py_compile, frontend tsc build, live network list smoke test
(defaults flagged, compose nets + in-use detected); main 104 routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 22:37:30 +00:00

97 lines
2.7 KiB
Python

"""Docker network management endpoints."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from sqlmodel import Session
from auth import get_current_user, require_admin
from database import get_session
from docker_client import DockerError
from models.user import User
from services import audit_service, network_service
router = APIRouter(prefix="/api/networks", tags=["networks"])
_STATUS = {"invalid_request": 400, "forbidden": 403, "not_found": 404}
def _ip(request: Request) -> str:
return request.client.host if request.client else "unknown"
def _map(exc: DockerError):
code = _STATUS.get(exc.error)
if code:
raise HTTPException(status_code=code, detail=exc.detail or exc.error)
raise exc # falls through to the global 502 DockerError handler
class NetworkCreate(BaseModel):
name: str
driver: str = "bridge"
subnet: str | None = None
gateway: str | None = None
internal: bool = False
attachable: bool = True
@router.get("")
def list_networks(_user: User = Depends(get_current_user)) -> list[dict]:
return network_service.list_networks()
@router.get("/{network_id}")
def inspect_network(network_id: str, _user: User = Depends(get_current_user)) -> dict:
return network_service.inspect_network(network_id)
@router.post("", status_code=201)
def create_network(
body: NetworkCreate,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(require_admin),
) -> dict:
try:
result = network_service.create_network(body.model_dump())
except DockerError as exc:
_map(exc)
audit_service.record(
session, user=user.username, action="network.create", target=body.name,
detail=body.driver, ip=_ip(request),
)
return result
@router.delete("/{network_id}")
def delete_network(
network_id: str,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(require_admin),
) -> dict:
try:
network_service.delete_network(network_id)
except DockerError as exc:
_map(exc)
audit_service.record(
session, user=user.username, action="network.delete", target=network_id,
ip=_ip(request),
)
return {"ok": True}
@router.post("/prune")
def prune_networks(
request: Request,
session: Session = Depends(get_session),
user: User = Depends(require_admin),
) -> dict:
result = network_service.prune_networks()
audit_service.record(
session, user=user.username, action="network.prune", target="networks",
detail=str(result.get("NetworksDeleted") or []), ip=_ip(request),
)
return result