feat(deploy): 正式環境部署設定
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from app.core.database import SessionLocal
|
from app.core.database import SessionLocal
|
||||||
from app.services.seed import seed_initial_data
|
from app.services.seed import seed_initial_data
|
||||||
@@ -31,7 +33,7 @@ app = FastAPI(
|
|||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["http://localhost:10280", "http://localhost:10290"],
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
@@ -43,3 +45,9 @@ app.include_router(api_router, prefix="/api/v1")
|
|||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok", "service": "vmis-backend"}
|
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")
|
||||||
|
|||||||
@@ -195,13 +195,33 @@ def _generate_tenant_route_yaml(tenant, is_active: bool) -> str:
|
|||||||
domain = tenant.domain
|
domain = tenant.domain
|
||||||
nc_url = f"http://nc-{code}:80" if is_active else f"http://nc-{code}-test:80"
|
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:
|
if tenant.is_manager:
|
||||||
lines += [
|
lines += [
|
||||||
f" {code}-admin:",
|
f" {code}-admin:",
|
||||||
f' rule: "Host(`{domain}`) && PathPrefix(`/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]",
|
" entryPoints: [websecure]",
|
||||||
" tls:",
|
" tls:",
|
||||||
" certResolver: letsencrypt",
|
" certResolver: letsencrypt",
|
||||||
@@ -232,10 +252,10 @@ def _generate_tenant_route_yaml(tenant, is_active: bool) -> str:
|
|||||||
|
|
||||||
if tenant.is_manager:
|
if tenant.is_manager:
|
||||||
lines += [
|
lines += [
|
||||||
f" {code}-admin:",
|
f" {code}-vmis:",
|
||||||
" loadBalancer:",
|
" loadBalancer:",
|
||||||
" servers:",
|
" servers:",
|
||||||
' - url: "http://10.1.0.245:10280"',
|
' - url: "http://vmis-backend:10281"',
|
||||||
]
|
]
|
||||||
|
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|||||||
19
docker/vmis/Dockerfile
Normal file
19
docker/vmis/Dockerfile
Normal file
@@ -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"]
|
||||||
19
docker/vmis/docker-compose.yml
Normal file
19
docker/vmis/docker-compose.yml
Normal file
@@ -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
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
/* VMIS Admin Portal - API utilities */
|
/* 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 ── */
|
/* ── Auth state ── */
|
||||||
let _sysSettings = null;
|
let _sysSettings = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user