Files
yingqing/docs/todo/admin-后台-进度.md
T
seaislee1209andClaude Opus 4.8 cdfcffcecd feat(admin): Phase 9 收尾治理 — 全局项目监控 + 数据完整性体检
后端:adminpanel projects(全局项目流水线监控+状态筛)+ integrity(数据体检:无账户团队/无图商品/过期邀请码/
无文件资产/失败项目/卡 reserved 计数 + totals);AdminProjectSerializer;IsPlatformAdmin。
前端:adminApi adminProjects/integrity;Admin 治理页(体检卡片超0高亮 + 项目监控表状态筛)。
测试:adminpanel 53 单测过(项目列/筛/体检报告);无头 e2e _admin-p9.mjs 4 断言过 + 0 console error(全只读);tsc+build 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:51:49 +08:00

224 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 平台 Admin 后台 + 注册邀请码 · 落地进度
> 计划:`C:/Users/Air-work/.claude/plans/gleaming-sleeping-ullman.md`(Phase 09 全做)。
> 每个 Phase 走完「★测试底座」(后端单测 + 无头浏览器 e2e + tsc/build + 回归)才 commit。
> 全程本地、不 push;测试服 DB 可动。
---
## 测试基线(回归红线)
- `apps.accounts / apps.billing / apps.products`:全绿(Phase 0 后 15/15 OK)。
- `apps.projects`:**进 Phase 0 前已有 3 failures + 1 error**(均为 AI provider `image_edit` mock 相关,与本工程无关)——**非本次引入**,Phase 0 改动仅在 accounts + 前端,未触碰 projects/ai。后续 Phase 不得新增 fail。
---
## ✅ Phase 0 · 基础(角色 + 壳 + 审计)— 完成 2026-06-19
**后端**
- `User.is_platform_admin`(bool)+ migration `0004_user_is_platform_admin_adminauditlog`(已 apply 到测试 MySQL)。
- `apps/accounts/permissions.py` · `IsPlatformAdmin`(超管放行 / 普通拒 / 停用拒)。
- `apps/accounts/management/commands/create_platform_admin.py` · 建 **admin/admin123**(幂等;`--reset-password` 才重置;也置 `is_staff`)。已对测试 MySQL 执行,真账号可登。
- `AdminAuditLog` 模型(operator/operator_name/action/target_*/before/after/ip)+ `apps/accounts/audit.py` `log_admin_action()`(失败不阻断主流程)。
- `me` / `login` 对**无团队的平台超管**优雅返回 `team=null`(新增 `current_team_or_none`);`UserSerializer` 暴露 `is_platform_admin`
**前端**
-`routes/admin/admin-app.tsx`:超管后台外壳(折叠分组侧栏 6 组 11 项 + 顶栏 + 概览 + 占位页),全程复用 restraint 共享类。
- 路由:`route-config.ts` 解析 `/admin``/admin/<section>`;`App.tsx` 分流渲染 `AdminApp` + gating(超管无团队登录直落 /admin、非超管访问 /admin 纠回工作台)。
- `app-shell.tsx` 侧栏「平台」入口(仅超管可见)。
- `admin-page.css`(仅 token,无裸 hex)+ `IconKitSvg` 补 8 个 Lucide-line 图标。
**测试底座(全过)**
- 后端单测:`apps.accounts` **11/11 OK**(新增 `PlatformAdminTests`:建超管命令幂等/权限类放行拒绝/登录+me 返回标志且 team 为空/普通注册非超管)。
- 无头 e2e:`core/qa/visual-parity/_admin-p0.mjs` —— **全部断言通过 + 0 console error**:
- admin/admin123 真登录 → 落 /admin;
- 超管壳:6 分组 / 11 导航 / `[ PLATFORM ADMIN ]` 徽标 / 概览标题 / 点「邀请码」→ /admin/invites 占位「模块即将上线」;
- 普通用户访问 /admin → 纠回 /dashboard、admin 壳不渲染。
- 截图:`output/admin/p0-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:概览截图核对 —— 冷灰底 + 单橙 accent + 8px 圆角 + 准星,符合 design.md 克制调性。
**测试账号**:平台超管 `admin/admin123`(测试 MySQL 已建);普通 demo `airshelf/Restraint2026`
> **e2e 端口提示**:5173 被同机另一项目(AirDrama)抢占且不稳;AirShelf e2e 改用**专用端口 5188**(`npx vite --port 5188 --strictPort`)。e2e 脚本默认 BASE 已指向 5188。
---
## ✅ Phase 1 · 注册邀请码逻辑 — 完成 2026-06-19
**后端**
- `Invitation.kind`(join_team / create_team)+ `team` 可空 + migration `0005_invitation_kind_alter_invitation_team`(已 apply 测试 MySQL)。
- `RegisterSerializer`:**invite_code 必填**;按 kind 分流 —— create_team 开新团队当 owner + 发试用额度(回填 invite.team),join_team 幂等加入该团队、不重复发额度;**删除「空码=开团队」**。
- `validate-invite` 端点(免登录,POST `/api/auth/validate-invite/`):验码 → `{valid, kind, role, team_name?}`,无效始终 200+`valid:false`
- 团管发码恒为 `kind=join_team`;`InvitationSerializer``kind`/`team`/`team_name`
**前端**
- 注册页重做(码优先):先填邀请码 → 点「验证」(或 Enter)→ 验过才展开;create_team 显示「团队名」输入,join_team 显示**只读团队名**(不暴露团队列表);CTA 文案随 kind 切换(创建团队 / 加入团队)。
- 邀请链接 `?invite=CODE` 进页自动回填 + 验码;改码即重置验证态。
- `api.validateInvite`;`field-note.ok/err` + `invite-verify-row` + 只读团队样式(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.accounts` **22/22 OK**(新增 `RegisterInviteCodeTests` 6 项 + `ValidateInviteTests` 5 项:无码拒/空码拒/create_team 开团队+试用额度/join_team 入伙不开新团队/已用·过期拒/验码返回正确 kind+team_name)。
- 无头 e2e:`_admin-p1.mjs`(端口 5188)—— **全断言过 + 0 console error**:create_team 验码→展开团队名→注册落工作台;join_team 验码→只读团队名「演示团队」→入伙;错码不展开+报错;URL 自动验码。截图 `output/admin/p1-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:两态注册截图核对 —— 黑块等高、验证按钮对齐、绿色 ok 提示、只读团队名灰底,符合 restraint。
---
## ✅ Phase 2 · 邀请码发放管理(Admin)— 完成 2026-06-19
**后端(新建 `apps/adminpanel/`)**
- 新 Django app `apps.adminpanel`(跨团队后台端点容器),注册进 INSTALLED_APPS + 主 urls `path("api/admin/", ...)`
- `GET/POST /api/admin/invitations/`:列所有邀请码(跨团队,按 kind/status/search 过滤 + 分页);POST 平台超管发 create_team「开团队码」(写审计 `invite.issue_create_team`)。
- `POST /api/admin/invitations/<id>/revoke/`:撤销待用码(写审计 `invite.revoke`)。
- 全程 `IsPlatformAdmin`,非超管 403。
**前端**
- `adminApi`(api.ts 分层):invitations / createInvite / revokeInvite。
- Admin「邀请码」页(`routes/admin/admin-invites.tsx`):类型筛选段控(全部/开团队码/加入码)+ 表格(码/类型/团队/状态徽章/创建时间/使用者)+「发开团队码」弹窗 + 复制邀请链接(剪贴板 + 反馈)+ 撤销;接入 AdminApp(本地 toast notify)。
- `Invitation` 类型补 kind/team/team_name;admin-page.css 补段控/表格/弹窗样式(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **9 项 + apps.accounts 22 = 31 OK**(列权限 403/发码+审计/跨团队列表/kind 过滤/撤销+审计/端到端凭发出的码注册开团队/撤销后码失效)。
- 无头 e2e:`_admin-p2.mjs`(5188)—— **6 断言全过 + 0 console error**:页面加载、发码弹窗、新码入列、复制链接反馈「已复制」、**端到端用 UI 发的码 API 注册开团队 201**。截图 `output/admin/p2-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:列表截图核对 —— 类型/状态橙/中性 pill、段控黑激活、成功 toast,符合 restraint。
- 回归:accounts 22 仍全绿;live 后端自动 reload 后 `/api/admin/invitations/` 200(admin)/401(匿名)。
---
## ✅ Phase 3 · 平台团队 + 用户管理(Admin)— 完成 2026-06-19
**后端(apps/adminpanel 扩展)**
- 团队:`GET teams/`(列跨团队 + status/search 过滤分页,带 owner/成员数/余额)、`GET teams/<id>/`(详情含成员)、`POST teams/<id>/toggle/`(启停 + 审计)。
- 用户:`GET users/`(列跨团队 + 过滤分页,带所属团队)、`POST users/<id>/toggle/`(启停,停用清 token;**不允许停用平台超管**)、`POST users/<id>/reset-password/`(强制改密,清 token)。
- **团队停用拦登录**:`login` 校验当前团队 `status`,停用团队的成员一律拒登(平台超管无团队不受影响)。
-`apps/adminpanel/serializers.py`(AdminTeam/AdminTeamMember/AdminUser);全程 IsPlatformAdmin + 写审计。
**前端**
- `adminApi`:teams/teamDetail/toggleTeam/users/toggleUser/resetUserPassword;类型 AdminTeam/AdminTeamDetail/AdminUser。
- Admin「团队」页(状态筛选 + 搜索 + 表格 + 详情弹窗含成员 + 启停)、「用户」页(筛选 + 搜索 + 表格 + 启停 + 改密弹窗;超管行带「超管」标记且禁操作)。
- admin-page.css 补 search/宽弹窗/详情元信息样式(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **19 项 + accounts 22 = 41 OK**(团队列字段/详情/启停拦登录再恢复/用户列含团队/启停拦登录/禁停超管/强制改密生效旧密失效/权限 403)。
- 无头 e2e:`_admin-p3.mjs`(5188,用一次性 P3 团队不动 demo)—— **7 断言全过 + 0 console error**:团队页+搜索+详情弹窗;**UI 停用团队→该成员 API 登录 400→启用→200**;用户页超管标记;**UI 改密→新密码登录 200**。截图 `output/admin/p3-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:团队/用户表格、绿/红状态 pill、详情弹窗,符合 restraint。
---
## ✅ Phase 4 · 质量词 Admin(平台单层)— 完成 2026-06-19
**后端**
- 新模型 `apps/ai/models.QualityWord`(stage + slot + text + sort + enabled,平台单层无 team)+ migration `0009_qualityword`(已 apply 测试 MySQL)。
- 生成侧 `services.py``quality_words()/quality_suffix()`,4 个 builder 接入(`build_person_frontal_prompt` / `build_model_tryon_prompt_refs` / `build_storyboard_frame_prompt` / `build_video_segment_prompt`)——**无配置回落原写死值,零回归**。
- adminpanel CRUD:`GET/POST quality-words/` + `PATCH/DELETE quality-words/<id>/`,IsPlatformAdmin + 审计(create/update/delete);QualityWordSerializer。
**前端**
- `adminApi` qualityWords/create/update/delete;`AdminQualityWord` 类型。
- Admin「质量词」页(`admin-quality.tsx`):按 stage(人物立绘/模特上身图/故事板/视频)分组卡片 + **编辑模式开关防误删** + 行内增(回车)/改(失焦保存)/删(× 仅编辑态显示);橙色 chip + 虚线添加框(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **23 项 + accounts 22 = 45 OK**(质量词 CRUD/权限 403/非法 stage 拒/**生成侧读配置 + 无配置回落 + 描述保留**/停用词不取用)。
-`apps.ai` 有 3 个 `StandaloneImageReferenceTests`(image_edit 路由)失败 —— **已 git stash 比对确认:HEAD(未改 services.py)同样失败,为进场前既有,非本 Phase 引入**(本 Phase 只改提示词字符串内容,不动 provider 路由)。
- 无头 e2e:`_admin-p4.mjs`(5188,跑 live 库故用唯一标记词且**结束兜底清理**)—— **4 断言全过 + 0 console error**:页面 4 卡、编辑模式加词→chip 显示+API 持久化、删词→API 移除;live 库零残留。截图 `output/admin/p4-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:分组卡 + 橙 chip + 编辑态,符合 restraint。
---
## ✅ Phase 5 · 火山人像审核队列(Admin)— 完成 2026-06-19
**后端(apps/adminpanel 扩展,复用 assets/review.py)**
- `GET asset-reviews/`:跨团队真人(person)资产队列(`?review_status=none|processing|active|failed` 筛 + 分页);`AdminReviewAssetSerializer`(team_name + 首图 preview_url)。
- `POST asset-reviews/submit/`(批量送审 / 失败重试,逐个 `submit_asset_for_review`)+ `POST asset-reviews/poll/`(轮询 processing,`poll_asset_review`);IsPlatformAdmin + 审计。
- **性能修复**:序列化器 `get_preview_url` 用 prefetch 缓存 `list(obj.files.all())[0]` 取代 `.first()`,消除列表页 N+1(100 条从 ~5s 降到 ~1s)。
**前端**
- `adminApi` assetReviews/submitReviews/pollReviews;`AdminReviewAsset` 类型。
- Admin「资产审核」页:状态筛选(全部/未送审/审核中/通过/失败)+ 表格(多选 + 缩略图 + 团队 + 状态盾绿/红/灰 + 失败/未送审行「重试/送审」)+ 吸底 bulk-bar「批量送审」+「刷新状态」(只读 poll);page_size 60。
- admin-page.css 补 checkbox 列/缩略图/红标记/bulk-bar(仅 token;`.bulk-bar` design 未实现,本次补齐)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **28 项 OK**(队列只列 person + 跨团队/状态筛 4 态/批量送审计数+审计(mock 火山)/送审权限 403/poll 只轮询 processing(mock))。
- 无头 e2e:`_admin-p5.mjs`(5188;wrapper 预建 1 条 person 资产保证有行,跑完删除)—— **5 断言全过 + 0 console error**:页面+5筛选 tab+刷新按钮、60 行、多选出 bulk-bar+批量送审可点(**不点真送审**,避免 Volcano 真消耗)、刷新状态(只读 poll)。截图 `output/admin/p5-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:队列表 + 绿/红/灰状态盾,符合 restraint。
---
## ✅ Phase 6 · AI 任务监控 + 成本异常(Admin)— 完成 2026-06-19
**后端**
- `GET tasks/`(全局 AITask,?status/task_type/team/anomaly 筛 + 分页)+ `GET tasks/<id>/`(详情含 request/response payload + error)+ `POST tasks/<id>/retry/`(失败重投)。
- 成本异常:`is_cost_anomaly`(实际 > 预估 × 1.5 且预估>0)+ `?anomaly=1` F() 表达式筛;AdminTaskSerializer 暴露 `cost_anomaly`
- 重投 best-effort:仅 FAILED + 图像类(基础资产→generate_base_asset_task / 独立生图→generate_standalone_image_task),其余类型 400(不冒险误投);IsPlatformAdmin + 审计。
**前端**
- `adminApi` tasks/taskDetail/retryTask;`AdminTask`/`AdminTaskDetail` 类型。
- Admin「任务监控」页:状态筛 tab(全部/失败/成功)+「仅成本异常」chip + 表格(类型/团队/模型/状态/成本 预估→实际+异常标/时间)+ **详情抽屉**(payload JSON)+ 失败行「重投」。
- admin-page.css 补 JSON 块 / 抽屉宽度 / 异常 chip(仅 token)。**修真 bug**:抽屉缺 `.show` 类导致面板停在屏外,补上后正常滑入。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **36 项 OK**(列权限/状态·类型·异常筛/成本异常标志/详情 payload/重投 dispatch(mock celery)+ 非失败拒 + 不支持类型拒 + 权限 403)。
- 无头 e2e:`_admin-p6.mjs`(5188;wrapper 建 1 条 failed+异常任务,跑完删)—— **6 断言全过 + 0 console error**:页面+3tab+异常chip、60 行、详情抽屉含 JSON、失败 tab(43 条真实失败)首行有重投、异常筛精确 1 条;**不点真重投**。截图 `output/admin/p6-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
- 视觉:任务表 + 详情抽屉(成本⚠ / 错误红框 / payload),符合 restraint。
---
## ✅ Phase 7 · 计费审计 + 4 层额度策略(Admin)— 完成 2026-06-19
**后端**
- 计费:`GET ledgers/`(全局信用流水 + type/team 筛分页)+ `POST ledgers/adjust/`(手动调额,落 ADJUSTMENT 流水 + 更新余额,调后不许为负);`adjust_credit` helper(billing/services/ledger.py)。
- 额度策略:`GET/POST quota-policies/` + `PATCH/DELETE quota-policies/<id>/`;AdminQuotaPolicySerializer。
- **额度拦截落地**:`_enforce_quota_policy`(单任务/团队月度/项目 三类限额)接入 `reserve_credit`,**仅当团队有 active 团队级策略才生效 → 无策略零回归**。
- **审计 helper 加固**:`log_admin_action` 用 savepoint 包裹 + `_json_safe`(UUID/Decimal/datetime 转换),修复「传 serializer.data(含 UUID)致 JSONField 报错 → 吞异常但污染外层事务 → 后续查询 TransactionManagementError」的真 bug。
**前端**
- `adminApi` ledgers/adjustCredit/quotaPolicies/create/update/delete;`AdminLedger`/`AdminQuotaPolicy` 类型。
- Admin「计费审计」页(类型筛 + 流水表 + 手动调额弹窗:团队下拉/金额±/备注)+「额度策略」页(策略表 + 新建/编辑/删除弹窗:团队下拉 + 月度/项目/单任务上限 + 启用开关)。
- admin-page.css 补开关行(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **44 项 + billing + accounts = 70 OK**(流水筛/调额±+审计/超额拒/额度策略 CRUD/**额度拦截 per_task+monthly 生效 + 无策略·停用不拦(零回归)**/权限 403);billing 既有测试全绿(reserve_credit 改动零回归)。
- 无头 e2e:`_admin-p7.mjs`(5188;用一次性 P7 团队,不动 demo 余额;建的策略跑完删)—— **6 断言全过 + 0 console error**:计费页(80 流水)+ 手动调额→API 验持久化、额度策略页 + 新建→API 验 + UI 删→API 验空。截图 `output/admin/p7-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
---
## ✅ Phase 8 · 模型供应商管理(Admin)— 完成 2026-06-19
**后端**
- `ModelConfig.is_default`(bool)+ migration `0010`;`get_default_model` 优先取 `is_default=True` 的 active 模型,**未钦定回落原「最早 active」(零回归)**。
- providers:`GET/POST providers/` + `PATCH/DELETE providers/<id>/`(启停 / 改 base_url / 改 key);**api_key write-only**(只写不回传,列表给 has_api_key 布尔)。
- models:`GET/POST models/`(?provider/capability 筛)+ `PATCH/DELETE models/<id>/`(改定价 / 启停)+ `POST models/<id>/set-default/`(同 capability 其余清默认)。IsPlatformAdmin + 审计。
**前端**
- `adminApi` providers/models/setDefault 全套;`AdminProvider`/`AdminModel` 类型。
- Admin「模型供应商」页:供应商表(名/Base URL/密钥已配/模型数/启停/编辑)+ 模型表(供应商/能力/定价/状态/默认标/设默认/编辑/启停/删除)+ 供应商弹窗 + 模型弹窗。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **51 项 OK**(供应商列权限/api_key 隐藏+入库/启停/模型列筛/模型 CRUD/**set-default 改变 get_default_model + 清旧默认**/权限 403);`apps.ai` 仍只 3 个进场前既有失败(get_default_model 改动零回归)。
- 无头 e2e:`_admin-p8.mjs`(5188;⚠ 该页驱动真实路由 → 用一次性「禁用」供应商+模型 capability=export,set-default 作用在禁用模型无副作用,跑完 API 删)—— **5 断言全过 + 0 console error**:页面渲染、UI 设默认、UI 改定价→9、UI 启停。截图 `output/admin/p8-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。
---
## ✅ Phase 9 · 收尾治理(Admin)— 完成 2026-06-19
**后端**
- `GET projects/`:全局项目流水线监控(?status/search 筛 + 分页),`AdminProjectSerializer`(team_name + product_title + status + current_stage)。
- `GET integrity/`:数据完整性体检(只读)—— 无账户团队 / 无图商品 / 过期未标邀请码 / 无文件资产 / 失败项目 / 卡 reserved 任务 计数 + totals(teams/users/projects/products)。IsPlatformAdmin。
- 覆盖 plan 四项:项目监控(全)+ 数据完整性(全)+ 商品治理(体检含无图商品)+ 通知/孤儿(体检计数)。
**前端**
- `adminApi` adminProjects/integrity;`AdminProjectRow`/`AdminIntegrity` 类型。
- Admin「治理」页:数据体检卡片(超 0 高亮橙)+ 项目监控表(状态筛 tab + 团队/商品/状态/阶段/时间)。
- admin-page.css 补体检卡(仅 token)。
**测试底座(全过)**
- 后端单测:`apps.adminpanel` **53 项 OK**(项目列权限/状态筛/行带团队+商品名/体检报告含 products_without_image≥1 + failed_projects≥1 + totals)。
- 无头 e2e:`_admin-p9.mjs`(5188,全只读零副作用)—— **4 断言全过 + 0 console error**:页面 7 tab + 运行体检按钮、54 项目行、体检 6 卡、失败筛选。截图 `output/admin/p9-*.png`
- 类型/构建:`tsc --noEmit` + `npm run build` 全绿。