Files

163 lines
5.6 KiB
Python
Raw Permalink Normal View History

2026-04-07 02:04:22 +05:30
"""YakPanel - Docker API - list/start/stop containers via docker CLI"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from app.core.utils import exec_shell_sync
from app.api.auth import get_current_user
from app.models.user import User
router = APIRouter(prefix="/docker", tags=["docker"])
class RunContainerRequest(BaseModel):
image: str
name: str = ""
ports: str = ""
cmd: str = ""
@router.get("/containers")
async def docker_containers(current_user: User = Depends(get_current_user)):
"""List Docker containers (docker ps -a)"""
out, err = exec_shell_sync(
'docker ps -a --format "{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}\t{{.Ports}}"',
timeout=10,
)
if err and "Cannot connect" in err:
return {"containers": [], "error": "Docker not available"}
containers = []
for line in out.strip().split("\n"):
line = line.strip()
if not line:
continue
parts = line.split("\t", 4)
if len(parts) >= 5:
containers.append({
"id": parts[0][:12],
"id_full": parts[0],
"image": parts[1],
"status": parts[2],
"names": parts[3],
"ports": parts[4],
})
elif len(parts) >= 4:
containers.append({
"id": parts[0][:12],
"id_full": parts[0],
"image": parts[1],
"status": parts[2],
"names": parts[3],
"ports": "",
})
return {"containers": containers}
@router.get("/images")
async def docker_images(current_user: User = Depends(get_current_user)):
"""List Docker images (docker images)"""
out, err = exec_shell_sync(
'docker images --format "{{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}"',
timeout=10,
)
if err and "Cannot connect" in err:
return {"images": [], "error": "Docker not available"}
images = []
for line in out.strip().split("\n"):
line = line.strip()
if not line:
continue
parts = line.split("\t", 3)
if len(parts) >= 4:
images.append({
"repository": parts[0],
"tag": parts[1],
"id": parts[2],
"size": parts[3],
})
return {"images": images}
@router.post("/pull")
async def docker_pull(
image: str,
current_user: User = Depends(get_current_user),
):
"""Pull Docker image (docker pull)"""
if not image or " " in image or "'" in image or '"' in image:
raise HTTPException(status_code=400, detail="Invalid image name")
out, err = exec_shell_sync(f"docker pull {image}", timeout=600)
if err and "error" in err.lower():
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
return {"status": True, "msg": "Pulled"}
@router.post("/run")
async def docker_run(
body: RunContainerRequest,
current_user: User = Depends(get_current_user),
):
"""Run a new container (docker run -d)"""
image = (body.image or "").strip()
if not image:
raise HTTPException(status_code=400, detail="Image required")
if " " in image or "'" in image or '"' in image:
raise HTTPException(status_code=400, detail="Invalid image name")
cmd = f"docker run -d {image}"
if body.name:
name = body.name.strip().replace(" ", "-")
if name and all(c.isalnum() or c in "-_" for c in name):
cmd += f" --name {name}"
if body.ports:
for p in body.ports.replace(",", " ").split():
p = p.strip()
if p:
cmd += f" -p {p}" if ":" in p else f" -p {p}:{p}"
if body.cmd:
cmd += f" {body.cmd}"
out, err = exec_shell_sync(cmd, timeout=60)
if err and "error" in err.lower():
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
return {"status": True, "msg": "Container started", "id": out.strip()[:12]}
@router.post("/{container_id}/start")
async def docker_start(
container_id: str,
current_user: User = Depends(get_current_user),
):
"""Start container"""
if " " in container_id or "'" in container_id or '"' in container_id:
raise HTTPException(status_code=400, detail="Invalid container ID")
out, err = exec_shell_sync(f"docker start {container_id}", timeout=30)
if err and "error" in err.lower():
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
return {"status": True, "msg": "Started"}
@router.post("/{container_id}/stop")
async def docker_stop(
container_id: str,
current_user: User = Depends(get_current_user),
):
"""Stop container"""
if " " in container_id or "'" in container_id or '"' in container_id:
raise HTTPException(status_code=400, detail="Invalid container ID")
out, err = exec_shell_sync(f"docker stop {container_id}", timeout=30)
if err and "error" in err.lower():
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
return {"status": True, "msg": "Stopped"}
@router.post("/{container_id}/restart")
async def docker_restart(
container_id: str,
current_user: User = Depends(get_current_user),
):
"""Restart container"""
if " " in container_id or "'" in container_id or '"' in container_id:
raise HTTPException(status_code=400, detail="Invalid container ID")
out, err = exec_shell_sync(f"docker restart {container_id}", timeout=30)
if err and "error" in err.lower():
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
return {"status": True, "msg": "Restarted"}