System Management: - System Settings page with 8 configurable parameters (admin only) - Audit Log page with filterable table (user, action, resource, date range) - Audit logging wired into auth, devices, users, alarms, reports API handlers - SystemSetting model + migration (002) Device Detail: - Dedicated /devices/:id page with 4 tabs (realtime, historical trends, alarm history, device info) - ECharts historical charts with granularity/time range selectors - Device name clickable in Devices and Monitoring tables → navigates to detail Email & Scheduling: - Email service with SMTP support (STARTTLS/SSL/plain) - Alarm email notification with professional HTML template - Report scheduler using APScheduler for cron-based auto-generation - Scheduled report task seeded (daily at 8am) UI Enhancements: - Dark mode toggle (persisted to localStorage, Ant Design darkAlgorithm) - Data comparison view in Analysis page (dual date range, side-by-side metrics) - i18n framework (i18next) with zh/en translations for menu and common UI - Language switcher in header (中文/English) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from pydantic import BaseModel
|
|
from app.core.database import get_db
|
|
from app.core.deps import get_current_user, require_roles
|
|
from app.core.security import hash_password
|
|
from app.models.user import User, Role
|
|
from app.services.audit import log_audit
|
|
|
|
router = APIRouter(prefix="/users", tags=["用户管理"])
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
username: str
|
|
password: str
|
|
full_name: str | None = None
|
|
email: str | None = None
|
|
phone: str | None = None
|
|
role: str = "visitor"
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
full_name: str | None = None
|
|
email: str | None = None
|
|
phone: str | None = None
|
|
role: str | None = None
|
|
is_active: bool | None = None
|
|
|
|
|
|
@router.get("")
|
|
async def list_users(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
|
):
|
|
count_q = select(func.count(User.id))
|
|
total = (await db.execute(count_q)).scalar()
|
|
result = await db.execute(select(User).offset((page - 1) * page_size).limit(page_size).order_by(User.id))
|
|
return {
|
|
"total": total,
|
|
"items": [{
|
|
"id": u.id, "username": u.username, "full_name": u.full_name,
|
|
"email": u.email, "phone": u.phone, "role": u.role,
|
|
"is_active": u.is_active, "last_login": str(u.last_login) if u.last_login else None,
|
|
} for u in result.scalars().all()]
|
|
}
|
|
|
|
|
|
@router.post("")
|
|
async def create_user(data: UserCreate, db: AsyncSession = Depends(get_db), user: User = Depends(require_roles("admin"))):
|
|
existing = await db.execute(select(User).where(User.username == data.username))
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=400, detail="用户名已存在")
|
|
new_user = User(
|
|
username=data.username, hashed_password=hash_password(data.password),
|
|
full_name=data.full_name, email=data.email, phone=data.phone, role=data.role,
|
|
)
|
|
db.add(new_user)
|
|
await db.flush()
|
|
await log_audit(db, user.id, "create", "user", detail=f"创建用户 {data.username}")
|
|
return {"id": new_user.id, "username": new_user.username}
|
|
|
|
|
|
@router.put("/{user_id}")
|
|
async def update_user(user_id: int, data: UserUpdate, db: AsyncSession = Depends(get_db), admin: User = Depends(require_roles("admin"))):
|
|
result = await db.execute(select(User).where(User.id == user_id))
|
|
target = result.scalar_one_or_none()
|
|
if not target:
|
|
raise HTTPException(status_code=404, detail="用户不存在")
|
|
updates = data.model_dump(exclude_unset=True)
|
|
for k, v in updates.items():
|
|
setattr(target, k, v)
|
|
await log_audit(db, admin.id, "update", "user", detail=f"更新用户 {target.username}: {', '.join(updates.keys())}")
|
|
return {"message": "已更新"}
|
|
|
|
|
|
@router.get("/roles")
|
|
async def list_roles(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
|
|
result = await db.execute(select(Role).order_by(Role.id))
|
|
return [{"id": r.id, "name": r.name, "display_name": r.display_name, "description": r.description}
|
|
for r in result.scalars().all()]
|