From 8eb3909ca90d1fa7ae35c871c714098ade562263 Mon Sep 17 00:00:00 2001 From: VMIS Developer Date: Sun, 15 Mar 2026 18:11:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E6=AD=A3=E5=BC=8F=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E9=83=A8=E7=BD=B2=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend/api.js: API URL 自動判斷 (localhost=dev, 其他=相對路徑 /api/v1) - main.py: 加入 StaticFiles 掛載 admin-portal,CORS 開放 - schedule_tenant: Traefik 路由範本更新 - 管理租戶加入 /api 路由 (priority 200) - /admin 加入 StripPrefix middleware - admin 服務改指向 vmis-backend:10281 - docker/vmis: 新增 Dockerfile + docker-compose.yml Co-Authored-By: Claude Sonnet 4.6 --- backend/app/main.py | 10 ++++++- .../app/services/scheduler/schedule_tenant.py | 28 ++++++++++++++++--- docker/vmis/Dockerfile | 19 +++++++++++++ docker/vmis/docker-compose.yml | 19 +++++++++++++ frontend/admin-portal/js/api.js | 4 ++- 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 docker/vmis/Dockerfile create mode 100644 docker/vmis/docker-compose.yml diff --git a/backend/app/main.py b/backend/app/main.py index 8afba67..5dd4b65 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,8 @@ +import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles from app.core.database import SessionLocal from app.services.seed import seed_initial_data @@ -31,7 +33,7 @@ app = FastAPI( app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:10280", "http://localhost:10290"], + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -43,3 +45,9 @@ app.include_router(api_router, prefix="/api/v1") @app.get("/health") def health(): return {"status": "ok", "service": "vmis-backend"} + + +# 靜態前端(admin portal)— 掛載在最後,讓 API 路由優先 +_frontend_dir = os.path.join(os.path.dirname(__file__), "..", "frontend") +if os.path.isdir(_frontend_dir): + app.mount("/", StaticFiles(directory=_frontend_dir, html=True), name="frontend") diff --git a/backend/app/services/scheduler/schedule_tenant.py b/backend/app/services/scheduler/schedule_tenant.py index abe030c..3ccb578 100644 --- a/backend/app/services/scheduler/schedule_tenant.py +++ b/backend/app/services/scheduler/schedule_tenant.py @@ -195,13 +195,33 @@ def _generate_tenant_route_yaml(tenant, is_active: bool) -> str: domain = tenant.domain nc_url = f"http://nc-{code}:80" if is_active else f"http://nc-{code}-test:80" - lines = ["http:", " routers:"] + lines = ["http:"] + + if tenant.is_manager: + lines += [ + " middlewares:", + " vmis-strip-admin:", + " stripPrefix:", + ' prefixes: ["/admin"]', + "", + ] + + lines += [" routers:"] if tenant.is_manager: lines += [ f" {code}-admin:", f' rule: "Host(`{domain}`) && PathPrefix(`/admin`)"', - f" service: {code}-admin", + f" service: {code}-vmis", + " entryPoints: [websecure]", + " middlewares: [vmis-strip-admin]", + " tls:", + " certResolver: letsencrypt", + " priority: 200", + "", + f" {code}-api:", + f' rule: "Host(`{domain}`) && PathPrefix(`/api`)"', + f" service: {code}-vmis", " entryPoints: [websecure]", " tls:", " certResolver: letsencrypt", @@ -232,10 +252,10 @@ def _generate_tenant_route_yaml(tenant, is_active: bool) -> str: if tenant.is_manager: lines += [ - f" {code}-admin:", + f" {code}-vmis:", " loadBalancer:", " servers:", - ' - url: "http://10.1.0.245:10280"', + ' - url: "http://vmis-backend:10281"', ] return "\n".join(lines) + "\n" diff --git a/docker/vmis/Dockerfile b/docker/vmis/Dockerfile new file mode 100644 index 0000000..6fbd9ce --- /dev/null +++ b/docker/vmis/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.12-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssh-client \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY backend/app ./app +COPY backend/alembic ./alembic +COPY backend/alembic.ini . +COPY frontend/admin-portal ./frontend + +EXPOSE 10281 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "10281"] diff --git a/docker/vmis/docker-compose.yml b/docker/vmis/docker-compose.yml new file mode 100644 index 0000000..84ea07c --- /dev/null +++ b/docker/vmis/docker-compose.yml @@ -0,0 +1,19 @@ +services: + vmis-backend: + build: + context: ../.. + dockerfile: docker/vmis/Dockerfile + image: vmis-backend:latest + container_name: vmis-backend + restart: unless-stopped + env_file: .env + volumes: + - /home/porsche/.ssh:/root/.ssh:ro + networks: + - traefik-network + labels: + - "traefik.enable=false" + +networks: + traefik-network: + external: true diff --git a/frontend/admin-portal/js/api.js b/frontend/admin-portal/js/api.js index 939277e..663a073 100644 --- a/frontend/admin-portal/js/api.js +++ b/frontend/admin-portal/js/api.js @@ -1,6 +1,8 @@ /* VMIS Admin Portal - API utilities */ -const API = 'http://localhost:10281/api/v1'; +const API = (location.hostname === 'localhost' || location.hostname === '127.0.0.1') + ? 'http://localhost:10281/api/v1' + : '/api/v1'; /* ── Auth state ── */ let _sysSettings = null;