222 lines
5.9 KiB
Python
222 lines
5.9 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,
|
|
list_directory, read_file, write_file, get_container_mounts,
|
|
)
|
|
|
|
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
|
|
|
|
|
|
# ========== 文件浏览 ==========
|
|
@app.get("/api/browser/ls")
|
|
def api_list_dir(path: str):
|
|
"""列出目录内容"""
|
|
return list_directory(path)
|
|
|
|
|
|
@app.get("/api/browser/cat")
|
|
def api_read_file(path: str, encoding: str = "utf-8"):
|
|
"""读取文件内容"""
|
|
return read_file(path, encoding=encoding)
|
|
|
|
|
|
@app.post("/api/browser/save")
|
|
def api_write_file(path: str, content: str, encoding: str = "utf-8"):
|
|
"""保存文件内容"""
|
|
return write_file(path, content, encoding=encoding)
|
|
|
|
|
|
@app.get("/api/browser/mounts")
|
|
def api_container_mounts():
|
|
"""获取容器挂载点"""
|
|
return get_container_mounts()
|
|
|
|
|
|
# ========== 前端静态文件 ==========
|
|
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")
|