Files
docker-manage/backend/main.py

196 lines
5.2 KiB
Python

"""Docker Web Manager - FastAPI Backend"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Optional
import os
from docker_client import (
list_containers, get_container, start_container, stop_container,
restart_container, remove_container, get_container_logs,
get_container_stats, list_compose_projects, compose_up, compose_down,
compose_restart, read_compose_file, write_compose_file,
discover_compose_files, get_system_info,
list_images, search_images, pull_image, remove_image, DEFAULT_MIRRORS,
)
app = FastAPI(title="Docker Web Manager", version="1.0.0")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ========== 路由 ==========
@app.get("/api/system")
def api_system():
"""Docker 系统信息"""
return get_system_info()
@app.get("/api/containers")
def api_list_containers(all: bool = True):
"""列出所有容器"""
return list_containers(all=all)
@app.get("/api/containers/{container_id}")
def api_get_container(container_id: str):
"""获取容器详情"""
return get_container(container_id)
class ContainerAction(BaseModel):
force: bool = False
@app.post("/api/containers/{container_id}/start")
def api_start(container_id: str):
"""启动容器"""
return start_container(container_id)
@app.post("/api/containers/{container_id}/stop")
def api_stop(container_id: str):
"""停止容器"""
return stop_container(container_id)
@app.post("/api/containers/{container_id}/restart")
def api_restart(container_id: str):
"""重启容器"""
return restart_container(container_id)
@app.delete("/api/containers/{container_id}")
def api_remove(container_id: str, force: bool = False):
"""删除容器"""
return remove_container(container_id, force=force)
@app.get("/api/containers/{container_id}/logs")
def api_logs(container_id: str, tail: int = 100, timestamps: bool = False):
"""获取容器日志"""
logs = get_container_logs(container_id, tail=tail, timestamps=timestamps)
return {"container_id": container_id, "logs": logs}
@app.get("/api/containers/{container_id}/stats")
def api_stats(container_id: str):
"""获取容器资源使用"""
return get_container_stats(container_id)
# ========== Docker Compose ==========
@app.get("/api/compose/projects")
def api_compose_list():
"""列出 Compose 项目"""
return list_compose_projects()
@app.get("/api/compose/discover")
def api_compose_discover(path: str = "/data/compose"):
"""发现 Compose 文件"""
return discover_compose_files(path)
class ComposeAction(BaseModel):
project_dir: str
project_name: Optional[str] = None
@app.post("/api/compose/up")
def api_compose_up(data: ComposeAction):
"""启动 Compose 项目"""
return compose_up(data.project_dir, data.project_name)
@app.post("/api/compose/down")
def api_compose_down(data: ComposeAction):
"""停止 Compose 项目"""
return compose_down(data.project_dir, data.project_name)
@app.post("/api/compose/restart")
def api_compose_restart(data: ComposeAction):
"""重启 Compose 项目"""
return compose_restart(data.project_dir, data.project_name)
@app.get("/api/compose/file")
def api_compose_read(path: str):
"""读取 Compose 文件"""
return read_compose_file(path)
@app.post("/api/compose/file")
def api_compose_write(path: str, content: str):
"""写入 Compose 文件"""
return write_compose_file(path, content)
# ========== 镜像管理 ==========
@app.get("/api/images")
def api_list_images():
"""列出本地镜像"""
return list_images()
@app.get("/api/images/search")
def api_search_images(q: str = "", limit: int = 20):
"""搜索 Docker Hub 镜像"""
if not q:
return []
return search_images(q, limit=limit)
@app.post("/api/images/pull")
def api_pull_image(image: str, mirror: str = ""):
"""拉取镜像(异步)"""
# 实际拉取在后台进行,这里返回操作状态
return pull_image(image, mirror_prefix=mirror)
@app.delete("/api/images/{image_id}")
def api_remove_image(image_id: str, force: bool = False):
"""删除本地镜像"""
return remove_image(image_id, force=force)
@app.get("/api/images/mirrors")
def api_get_mirrors():
"""获取可用镜像源列表"""
return DEFAULT_MIRRORS
# ========== 前端静态文件 ==========
frontend_path = os.environ.get("FRONTEND_DIST", "/app/frontend/dist")
@app.get("/")
def root():
"""前端入口"""
index_path = os.path.join(frontend_path, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
return {"message": "Docker Web Manager API - Frontend not built"}
@app.get("/{path:path}")
def frontend_static(path: str):
"""前端路由"""
file_path = os.path.join(frontend_path, path)
if os.path.exists(file_path):
return FileResponse(file_path)
index_path = os.path.join(frontend_path, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
raise HTTPException(status_code=404, detail="Not found")