feat(saas): Phase 2 Admin Web 管理后台 — 完整 CRUD + Dashboard 统计
后端: - 添加 GET /api/v1/stats/dashboard 聚合统计端点 (账号数/活跃服务商/今日请求/今日Token用量等7项指标) - 需要 account:admin 权限 Admin 前端 (Next.js 14 + shadcn/ui + Tailwind + Recharts): - 设计系统: Dark Mode OLED (#020617 背景, #22C55E CTA) - 登录页: 双栏布局, 品牌区 + 表单 - Dashboard 布局: Sidebar 导航 + Header + 主内容区 - 仪表盘: 4 统计卡片 + AreaChart 请求趋势 + BarChart Token用量 - 8 个 CRUD 页面: - 账号管理 (搜索/角色/状态筛选, 编辑/启用禁用) - 服务商 (CRUD + API Key masked) - 模型管理 (Provider筛选, CRUD) - API 密钥 (创建/撤销, 一次性显示token) - 用量统计 (LineChart + BarChart) - 中转任务 (状态筛选, 展开详情) - 系统配置 (分类Tab, 编辑) - 操作日志 (Action筛选, 展开详情) - 14 个 shadcn 风格 UI 组件 (手写实现) - 类型化 API 客户端 (SaaSClient, 20+ 方法, 401 自动跳转) - AuthGuard 路由保护 + useAuth() hook 验证: tsc --noEmit 零 error, pnpm build 13 页面成功, cargo test 21 通过
This commit is contained in:
@@ -130,3 +130,39 @@ pub async fn list_operation_logs(
|
||||
|
||||
Ok(Json(items))
|
||||
}
|
||||
|
||||
/// GET /api/v1/stats/dashboard — 仪表盘聚合统计 (需要 admin 权限)
|
||||
pub async fn dashboard_stats(
|
||||
State(state): State<AppState>,
|
||||
Extension(ctx): Extension<AuthContext>,
|
||||
) -> SaasResult<Json<serde_json::Value>> {
|
||||
require_admin(&ctx)?;
|
||||
|
||||
let total_accounts: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM accounts")
|
||||
.fetch_one(&state.db).await?;
|
||||
let active_accounts: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM accounts WHERE status = 'active'")
|
||||
.fetch_one(&state.db).await?;
|
||||
let tasks_today: (i64,) = sqlx::query_as(
|
||||
"SELECT COUNT(*) FROM relay_tasks WHERE date(created_at) = date('now')"
|
||||
).fetch_one(&state.db).await?;
|
||||
let active_providers: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM providers WHERE enabled = 1")
|
||||
.fetch_one(&state.db).await?;
|
||||
let active_models: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM models WHERE enabled = 1")
|
||||
.fetch_one(&state.db).await?;
|
||||
let tokens_today_input: (i64,) = sqlx::query_as(
|
||||
"SELECT COALESCE(SUM(input_tokens), 0) FROM usage_records WHERE date(created_at) = date('now')"
|
||||
).fetch_one(&state.db).await?;
|
||||
let tokens_today_output: (i64,) = sqlx::query_as(
|
||||
"SELECT COALESCE(SUM(output_tokens), 0) FROM usage_records WHERE date(created_at) = date('now')"
|
||||
).fetch_one(&state.db).await?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"total_accounts": total_accounts.0,
|
||||
"active_accounts": active_accounts.0,
|
||||
"tasks_today": tasks_today.0,
|
||||
"active_providers": active_providers.0,
|
||||
"active_models": active_models.0,
|
||||
"tokens_today_input": tokens_today_input.0,
|
||||
"tokens_today_output": tokens_today_output.0,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ pub fn routes() -> axum::Router<crate::state::AppState> {
|
||||
.route("/api/v1/tokens", post(handlers::create_token))
|
||||
.route("/api/v1/tokens/{id}", delete(handlers::revoke_token))
|
||||
.route("/api/v1/logs/operations", get(handlers::list_operation_logs))
|
||||
.route("/api/v1/stats/dashboard", get(handlers::dashboard_stats))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user