Skip to content

Commit 2d00491

Browse files
committed
fix: centralize auth controls in system settings
1 parent 74fde03 commit 2d00491

4 files changed

Lines changed: 61 additions & 36 deletions

File tree

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- 支持按调用次数轮换、按错误次数轮换、手动切换账号。
1212
- OpenAI 兼容接口:`/v1/chat/completions``/v1/completions``/v1/responses``/v1/models`
1313
- Gemini 原生接口:生成、流式生成、Gems、Deep Research、文件上传、媒体结果索引。
14-
- 管理端控制台:请求看板、账户设置、授权登录、授权 Cookie 诊断、Gems、Deep Research、媒体生成和媒体结果。
14+
- 管理端控制台:请求看板、账户设置、系统设置集中授权、授权 Cookie 诊断、Gems、Deep Research、媒体生成和媒体结果。
1515
- 服务器部署可开启管理员登录,保护网页控制台和管理接口;外部调用继续使用 API Key。
1616
- 媒体生成支持 `image``video``audio` 模式;生成结果会保存索引,并尽量缓存到本地,避免 Gemini 原始链接过期后无法查看。
1717
- 自用保护:图片/视频/音频生成会记录尝试和冷却状态,媒体额度错误或上游 2xx 但没有产出媒体时默认冷却 5 小时,避免额度异常时反复请求。
@@ -316,16 +316,16 @@ python scripts/smoke_deploy.py --base-url http://localhost:7860 --api-key sk-you
316316

317317
## 添加账号
318318

319-
推荐使用管理端的“网页授权”:
319+
推荐使用管理端“系统设置”里的“网页授权”:
320320

321321
1. 打开 `http://localhost:7860`
322-
2. 进入“账户设置
322+
2. 进入“系统设置
323323
3. 点击“网页授权”
324324
4. 在弹出的 noVNC 浏览器中登录 Google/Gemini
325325
5. 回到管理端点击“检查授权状态”
326326
6. 看到“已发现 Gemini 登录 Cookie”后,再点击“保存授权 Cookie”
327327

328-
如果“检查授权状态”提示还没有发现 `__Secure-1PSID`,说明容器内授权浏览器还没有完成 Google/Gemini 登录,或当前页面不在有效登录态。此时不要反复保存,先回到 noVNC 授权浏览器完成登录,再重新检查。诊断接口只返回是否发现 Cookie、Cookie 数量、当前页面 URL 等摘要,不会返回 Cookie 明文。
328+
如果“检查授权状态”提示还没有发现 `__Secure-1PSID`,说明容器内授权浏览器还没有完成 Google/Gemini 登录,或当前页面不在有效登录态。此时不要反复保存,先回到 noVNC 授权浏览器完成登录,再重新检查。诊断接口只返回是否发现 Cookie、Cookie 数量、当前页面 URL 等摘要,不会返回 Cookie 明文。保存授权 Cookie 成功后,管理端会自动关闭授权界面和后端授权会话。
329329

330330
也可以复制示例文件后导入:
331331

@@ -862,7 +862,8 @@ curl http://localhost:7860/v1/gemini/stream \
862862
- `Gems`:查看、创建、更新、删除自定义 system prompt。
863863
- `Deep Research`:创建研究计划、启动、轮询状态和查看结果。
864864
- `媒体结果`:上方生成图片/视频/音频,下方查看媒体历史和缓存/代理链接。
865-
- `账户设置`:调整轮换策略、授权操作、检查授权 Cookie 状态、导入/导出/验证/切换账号,并可手动解除账号媒体冷却。
865+
- `账户设置`:调整轮换策略、导入/导出/验证/切换账号,并可手动解除账号媒体冷却。
866+
- `系统设置`:生成外部 API Key、配置对象存储,并集中进行网页授权、授权 Cookie 诊断和保存。
866867

867868
请求日志会记录输出类型、任务/请求 id、媒体数量和错误信息;媒体生成完成后会回填实际 `media_count`,方便在看板里判断图片、视频或音频是否真正产出。早期没有 `job_id/request_id` 的历史日志无法可靠关联媒体结果,会保留原始计数。
868869

scripts/smoke_deploy.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,18 @@ def _require_admin_ui(body: str) -> None:
499499
_require("外部调用未就绪" in body, "admin UI missing readiness diagnostic copy")
500500
_require("网页授权" in body, "admin UI missing web authorization action")
501501
_require("检查授权状态" in body, "admin UI missing auth cookie diagnostics action")
502+
_require(
503+
"网页授权集中在系统设置中操作" in body,
504+
"admin UI auth controls are not centralized in system settings",
505+
)
506+
_require(
507+
"data-panel-action=\"system-auth\"" in body,
508+
"admin UI missing system settings auth navigation action",
509+
)
510+
_require(
511+
"data-panel-action=\"auth\"" not in body,
512+
"admin UI still exposes direct auth actions outside system settings",
513+
)
502514
_require("gemini-3.1-pro" in body, "admin UI missing gemini-3.1-pro model option")
503515
_require("gemini-3.5-flash" in body, "admin UI missing gemini-3.5-flash model option")
504516
_require(

src/gemini_webapi/server/static/index.html

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -693,19 +693,6 @@ <h2>媒体结果</h2>
693693
<span class="hint">设置为 0 表示关闭对应自动切换;429/503 和鉴权错误仍会保护性切换。</span>
694694
</div>
695695
</section>
696-
<section>
697-
<div class="section-head"><h2>授权操作</h2><button id="saveAuthBtn" class="primary">保存授权 Cookie</button></div>
698-
<div class="content stack">
699-
<div class="notice">点击“网页授权”会启动容器内浏览器,并在新界面打开 noVNC。登录完成后回到这里保存 Cookie。</div>
700-
<div class="row button-row">
701-
<button id="reopenAuthBtn">打开授权界面</button>
702-
<button id="reloadAuthBtn">刷新授权浏览器</button>
703-
<button id="diagnoseAuthBtn">检查授权状态</button>
704-
<button id="closeAuthBtn">关闭授权会话</button>
705-
</div>
706-
<div id="authDiagnoseBox" class="hint">授权浏览器启动后,可先检查是否已经拿到 Gemini Cookie,再保存到账户池。</div>
707-
</div>
708-
</section>
709696
</div>
710697
<section>
711698
<div class="section-head"><h2>账户池</h2><button id="refreshAccountsBtn">刷新</button></div>
@@ -717,7 +704,6 @@ <h2>媒体结果</h2>
717704
<button id="validateCurrentBtn">验证当前</button>
718705
<button id="validateAllBtn">验证全部</button>
719706
<button id="manualOpenBtn">手动添加</button>
720-
<button id="authOpenBtn" class="primary">网页授权</button>
721707
</div>
722708
<div id="accountList" class="account-grid"></div>
723709
</div>
@@ -755,6 +741,15 @@ <h2>系统设置</h2>
755741
<label>Public URL<input id="objectStoragePublicUrl" placeholder="https://cdn.example.com/media"></label>
756742
</div>
757743
<div class="stack">
744+
<div class="notice">网页授权集中在系统设置中操作:启动授权浏览器后,在 noVNC 里完成 Google/Gemini 登录,检查到 Cookie 后保存到账户池。保存完成会自动关闭授权界面。</div>
745+
<div class="row button-row">
746+
<button id="authOpenBtn" class="primary">网页授权</button>
747+
<button id="reloadAuthBtn">刷新授权浏览器</button>
748+
<button id="diagnoseAuthBtn">检查授权状态</button>
749+
<button id="saveAuthBtn">保存授权 Cookie</button>
750+
<button id="closeAuthBtn">关闭授权会话</button>
751+
</div>
752+
<div id="authDiagnoseBox" class="hint">授权浏览器启动后,可先检查是否已经拿到 Gemini Cookie,再保存到账户池。</div>
758753
<div class="split">
759754
<label>Access Key ID<input id="objectStorageAccessKey" autocomplete="off"></label>
760755
<label>Secret Access Key<input id="objectStorageSecretKey" type="password" autocomplete="new-password" placeholder="保存后会脱敏显示"></label>
@@ -993,7 +988,7 @@ <h2>系统设置</h2>
993988
}
994989
function readinessReasonText(reason) {
995990
const map = {
996-
no_accounts: "还没有授权 Gemini 账户,请到“账户设置”使用网页授权或手动添加 Cookie。",
991+
no_accounts: "还没有授权 Gemini 账户,请到“系统设置”使用网页授权,或到“账户设置”手动添加 Cookie。",
997992
no_available_accounts: "账户都已停用或过期,请重新验证、启用或重新授权。",
998993
readiness_error: "调用就绪检查失败,请检查外部 API Key、管理员会话或服务状态。",
999994
};
@@ -1021,7 +1016,7 @@ <h2>系统设置</h2>
10211016
</div>
10221017
</div>
10231018
<div class="diagnostic-actions">
1024-
<button data-panel-action="auth">网页授权</button>
1019+
<button data-panel-action="system-auth">去系统设置授权</button>
10251020
<button data-panel-action="validate-all">验证全部账户</button>
10261021
</div>
10271022
</div>`;
@@ -1101,10 +1096,10 @@ <h2>系统设置</h2>
11011096
<div class="diagnostic">
11021097
<div>
11031098
<div class="diagnostic-title">当前没有可用账户</div>
1104-
<div class="hint">请添加 Cookie,或使用网页授权重新登录 Gemini。</div>
1099+
<div class="hint">请到系统设置完成网页授权,或手动添加 Cookie。</div>
11051100
</div>
11061101
<div class="diagnostic-actions">
1107-
<button class="primary" data-panel-action="auth">网页授权</button>
1102+
<button class="primary" data-panel-action="system-auth">去系统设置授权</button>
11081103
<button data-panel-action="validate-all">验证全部账户</button>
11091104
</div>
11101105
</div>`;
@@ -1138,7 +1133,7 @@ <h2>系统设置</h2>
11381133
</div>
11391134
</div>
11401135
<div class="diagnostic-actions">
1141-
${current.expired ? `<button class="primary" data-panel-action="auth">重新网页授权</button>` : ""}
1136+
${current.expired ? `<button class="primary" data-panel-action="system-auth">去系统设置重新授权</button>` : ""}
11421137
<button data-panel-action="validate-current">验证当前</button>
11431138
<button data-panel-action="validate-all">验证全部</button>
11441139
</div>
@@ -1353,14 +1348,17 @@ <h2>系统设置</h2>
13531348
$(infoId).textContent = "";
13541349
setMsg("已清空参考附件");
13551350
}
1356-
1357-
document.querySelectorAll("nav button").forEach(btn => btn.onclick = () => {
1351+
function activateTab(tabId) {
1352+
const btn = document.querySelector(`nav button[data-tab="${tabId}"]`);
1353+
if (!btn) return;
13581354
document.querySelectorAll("nav button").forEach(b => b.classList.toggle("active", b === btn));
1359-
document.querySelectorAll(".view").forEach(v => v.classList.toggle("active", v.id === btn.dataset.tab));
1360-
if (btn.dataset.tab === "gems") refreshGems().catch(e => setMsg(e.message));
1361-
if (btn.dataset.tab === "media") refreshMedia().catch(e => setMsg(e.message));
1362-
if (btn.dataset.tab === "system") refreshSystemSettings().catch(e => setMsg(e.message));
1363-
});
1355+
document.querySelectorAll(".view").forEach(v => v.classList.toggle("active", v.id === tabId));
1356+
if (tabId === "gems") refreshGems().catch(e => setMsg(e.message));
1357+
if (tabId === "media") refreshMedia().catch(e => setMsg(e.message));
1358+
if (tabId === "system") refreshSystemSettings().catch(e => setMsg(e.message));
1359+
}
1360+
1361+
document.querySelectorAll("nav button").forEach(btn => btn.onclick = () => activateTab(btn.dataset.tab));
13641362
document.body.onclick = event => {
13651363
const close = event.target.closest("[data-close-modal]");
13661364
if (close) closeModal(close.dataset.closeModal);
@@ -1632,8 +1630,9 @@ <h2>系统设置</h2>
16321630
const btn = event.target.closest("button[data-panel-action]");
16331631
if (!btn) return;
16341632
const action = btn.dataset.panelAction;
1635-
if (action === "auth") {
1636-
await startAuth().catch(e => setMsg(e.message));
1633+
if (action === "system-auth") {
1634+
activateTab("system");
1635+
setMsg("请在系统设置中完成网页授权。");
16371636
return;
16381637
}
16391638
if (action === "validate-current") {
@@ -1654,6 +1653,11 @@ <h2>系统设置</h2>
16541653
if (!state.authWindow) throw new Error("浏览器阻止了弹窗,请允许此站点打开新窗口。");
16551654
$("authDiagnoseBox").textContent = "授权浏览器已打开。登录完成后点击“检查授权状态”,确认 Cookie 可用后再保存。";
16561655
}
1656+
async function closeAuthSession() {
1657+
await api("/v1/auth/session", { method: "DELETE" });
1658+
if (state.authWindow && !state.authWindow.closed) state.authWindow.close();
1659+
state.authWindow = null;
1660+
}
16571661
async function diagnoseAuth() {
16581662
const data = await api("/v1/auth/diagnose");
16591663
const ok = Boolean(data.has_secure_1psid);
@@ -1666,20 +1670,24 @@ <h2>系统设置</h2>
16661670
return data;
16671671
}
16681672
$("authOpenBtn").onclick = () => startAuth().catch(e => setMsg(e.message));
1669-
$("reopenAuthBtn").onclick = () => startAuth().catch(e => setMsg(e.message));
16701673
$("reloadAuthBtn").onclick = () => api("/v1/auth/press", { method: "POST", body: JSON.stringify({ key: "F5" }) }).catch(e => setMsg(e.message));
16711674
$("diagnoseAuthBtn").onclick = () => diagnoseAuth().catch(e => {
16721675
$("authDiagnoseBox").textContent = `无法检查授权状态:${e.message}`;
16731676
setMsg(e.message);
16741677
});
1675-
$("closeAuthBtn").onclick = async () => { await api("/v1/auth/session", { method: "DELETE" }); if (state.authWindow && !state.authWindow.closed) state.authWindow.close(); };
1678+
$("closeAuthBtn").onclick = async () => {
1679+
await closeAuthSession();
1680+
$("authDiagnoseBox").textContent = "授权会话已关闭。";
1681+
};
16761682
$("authSaveConfirmBtn").onclick = async () => {
16771683
try {
16781684
const data = await api("/v1/auth/save", { method: "POST", body: JSON.stringify({ name: $("authSaveName").value || null }) });
16791685
closeModal("authSaveModal");
16801686
await refreshAll();
1687+
await closeAuthSession().catch(() => {});
16811688
const savedText = data.saved ? `授权 Cookie 已保存到账户池 #${data.account_id},当前共 ${data.account_count ?? (data.accounts || []).length} 个账户。` : "授权 Cookie 未确认保存。";
1682-
setMsg(data.validation?.valid ? `${savedText} 验证通过。` : `${savedText} 但验证失败:${data.validation?.message || "请稍后重新验证。"}`);
1689+
$("authDiagnoseBox").textContent = `${savedText} 授权界面已关闭。`;
1690+
setMsg(data.validation?.valid ? `${savedText} 验证通过,授权界面已关闭。` : `${savedText} 但验证失败:${data.validation?.message || "请稍后重新验证。"} 授权界面已关闭。`);
16831691
} catch (error) {
16841692
$("authSaveDiagnose").textContent = `保存失败:${error.message}。请先点击“检查授权状态”,确认已发现 __Secure-1PSID。`;
16851693
setMsg(error.message);

tests/test_smoke_deploy.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,8 @@ def fake_urlopen(request, timeout):
11951195
<title>Gemini API 管理端</title>
11961196
<div id="readinessPanel">外部调用未就绪</div>
11971197
<strong id="metricReadiness">未知</strong>
1198+
<div>网页授权集中在系统设置中操作</div>
1199+
<button data-panel-action="system-auth">去系统设置授权</button>
11981200
<button>网页授权</button>
11991201
<button>检查授权状态</button>
12001202
<option value="gemini-3.1-pro">gemini-3.1-pro</option>
@@ -1237,6 +1239,8 @@ def fake_urlopen(request, timeout):
12371239
<title>Gemini API 管理端</title>
12381240
<div id="readinessPanel">外部调用未就绪</div>
12391241
<strong id="metricReadiness">未知</strong>
1242+
<div>网页授权集中在系统设置中操作</div>
1243+
<button data-panel-action="system-auth">去系统设置授权</button>
12401244
<button>网页授权</button>
12411245
<button>检查授权状态</button>
12421246
<option value="gemini-3.1-pro">gemini-3.1-pro</option>

0 commit comments

Comments
 (0)