TaskPulse/backend/app/main.py
Steven 6f64eecb43 fix: heartbeat 500 error, timezone to Beijing, tasks guide collapse
- fix(api): heartbeat endpoint no longer requires request body, fix spa_fallback
  404 handler causing 500 on API routes, add db.refresh() in agent update/heartbeat
- feat(time): unify all frontend time display to Beijing time (Asia/Shanghai)
  via new beijing() utility; backend alert messages also use BJT
- feat(ui): collapse AI agent instruction panel on Tasks page by default
- chore: add backend/app/utils.py and frontend/src/utils/time.js
2026-06-15 03:29:07 +08:00

88 lines
3.0 KiB
Python

"""FastAPI application entry point."""
import asyncio
import logging
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from app.api.agents import router as agents_router
from app.api.auth import router as auth_router
from app.api.tasks import router as tasks_router
from app.api.executions import router as executions_router
from app.api.notifications import router as notifications_router
from app.api.dashboard import router as dashboard_router
from app.api.system import router as system_router
from app.config import settings
from app.database import engine, Base, async_session_factory
from app.services.scheduler import SchedulerService
from app.services.auth import AuthService
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
logger = logging.getLogger("taskpulse")
scheduler = SchedulerService()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup: create tables + start background scheduler."""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("Database tables ensured")
# Ensure default admin
async with async_session_factory() as db:
await AuthService.ensure_default_admin(db)
# Start background scheduler
task = asyncio.create_task(scheduler.start())
yield
# Shutdown
await scheduler.stop()
task.cancel()
await engine.dispose()
app = FastAPI(
title="TaskPulse — AI Agent Task Monitor",
description="Unified dashboard for tracking, monitoring and alerting on AI agent scheduled tasks.",
version="1.0.0",
lifespan=lifespan,
)
# API routers
app.include_router(agents_router)
app.include_router(auth_router)
app.include_router(tasks_router)
app.include_router(executions_router)
app.include_router(notifications_router)
app.include_router(dashboard_router)
app.include_router(system_router)
# Serve Vue3 static files (SPA — monolithic deployment)
FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"
if FRONTEND_DIST.is_dir():
# Mount assets directory for JS/CSS/fonts
app.mount("/assets", StaticFiles(directory=str(FRONTEND_DIST / "assets")), name="assets")
@app.exception_handler(404)
async def spa_fallback(request: Request, exc):
"""Return index.html for any non-API route (Vue Router SPA)."""
if not request.url.path.startswith("/api"):
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
return HTMLResponse(content=content, status_code=200)
from fastapi.responses import JSONResponse
return JSONResponse(status_code=404, content={"detail": "Not Found"})
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
else:
logger.info("Frontend dist not found at %s — API-only mode", FRONTEND_DIST)
@app.get("/api/health")
async def health():
return {"status": "ok", "service": "taskpulse"}