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
This commit is contained in:
Steven
2026-06-15 03:29:07 +08:00
parent cd41e59afc
commit 6f64eecb43
12 changed files with 445 additions and 130 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ async def update_agent(agent_id: int, body: AgentUpdate, db: AsyncSession = Depe
@router.post("/{agent_id}/heartbeat", response_model=AgentOut)
async def heartbeat(agent_id: int, body: AgentHeartbeat, db: AsyncSession = Depends(get_db)):
async def heartbeat(agent_id: int, db: AsyncSession = Depends(get_db)):
svc = AgentService(db)
agent = await svc.heartbeat(agent_id)
if not agent:
+2 -1
View File
@@ -74,7 +74,8 @@ if FRONTEND_DIST.is_dir():
if not request.url.path.startswith("/api"):
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
return HTMLResponse(content=content, status_code=200)
raise exc
from fastapi.responses import JSONResponse
return JSONResponse(status_code=404, content={"detail": "Not Found"})
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
else:
+2
View File
@@ -38,6 +38,7 @@ class AgentService:
if v is not None and hasattr(agent, k):
setattr(agent, k, v)
await self.db.flush()
await self.db.refresh(agent)
return agent
async def heartbeat(self, agent_id: int) -> Agent | None:
@@ -46,6 +47,7 @@ class AgentService:
if agent:
agent.last_heartbeat_at = datetime.utcnow()
await self.db.flush()
await self.db.refresh(agent)
return agent
async def get_task_count(self, agent_id: int) -> int:
+3 -2
View File
@@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database import async_session_factory
from app.models import ScheduledTask, Alert, AlertRule, NotificationChannel
from app.utils import fmt_bj
logger = logging.getLogger("taskpulse.scheduler")
@@ -66,9 +67,9 @@ class SchedulerService:
alert_type="missed_run",
message=(
f"任务 [{task.name}](ID={task.id}) 超过预定时间未执行。\n"
f"预期执行时间: {task.next_run_at}\n"
f"预期执行时间: {fmt_bj(task.next_run_at)}\n"
f"容忍窗口: {task.grace_period}s\n"
f"当前时间: {now}"
f"当前时间: {fmt_bj(now)}"
),
status="pending",
)
+29
View File
@@ -0,0 +1,29 @@
"""Time utility — Beijing time (Asia/Shanghai, UTC+8)."""
from datetime import datetime, timezone, timedelta, tzinfo
# Beijing timezone (UTC+8, no DST)
_BJ_TZ = timezone(timedelta(hours=8))
def now() -> datetime:
"""Return current time in UTC (naive, for DB storage)."""
return datetime.utcnow()
def now_bj() -> datetime:
"""Return current time as Beijing time (aware)."""
return datetime.now(_BJ_TZ)
def fmt_bj(dt: datetime | None) -> str:
"""Format a naive datetime (assumed UTC) as Beijing time string.
Returns e.g. '2026-06-15 10:30:00'
"""
if dt is None:
return ""
# Assume naive dt is UTC
utc_dt = dt.replace(tzinfo=timezone.utc)
bj_dt = utc_dt.astimezone(_BJ_TZ)
return bj_dt.strftime("%Y-%m-%d %H:%M:%S")