feat: add applications management to the agent and backend
All checks were successful
Build and Push Docker Images / docker (push) Successful in 25s
All checks were successful
Build and Push Docker Images / docker (push) Successful in 25s
- Updated agent to version 1.4.0 with new endpoints for managing Docker Compose applications. - Implemented API requests in the backend for listing, creating, editing, and deploying applications. - Introduced a new AppsModal component in the frontend for user interaction with applications. - Added YAML editor for editing Docker Compose files with validation. - Enhanced VpsCard component to include options for managing applications. - Updated client API functions to support new application management features.
This commit is contained in:
@@ -128,6 +128,18 @@ class ComposeUpdateRequest(BaseModel):
|
||||
project: str
|
||||
|
||||
|
||||
class ComposeWriteRequest(BaseModel):
|
||||
content: str
|
||||
deploy: bool = False
|
||||
|
||||
|
||||
class AppCreateRequest(BaseModel):
|
||||
name: str
|
||||
content: str
|
||||
filename: str = "compose.yaml"
|
||||
deploy: bool = False
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
@@ -601,6 +613,25 @@ async def agent_post(vps: dict, path: str, payload: dict | None = None):
|
||||
return await r.json()
|
||||
|
||||
|
||||
async def agent_request(vps: dict, method: str, path: str,
|
||||
payload: dict | None = None, timeout: int = AGENT_TIMEOUT):
|
||||
"""Appel générique vers un agent — utilisé pour les verbes autres que GET/POST
|
||||
et pour les opérations longues (déploiement compose)."""
|
||||
url = f"http://{vps['host']}:{vps['port']}{path}"
|
||||
headers = {"X-API-Key": vps["api_key"]}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request(
|
||||
method, url, headers=headers, json=payload,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout),
|
||||
) as r:
|
||||
body = await r.json(content_type=None)
|
||||
if r.status >= 400:
|
||||
# Remonte le message de l'agent (validation compose, conflit…)
|
||||
detail = body.get("detail") if isinstance(body, dict) else str(body)
|
||||
raise HTTPException(status_code=r.status, detail=detail or f"HTTP {r.status}")
|
||||
return body
|
||||
|
||||
|
||||
async def fetch_vps_status(vps: dict) -> dict:
|
||||
"""Interroge un agent et retourne son état complet.
|
||||
|
||||
@@ -1472,6 +1503,97 @@ async def container_action(
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
# ─── Applications compose ────────────────────────────────────────────────────
|
||||
#
|
||||
# Éditer un fichier compose revient à choisir ce qui tourne sur la machine :
|
||||
# ces routes sont réservées aux administrateurs, comme le terminal.
|
||||
|
||||
DEPLOY_TIMEOUT = 900
|
||||
|
||||
|
||||
def _require_vps(vps_id: str) -> dict:
|
||||
vps = next((v for v in load_vps() if v["id"] == vps_id), None)
|
||||
if not vps:
|
||||
raise HTTPException(status_code=404, detail="VPS introuvable")
|
||||
return vps
|
||||
|
||||
|
||||
@app.get("/api/vps/{vps_id}/apps")
|
||||
async def list_apps(vps_id: str, _: Annotated[dict, Depends(require_admin)]):
|
||||
"""Liste les applications compose présentes sur le VPS."""
|
||||
vps = _require_vps(vps_id)
|
||||
try:
|
||||
return await agent_request(vps, "GET", "/apps", timeout=15)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"Agent injoignable ({e}). "
|
||||
f"Version 1.4.0 de l'agent requise.")
|
||||
|
||||
|
||||
@app.get("/api/vps/{vps_id}/apps/{name}/compose")
|
||||
async def read_app_compose(vps_id: str, name: str, _: Annotated[dict, Depends(require_admin)]):
|
||||
"""Retourne le contenu du fichier compose d'une application."""
|
||||
vps = _require_vps(vps_id)
|
||||
try:
|
||||
return await agent_request(vps, "GET", f"/apps/{name}/compose", timeout=15)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
@app.put("/api/vps/{vps_id}/apps/{name}/compose")
|
||||
async def write_app_compose(
|
||||
vps_id: str, name: str, body: ComposeWriteRequest,
|
||||
_: Annotated[dict, Depends(require_admin)],
|
||||
):
|
||||
"""Enregistre le fichier compose, avec déploiement optionnel."""
|
||||
vps = _require_vps(vps_id)
|
||||
try:
|
||||
return await agent_request(
|
||||
vps, "PUT", f"/apps/{name}/compose",
|
||||
payload={"content": body.content, "deploy": body.deploy},
|
||||
timeout=DEPLOY_TIMEOUT if body.deploy else 30,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/vps/{vps_id}/apps", status_code=201)
|
||||
async def create_app(
|
||||
vps_id: str, body: AppCreateRequest,
|
||||
_: Annotated[dict, Depends(require_admin)],
|
||||
):
|
||||
"""Crée une application : dossier et fichier compose sur le VPS."""
|
||||
vps = _require_vps(vps_id)
|
||||
try:
|
||||
return await agent_request(
|
||||
vps, "POST", "/apps",
|
||||
payload={"name": body.name, "content": body.content,
|
||||
"filename": body.filename, "deploy": body.deploy},
|
||||
timeout=DEPLOY_TIMEOUT if body.deploy else 30,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/vps/{vps_id}/apps/{name}/up")
|
||||
async def deploy_app(vps_id: str, name: str, _: Annotated[dict, Depends(require_admin)]):
|
||||
"""Déploie une application existante (pull + up -d)."""
|
||||
vps = _require_vps(vps_id)
|
||||
try:
|
||||
return await agent_request(vps, "POST", f"/apps/{name}/up", timeout=DEPLOY_TIMEOUT)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/vps/{vps_id}/containers/{container_id}/exec/ticket")
|
||||
async def container_exec_ticket(
|
||||
vps_id: str, container_id: str,
|
||||
|
||||
Reference in New Issue
Block a user