diff --git a/deploy/tksea-portal/admin-batch-import.html b/deploy/tksea-portal/admin-batch-import.html index 62828a5a..33e3fa07 100644 --- a/deploy/tksea-portal/admin-batch-import.html +++ b/deploy/tksea-portal/admin-batch-import.html @@ -3,415 +3,78 @@ - Batch Import 管理台 + Batch Import Admin · 管理台 + + -
- +
+ -
-
-
Batch Import Admin
-

供应商批量导入管理页

-

- 这个页面只做三件事:发起 batch import、查看 run 摘要、拉取 item 级复用结果。 - 后端仍然以现有 `POST /api/batch-import/runs` 与 `GET /api/batch-import/runs/*` 为准, - 页面不引入额外协议。默认通过同域 `portal-admin-api` 访问 CRM。 +

+
+ Batch Import +

live batch-import:拉取 run 与 item 级别的 account_resolution

+

+ 这页继续负责 live batch-import:创建 run、拉取 run summary、查看 item 级别的 + matched_account_stateaccount_resolution。批量导入第三方 key,验证 + reused / created / reactivated / replaced 状态语义。

-
    -
  • 直接展示 `matched_account_state`
  • -
  • 直接展示 `account_resolution`
  • -
  • 复用 / 快速启用 / 替换 一眼可见
  • +
      +
    • 默认 API Base:/portal-admin-api
    • +
    • Runs 列表:/api/batch-import/runs
    • +
    • Run 详情:/api/batch-import/runs/{run_id}/items
    -
- +
+
+
+

Run Items

+

-

+
+
+
- -
+

发起导入

@@ -575,7 +238,412 @@ https://api.other.com/v1|sk-example-2|deepseek-chat,gpt-5.4

+ + + - + diff --git a/deploy/tksea-portal/admin/accounts.html b/deploy/tksea-portal/admin/accounts.html index 32fd689c..bc6511e6 100644 --- a/deploy/tksea-portal/admin/accounts.html +++ b/deploy/tksea-portal/admin/accounts.html @@ -4,424 +4,97 @@ Provider Accounts Admin + + -
- +
+ -
-
-
Provider Accounts
-

把导入结果升级成可读、可筛选、可启停的帐号资产库存

-

- 这页直接消费 /api/provider-accounts 与三个启停动作,把每条供应商帐号摊开到 - provider / logical_group / route / shadow_group / shadow_host 维度。 - 当前首版明确只修改插件 SQLite 里的帐号资产状态,不假装已经联动修改宿主 account 记录。 -

-
    -
  • 默认 API Base:/portal-admin-api
  • -
  • 列表会先做一次 provider_accounts 回填
  • -
  • 人工 disabled / deprecated 不会被列表刷新刷回 active
  • +
    +
    + Provider Accounts +

    把 provider_accounts 库存与归属整理收进同一面

    +

    这页把导入结果收成插件侧 provider_accounts 库存,直接展示帐号属于哪个 logical_group / route / shadow_group / shadow_host,并提供人工 enable / disable / retire 动作。显式整理归属是冲突(conflict)下的关键流程。

    +
      +
    • 默认 API Base:/portal-admin-api
    • +
    • 启停只改插件库存,不直接改宿主 account 记录
    • +
    • 显式整理归属:/binding
    -
- - +
- -
+

连接与过滤

@@ -588,7 +261,458 @@
+ + + - + diff --git a/deploy/tksea-portal/admin/batch-import.html b/deploy/tksea-portal/admin/batch-import.html index 3da22c44..174e87db 100644 --- a/deploy/tksea-portal/admin/batch-import.html +++ b/deploy/tksea-portal/admin/batch-import.html @@ -5,35 +5,28 @@ Batch Import Admin Redirect + + -
-

正在跳转到导入供应商帐号页面

-

如果浏览器没有自动跳转,请手动打开:

-

/portal/admin-batch-import.html

+
+

正在跳转到导入供应商帐号页面

+

如果浏览器没有自动跳转,请手动打开:

+

/portal/admin-batch-import.html

+ diff --git a/deploy/tksea-portal/admin/index.html b/deploy/tksea-portal/admin/index.html index 5f16377a..7cd315c2 100644 --- a/deploy/tksea-portal/admin/index.html +++ b/deploy/tksea-portal/admin/index.html @@ -1,436 +1,341 @@ - - - Admin Portal - + + + Admin Portal · Sub2API 中继管理 + + -
- +
-
-
-
Admin Portal
-

把新增模型与导入帐号收进同一套入口

-

- 这个入口不再把“新增模型供应商”和“导入供应商帐号”拆散在不同地址。当前版本统一从 - /portal/admin/ 进入:一边看 pack/provider 目录、做 preview/import,一边继续保留 - item 级 reused / reactivated / replaced 的 batch-import 结果面板。 + +

+ + +
+
+ Admin Portal +

把新增模型、导入帐号与 Route 收进同一套入口

+

+ 当前版本统一从 /portal/admin/ 进入:一边看 pack/provider 目录、做 preview/import, + 一边继续保留 item 级 reused / reactivated / replaced 的 batch-import 结果面板。 + 所有写操作都走 CRM /portal-admin-api,浏览器不直连 Git。

-
    -
  • 默认同域走 /portal-admin-api/
  • -
  • 静态页与 CRM API 解耦
  • -
  • 保留旧地址兼容,不打断现有操作
  • -
-
+ + - +
-
-
-

逻辑分组 / 路由

-

- 这页给插件前置路由使用,负责维护 logical_grouppublic_model、 - routeshadow_host_id / shadow_group_id 的关系。当前首版已经能直接调 - /api/logical-groups 系列接口,适合先把 canonical shadow route 收进统一管理面。 -

-
- 打开逻辑分组页 + +
+
+

核心模块

+

每个模块都对应一个 admin 页面 + 同一套 API base(/portal-admin-api)。

+
+
+ +
+ + +
+
+
+
+
+
+

逻辑分组 / 路由

+

logical_group · public_model · route · shadow_*

+
+
+ 运行中 +
+

+ 给插件前置路由使用,维护 logical_grouppublic_modelroute 与 + shadow_host_id / shadow_group_id 的关系。当前首版已经能直接调 + /api/logical-groups 系列接口,适合先把 canonical shadow route 收进统一管理面。 +

+
+ 打开逻辑分组页 + 首版页面只覆盖新增与查看 +
-
    -
  • - 适用动作 - 创建 logical group、绑定 public model、维护 route 与 shadow group 映射。 -
  • -
  • - 默认 API Base - https://sub.tksea.top/portal-admin-api -
  • -
-
-

新增模型 / 供应商目录

-

- 这页负责浏览已安装 pack、选择 provider、调用 preview-import / - import,同时提供 provider manifest 草稿生成与发布。当前版本已经支持先保存草稿,再经由 CRM - 服务端写入 pack/provider 文件并自动提交到仓库。 -

-
- 打开供应商页 - 跳到 manifest 草稿 + +
+
+
+
+
+
+

新增模型 / 供应商目录

+

pack · provider · preview · manifest draft

+
+
+ 推荐入口 +
+

+ 这页负责浏览已安装 pack、选择 provider、调用 preview-import / import, + 同时提供 provider manifest 草稿生成与发布。当前版本已经支持先保存草稿,再经由 CRM 服务端写入 + pack/provider 文件并自动提交到仓库。 +

+
-
    -
  • - 适用动作 - 查看 pack 与 provider、输入 keys 做 preview/import、生成 provider 草稿,并一键发布到仓库。 -
  • -
  • - 默认 API Base - https://sub.tksea.top/portal-admin-api -
  • -
-
-

Route 健康视图

-

- 这页专门给运营看 route 当前运行状态,聚合 routefailroutecool、 - 最近一次选路与 failover 事件。首版只做只读健康视图,不在这里直接改 route。 -

-
- 打开健康页 + +
+
+
+
+
+
+

Route 健康视图

+

healthy · cooldown · failing · disabled

+
+
+ 只读 +
+

+ 这页专门给运营看 route 当前运行状态,聚合 routefailroutecool、 + 最近一次选路与 failover 事件。首版只做只读健康视图,不在这里直接改 route。 +

+
-
    -
  • - 适用动作 - 查看 healthy / cooldown / failing / disabled,确认 sticky、failover 与最近错误是否一致。 -
  • -
  • - 默认 API Base - https://sub.tksea.top/portal-admin-api -
  • -
-
-

帐号资产

-

- 这页把导入结果收成插件侧 provider_accounts 库存,直接展示帐号属于哪个 - logical_group / route / shadow_group / shadow_host,并提供人工 - enable / disable / retire 动作。 -

-
- 打开帐号资产页 + +
+
+
+
+
+
+

帐号资产

+

logical_group · route · shadow_group · shadow_host

+
+
+ 库存 +
+

+ 把导入结果收成插件侧 provider_accounts 库存,直接展示帐号属于哪个 + logical_group / route / shadow_group / shadow_host,并提供人工 + enable / disable / retire 动作。启停动作当前只修改插件库存状态,不直接改宿主 account 记录。 +

+
-
    -
  • - 适用动作 - 查看帐号库存、筛选 route 归属、执行人工启停与退役。 -
  • -
  • - 当前边界 - 启停动作当前只修改插件库存状态,不直接改宿主 account 记录。 -
  • -
+
-
-

导入供应商帐号

-

+ +

+
+
+
+
+
+

导入供应商帐号

+

reused · created · reactivated · replaced

+
+
+ 实时 +
+

这页继续负责 live batch-import:创建 run、拉取 run summary、查看 item 级别的 - matched_account_stateaccount_resolution。 + matched_account_stateaccount_resolution。批量导入第三方 key,验证 + reused / created / reactivated / replaced 状态语义。

-
- 打开导入页 - 旧地址兼容入口 + -
    -
  • - 适用动作 - 批量导入第三方 key,验证 reused / created / reactivated / replaced。 -
  • -
  • - 默认 API Base - https://sub.tksea.top/portal-admin-api -
  • -
-
+
+
+ + +
+
+

当前边界与安全前提

+

明确告诉你"哪里能写、哪里只读、谁有权限",避免误操作。

+
-
-
-
可立即使用
- 逻辑分组 + Provider 导入 -

依赖现有 /api/logical-groups/api/packs/api/providers/*/api/batch-import/* 即可完成。

+
+
+
+
+ + 可立即使用 +
+

逻辑分组 + Provider 导入

+

+ 依赖现有 /api/logical-groups/api/packs、 + /api/providers/*/api/batch-import/* 即可完成。 +

+
-
-
当前边界
- 浏览器提交到 CRM,再由 CRM 写仓库 -

页面不会直接拼 Git 命令;所有写 pack/provider 与提交仓库的动作,都统一走 CRM 服务端的发布接口。

+ +
+
+
+ + 当前边界 +
+

浏览器提交到 CRM,再由 CRM 写仓库

+

+ 页面不会直接拼 Git 命令;所有写 pack/provider 与提交仓库的动作,都统一走 CRM 服务端的发布接口。 +

+
-
-
安全前提
- 仍需 Admin Token -

CRM 的 API 权限仍由 Bearer token 控制,同域反代只解决浏览器可达性,不降低鉴权门槛。

+ +
+
+
+ + 安全前提 +
+

仍需 Admin Token

+

+ CRM 的 API 权限仍由 Bearer token 控制,同域反代只解决浏览器可达性,不降低鉴权门槛。 +

+
+
+ + +
+
+
+
+

管理员会话

+

可在此处建立 session 检查当前鉴权状态,或留作跨页签到。

+
+
+ +
+
+ +
+
+ + + 同域走 /portal-admin-api,调试时可改成完整 URL。 +
+
+ + + 已登录管理员 session 时可不填。 +
+
+ +
+ 点击「检查会话」可拉取 /api/admin/session。 +
+
+
+ + + + + diff --git a/deploy/tksea-portal/admin/logical-groups.html b/deploy/tksea-portal/admin/logical-groups.html index cc94a450..1779ae36 100644 --- a/deploy/tksea-portal/admin/logical-groups.html +++ b/deploy/tksea-portal/admin/logical-groups.html @@ -4,6 +4,7 @@ Logical Group Admin + + + +
+ - * { box-sizing: border-box; } - body { - margin: 0; - color: var(--ink); - font-family: var(--font-sans); - background: - radial-gradient(circle at top left, rgba(11, 107, 203, 0.16), transparent 26rem), - radial-gradient(circle at bottom right, rgba(18, 107, 67, 0.12), transparent 24rem), - var(--bg); - } - a { color: inherit; } - code, pre { - font-family: var(--font-mono); - font-size: 12px; - } - .shell { - max-width: 1480px; - margin: 0 auto; - padding: 34px 20px 64px; - } - .topnav { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 18px; - } - .topnav a { - text-decoration: none; - padding: 10px 14px; - border-radius: 999px; - border: 1px solid var(--line); - background: rgba(255,255,255,0.78); - color: var(--muted); - font-size: 13px; - font-weight: 700; - transition: transform 120ms ease, background 120ms ease; - } - .topnav a:hover { transform: translateY(-1px); background: #fff; } - .topnav a.is-current { - background: var(--ink); - border-color: var(--ink); - color: #fff; - } - .hero { - display: grid; - grid-template-columns: 1.2fr 0.8fr; - gap: 18px; - margin-bottom: 18px; - } - .card { - background: var(--panel); - border: 1px solid var(--line); - border-radius: var(--radius); - box-shadow: var(--shadow); - } - .hero-card, .panel { - padding: 26px; - } - .hero-card { - position: relative; - overflow: hidden; - } - .hero-card::after { - content: ""; - position: absolute; - right: -4rem; - bottom: -4rem; - width: 18rem; - height: 18rem; - border-radius: 999px; - background: linear-gradient(135deg, rgba(11, 107, 203, 0.18), rgba(18, 107, 67, 0.06)); - filter: blur(10px); - } - .eyebrow { - display: inline-flex; - align-items: center; - padding: 8px 12px; - border-radius: 999px; - background: var(--accent-soft); - color: var(--accent); - font-size: 12px; - font-weight: 800; - letter-spacing: 0.06em; - text-transform: uppercase; - } - h1 { - margin: 18px 0 10px; - font-size: clamp(32px, 4vw, 46px); - line-height: 1.02; - letter-spacing: -0.05em; - } - h2 { - margin: 0 0 8px; - font-size: 24px; - letter-spacing: -0.04em; - } - h3 { - margin: 0 0 8px; - font-size: 18px; - letter-spacing: -0.03em; - } - .hero-copy, .panel-desc { - color: var(--muted); - line-height: 1.75; - font-size: 15px; - } - .hero-points { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin: 18px 0 0; - padding: 0; - list-style: none; - } - .hero-points li { - padding: 8px 12px; - border-radius: 999px; - border: 1px solid var(--line); - background: rgba(255,255,255,0.78); - font-size: 13px; - font-weight: 700; - } - .metrics { - display: grid; - gap: 12px; - align-content: start; - } - .metric { - border-radius: 20px; - border: 1px solid var(--line); - background: #fff; - padding: 16px; - } - .metric-label { - color: var(--muted); - font-size: 12px; - letter-spacing: 0.05em; - text-transform: uppercase; - } - .metric-value { - margin-top: 8px; - font-size: 24px; - font-weight: 800; - letter-spacing: -0.04em; - word-break: break-word; - } - .layout { - display: grid; - grid-template-columns: 430px minmax(0, 1fr); - gap: 18px; - } - .stack { - display: grid; - gap: 18px; - } - .field-grid { - display: grid; - gap: 12px; - } - .field-grid.two { - grid-template-columns: 1fr 1fr; - } - .field-grid.three { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - label { - display: grid; - gap: 7px; - color: var(--muted); - font-size: 13px; - font-weight: 700; - } - input, select, textarea { - width: 100%; - border: 1px solid var(--line); - border-radius: 14px; - padding: 12px 14px; - font: inherit; - color: var(--ink); - background: #fff; - } - textarea { - min-height: 108px; - resize: vertical; - } - .hint { - color: var(--muted); - font-size: 12px; - line-height: 1.5; - font-weight: 500; - } - .actions { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-top: 14px; - } - button { - border: 0; - cursor: pointer; - border-radius: 999px; - padding: 12px 18px; - font: inherit; - font-weight: 800; - transition: transform 120ms ease, opacity 120ms ease, background 120ms ease; - } - button:hover { transform: translateY(-1px); } - button:disabled { cursor: not-allowed; opacity: 0.6; transform: none; } - .primary { background: var(--ink); color: #fff; } - .secondary { background: var(--accent-soft); color: var(--accent); } - .ghost { - border: 1px solid var(--line); - background: transparent; - color: var(--muted); - } - .danger { - background: var(--danger-soft); - color: var(--danger); - border: 1px solid rgba(178, 49, 49, 0.2); - } - .statusbar { - margin-top: 16px; - min-height: 54px; - padding: 14px 16px; - border-radius: 16px; - border: 1px solid var(--line); - background: #fff; - display: flex; - align-items: center; - color: var(--muted); - font-size: 14px; - line-height: 1.5; - } - .statusbar[data-tone="success"] { background: var(--success-soft); color: var(--success); border-color: rgba(18,107,67,0.2); } - .statusbar[data-tone="warning"] { background: var(--warn-soft); color: var(--warn); border-color: rgba(155,98,21,0.2); } - .statusbar[data-tone="danger"] { background: var(--danger-soft); color: var(--danger); border-color: rgba(178,49,49,0.2); } - .catalog { - display: grid; - gap: 12px; - margin-top: 16px; - max-height: 32rem; - overflow: auto; - padding-right: 4px; - } - .catalog-item, - .route-item { - padding: 16px; - border-radius: 18px; - border: 1px solid var(--line); - background: rgba(255,255,255,0.84); - cursor: pointer; - transition: transform 120ms ease, border-color 120ms ease, background 120ms ease; - } - .catalog-item:hover, - .route-item:hover { - transform: translateY(-1px); - border-color: rgba(11,107,203,0.22); - } - .catalog-item.is-selected, - .route-item.is-selected { - background: rgba(11,107,203,0.08); - border-color: rgba(11,107,203,0.22); - } - .catalog-item strong, - .route-item strong { - display: block; - margin-bottom: 6px; - font-size: 16px; - } - .catalog-meta { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 10px; - } - .pill { - display: inline-flex; - align-items: center; - padding: 6px 10px; - border-radius: 999px; - background: rgba(255,255,255,0.72); - border: 1px solid var(--line); - font-size: 12px; - font-weight: 700; - color: var(--muted); - } - .tone-ready { background: var(--success-soft); color: var(--success); border-color: rgba(18,107,67,0.18); } - .tone-note { background: var(--accent-soft); color: var(--accent); border-color: rgba(11,107,203,0.18); } - .tone-warn { background: var(--warn-soft); color: var(--warn); border-color: rgba(155,98,21,0.18); } - .grid-columns { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 18px; - } - .section { - display: grid; - gap: 18px; - } - .list { - display: grid; - gap: 10px; - } - .list-card { - padding: 14px; - border-radius: 16px; - border: 1px solid var(--line); - background: rgba(255,255,255,0.82); - } - .list-card strong { - display: block; - margin-bottom: 6px; - } - .empty { - color: var(--muted); - font-size: 13px; - line-height: 1.6; - } - .mini-actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 10px; - } - .mini-actions button { - padding: 8px 12px; - font-size: 12px; - } - .inline-code { - font-family: var(--font-mono); - font-size: 12px; - color: var(--muted); - word-break: break-word; - } - @media (max-width: 1200px) { - .hero, .layout, .grid-columns, .field-grid.two, .field-grid.three { grid-template-columns: 1fr; } - } - - - -
- - -
-
-
Logical Group Admin
-

把 logical group、route 与 shadow group 放进同一套管理面

-

- 这页专门给插件前置路由使用。你可以在这里维护 logical_group、绑定 - public_model,再把它映射到某个 route -> shadow_host_id -> shadow_group_id。 - 当前首版覆盖最小运营流:创建 / 编辑分组、补 public model、创建 / 编辑 route、补 route model 映射。 -

-
    -
  • 默认 API Base:/portal-admin-api
  • -
  • 支持管理员登录会话,也保留 Bearer admin token 兜底
  • -
  • route model 当前支持新增与查看,删除 / 更新 API 仍待后续补齐
  • -
-
- - -
+
+
+ Logical Group Admin +

把 logical group、route 与 shadow group 放进同一套管理面

+

+ 这页专门给插件前置路由使用。你可以在这里维护 logical_group、绑定 + public_model,再把它映射到某个 route -> shadow_host_id -> shadow_group_id。 + 当前首版覆盖最小运营流:创建 / 编辑分组、补 public model、创建 / 编辑 route、补 route model 映射。 + 首版页面只覆盖新增与查看,编辑与删除将通过同一套 API 路径继续扩展。 +

+
    +
  • 默认 API Base:/portal-admin-api
  • +
  • 支持管理员登录会话,也保留 Bearer admin token 兜底
  • +
  • route model 当前支持新增与查看,删除 / 更新 API 仍待后续补齐
  • +
+
+
+
+
+
+

API Root

+

-

+
+
+
+
+
+

Logical Groups

+

0

+
+
+
+
+
+

当前分组

+

-

+
+
+
+
+
+

当前 Route

+

-

+
+
+
+
@@ -707,7 +433,19 @@
+ + + + + - + diff --git a/deploy/tksea-portal/admin/route-health.html b/deploy/tksea-portal/admin/route-health.html index 6230ef64..3527d0da 100644 --- a/deploy/tksea-portal/admin/route-health.html +++ b/deploy/tksea-portal/admin/route-health.html @@ -4,386 +4,88 @@ Route Health Admin + + -
- +
+ -
-
-
Route Health
+
+
+ Route Health

把 cooldown、failure 与最近一次真实选路收进一个只读健康面

-

+

这页聚合 logical_group_routes、运行态 routefail / routecool、 最近一次 route_decision_logs 和最近 failover 计数。首版只做读,不直接改 route, 目标是让运营能快速判断某条 route 当前是 healthycooldownfailing 还是 disabled

-
    -
  • 默认 API Base:/portal-admin-api
  • -
  • 优先使用管理员会话,也保留 Bearer token 兜底
  • -
  • 页面只读,写 failure / cooldown 仍走现有管理 API
  • +
      +
    • 默认 API Base:/portal-admin-api
    • +
    • 优先使用管理员会话,也保留 Bearer token 兜底
    • +
    • 页面只读,写 failure / cooldown 仍走现有管理 API
    -
- - +
@@ -499,7 +201,18 @@
+ + +