Фронтенд: стабильность прокси и рендера

- main.py: таймаут прокси 150с (холодная загрузка бэкенда до ~2мин), /healthz, чистый список маршрутов
- app.js: таймаут fetch (AbortController), защита от гонок при переключении вкладок (requestSeq)
- style.css: стили chипа ошибки кэша (multi-line заметки уже поддерживались)
This commit is contained in:
2026-09-09 11:33:43 +03:00
parent 86c2a3097d
commit 2febf9b4ff
3 changed files with 90 additions and 34 deletions

View File

@@ -1,4 +1,9 @@
"""Фронтенд расписания: статики + прокси к API бэкенда."""
from __future__ import annotations
import os
import logging
from pathlib import Path
import requests
@@ -8,17 +13,28 @@ from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
log = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
load_dotenv(Path(__file__).parent / ".env")
# Бэкенд на холодном кэше скачивает таблицу (до 2 минут), таймаут выше.
SCHEDULE_API = os.getenv("SCHEDULE_API", "http://127.0.0.1:8000").rstrip("/")
PROXY_TIMEOUT = float(os.getenv("SCHEDULE_PROXY_TIMEOUT", "150"))
STATIC_DIR = Path(__file__).parent / "static"
app = FastAPI(title="Расписание — фронт")
def _proxy(path: str, params: dict):
@app.get("/healthz")
def healthz() -> dict:
"""Живость процесса; не зависит от бэкенда."""
return {"ok": True}
def _proxy(path: str, params: dict) -> object:
try:
resp = requests.get(f"{SCHEDULE_API}/{path}", params=params, timeout=30)
resp = requests.get(f"{SCHEDULE_API}/{path}", params=params, timeout=PROXY_TIMEOUT)
except requests.RequestException as exc:
raise HTTPException(status_code=502, detail=f"Бэкенд недоступен: {exc}")
if resp.status_code != 200:
@@ -30,54 +46,60 @@ def _proxy(path: str, params: dict):
return resp.json()
# --- API-маршруты -----------------------------------------------------------
@app.get("/api/groups")
def api_groups(force: bool = Query(False)):
return _proxy("groups", {"force": force})
def api_groups():
return _proxy("groups", {})
@app.get("/api/schedule/{group}")
def api_schedule(group: str, force: bool = Query(False)):
return _proxy(f"schedule/{group}", {"force": force})
def api_schedule(group: str):
return _proxy(f"schedule/{group}", {})
@app.get("/api/teachers")
def api_teachers(force: bool = Query(False)):
return _proxy("teachers", {"force": force})
def api_teachers():
return _proxy("teachers", {})
@app.get("/api/rooms")
def api_rooms(force: bool = Query(False)):
return _proxy("rooms", {"force": force})
def api_rooms():
return _proxy("rooms", {})
@app.get("/api/teacher/{name}")
def api_teacher(name: str, force: bool = Query(False)):
return _proxy(f"teacher/{name}", {"force": force})
def api_teacher(name: str):
return _proxy(f"teacher/{name}", {})
@app.get("/api/room/{label}")
def api_room(label: str, force: bool = Query(False)):
return _proxy(f"room/{label}", {"force": force})
def api_room(label: str):
return _proxy(f"room/{label}", {})
@app.get("/api/rooms-free")
def api_rooms_free(day: str, period: int, force: bool = Query(False)):
return _proxy("rooms-free", {"day": day, "period": period, "force": force})
def api_rooms_free(day: str, period: int):
return _proxy("rooms-free", {"day": day, "period": period})
@app.get("/api/rooms-day")
def api_rooms_day(day: str, force: bool = Query(False)):
return _proxy("rooms-day", {"day": day, "force": force})
def api_rooms_day(day: str):
return _proxy("rooms-day", {"day": day})
@app.get("/api/week")
def api_week(force: bool = Query(False)):
return _proxy("week", {"force": force})
def api_week():
return _proxy("week", {})
@app.get("/api/status")
def api_status(force: bool = Query(False)):
return _proxy("status", {"force": force})
def api_status():
return _proxy("status", {})
# --- статики -----------------------------------------------------------------
@app.get("/")
@@ -89,5 +111,4 @@ app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8080)
uvicorn.run("main:app", host="127.0.0.1", port=8080, reload=True)