diff --git a/app/credential_actions.py b/app/credential_actions.py index 4141b06..3226ffa 100644 --- a/app/credential_actions.py +++ b/app/credential_actions.py @@ -50,11 +50,11 @@ def run(gateway, action, identity=None, *, consent_revision=None): result.update(travel=followup, checkin_ok=result["ok"], ok=result["ok"] if followup.get("buddy_blocked") else result["ok"] and followup["ok"], message=result["message"] + ";" + followup["message"]) - except Exception: + except Exception as error: # Upstream exception text may contain headers or credential file paths. if action == "checkin" and result["ok"]: result["checkin_ok"] = True - result.update(ok=False, message="操作失败,保留已有数据;请检查账号状态后重试") + result.update(ok=False, message=f"操作失败({type(error).__name__}),保留已有数据;请检查账号状态后重试") results.append(result) _audit(config, result, started) response = {"ok": bool(results) and all(r["ok"] for r in results), "results": results} diff --git a/app/gateway_management.py b/app/gateway_management.py index c21ee33..8293bba 100644 --- a/app/gateway_management.py +++ b/app/gateway_management.py @@ -59,6 +59,7 @@ def admin_credential_inventory(self): "http_403" if entry.get("last_error") == "backend HTTP 403" else "credential_error" if entry.get("last_error") else None), last_failure_at=entry.get("last_failure_at"), + sync_error=(balance.get("error") or entry.get("last_error") or None), cooldowns=cooldowns, credits=balance.get("credits") or None, sync_pending=path in pool._sync_pending or path in pool._syncing or path in pool._sync_retry, catalog_ready=(self.CONFIG.get("account_catalogs") or {}).get(identity, {}).get("models") is not None, diff --git a/web/src/pages/Credentials.tsx b/web/src/pages/Credentials.tsx index 3a87787..5de8bac 100644 --- a/web/src/pages/Credentials.tsx +++ b/web/src/pages/Credentials.tsx @@ -37,6 +37,15 @@ function expiry(value: unknown, milliseconds = false) { ? new Date(milliseconds ? value : value * 1000).toLocaleString("zh-CN") : text(value); } +const lastErrorLabels: Record = { + http_401: "上游 401(登录态被拒)", + http_403: "上游 403(无权限)", + credential_error: "凭证同步失败(详见操作结果)", +}; +function lastErrorLabel(value: unknown) { + const key = text(value); + return lastErrorLabels[key] ?? key; +} export { safeOAuthUrl } from "../OAuth"; function boundModels(credential: Credential): string[] { return Array.isArray(credential.bindings) @@ -430,6 +439,17 @@ export function Credentials() { : text(c.health)} {c.token_expired === true ? "Token 已过期" : ""} + {c.last_error_code != null && ( + + 最近错误:{text(lastErrorLabel(c.last_error_code))} + {number(c.last_failure_at) !== null + ? ` · ${expiry(c.last_failure_at)}` + : ""} + + )} + {typeof c.sync_error === "string" && c.sync_error !== "" && ( + 失败原因:{text(c.sync_error)} + )} {cooldowns ? (