fix: flatten React Router structure — no nested Routes, each page is a top-level route
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/backend/ 下实现完整的 JWT 鉴权 + 用户管理 + 审计日志系统。全部使用 backend/.venv/bin/python 和 backend/.venv/bin/pip。
|
||||
|
||||
已有: User + AuditLog ORM 模型, FastAPI 入口, database.py, config.py
|
||||
|
||||
## 第一部分: auth_service.py
|
||||
|
||||
创建 backend/app/services/__init__.py (空)
|
||||
创建 backend/app/services/auth_service.py
|
||||
|
||||
核心函数:
|
||||
- hash_password(password: str) -> str — bcrypt (passlib)
|
||||
- verify_password(plain: str, hashed: str) -> bool
|
||||
- create_access_token(user_id: str, role: str) -> str — JWT(HS256, config.jwt_secret, expires=config.jwt_expire_minutes)
|
||||
- decode_access_token(token: str) -> dict — jose.jwt.decode, 捕获 ExpiredSignatureError/JWTError 抛 HTTPException(401)
|
||||
- get_user_by_id(db, user_id) -> User | None
|
||||
- get_user_by_username(db, username) -> User | None
|
||||
- authenticate_user(db, username, password) -> User — 验证密码, 失败检查/递增 login_attempts, >=5 次锁定 15 分钟, 成功后重置 login_attempts
|
||||
- create_user(db, username, email, password) -> User — 首个用户自动 Owner + is_approved=True, 后续用户 Viewer + is_approved=False
|
||||
|
||||
## 第二部分: auth middleware
|
||||
|
||||
创建 backend/app/middleware/__init__.py (空)
|
||||
创建 backend/app/middleware/auth.py
|
||||
|
||||
```python
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.services.auth_service import decode_access_token, get_user_by_id
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
async def require_auth(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
payload = decode_access_token(credentials.credentials)
|
||||
user = await get_user_by_id(db, payload["sub"])
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(status_code=401, detail="Invalid or inactive user")
|
||||
return user
|
||||
|
||||
def require_role(*roles: str):
|
||||
async def role_check(user = Depends(require_auth)):
|
||||
if user.role.value not in roles:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
return user
|
||||
return role_check
|
||||
```
|
||||
|
||||
## 第三部分: Auth Schemas
|
||||
|
||||
创建 backend/app/schemas/__init__.py (空)
|
||||
创建 backend/app/schemas/auth.py
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
username: str
|
||||
email: str # 先用 str,后续升级 EmailStr
|
||||
password: str
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_in: int
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
```
|
||||
|
||||
创建 backend/app/schemas/user.py
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
username: str
|
||||
email: str
|
||||
role: str
|
||||
is_active: bool
|
||||
is_approved: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class UserUpdateRequest(BaseModel):
|
||||
role: str | None = None
|
||||
is_active: bool | None = None
|
||||
```
|
||||
|
||||
## 第四部分: Auth API
|
||||
|
||||
创建 backend/app/api/__init__.py (空)
|
||||
创建 backend/app/api/auth.py
|
||||
|
||||
POST /api/v1/auth/register — 注册, 返回 201
|
||||
POST /api/v1/auth/login — 登录, 返回 TokenResponse
|
||||
POST /api/v1/auth/refresh — 刷新 token, 需 require_auth
|
||||
POST /api/v1/auth/change-password — 改密码, 需 require_auth
|
||||
|
||||
## 第五部分: User Management API
|
||||
|
||||
创建 backend/app/api/users.py
|
||||
|
||||
GET /api/v1/users — 用户列表 (需 owner), 支持分页
|
||||
GET /api/v1/users/{id} — 用户详情 (需 owner)
|
||||
PATCH /api/v1/users/{id} — 改角色/状态 (需 owner)
|
||||
POST /api/v1/users/{id}/approve — 审批新用户 (需 owner)
|
||||
|
||||
## 第六部分: Audit Middleware + API
|
||||
|
||||
创建 backend/app/middleware/audit.py
|
||||
|
||||
```python
|
||||
# 审计中间件: 拦截所有 POST/PUT/PATCH/DELETE 请求
|
||||
# 从 request.state 获取当前用户 (需 auth middleware 先执行)
|
||||
# 记录: user_id, username, action, target, detail, ip_address
|
||||
# 写入 AuditLog 表 (异步)
|
||||
```
|
||||
|
||||
创建 backend/app/api/audit.py
|
||||
|
||||
GET /api/v1/audit — 审计日志列表 (需 owner), 支持分页 + 按 action 筛选
|
||||
|
||||
## 第七部分: 注册路由
|
||||
|
||||
修改 backend/app/main.py, 注册所有 router:
|
||||
```python
|
||||
from app.api import auth, users, audit
|
||||
app.include_router(auth.router)
|
||||
app.include_router(users.router)
|
||||
app.include_router(audit.router)
|
||||
```
|
||||
|
||||
## 第八部分: 测试
|
||||
|
||||
创建 backend/tests/test_auth.py
|
||||
- test_register_success (201)
|
||||
- test_register_duplicate (409)
|
||||
- test_login_success (200 + JWT)
|
||||
- test_login_wrong_password (401)
|
||||
- test_login_locked (423 after 5 failures)
|
||||
- test_require_auth_no_token (401)
|
||||
- test_viewer_cannot_manage_users (403)
|
||||
|
||||
创建 backend/tests/conftest.py
|
||||
- async test fixtures: async_client (httpx.AsyncClient + FastAPI app)
|
||||
- test database (SQLite in-memory 或者用现有的 PG)
|
||||
|
||||
注意: 测试使用 pytest + pytest-asyncio + httpx.AsyncClient
|
||||
|
||||
## 第九部分: 提交
|
||||
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && \
|
||||
git add backend/app/services/ backend/app/middleware/ backend/app/schemas/ backend/app/api/ backend/tests/ backend/app/main.py && \
|
||||
git commit -m "feat: JWT auth, user management, audit logging system"
|
||||
```
|
||||
|
||||
请按顺序执行以上 9 个部分,每个部分完成后再进入下一部分。pip install 需要时使用 `cd backend && .venv/bin/pip install <package>`。
|
||||
@@ -0,0 +1,84 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/frontend/ 下实现前端鉴权体系。
|
||||
|
||||
## 1. API 客户端
|
||||
|
||||
创建 frontend/src/api/client.ts:
|
||||
- axios 实例 baseURL='/api/v1'
|
||||
- 请求拦截器: 从 localStorage('access_token') 读取 JWT, 加到 Authorization: Bearer header
|
||||
- 响应拦截器: 401 自动清除 token → 跳转 /login
|
||||
|
||||
创建 frontend/src/api/auth.ts:
|
||||
```typescript
|
||||
import client from './client'
|
||||
export const login = (username: string, password: string) => client.post('/auth/login', {username, password})
|
||||
export const register = (data: {username, email, password}) => client.post('/auth/register', data)
|
||||
export const refresh = () => client.post('/auth/refresh')
|
||||
export const changePassword = (data: {old_password, new_password}) => client.post('/auth/change-password', data)
|
||||
```
|
||||
|
||||
创建 frontend/src/api/users.ts:
|
||||
```typescript
|
||||
import client from './client'
|
||||
export const getUsers = (params?) => client.get('/users', {params})
|
||||
export const getUser = (id: string) => client.get(`/users/${id}`)
|
||||
export const updateUser = (id: string, data) => client.patch(`/users/${id}`, data)
|
||||
export const approveUser = (id: string) => client.post(`/users/${id}/approve`)
|
||||
```
|
||||
|
||||
创建 frontend/src/api/audit.ts:
|
||||
```typescript
|
||||
import client from './client'
|
||||
export const getAuditLogs = (params?) => client.get('/audit', {params})
|
||||
```
|
||||
|
||||
## 2. useAuth hook
|
||||
|
||||
创建/修改 frontend/src/hooks/useAuth.ts:
|
||||
```typescript
|
||||
import { useState, useEffect, createContext, useContext } from 'react'
|
||||
// AuthContext 提供: user, token, login(), logout(), isAuthenticated
|
||||
// token 存 localStorage('access_token')
|
||||
// user 从 JWT payload 解码获取 (username, role, user_id)
|
||||
```
|
||||
|
||||
## 3. Login 页面
|
||||
|
||||
修改 frontend/src/pages/LoginPage.tsx:
|
||||
- 居中卡片, 暗色背景
|
||||
- Logo "🐝 知微蜂巢" + "Hive Manage v3"
|
||||
- 用户名 + 密码输入框
|
||||
- 登入按钮 (primary, full width)
|
||||
- 注册链接 (跳转注册页)
|
||||
- 错误提示 (用户名/密码错误, 账户锁定 15 分钟等)
|
||||
- 成功后跳转 /dashboard
|
||||
|
||||
## 4. 用户管理页面
|
||||
|
||||
修改 frontend/src/pages/UserManagementPage.tsx:
|
||||
- 使用 Table 组件
|
||||
- 列: 用户名, 邮箱, 角色(下拉可改), 状态(active/inactive), 审批状态, 创建时间, 操作
|
||||
- 仅 owner 可见操作按钮
|
||||
- 角色修改: owner 可改, API PATCH /users/{id}
|
||||
- 审批: owner 可点, API POST /users/{id}/approve
|
||||
- 分页: 每页 20 条
|
||||
|
||||
## 5. 审计日志页面
|
||||
|
||||
修改 frontend/src/pages/AuditLogPage.tsx:
|
||||
- 使用 Table 组件
|
||||
- 列: 时间, 用户, 操作(action), 目标(target), 详情(detail), IP
|
||||
- action 筛选: 下拉列表 (全部/keys.batch_overwrite/gateway.toggle/cron.trigger/...)
|
||||
- 分页: 每页 20 条
|
||||
- 仅 owner 可见
|
||||
|
||||
## 6. 路由守卫
|
||||
|
||||
修改 App.tsx:
|
||||
- 未登录: 任何页面 → 重定向 /login
|
||||
- 登录后: 根据角色隐藏/显示导航项和操作按钮
|
||||
- Viewer 看不到: 用户管理、审计日志、Gateway 启停、密钥覆盖、Cron 操作
|
||||
|
||||
完成后提交:
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add frontend/src/api/ frontend/src/hooks/useAuth.ts frontend/src/pages/LoginPage.tsx frontend/src/pages/UserManagementPage.tsx frontend/src/pages/AuditLogPage.tsx frontend/src/App.tsx && git commit -m "feat: frontend auth flow (login, user management, audit logs, route guards)"
|
||||
```
|
||||
@@ -0,0 +1,114 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/ 实现 Dashboard 增强(P0-3)。后端 API + 前端页面。
|
||||
|
||||
## 后端: Dashboard API
|
||||
|
||||
创建 backend/app/api/dashboard.py,所有路由需 require_auth。
|
||||
|
||||
### 1. GET /api/v1/dashboard/summary
|
||||
返回聚合统计数据:
|
||||
```json
|
||||
{
|
||||
"online_count": 12,
|
||||
"offline_count": 46,
|
||||
"total_profiles": 58,
|
||||
"running_tasks": 3,
|
||||
"cron_failures_today": 1,
|
||||
"updated_at": "2026-07-22T16:00:00"
|
||||
}
|
||||
```
|
||||
数据来源: hermes CLI (`hermes gateway status` 解析) + Kanban 聚合 + Cron 统计。如需真实数据可先返回 Mock 数据(标注 TODO: replace with CLI)。
|
||||
|
||||
### 2. GET /api/v1/dashboard/health-distribution
|
||||
返回健康分四级分布:
|
||||
```json
|
||||
{
|
||||
"A": 22, "B": 18, "C": 10, "D": 8, "total": 58,
|
||||
"d_profiles": ["ops-engineer", "monitor", "visualizer", "hebo", "..."]
|
||||
}
|
||||
```
|
||||
暂用占位数据,Phase 4 实现真实计算。
|
||||
|
||||
### 3. GET /api/v1/dashboard/gateway-trend
|
||||
返回最近 24h Gateway 异常事件按小时聚合:
|
||||
```json
|
||||
[
|
||||
{"hour": "2026-07-22T08:00", "restarts": 2, "feishu_disconnects": 1, "heartbeat_timeouts": 0},
|
||||
...
|
||||
]
|
||||
```
|
||||
暂用占位数据。
|
||||
|
||||
### 4. GET /api/v1/dashboard/kanban-summary
|
||||
返回活跃看板概览:
|
||||
```json
|
||||
[
|
||||
{"board": "europe-briefing", "running": "S4", "ready": "S5"},
|
||||
{"board": "tech-weekly", "running": "S1", "ready": null}
|
||||
]
|
||||
```
|
||||
暂用占位数据。
|
||||
|
||||
### 5. GET /api/v1/dashboard/cron-recent
|
||||
返回最近 Cron 执行记录:
|
||||
```json
|
||||
[
|
||||
{"job": "taiwan-briefing", "status": "success", "time": "07:15"},
|
||||
{"job": "hindsight", "status": "failure", "time": "21:13"},
|
||||
{"job": "guangxi-donation", "status": "paused"}
|
||||
]
|
||||
```
|
||||
暂用占位数据。
|
||||
|
||||
### 6. GET /api/v1/search?q=keyword
|
||||
全局搜索: 搜索 Profile 名、任务名、Cron 名、事件关键词。返回分组结果:
|
||||
```json
|
||||
{
|
||||
"profiles": [{"name": "gateway-monitor", "status": "online"}],
|
||||
"kanban_tasks": [{"title": "...", "column": "RUNNING"}],
|
||||
"cron_jobs": [{"job": "...", "status": "active"}],
|
||||
"events": [{"title": "...", "status": "active"}]
|
||||
}
|
||||
```
|
||||
暂用占位数据,含模糊匹配逻辑。
|
||||
|
||||
## 前端: Dashboard 页面
|
||||
|
||||
修改 frontend/src/pages/DashboardPage.tsx:
|
||||
|
||||
### 统计卡片行 (4 列 grid)
|
||||
- 在线 Gateway (绿色数值 + "↑ N vs 昨天")
|
||||
- 离线 Gateway (红色数值 + "↓ N vs 昨天")
|
||||
- 运行中任务 (蓝色数值)
|
||||
- 今日 Cron 失败 (红色数值,失败>3 标红)
|
||||
|
||||
### 左侧: 蜂群健康分布
|
||||
- 使用 Card 组件 + 自定义柱状图 (CSS bar chart)
|
||||
- A(绿) B(蓝) C(黄) D(红) 四柱
|
||||
- 点击柱跳转 /health?filter=X
|
||||
|
||||
### 右侧: Gateway 异常趋势 (24h)
|
||||
- 使用 Timeline 组件,展示 24h 内事件点分布
|
||||
- 图例: 🟢正常 🔴心跳超时 🟠飞书断连
|
||||
|
||||
### 底部: Kanban 概览 + Cron 最近执行
|
||||
- 两列并排
|
||||
- Kanban 概览: 看板名 + RUNNING 任务 + READY 任务
|
||||
- Cron 最近: Job 名 + 状态(✅/❌/⏸) + 时间
|
||||
|
||||
### 搜索框
|
||||
- 已在 Header 中通过 SearchBox 组件实现
|
||||
- 输入关键词 → 调 /api/v1/search → 下拉面板分组展示
|
||||
- 点击结果跳转对应页面
|
||||
|
||||
### 四态覆盖
|
||||
- Loading: Skeleton (card 形状)
|
||||
- Empty: "暂无数据"
|
||||
- Error: 错误提示 + 重试按钮
|
||||
- Normal: 数据正常展示
|
||||
|
||||
注册路由: 在 main.py 添加 `from app.api import dashboard; app.include_router(dashboard.router, prefix="/api/v1")`
|
||||
|
||||
完成后提交:
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add backend/app/api/dashboard.py backend/app/main.py frontend/src/pages/DashboardPage.tsx frontend/src/components/layout/Header.tsx && git commit -m "feat: Dashboard with summary cards, health distribution, gateway trend, global search"
|
||||
```
|
||||
@@ -0,0 +1,84 @@
|
||||
实现 Gateway 健康监控(P0-1)。后端 Hermes CLI 封装 + API + 前端页面。
|
||||
|
||||
## 第一部分: Hermes CLI 服务(真实数据源)
|
||||
|
||||
创建 backend/app/services/hermes_cli.py:
|
||||
- 封装 `hermes gateway status` CLI 调用
|
||||
- 用 subprocess.run 执行,解析 JSON 输出
|
||||
- 出错时返回空数据 + 日志告警,不抛异常
|
||||
- 函数: gateway_status_all() -> list[GatewayInfo]
|
||||
- 数据类型: id, name, status(online/offline/warn), downtime_minutes, restarts_today, last_event
|
||||
|
||||
## 第二部分: Gateway API
|
||||
|
||||
创建 backend/app/api/gateway.py,全部 require_auth
|
||||
|
||||
### GET /api/v1/gateway/status
|
||||
返回全部 Profile 的 Gateway 状态列表,支持:
|
||||
- query param `?status=online|offline|warn` 筛选
|
||||
- 每行: name, status(🟢/🔴/🟡), downtime, restarts_today, last_event, profile_type
|
||||
- 数据来源: hermes_cli.gateway_status_all()
|
||||
|
||||
### GET /api/v1/gateway/events
|
||||
返回最近 24h 异常事件:
|
||||
```json
|
||||
[
|
||||
{"time": "2026-07-22T18:45", "profile": "monitor", "type": "heartbeat_timeout", "detail": "连续 3 次心跳无响应"},
|
||||
{"time": "2026-07-22T14:20", "profile": "feishu-gw", "type": "feishu_disconnect", "detail": "WebSocket 断开 8 分钟"},
|
||||
{"time": "2026-07-22T10:30", "profile": "gateway-monitor", "type": "restart", "detail": "Gateway 重启"}
|
||||
]
|
||||
```
|
||||
数据来源: hermes CLI 日志或系统日志解析。暂用占位数据(TODO 标注)。
|
||||
|
||||
### POST /api/v1/gateway/{profile}/start, /stop, /restart
|
||||
Gateway 启停操作:
|
||||
- 执行 `hermes gateway start/stop/restart --profile {profile}`
|
||||
- 需要 operator 或 owner 角色(require_role("owner","operator"))
|
||||
- 返回操作结果 + 写入审计日志
|
||||
- 操作前二次确认(前端处理)
|
||||
|
||||
## 第三部分: 前端 Gateway 页面
|
||||
|
||||
修改 frontend/src/pages/GatewayPage.tsx:
|
||||
|
||||
### 顶部: 异常事件时间线
|
||||
- Timeline 组件,24h 横向展示
|
||||
- 事件类型: 🔴心跳超时 🟠飞书断连 🔵Gateway 重启
|
||||
- Hover 显示详情 tooltip
|
||||
|
||||
### 筛选栏
|
||||
- 状态筛选: 全部/在线/离线/异常
|
||||
- 蜂型筛选: 全部/工蜂/蜂王
|
||||
- 筛选器即时生效(前端过滤,不重新请求)
|
||||
|
||||
### 状态表
|
||||
- Table 组件,10s 轮询 (usePolling hook)
|
||||
- 列: Profile 名, 状态 Badge, 掉线时长, 今日重启, 最近异常, 操作
|
||||
- 异常行标红: Gateway 连续重启≥3次 或 掉线≥30分钟
|
||||
- 操作按钮: 启动/停止/重启 (二次确认 Modal)
|
||||
|
||||
### 四态覆盖
|
||||
- Loading: Skeleton (table 形状)
|
||||
- Empty: "全部 Gateway 正常运行 🎉"
|
||||
- Error: 错误 + 重试
|
||||
- Normal: 数据展示
|
||||
|
||||
### usePolling hook
|
||||
创建 frontend/src/hooks/usePolling.ts:
|
||||
```typescript
|
||||
import { useEffect, useRef } from 'react'
|
||||
export function usePolling(callback: () => void, intervalMs: number, enabled: boolean = true) {
|
||||
const savedCallback = useRef(callback)
|
||||
useEffect(() => { savedCallback.current = callback }, [callback])
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
savedCallback.current() // 立即执行一次
|
||||
const id = setInterval(() => savedCallback.current(), intervalMs)
|
||||
return () => clearInterval(id)
|
||||
}, [intervalMs, enabled])
|
||||
}
|
||||
```
|
||||
|
||||
注册路由: main.py 添加 `from app.api import gateway; app.include_router(gateway.router, prefix="/api/v1")`
|
||||
|
||||
完成后提交。
|
||||
@@ -0,0 +1,73 @@
|
||||
实现 Profile 健康分(P0-2)。评分引擎 + API + 前端页面。
|
||||
|
||||
## 第一部分: 健康分计算引擎
|
||||
|
||||
创建 backend/app/services/health_scorer.py:
|
||||
|
||||
评分规则(PRD §P0-2):
|
||||
- Gateway 稳定性 40%: 7天在线率×40 + 日均重启≤0.3得满分
|
||||
- 配置完整性 25%: 必填字段检查(.env+config.yaml),缺1个扣10%
|
||||
- Skill 一致性 20%: 与基准 Skill 清单对比
|
||||
- Cron 成功率 15%: 24h Cron 成功率×15
|
||||
|
||||
等级: A(80-100) B(60-79) C(40-59) D(0-39)
|
||||
|
||||
函数:
|
||||
- calculate_health_score(profile_name: str) -> HealthScore
|
||||
- calculate_all_scores() -> list[HealthScore]
|
||||
- 数据来源: 读取 .env + config.yaml 文件, 调 hermes CLI 获取 Gateway/Cron 状态
|
||||
- 暂用占位数据(TODO: connect to real data sources),但评分逻辑要完整实现
|
||||
- 评分结果缓存到 score_results dict, 每天凌晨自动刷新
|
||||
|
||||
## 第二部分: Health API
|
||||
|
||||
创建 backend/app/api/health.py,全部 require_auth
|
||||
|
||||
### GET /api/v1/health/scores
|
||||
返回全部 Profile 健康分排行列表
|
||||
- query: ?grade=A|B|C|D&sort=asc|desc
|
||||
- 默认: 从低到高(D 排最前)
|
||||
- 每行: rank, name, total_score, grade, gateway_score, config_score, skill_score, cron_score, deduct_summary
|
||||
|
||||
### GET /api/v1/health/scores/{profile}
|
||||
返回单 Profile 详情:
|
||||
- 四维度得分/满分 + 具体扣分项列表 + 修复建议
|
||||
|
||||
### POST /api/v1/health/compare
|
||||
body: {"profile_names": ["hebo", "ops-engineer"]}
|
||||
返回对比表: 横向 Profile, 纵向配置/Skill 字段, ✅/❌/⚠
|
||||
|
||||
### POST /api/v1/health/calculate
|
||||
手动触发全量评分计算 (需 owner)
|
||||
|
||||
## 第三部分: 前端 Health 页面
|
||||
|
||||
修改 frontend/src/pages/HealthPage.tsx:
|
||||
|
||||
### 顶部: 四级分布卡片
|
||||
- 4 个 Card: A(绿) B(蓝) C(黄) D(红)
|
||||
- 显示数量 + 等级标签
|
||||
- 点击卡片筛选对应等级
|
||||
|
||||
### 筛选栏
|
||||
- 等级筛选: 全部 A B C D
|
||||
- 对比按钮: 选中≥2 个 Profile 后启用
|
||||
- 排序切换: 默认问题优先(D→A)
|
||||
|
||||
### 评分列表
|
||||
- Table 组件
|
||||
- 列: 排名, Profile名, 总分(颜色编码), 等级 Badge, 四维度分, 主要扣分项, 操作
|
||||
- 可展开行: 点击展开四维度扣分明细面板(内嵌展开,不跳转)
|
||||
- D 级行标红
|
||||
|
||||
### 批量对比视图
|
||||
- 勾选 2-20 个 Profile → "对比选中"按钮
|
||||
- 对比表: 横向 Profile, 纵向字段, ✅有/❌无/⚠版本不同
|
||||
- 差异行黄色背景高亮
|
||||
- 支持"只看差异"切换
|
||||
|
||||
### 四态覆盖
|
||||
|
||||
注册路由: main.py 添加 health router。
|
||||
|
||||
完成后提交。
|
||||
@@ -0,0 +1,63 @@
|
||||
实现 Cron 管理增强(P0-4b)。后端 API + 前端页面。
|
||||
|
||||
## 后端: Cron API
|
||||
|
||||
创建 backend/app/api/cron.py,全部 require_auth
|
||||
|
||||
### 1. GET /api/v1/cron/jobs
|
||||
跨 Profile Cron 聚合列表,支持 ?status=active|paused&profile=all|xxx|system
|
||||
- 每行: job_name, profile, schedule, status, last_run(time+✅/❌), success_rate, has_logs
|
||||
- 数据来源: hermes CLI `hermes cron list --all` + 系统 Cron 硬编码清单
|
||||
- 系统 Cron 示例: Hindsight(0 21 * * *), 健康巡检(*/30 * * * *), Skill检查(0 2 * * *), Memory归档(0 3 * * 0)
|
||||
- 系统 Cron 行有 [system] 标记
|
||||
- Cron 连续失败≥2次 → 行标红
|
||||
- 当日有失败时返回 has_failures: true
|
||||
|
||||
### 2. GET /api/v1/cron/jobs/{id}/logs
|
||||
返回最近 8 条运行日志:
|
||||
```json
|
||||
{"logs": [{"time": "", "status": "success|failure", "message": "..."}]}
|
||||
```
|
||||
数据来源: hermes CLI + 日志文件。暂用占位数据。
|
||||
|
||||
### 3. POST /api/v1/cron/jobs/{id}/run, /pause, /resume
|
||||
操作 Cron (需 operator+):
|
||||
- run: 手动触发一次
|
||||
- pause: 暂停 Cron
|
||||
- resume: 恢复 Cron
|
||||
- 操作结果写入审计日志
|
||||
|
||||
## 前端: Cron 页面
|
||||
|
||||
修改 frontend/src/pages/CronPage.tsx:
|
||||
|
||||
### 筛选栏
|
||||
- 状态筛选: 全部/活跃/暂停/今日失败
|
||||
- Profile 筛选: 全部/各 Profile/仅系统 Cron
|
||||
- 前端即时过滤
|
||||
|
||||
### Cron 列表
|
||||
- Table 组件, 30s 轮询
|
||||
- 列: Job名, Profile, Cron表达式(等宽字体), 状态 Badge, 上次运行(时间+✅❌), 成功率(<80%标黄), 操作
|
||||
- 系统 Cron 灰底 + [system] 标记
|
||||
- 连续失败≥2 行标红
|
||||
|
||||
### 日志面板
|
||||
- 每行操作列 "📋 日志" 按钮
|
||||
- 点击弹出居中面板(遮罩+Modal)
|
||||
- 标题: "{job} — 最近运行日志"
|
||||
- 列表(最多8条): 时间戳, 状态✅/❌, 日志消息(INFO/WARN/ERROR 按级别着色)
|
||||
- 失败消息红色背景, 包含完整错误详情
|
||||
- 关闭: ✕ / 点遮罩 / Esc
|
||||
|
||||
### 操作按钮
|
||||
- 运行(二次确认), 暂停, 恢复
|
||||
- Toast 结果反馈
|
||||
|
||||
### 导航栏 Badge
|
||||
- Dashboard 今日 Cron 失败计数同步
|
||||
- 导航栏 Cron 项显示红色 Badge(失败数)
|
||||
|
||||
### 四态覆盖
|
||||
|
||||
注册路由, 完成后提交。
|
||||
@@ -0,0 +1,82 @@
|
||||
实现密钥批量管理(P0-5)。后端 .env 读写 + API + 前端页面。
|
||||
|
||||
## 后端: Key Manager 服务
|
||||
|
||||
创建 backend/app/services/key_manager.py:
|
||||
|
||||
核心功能:
|
||||
- 读取 Profile 的 .env 文件中指定密钥字段
|
||||
- 脱敏显示: 前3字符 + "**...**" + 后4字符 (如 "sk-**...**b12c")
|
||||
- 白名单字段: DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, FEISHU_APP_ID, FEISHU_APP_SECRET
|
||||
- 批量覆盖: 写入新值到 .env 文件 (操作前自动备份 .env → .env.bak)
|
||||
- 批量检查: 扫描全部 Profile 的 .env, 返回缺失字段列表
|
||||
- 差异视图: 对比各 Profile 同一字段的值 (只显示有无和长度, 不显示原文)
|
||||
- 不写入审计日志原文(只记录 action + profile 名 + 字段名)
|
||||
|
||||
## 后端: Keys API
|
||||
|
||||
创建 backend/app/api/keys.py, 全部 require_role("owner","operator")
|
||||
|
||||
### GET /api/v1/keys
|
||||
返回密钥字段列表:
|
||||
```json
|
||||
[{"profile": "hebo", "fields": {"DEEPSEEK_API_KEY": "sk-**...**b12c", ...}, "missing": 0}]
|
||||
```
|
||||
|
||||
### POST /api/v1/keys/batch-overwrite
|
||||
批量覆盖密钥:
|
||||
- body: {profile_names: [...], key_name: "DEEPSEEK_API_KEY", new_value: "sk-xxx", confirmation: "我确认覆盖 N 个 Profile"}
|
||||
- 验证 confirmation 短语精确匹配
|
||||
- 逐 Profile 写入 + 返回成功/失败列表
|
||||
|
||||
### POST /api/v1/keys/batch-scan
|
||||
批量检查缺失:
|
||||
- body: {profile_names: [...]} 或全部 Profile
|
||||
- 返回缺失统计 + 详情
|
||||
|
||||
### GET /api/v1/keys/diff
|
||||
差异视图:
|
||||
- query: ?profiles=hebo,ops-engineer
|
||||
- 返回对比表: 横向 Profile, 纵向字段, 值状态(有/无/长度)
|
||||
|
||||
### GET /api/v1/keys/export
|
||||
CSV 导出批量检查结果
|
||||
|
||||
## 前端: Keys 页面
|
||||
|
||||
修改 frontend/src/pages/KeysPage.tsx:
|
||||
|
||||
### 操作栏
|
||||
- [批量覆盖] 按钮 → Modal
|
||||
- [批量检查] 按钮 → 扫描结果展示
|
||||
- Profile 筛选下拉
|
||||
- 搜索密钥字段
|
||||
|
||||
### 密钥列表
|
||||
- Table 组件
|
||||
- 列: ☐复选框, Profile名, 各密钥字段(脱敏显示), 缺失计数
|
||||
- 缺失字段标红 ❌
|
||||
- 差异视图切换
|
||||
|
||||
### 批量覆盖 Modal
|
||||
- 下拉选择密钥字段
|
||||
- 输入新值(密码框, 可切换明文)
|
||||
- 预览: "将覆盖 N 个 Profile 的 {KEY_NAME}"
|
||||
- 当前值脱敏预览
|
||||
- 输入确认短语: "我确认覆盖 N 个 Profile"
|
||||
- 确认后执行 → 进度提示 → 成功/失败列表
|
||||
|
||||
### 批量检查结果
|
||||
- 显示扫描结果表: Profile, 缺失字段列表, 缺失数
|
||||
- 支持导出 CSV (下载按钮)
|
||||
- 缺失行标红
|
||||
|
||||
### 差异视图
|
||||
- 勾选 2+ Profile → "查看差异"
|
||||
- 对比表: 横向 Profile, 纵向字段, ✅有/❌无
|
||||
- 差异行黄色高亮
|
||||
- "只看差异" 切换
|
||||
|
||||
### 四态覆盖 + 审计日志
|
||||
|
||||
注册路由, 完成后提交。
|
||||
@@ -0,0 +1,84 @@
|
||||
实现 Kanban 增强(P0-4)。后端 API + 前端五列看板页面。
|
||||
|
||||
核心定位: Kanban 是蜂群任务的电子沙盘——观察全貌、发现异常。不做批量 dispatch/创建任务/goal_mode 配置(这些走 Hermes CLI)。
|
||||
|
||||
## 后端: Kanban API
|
||||
|
||||
创建 backend/app/api/kanban.py,全部 require_auth
|
||||
|
||||
### 1. GET /api/v1/kanban/tasks
|
||||
跨 Profile 五列任务聚合:
|
||||
- 数据来源: hermes CLI `hermes kanban list` 解析
|
||||
- 支持 ?worker=xxx&board=xxx&anomaly=blocked_timeout|unclaimed|retry_failure
|
||||
- 返回五列数据: {todo: [...], ready: [...], running: [...], blocked: [...], done: [...]}
|
||||
- 概览条: total_tasks, profile_count, running_count, blocked_count
|
||||
- 暂用 Mock 数据(TODO: CLI integration)
|
||||
|
||||
### 2. GET /api/v1/kanban/tasks/{id}
|
||||
任务详情:
|
||||
- Meta: board, worker_profile, worker_type, priority, created_at, updated_at, goal_mode, retries/failures
|
||||
- Body: 任务描述(Markdown)
|
||||
- 关联: 依赖任务, 被依赖任务
|
||||
- 活动: 最近操作时间线
|
||||
|
||||
### 3. PATCH /api/v1/kanban/tasks/{id}/move
|
||||
拖拽移动:
|
||||
- body: {column: "ready"|"running"|"blocked"|"done", block_reason?: "阻塞原因(≥10字)"}
|
||||
- 拖入 BLOCKED 需填写阻塞原因
|
||||
- 拖入 DONE 二次确认(前端)
|
||||
|
||||
### 4. 异常检测(内置逻辑)
|
||||
服务端计算任务异常标记:
|
||||
- blocked_timeout: BLOCKED 列超 24h → 标红
|
||||
- unclaimed: TODO 列超 3 天 → 标黄
|
||||
- retry_failure: 失败重试≥3 次 → 标红
|
||||
|
||||
## 前端: Kanban 页面
|
||||
|
||||
修改 frontend/src/pages/KanbanPage.tsx:
|
||||
|
||||
### 顶部概览条
|
||||
- "当前 N 个任务 · M 个 Profile · K 个 RUNNING · B 个阻塞"
|
||||
- RUNNING 蓝色, 阻塞橙色
|
||||
- 右侧筛选下拉: Worker / 看板 / 异常状态
|
||||
|
||||
### 五列看板
|
||||
- TODO / READY / RUNNING / BLOCKED / DONE
|
||||
- 每列头: 列名(彩色) + 任务计数
|
||||
- BLOCKED 列橙色边框 + 左边框
|
||||
- 列宽 min 240px, 五列一屏可见
|
||||
- 列内容纵向滚动, 列头 sticky
|
||||
|
||||
### 任务卡片
|
||||
- 标题(粗体, 最多两行) + Worker Profile 名 + 蜂型图标
|
||||
- 状态标记: 🎯 goal_mode, ⚡ 高优先级
|
||||
- 创建时间(相对时间 "2h ago")
|
||||
- BLOCKED 列卡片显示阻塞原因(一行截断)
|
||||
- 卡片左侧色条: P0=红, goal_mode=金, blocked=橙
|
||||
- 卡片 hover 边框变 honey
|
||||
- 异常卡片: 阻塞超时红色, 长期无主黄色, 失败重试红色
|
||||
- 点击卡片 → 打开详情抽屉
|
||||
- 卡片 Checkbox (hover 显示)
|
||||
|
||||
### 拖拽
|
||||
- 桌面端支持拖拽卡片到任意列
|
||||
- 拖入 BLOCKED 弹出阻塞原因输入框(必填 ≥10字)
|
||||
- 拖入 DONE 二次确认
|
||||
- 移动端禁用拖拽
|
||||
|
||||
### 任务详情抽屉 (Drawer 组件, 480px)
|
||||
- Header: 状态 Badge + 标题(可编辑) + ✕
|
||||
- Meta: 看板, Worker, 类型, 优先级, 创建/更新时间, goal_mode, 重试次数
|
||||
- Body: Markdown 描述, goal_mode 验收标准 checklist(可勾选), 阻塞原因
|
||||
- 关联: 依赖/被依赖任务链接(点击跳转)
|
||||
- 活动: 时间线(操作人+时间)
|
||||
- Footer: [编辑] [标记阻塞] [标记完成] [删除]
|
||||
|
||||
### 批量操作栏(底部浮现)
|
||||
- 勾选≥1 张卡片后底部浮现操作栏
|
||||
- [标记完成] [标记阻塞] [清除选择]
|
||||
- 不做 Dispatch(走 CLI)
|
||||
|
||||
### 四态覆盖 + 乐观更新(拖拽后即时更新 UI,失败回滚)
|
||||
|
||||
注册路由, 完成后提交。
|
||||
@@ -0,0 +1,73 @@
|
||||
实现事件档案管理(P0-7)。后端数据爬取 + API + 前端卡片页面。
|
||||
|
||||
## 后端: Event Crawler
|
||||
|
||||
创建 backend/app/services/event_crawler.py:
|
||||
- 爬取 hm.zhiweidata.ai/events 数据
|
||||
- 存入 events 表 (新建 ORM 模型)
|
||||
- 定时每天凌晨执行
|
||||
- 暂用占位数据(TODO: real crawler)
|
||||
|
||||
## 后端: Event Model
|
||||
|
||||
创建 backend/app/models/event.py:
|
||||
```python
|
||||
class EventStatus(str, enum.Enum):
|
||||
ACTIVE = "active"
|
||||
FADING = "fading"
|
||||
DISSIPATING = "dissipating"
|
||||
CLOSED = "closed"
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "events"
|
||||
id: str (UUID)
|
||||
title: str
|
||||
level: str (A/B/C)
|
||||
domain: str (12个领域之一)
|
||||
status: EventStatus
|
||||
occurred_date: date
|
||||
last_reviewed: datetime | None
|
||||
no_new_info_count: int (连续无新知次数)
|
||||
next_review: datetime | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
创建 Alembic 迁移。
|
||||
|
||||
## 后端: Events API
|
||||
|
||||
创建 backend/app/api/events.py,全部 require_auth
|
||||
|
||||
### GET /api/v1/events
|
||||
事件卡片列表,三维筛选(前端即时过滤):
|
||||
- query: ?status=active|fading|dissipating|closed&domain=xxx&search=keyword
|
||||
- 每行含超期标记: overdue_days (下一次复查逾期天数)
|
||||
- 空结果返回 []
|
||||
|
||||
## 前端: Event Archive 页面
|
||||
|
||||
修改 frontend/src/pages/EventArchivePage.tsx:
|
||||
|
||||
### 筛选工具栏
|
||||
- 状态筛选: 全部/活跃期/消减期/消散期/已完结
|
||||
- 领域筛选: 12 个领域下拉
|
||||
- 搜索框: 按标题模糊搜索
|
||||
- 前端即时过滤,组合生效
|
||||
- [+ 新建事件] 按钮(P0 先做占位 Toast)
|
||||
|
||||
### 事件卡片网格
|
||||
- 自适应列数 grid (minmax 340px)
|
||||
- 每张卡片:
|
||||
- 标题(粗体)
|
||||
- 等级 Badge + 领域 Tag (蓝色)
|
||||
- 状态 Badge(绿/黄/灰)
|
||||
- Meta: 发生日期, 上次复查, 连续无新知(≥5标黄), 下次复查
|
||||
- 超期标记: 红色 "超期 N 天"
|
||||
- Hover: 边框变 honey-dim
|
||||
- 点击: Toast 显示事件标题(P1 升级详情面板)
|
||||
|
||||
### 四态覆盖
|
||||
- Empty: "无匹配事件"
|
||||
|
||||
注册路由 + 迁移 + 提交。
|
||||
@@ -0,0 +1,91 @@
|
||||
请创建以下 3 个文件。backend 目录已存在,但 backend/app/ 需要新建。
|
||||
|
||||
## 文件 1: backend/app/__init__.py
|
||||
空文件。
|
||||
|
||||
## 文件 2: backend/app/config.py
|
||||
```python
|
||||
"""应用配置 — 从环境变量 / .env 读取。"""
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
database_url: str = "postgresql+asyncpg://hive_admin:changeme@localhost:5432/hive_manage_v3"
|
||||
jwt_secret: str = "change-me"
|
||||
jwt_algorithm: str = "HS256"
|
||||
jwt_expire_minutes: int = 1440
|
||||
hermes_cli_path: str = "/home/enzo/.local/bin/hermes"
|
||||
hermes_profiles_root: str = "/home/enzo/.hermes/profiles"
|
||||
admin_email: str = "admin@zhiwei.local"
|
||||
admin_password: str = "admin123"
|
||||
|
||||
model_config = {"env_file": ".env"}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
## 文件 3: backend/app/database.py
|
||||
```python
|
||||
"""异步 SQLAlchemy 引擎 + session 工厂。"""
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from app.config import settings
|
||||
|
||||
engine = create_async_engine(settings.database_url, echo=False)
|
||||
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
```
|
||||
|
||||
## 文件 4: backend/app/main.py
|
||||
```python
|
||||
"""FastAPI 应用入口。"""
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.database import engine, Base
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Hive Manage v3",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/v1/health")
|
||||
async def health_check():
|
||||
return {"status": "ok"}
|
||||
```
|
||||
|
||||
创建完成后,执行以下验证:
|
||||
1. `cat` 检查每个文件内容
|
||||
2. `cd backend && pip install -e ".[dev]"` 安装依赖
|
||||
3. `cd backend && python -c "from app.main import app; print('FastAPI app created OK')"` 验证导入
|
||||
4. git add + git commit -m "feat: FastAPI skeleton with config, database, health endpoint"
|
||||
@@ -0,0 +1,122 @@
|
||||
请完成以下任务。项目在 /home/enzo/projects/hive-manage-v3/,已有 backend/ 目录和 venv (.venv/)。
|
||||
|
||||
## 步骤 1: 初始化 Alembic
|
||||
|
||||
在 backend/ 目录执行:
|
||||
```
|
||||
cd backend && .venv/bin/alembic init alembic
|
||||
```
|
||||
|
||||
## 步骤 2: 修改 backend/alembic/env.py
|
||||
|
||||
配置异步引擎,关键改动:
|
||||
- 导入 `from app.database import Base, engine`
|
||||
- 导入 `from app.models import * # noqa: F401,F403`
|
||||
- `target_metadata = Base.metadata`
|
||||
- 重写 `run_migrations_online` 以使用异步引擎:
|
||||
|
||||
```python
|
||||
from app.config import settings
|
||||
from app.database import Base, engine
|
||||
from app.models import * # noqa: F401,F403
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
def run_migrations_offline():
|
||||
url = settings.database_url
|
||||
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
def do_run_migrations(connection):
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
async def run_async_migrations():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(do_run_migrations)
|
||||
|
||||
def run_migrations_online():
|
||||
import asyncio
|
||||
asyncio.run(run_async_migrations())
|
||||
```
|
||||
|
||||
## 步骤 3: 创建 backend/app/models/__init__.py
|
||||
|
||||
空的 __init__.py。
|
||||
|
||||
## 步骤 4: 创建 backend/app/models/user.py
|
||||
|
||||
```python
|
||||
"""用户 ORM 模型。"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, Boolean, DateTime, Enum as SAEnum
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
import enum
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
OWNER = "owner"
|
||||
OPERATOR = "operator"
|
||||
VIEWER = "viewer"
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
|
||||
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
||||
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
role: Mapped[UserRole] = mapped_column(SAEnum(UserRole), default=UserRole.VIEWER, nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
is_approved: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
login_attempts: Mapped[int] = mapped_column(default=0)
|
||||
locked_until: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
```
|
||||
|
||||
## 步骤 5: 创建 backend/app/models/audit.py
|
||||
|
||||
```python
|
||||
"""审计日志 ORM 模型。"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
user_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||
username: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
target: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
detail: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
```
|
||||
|
||||
## 步骤 6: 生成并执行迁移
|
||||
|
||||
```bash
|
||||
cd backend && .venv/bin/alembic revision --autogenerate -m "create users and audit_logs tables"
|
||||
cd backend && .venv/bin/alembic upgrade head
|
||||
```
|
||||
|
||||
## 步骤 7: 验证并提交
|
||||
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add backend/alembic/ backend/app/models/ && git commit -m "feat: Alembic setup, User and AuditLog models with migration"
|
||||
```
|
||||
|
||||
请严格按照以上步骤执行。遇到 pip/alembic 命令时使用 backend/.venv/bin/ 前缀。
|
||||
@@ -0,0 +1,156 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/frontend/ 下创建 React 19 + TypeScript + Vite + Tailwind 前端脚手架。
|
||||
|
||||
## 步骤 1: 创建 frontend/package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "hive-manage-v3-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0",
|
||||
"react-dom": "^19.0",
|
||||
"react-router-dom": "^7.0",
|
||||
"axios": "^1.7",
|
||||
"lucide-react": "^0.460"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0",
|
||||
"@types/react-dom": "^19.0",
|
||||
"@vitejs/plugin-react": "^4.3",
|
||||
"autoprefixer": "^10.4",
|
||||
"postcss": "^8.4",
|
||||
"tailwindcss": "^3.4",
|
||||
"typescript": "^5.6",
|
||||
"vite": "^6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 步骤 2: 创建配置文件
|
||||
|
||||
frontend/tsconfig.json:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
frontend/vite.config.ts:
|
||||
```typescript
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': 'http://127.0.0.1:8000',
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
frontend/tailwind.config.ts:
|
||||
```typescript
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
honey: { DEFAULT: '#f0b90b', dim: '#b8970e' },
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config
|
||||
```
|
||||
|
||||
frontend/postcss.config.js:
|
||||
```js
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 步骤 3: 创建入口文件
|
||||
|
||||
frontend/index.html:
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hive Manage v3</title>
|
||||
</head>
|
||||
<body class="bg-[var(--bg-root)] text-[var(--text)]">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
frontend/src/main.tsx:
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode><App /></React.StrictMode>
|
||||
)
|
||||
```
|
||||
|
||||
frontend/src/App.tsx:
|
||||
```tsx
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center" style={{background:'var(--bg-root)', color:'var(--text)'}}>
|
||||
<h1 className="text-2xl font-bold">Hive Manage v3</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 步骤 4: 安装依赖并验证
|
||||
|
||||
```bash
|
||||
cd frontend && npm install
|
||||
```
|
||||
|
||||
## 步骤 5: 提交
|
||||
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add frontend/ && git commit -m "feat: Vite + React 19 + Tailwind + TypeScript scaffold"
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/frontend/src/styles/ 下创建 CSS 变量双模体系文件 tokens.css。
|
||||
|
||||
内容必须精确如下(这是 UI 设计规范的核心,不可改动):
|
||||
|
||||
```css
|
||||
/* ===== 暗色模式(默认) ===== */
|
||||
:root {
|
||||
--bg-root: #0f1117;
|
||||
--bg-nav: #161822;
|
||||
--bg-card: #1a1d2e;
|
||||
--bg-hover: #1e2134;
|
||||
--bg-elevated: #222639;
|
||||
--border: #2a2d3e;
|
||||
--border-light: rgba(42, 45, 62, 0.5);
|
||||
--honey: #f0b90b;
|
||||
--honey-dim: #b8970e;
|
||||
--honey-glow: rgba(240, 185, 11, 0.12);
|
||||
--green: #00c853;
|
||||
--red: #ff3d3d;
|
||||
--yellow: #ffab00;
|
||||
--blue: #448aff;
|
||||
--orange: #ff6d00;
|
||||
--text: #e0e0e0;
|
||||
--text-dim: #8b8d9a;
|
||||
--text-muted: #5a5d6a;
|
||||
--bg-success: #0d2818;
|
||||
--bg-error: #2d0f0f;
|
||||
--bg-warn: #2d2000;
|
||||
--bg-info: #0d1b2d;
|
||||
--radius: 8px;
|
||||
--radius-sm: 4px;
|
||||
--radius-lg: 12px;
|
||||
--radius-full: 9999px;
|
||||
--shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
|
||||
--shadow-modal: 0 24px 64px rgba(0, 0, 0, 0.65);
|
||||
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-mono: "SF Mono", "Fira Code", Consolas, monospace;
|
||||
--header-h: 48px;
|
||||
--sidebar-w: 220px;
|
||||
}
|
||||
|
||||
/* ===== 浅色模式 ===== */
|
||||
[data-theme="light"] {
|
||||
--bg-root: #f5f6fa;
|
||||
--bg-nav: #ffffff;
|
||||
--bg-card: #ffffff;
|
||||
--bg-hover: #edf0f5;
|
||||
--bg-elevated: #ffffff;
|
||||
--border: #e2e5ee;
|
||||
--border-light: rgba(0, 0, 0, 0.06);
|
||||
--honey: #e5a800;
|
||||
--honey-dim: #c48f00;
|
||||
--honey-glow: rgba(229, 168, 0, 0.08);
|
||||
--green: #008c3a;
|
||||
--red: #d32f2f;
|
||||
--yellow: #e65100;
|
||||
--blue: #1565c0;
|
||||
--orange: #d84315;
|
||||
--text: #1a1d2e;
|
||||
--text-dim: #555a6e;
|
||||
--text-muted: #8b8f9e;
|
||||
--bg-success: #e8f5e9;
|
||||
--bg-error: #fdecea;
|
||||
--bg-warn: #fff3e0;
|
||||
--bg-info: #e3f2fd;
|
||||
--shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||
--shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
/* ===== 全局基础 ===== */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font);
|
||||
font-size: 13px;
|
||||
background: var(--bg-root);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* 主题切换过渡 */
|
||||
* {
|
||||
transition: background-color 0.15s, border-color 0.15s, color 0.15s;
|
||||
}
|
||||
```
|
||||
|
||||
创建后,在 frontend/src/main.tsx 中添加 `import './styles/tokens.css'`。
|
||||
|
||||
完成后提交:
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add frontend/src/styles/ && git commit -m "feat: CSS variable dual-theme system (dark default + light)"
|
||||
```
|
||||
@@ -0,0 +1,106 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/frontend/src/components/ui/ 下创建 11 个 React + TypeScript 共享 UI 组件。
|
||||
|
||||
所有组件必须:
|
||||
- 使用 CSS 变量(如 var(--bg-card), var(--text), var(--honey) 等,定义在 tokens.css)
|
||||
- 支持 className prop 扩展
|
||||
- 导出类型(Props interface)
|
||||
- 使用 Tailwind 辅助类 + CSS 变量混合
|
||||
|
||||
## 组件 1: Button.tsx
|
||||
|
||||
Props: variant ('primary'|'danger'|'outline'|'ghost'), size ('sm'|'md'), loading, disabled, children, onClick, className, type
|
||||
- Primary: bg=var(--honey) color=#000 font-weight:600
|
||||
- Danger: border=var(--red) color=var(--red)
|
||||
- Outline: border=var(--border) color=var(--text-dim)
|
||||
- Ghost: color=var(--text-dim), border:none
|
||||
- 尺寸: sm padding:4px 10px font-size:12px, md padding:7px 14px font-size:13px
|
||||
- Loading 态: 文字透明 + 14px spinner
|
||||
- Disabled: opacity:0.4 cursor:not-allowed
|
||||
|
||||
## 组件 2: Card.tsx
|
||||
|
||||
Props: label, value, valueColor, change, changeDirection ('up'|'down'|'neutral'), onClick, className
|
||||
- 渲染: 顶部 label(12px uppercase), 中间 value(28px bold), 底部 change(12px)
|
||||
- valueColor 支持: 'green','red','yellow','blue' 映射到对应 CSS 变量
|
||||
- changeDirection: up=var(--green) '↑', down=var(--red) '↓', neutral=var(--text-dim) '→'
|
||||
|
||||
## 组件 3: Table.tsx
|
||||
|
||||
Props: columns ({key,label,width?}[]), data (record[]), rowKey, onRowClick, rowClassName (record=>string), emptyText
|
||||
- 表头 sticky top:0, 12px uppercase text-dim
|
||||
- 行高 44px, hover 时 bg=var(--bg-hover)
|
||||
- 支持 row-danger 样式(bg=var(--bg-error))
|
||||
- 空数据: 显示 emptyText 或 "暂无数据"
|
||||
|
||||
## 组件 4: Badge.tsx
|
||||
|
||||
Props: variant ('success'|'danger'|'warning'|'info'|'honey'|'outline'), children, className
|
||||
- 胶囊形: padding:2px 8px, border-radius:10px, font-size:11px, font-weight:600
|
||||
- 每种 variant 对应不同背景色(rgba 半透明)+ 文字色
|
||||
|
||||
## 组件 5: Modal.tsx
|
||||
|
||||
Props: open, onClose, title, children, footer, confirmText, onConfirm, confirmDanger, width
|
||||
- 遮罩: rgba(0,0,0,0.6), 居中 flex
|
||||
- 弹窗: bg=var(--bg-nav), border-radius:8px, max-width:500px
|
||||
- 关闭方式: ✕ 按钮 / 点遮罩 / Esc 键
|
||||
- confirmDanger 时确认按钮变红色
|
||||
- Portal 渲染到 document.body
|
||||
|
||||
## 组件 6: Drawer.tsx
|
||||
|
||||
Props: open, onClose, title, children, footer, width (默认 480px)
|
||||
- 右侧滑入: transform translateX(100%→0), transition 250ms ease
|
||||
- 遮罩 + Esc 关闭
|
||||
- Header 固定顶部, Body 可滚动, Footer 固定底部
|
||||
|
||||
## 组件 7: Toast.tsx
|
||||
|
||||
导出: toast 函数(命令式调用),非组件
|
||||
```typescript
|
||||
toast.success('操作成功')
|
||||
toast.error('操作失败')
|
||||
toast.warn('请注意')
|
||||
toast.info('提示信息')
|
||||
```
|
||||
- 顶部居中, 最大宽度 480px
|
||||
- success 3s 消失, error 5s 消失, warn 手动关闭
|
||||
- 堆叠: 最多 3 条
|
||||
- 动画: translateY + opacity
|
||||
|
||||
## 组件 8: Skeleton.tsx
|
||||
|
||||
Props: variant ('card'|'table'|'text'), count (行数)
|
||||
- card: 矩形块占位
|
||||
- table: 横向条占位
|
||||
- text: 短条占位
|
||||
- shimmer 动画: linear-gradient 1500ms infinite
|
||||
|
||||
## 组件 9: Toggle.tsx
|
||||
|
||||
Props: checked, onChange, label, disabled
|
||||
- 44×24px, 滑块 20×20px 白色
|
||||
- Off: bg=var(--bg-hover), On: bg=var(--honey)
|
||||
- 滑块动画 translateX(20px)
|
||||
- 标签在右侧, gap 8px, font-size 13px
|
||||
|
||||
## 组件 10: Timeline.tsx
|
||||
|
||||
Props: events ({time, type, label, color?}[])
|
||||
- 横向时间线, flex 布局
|
||||
- 每个点 8px 直径, 线 2px var(--border)
|
||||
- Hover 点上浮 2px + tooltip
|
||||
- 颜色: green(心跳恢复), red(超时), orange(飞书断连), blue(重启)
|
||||
|
||||
## 组件 11: SearchBox.tsx
|
||||
|
||||
Props: onSearch, placeholder, debounceMs (默认 300)
|
||||
- 输入框: bg=var(--bg-card), border focus 变 var(--honey)
|
||||
- 下拉面板: 分组结果, bg=var(--bg-elevated)
|
||||
- 键盘: ↑↓ 导航, Enter 选择, Esc 关闭
|
||||
- 宽度: 桌面 420px
|
||||
|
||||
创建完成后提交:
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add frontend/src/components/ui/ && git commit -m "feat: shared UI component library (11 components)"
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
在 /home/enzo/projects/hive-manage-v3/frontend/src/components/layout/ 下创建布局组件,并在 App.tsx 中集成路由框架。
|
||||
|
||||
## 组件 1: Sidebar.tsx
|
||||
|
||||
导航项结构(参考原型):
|
||||
- 蜂群态势: 📊 蜂群总览, 📡 Gateway, 🏥 健康分, 🤖 工蜂列表
|
||||
- 任务编排: 📋 看板追踪, ⏰ Cron, 🔔 事件管理
|
||||
- 通信频道: 📶 频道配置
|
||||
- 运维工具: 🔑 密钥, 📁 文件, 💻 终端, 📜 日志, 🔌 MCP接入, 📤 MCP对外
|
||||
- 知识库管理: 📚 事件档案
|
||||
- 系统: ⚙ 设置
|
||||
|
||||
Props: activePath, onNavigate(path:string)
|
||||
- 宽度 220px, bg=var(--bg-nav), 右侧 border
|
||||
- 顶部 Logo 48px: "🐝 知微蜂巢"
|
||||
- Section 标题 11px uppercase text-muted
|
||||
- NavItem: 默认 text-dim, hover bg-hover, active honey-glow + honey 文字 + 600 weight
|
||||
- Badge: 红色(失败计数)
|
||||
|
||||
## 组件 2: Header.tsx
|
||||
|
||||
Props: breadcrumb, onlineCount, runningCount, onSearch, theme, onToggleTheme
|
||||
- 高度 48px, bg=var(--bg-nav), 底部 border
|
||||
- 左侧: Logo + 面包屑
|
||||
- 中间: 搜索框 260px, bg=var(--bg-card)
|
||||
- 右侧: 在线数/运行中 标签 + 主题切换按钮(🌙/☀️) + 用户头像
|
||||
- useTheme hook: 读取 localStorage 'hive-theme', 默认跟随系统 prefers-color-scheme
|
||||
|
||||
## 组件 3/4: MobileNav.tsx + TabBar.tsx
|
||||
|
||||
MobileNav: 汉堡菜单 → 左侧抽屉 280px 滑入 + 遮罩
|
||||
TabBar: 底部固定 56px, 4项(📊总览/📡Gateway/📋Kanban/⏰Cron)等宽分布, 激活态 honey
|
||||
|
||||
## 组件 5: AppLayout.tsx
|
||||
|
||||
桌面: CSS Grid (sidebar 220px + header 48px + main)
|
||||
平板: sidebar 180px
|
||||
移动 (<768px): 顶部 Header + TabBar + 汉堡抽屉
|
||||
使用 useTheme hook 管理 data-theme 属性
|
||||
|
||||
## App.tsx 更新
|
||||
|
||||
集成 react-router-dom:
|
||||
- / → 重定向到 /dashboard
|
||||
- /dashboard → DashboardPage
|
||||
- /gateway → GatewayPage
|
||||
- /health → HealthPage
|
||||
- /kanban → KanbanPage
|
||||
- /cron → CronPage
|
||||
- /keys → KeysPage
|
||||
- /events → EventArchivePage
|
||||
- /users → UserManagementPage
|
||||
- /audit → AuditLogPage
|
||||
- /login → LoginPage
|
||||
- 其余 → 404
|
||||
|
||||
路由配置用懒加载 React.lazy + Suspense(Skeleton)。
|
||||
|
||||
## useTheme hook
|
||||
|
||||
```typescript
|
||||
// hooks/useTheme.ts
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Theme = 'dark' | 'light' | 'system'
|
||||
|
||||
export function useTheme() {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
return (localStorage.getItem('hive-theme') as Theme) || 'system'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement
|
||||
if (theme === 'system') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
root.setAttribute('data-theme', prefersDark ? 'dark' : 'light')
|
||||
} else {
|
||||
root.setAttribute('data-theme', theme)
|
||||
}
|
||||
localStorage.setItem('hive-theme', theme)
|
||||
}, [theme])
|
||||
|
||||
const toggle = () => setTheme(t => t === 'dark' ? 'light' : 'dark')
|
||||
return { theme, setTheme, toggle }
|
||||
}
|
||||
```
|
||||
|
||||
创建完成后提交:
|
||||
```bash
|
||||
cd /home/enzo/projects/hive-manage-v3 && git add frontend/src/components/layout/ frontend/src/App.tsx frontend/src/hooks/ && git commit -m "feat: layout components (Sidebar, Header, MobileNav, TabBar, AppLayout) with routing"
|
||||
```
|
||||
Reference in New Issue
Block a user