Files
sub2api-cn-relay-manager/deploy/tksea-portal/admin/index.html

410 lines
13 KiB
HTML
Raw Normal View History

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin Portal</title>
<style>
:root {
--bg: #f4efe6;
--panel: rgba(255, 252, 246, 0.92);
--ink: #1f1a16;
--muted: #665c53;
--line: rgba(31, 26, 22, 0.12);
--accent: #0c6cc9;
--accent-soft: rgba(12, 108, 201, 0.12);
--success: #136f46;
--success-soft: rgba(19, 111, 70, 0.1);
--warn: #9f6417;
--warn-soft: rgba(159, 100, 23, 0.12);
--shadow: 0 24px 70px rgba(46, 37, 28, 0.1);
--radius: 24px;
--radius-sm: 16px;
--font-sans: "IBM Plex Sans", "Noto Sans SC", "PingFang SC", sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
color: var(--ink);
font-family: var(--font-sans);
background:
radial-gradient(circle at top left, rgba(12, 108, 201, 0.18), transparent 24rem),
radial-gradient(circle at bottom right, rgba(19, 111, 70, 0.12), transparent 28rem),
linear-gradient(180deg, #f8f3ea 0%, #f1e9dd 100%);
}
a { color: inherit; }
.shell {
max-width: 1280px;
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.74);
color: var(--muted);
font-size: 13px;
font-weight: 700;
}
.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 {
padding: 30px;
position: relative;
overflow: hidden;
}
.hero-card::after {
content: "";
position: absolute;
right: -4rem;
bottom: -4rem;
width: 16rem;
height: 16rem;
border-radius: 999px;
background: linear-gradient(135deg, rgba(12, 108, 201, 0.2), rgba(19, 111, 70, 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(34px, 5vw, 50px);
line-height: 1;
letter-spacing: -0.05em;
}
.hero-copy {
max-width: 56rem;
color: var(--muted);
line-height: 1.75;
font-size: 16px;
}
.hero-points {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 0;
margin: 18px 0 0;
list-style: none;
}
.hero-points li {
padding: 9px 12px;
border-radius: 999px;
border: 1px solid var(--line);
background: rgba(255,255,255,0.74);
font-size: 13px;
font-weight: 700;
}
.stack {
padding: 24px;
display: grid;
gap: 12px;
align-content: start;
}
.metric {
border-radius: 20px;
padding: 16px;
border: 1px solid var(--line);
background: #fff;
}
.metric-label {
color: var(--muted);
font-size: 12px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.metric-value {
margin-top: 8px;
font-size: 26px;
font-weight: 800;
letter-spacing: -0.04em;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.panel {
padding: 24px;
}
.panel h2 {
margin: 0 0 8px;
font-size: 24px;
letter-spacing: -0.04em;
}
.panel p {
margin: 0;
color: var(--muted);
line-height: 1.7;
font-size: 14px;
}
.cta-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 18px;
}
.cta {
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 10rem;
padding: 12px 18px;
border-radius: 999px;
font-weight: 800;
border: 1px solid var(--line);
transition: transform 120ms ease, background 120ms ease, color 120ms ease;
}
.cta:hover { transform: translateY(-1px); }
.cta.primary { background: var(--ink); color: #fff; border-color: var(--ink); }
.cta.secondary { background: var(--accent-soft); color: var(--accent); }
.list {
margin: 18px 0 0;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
}
.list li {
padding: 14px 16px;
border-radius: 18px;
border: 1px solid var(--line);
background: rgba(255,255,255,0.8);
}
.list strong {
display: block;
margin-bottom: 4px;
font-size: 15px;
}
.status-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-top: 18px;
}
.status-card {
padding: 16px;
border-radius: 20px;
border: 1px solid var(--line);
background: #fff;
}
.status-card strong {
display: block;
margin-top: 8px;
font-size: 24px;
letter-spacing: -0.04em;
}
.status-available {
background: linear-gradient(180deg, #fff 0%, rgba(19, 111, 70, 0.08) 100%);
}
.status-caution {
background: linear-gradient(180deg, #fff 0%, rgba(159, 100, 23, 0.08) 100%);
}
.status-note {
background: linear-gradient(180deg, #fff 0%, rgba(12, 108, 201, 0.08) 100%);
}
code {
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
font-size: 12px;
}
@media (max-width: 980px) {
.hero, .grid, .status-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<main class="shell">
<nav class="topnav" aria-label="Admin Navigation">
<a href="/portal/admin/" class="is-current">管理首页</a>
<a href="/portal/admin/logical-groups.html">逻辑分组 / 路由</a>
<a href="/portal/admin/route-health.html">Route 健康视图</a>
<a href="/portal/admin/providers.html">新增模型 / 供应商目录</a>
<a href="/portal/admin/batch-import.html">导入供应商帐号</a>
<a href="/portal/" target="_blank" rel="noreferrer">用户 Portal</a>
</nav>
<section class="hero">
<article class="card hero-card">
<div class="eyebrow">Admin Portal</div>
<h1>把新增模型与导入帐号收进同一套入口</h1>
<p class="hero-copy">
这个入口不再把“新增模型供应商”和“导入供应商帐号”拆散在不同地址。当前版本统一从
<code>/portal/admin/</code> 进入:一边看 pack/provider 目录、做 preview/import一边继续保留
item 级 <code>reused / reactivated / replaced</code> 的 batch-import 结果面板。
</p>
<ul class="hero-points">
<li>默认同域走 <code>/portal-admin-api/</code></li>
<li>静态页与 CRM API 解耦</li>
<li>保留旧地址兼容,不打断现有操作</li>
</ul>
</article>
<aside class="card stack">
<div class="metric">
<div class="metric-label">统一入口</div>
<div class="metric-value">/portal/admin/</div>
</div>
<div class="metric">
<div class="metric-label">Logical Group</div>
<div class="metric-value">/logical-groups</div>
</div>
<div class="metric">
<div class="metric-label">Provider 目录</div>
<div class="metric-value">/providers</div>
</div>
<div class="metric">
<div class="metric-label">Route Health</div>
<div class="metric-value">/route-health</div>
</div>
<div class="metric">
<div class="metric-label">Batch Import</div>
<div class="metric-value">/batch-import</div>
</div>
</aside>
</section>
<section class="grid">
<article class="card panel">
<h2>逻辑分组 / 路由</h2>
<p>
这页给插件前置路由使用,负责维护 <code>logical_group</code><code>public_model</code>
<code>route</code><code>shadow_host_id / shadow_group_id</code> 的关系。当前首版已经能直接调
<code>/api/logical-groups</code> 系列接口,适合先把 canonical shadow route 收进统一管理面。
</p>
<div class="cta-row">
<a class="cta primary" href="/portal/admin/logical-groups.html">打开逻辑分组页</a>
</div>
<ul class="list">
<li>
<strong>适用动作</strong>
创建 logical group、绑定 public model、维护 route 与 shadow group 映射。
</li>
<li>
<strong>默认 API Base</strong>
<code>https://sub.tksea.top/portal-admin-api</code>
</li>
</ul>
</article>
<article class="card panel">
<h2>新增模型 / 供应商目录</h2>
<p>
这页负责浏览已安装 pack、选择 provider、调用 <code>preview-import</code> /
<code>import</code>,同时提供 provider manifest 草稿生成与发布。当前版本已经支持先保存草稿,再经由 CRM
服务端写入 pack/provider 文件并自动提交到仓库。
</p>
<div class="cta-row">
<a class="cta primary" href="/portal/admin/providers.html">打开供应商页</a>
<a class="cta secondary" href="/portal/admin/providers.html#manifest-draft">跳到 manifest 草稿</a>
</div>
<ul class="list">
<li>
<strong>适用动作</strong>
查看 pack 与 provider、输入 keys 做 preview/import、生成 provider 草稿,并一键发布到仓库。
</li>
<li>
<strong>默认 API Base</strong>
<code>https://sub.tksea.top/portal-admin-api</code>
</li>
</ul>
</article>
<article class="card panel">
<h2>Route 健康视图</h2>
<p>
这页专门给运营看 route 当前运行状态,聚合 <code>routefail</code><code>routecool</code>
最近一次选路与 failover 事件。首版只做只读健康视图,不在这里直接改 route。
</p>
<div class="cta-row">
<a class="cta primary" href="/portal/admin/route-health.html">打开健康页</a>
</div>
<ul class="list">
<li>
<strong>适用动作</strong>
查看 <code>healthy / cooldown / failing / disabled</code>,确认 sticky、failover 与最近错误是否一致。
</li>
<li>
<strong>默认 API Base</strong>
<code>https://sub.tksea.top/portal-admin-api</code>
</li>
</ul>
</article>
<article class="card panel">
<h2>导入供应商帐号</h2>
<p>
这页继续负责 live batch-import创建 run、拉取 run summary、查看 item 级别的
<code>matched_account_state</code><code>account_resolution</code>
</p>
<div class="cta-row">
<a class="cta primary" href="/portal/admin/batch-import.html">打开导入页</a>
<a class="cta secondary" href="/portal/admin-batch-import.html">旧地址兼容入口</a>
</div>
<ul class="list">
<li>
<strong>适用动作</strong>
批量导入第三方 key验证 <code>reused / created / reactivated / replaced</code>
</li>
<li>
<strong>默认 API Base</strong>
<code>https://sub.tksea.top/portal-admin-api</code>
</li>
</ul>
</article>
</section>
<section class="status-grid">
<article class="status-card status-available">
<div class="metric-label">可立即使用</div>
<strong>逻辑分组 + Provider 导入</strong>
<p>依赖现有 <code>/api/logical-groups</code><code>/api/packs</code><code>/api/providers/*</code><code>/api/batch-import/*</code> 即可完成。</p>
</article>
<article class="status-card status-note">
<div class="metric-label">当前边界</div>
<strong>浏览器提交到 CRM再由 CRM 写仓库</strong>
<p>页面不会直接拼 Git 命令;所有写 pack/provider 与提交仓库的动作,都统一走 CRM 服务端的发布接口。</p>
</article>
<article class="status-card status-caution">
<div class="metric-label">安全前提</div>
<strong>仍需 Admin Token</strong>
<p>CRM 的 API 权限仍由 Bearer token 控制,同域反代只解决浏览器可达性,不降低鉴权门槛。</p>
</article>
</section>
</main>
</body>
</html>