2026-04-29 10:00:40 +00:00
|
|
|
# --- main.py ---
|
|
|
|
|
import os
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
from fastapi.responses import HTMLResponse
|
2026-05-20 09:19:40 +00:00
|
|
|
from contextlib import asynccontextmanager
|
2026-06-11 02:24:09 +00:00
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware # 🌟 新增引入
|
2026-05-20 09:19:40 +00:00
|
|
|
import database
|
2026-06-15 10:04:20 +00:00
|
|
|
import asyncio
|
2026-04-29 10:00:40 +00:00
|
|
|
|
|
|
|
|
# 引入我們剛剛拆分出來的路由模組
|
2026-06-15 10:04:20 +00:00
|
|
|
from routers import query, config, terminal, lock, leaf_options, backup, diagnostics, auth, logs
|
2026-04-29 10:00:40 +00:00
|
|
|
|
2026-05-20 09:19:40 +00:00
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
# Startup
|
|
|
|
|
await database.init_db_pool()
|
2026-06-15 10:04:20 +00:00
|
|
|
# 🌟 新增:初始化 Log Broadcaster 的 Event Loop 參考,確保跨執行緒排程安全
|
|
|
|
|
logs.log_broadcaster.loop = asyncio.get_running_loop()
|
2026-05-20 09:19:40 +00:00
|
|
|
yield
|
|
|
|
|
# Shutdown
|
|
|
|
|
await database.close_db_pool()
|
|
|
|
|
|
|
|
|
|
app = FastAPI(title="Harmonic CMTS Manager", version="2.0", lifespan=lifespan)
|
2026-04-29 10:00:40 +00:00
|
|
|
|
2026-06-11 02:24:09 +00:00
|
|
|
# 🌟 新增:全域安全與快取 Middleware
|
|
|
|
|
class SecurityAndCacheMiddleware(BaseHTTPMiddleware):
|
|
|
|
|
async def dispatch(self, request, call_next):
|
|
|
|
|
response = await call_next(request)
|
|
|
|
|
|
|
|
|
|
# 1. 補上安全性標頭 (圖片 7 的警告)
|
|
|
|
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
|
|
|
|
|
|
|
|
# 2. 針對靜態檔案補上 Cache-Control (圖片 2 的警告)
|
|
|
|
|
# 讓 JS/CSS 快取 1 小時 (3600秒),提升前端載入效能
|
|
|
|
|
if request.url.path.startswith("/static/"):
|
|
|
|
|
response.headers["Cache-Control"] = "public, max-age=3600"
|
|
|
|
|
else:
|
|
|
|
|
# API 請求不快取,確保拿到最新資料
|
|
|
|
|
response.headers["Cache-Control"] = "no-store"
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
app.add_middleware(SecurityAndCacheMiddleware) # 🌟 掛載 Middleware
|
|
|
|
|
|
2026-04-29 10:00:40 +00:00
|
|
|
# 掛載靜態檔案目錄 (對應 static/style.css 與 static/app.js)
|
|
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
|
|
2026-05-13 10:31:34 +00:00
|
|
|
# 🌟 統一標準:所有 REST API 都在這裡掛載 /api/v1 前綴
|
|
|
|
|
app.include_router(query.router, prefix="/api/v1")
|
|
|
|
|
app.include_router(config.router, prefix="/api/v1")
|
|
|
|
|
app.include_router(leaf_options.router, prefix="/api/v1")
|
|
|
|
|
app.include_router(lock.router, prefix="/api/v1")
|
2026-05-25 08:40:55 +00:00
|
|
|
app.include_router(backup.router, prefix="/api/v1")
|
2026-06-04 10:09:45 +00:00
|
|
|
app.include_router(diagnostics.router, prefix="/api/v1")
|
2026-06-08 09:01:55 +00:00
|
|
|
app.include_router(auth.router, prefix="/api/v1")
|
2026-05-13 10:31:34 +00:00
|
|
|
|
|
|
|
|
# WebSocket 通常獨立於 API 版本之外,所以不加前綴
|
|
|
|
|
app.include_router(terminal.router)
|
2026-06-15 10:04:20 +00:00
|
|
|
app.include_router(logs.router) # 🌟 新增:掛載日誌 WebSocket 路由
|
2026-04-29 10:00:40 +00:00
|
|
|
|
|
|
|
|
# 根目錄路由:回傳前端 UI
|
|
|
|
|
@app.get("/", response_class=HTMLResponse, tags=["UI"])
|
|
|
|
|
async def read_root():
|
|
|
|
|
# 讀取同層級的 index.html
|
|
|
|
|
html_path = os.path.join(os.path.dirname(__file__), "index.html")
|
|
|
|
|
with open(html_path, "r", encoding="utf-8") as f:
|
|
|
|
|
return f.read()
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import uvicorn
|
|
|
|
|
# 啟動伺服器
|
|
|
|
|
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True)
|