icebear0828 commited on
Commit
3cf831b
·
1 Parent(s): af3e19d

fix: update checker shows correct status + version display + dark mode fixes

Browse files

- Fix 'Up to date' shown incorrectly when Codex version changed (add version_changed field)
- Change update status text from <span> to clickable <button>
- Add proxy version number next to 'Server Online' badge
- Fix dark mode for ProxyPool form inputs and AccountCard proxy selector
- Add fingerprintUpdating translation key (zh/en)

README.md CHANGED
@@ -116,6 +116,13 @@ curl http://localhost:8080/v1/chat/completions \
116
  - **稳定连接** — 自动对齐 Codex Desktop 请求特征,Cookie 持久化减少重复验证
117
  - **Web 控制面板** — 账号管理、用量监控、状态总览,中英双语
118
 
 
 
 
 
 
 
 
119
  ## 🏗️ 技术架构 (Architecture)
120
 
121
  ```
@@ -309,6 +316,17 @@ server:
309
  | `/auth/accounts` | GET | 账号列表(`?quota=true` 含配额) |
310
  | `/auth/accounts/login` | GET | OAuth 登录入口 |
311
  | `/debug/fingerprint` | GET | 调试:查看当前伪装头信息 |
 
 
 
 
 
 
 
 
 
 
 
312
 
313
  ## 🔧 命令 (Commands)
314
 
 
116
  - **稳定连接** — 自动对齐 Codex Desktop 请求特征,Cookie 持久化减少重复验证
117
  - **Web 控制面板** — 账号管理、用量监控、状态总览,中英双语
118
 
119
+ ### 3. 🌐 代理池 (Proxy Pool)
120
+ - **Per-Account 代理路由** — 为不同账号配置不同的上游代理,实现 IP 多样化和风险隔离
121
+ - **四种分配模式** — Global Default(全局代理)、Direct(直连)、Auto(Round-Robin 轮转)、指定代理
122
+ - **健康检查** — 定时(默认 5 分钟)+ 手动,通过 ipify API 获取出口 IP 和延迟
123
+ - **不可达自动标记** — 代理不可达时自动标记为 unreachable,不参与自动轮转
124
+ - **Dashboard 管理面板** — 添加/删除/检查/启用/禁用代理,每个账号可选择代理或模式
125
+
126
  ## 🏗️ 技术架构 (Architecture)
127
 
128
  ```
 
316
  | `/auth/accounts` | GET | 账号列表(`?quota=true` 含配额) |
317
  | `/auth/accounts/login` | GET | OAuth 登录入口 |
318
  | `/debug/fingerprint` | GET | 调试:查看当前伪装头信息 |
319
+ | `/api/proxies` | GET | 代理池列表(含分配信息) |
320
+ | `/api/proxies` | POST | 添加代理(HTTP/HTTPS/SOCKS5) |
321
+ | `/api/proxies/:id` | PUT | 更新代理配置 |
322
+ | `/api/proxies/:id` | DELETE | 删除代理 |
323
+ | `/api/proxies/:id/check` | POST | 单个代理健康检查 |
324
+ | `/api/proxies/:id/enable` | POST | 启用代理 |
325
+ | `/api/proxies/:id/disable` | POST | 禁用代理 |
326
+ | `/api/proxies/check-all` | POST | 全部代理健康检查 |
327
+ | `/api/proxies/assign` | POST | 为账号分配代理 |
328
+ | `/api/proxies/assign/:accountId` | DELETE | 取消账号代理分配 |
329
+ | `/api/proxies/settings` | PUT | 更新代理池全局设置 |
330
 
331
  ## 🔧 命令 (Commands)
332
 
README_EN.md CHANGED
@@ -109,6 +109,13 @@ curl http://localhost:8080/v1/chat/completions \
109
  - **Auto token refresh** — JWT renewed automatically before expiry
110
  - **Real-time quota monitoring** — dashboard shows remaining usage per account
111
 
 
 
 
 
 
 
 
112
  ### 3. 🛡️ Anti-Detection & Protocol Impersonation
113
  - **Chrome TLS fingerprint** — curl-impersonate replicates the full Chrome 136 TLS handshake
114
  - **Desktop header replication** — `originator`, `User-Agent`, `sec-ch-*` headers in exact Codex Desktop order
@@ -292,6 +299,17 @@ All configuration is in `config/default.yaml`:
292
  | `/auth/accounts` | GET | Account list and quota |
293
  | `/auth/login` | GET | OAuth login entry |
294
  | `/debug/fingerprint` | GET | Debug: view current impersonation headers |
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  ## 🔧 Commands
297
 
 
109
  - **Auto token refresh** — JWT renewed automatically before expiry
110
  - **Real-time quota monitoring** — dashboard shows remaining usage per account
111
 
112
+ ### 3. 🌐 Proxy Pool
113
+ - **Per-account proxy routing** — assign different upstream proxies to different accounts for IP diversity and risk isolation
114
+ - **Four assignment modes** — Global Default, Direct (no proxy), Auto (round-robin rotation), or a specific proxy
115
+ - **Health checks** — scheduled (default every 5 min) + manual, reports exit IP and latency via ipify API
116
+ - **Auto-mark unreachable** — unreachable proxies are automatically flagged and excluded from auto-rotation
117
+ - **Dashboard management** — add/remove/check/enable/disable proxies, per-account proxy selector
118
+
119
  ### 3. 🛡️ Anti-Detection & Protocol Impersonation
120
  - **Chrome TLS fingerprint** — curl-impersonate replicates the full Chrome 136 TLS handshake
121
  - **Desktop header replication** — `originator`, `User-Agent`, `sec-ch-*` headers in exact Codex Desktop order
 
299
  | `/auth/accounts` | GET | Account list and quota |
300
  | `/auth/login` | GET | OAuth login entry |
301
  | `/debug/fingerprint` | GET | Debug: view current impersonation headers |
302
+ | `/api/proxies` | GET | Proxy pool list (with assignments) |
303
+ | `/api/proxies` | POST | Add proxy (HTTP/HTTPS/SOCKS5) |
304
+ | `/api/proxies/:id` | PUT | Update proxy config |
305
+ | `/api/proxies/:id` | DELETE | Remove proxy |
306
+ | `/api/proxies/:id/check` | POST | Health check single proxy |
307
+ | `/api/proxies/:id/enable` | POST | Enable proxy |
308
+ | `/api/proxies/:id/disable` | POST | Disable proxy |
309
+ | `/api/proxies/check-all` | POST | Health check all proxies |
310
+ | `/api/proxies/assign` | POST | Assign proxy to account |
311
+ | `/api/proxies/assign/:accountId` | DELETE | Unassign proxy from account |
312
+ | `/api/proxies/settings` | PUT | Update proxy pool settings |
313
 
314
  ## 🔧 Commands
315
 
shared/hooks/use-update-status.ts CHANGED
@@ -31,6 +31,7 @@ export interface CheckResult {
31
  update_available: boolean;
32
  current_version: string;
33
  latest_version: string | null;
 
34
  error?: string;
35
  };
36
  proxy_update_in_progress: boolean;
 
31
  update_available: boolean;
32
  current_version: string;
33
  latest_version: string | null;
34
+ version_changed?: boolean;
35
  error?: string;
36
  };
37
  proxy_update_in_progress: boolean;
shared/i18n/translations.ts CHANGED
@@ -65,6 +65,7 @@ export const translations = {
65
  upToDate: "Up to date",
66
  updateApplied: "Update complete, please restart server",
67
  fingerprintUpdated: "Fingerprint updated",
 
68
  proxyUpdating: "Updating proxy...",
69
  proxyBehind: "behind",
70
  dockerUpdateHint: "Run: docker compose up -d --build",
@@ -171,6 +172,7 @@ export const translations = {
171
  upToDate: "\u5df2\u662f\u6700\u65b0",
172
  updateApplied: "\u66f4\u65b0\u5b8c\u6210\uff0c\u8bf7\u91cd\u542f\u670d\u52a1",
173
  fingerprintUpdated: "\u6307\u7eb9\u5df2\u66f4\u65b0",
 
174
  proxyUpdating: "\u6b63\u5728\u66f4\u65b0\u4ee3\u7406...",
175
  proxyBehind: "\u843d\u540e",
176
  dockerUpdateHint: "\u8bf7\u6267\u884c: docker compose up -d --build",
 
65
  upToDate: "Up to date",
66
  updateApplied: "Update complete, please restart server",
67
  fingerprintUpdated: "Fingerprint updated",
68
+ fingerprintUpdating: "Updating fingerprint...",
69
  proxyUpdating: "Updating proxy...",
70
  proxyBehind: "behind",
71
  dockerUpdateHint: "Run: docker compose up -d --build",
 
172
  upToDate: "\u5df2\u662f\u6700\u65b0",
173
  updateApplied: "\u66f4\u65b0\u5b8c\u6210\uff0c\u8bf7\u91cd\u542f\u670d\u52a1",
174
  fingerprintUpdated: "\u6307\u7eb9\u5df2\u66f4\u65b0",
175
+ fingerprintUpdating: "\u6307\u7eb9\u66f4\u65b0\u4e2d...",
176
  proxyUpdating: "\u6b63\u5728\u66f4\u65b0\u4ee3\u7406...",
177
  proxyBehind: "\u843d\u540e",
178
  dockerUpdateHint: "\u8bf7\u6267\u884c: docker compose up -d --build",
src/routes/web.ts CHANGED
@@ -151,7 +151,7 @@ export function createWebRoutes(accountPool: AccountPool): Hono {
151
  app.post("/admin/check-update", async (c) => {
152
  const results: {
153
  proxy?: { commits_behind: number; current_commit: string | null; latest_commit: string | null; update_applied?: boolean; error?: string };
154
- codex?: { update_available: boolean; current_version: string; latest_version: string | null; error?: string };
155
  } = {};
156
 
157
  // 1. Proxy self-update check
@@ -183,11 +183,13 @@ export function createWebRoutes(accountPool: AccountPool): Hono {
183
  // 2. Codex fingerprint check
184
  if (!isEmbedded()) {
185
  try {
 
186
  const codexState = await checkForUpdate();
187
  results.codex = {
188
  update_available: codexState.update_available,
189
  current_version: codexState.current_version,
190
  latest_version: codexState.latest_version,
 
191
  };
192
  } catch (err) {
193
  results.codex = {
 
151
  app.post("/admin/check-update", async (c) => {
152
  const results: {
153
  proxy?: { commits_behind: number; current_commit: string | null; latest_commit: string | null; update_applied?: boolean; error?: string };
154
+ codex?: { update_available: boolean; current_version: string; latest_version: string | null; version_changed?: boolean; error?: string };
155
  } = {};
156
 
157
  // 1. Proxy self-update check
 
183
  // 2. Codex fingerprint check
184
  if (!isEmbedded()) {
185
  try {
186
+ const prevVersion = getUpdateState()?.current_version ?? null;
187
  const codexState = await checkForUpdate();
188
  results.codex = {
189
  update_available: codexState.update_available,
190
  current_version: codexState.current_version,
191
  latest_version: codexState.latest_version,
192
+ version_changed: prevVersion !== null && codexState.current_version !== prevVersion,
193
  };
194
  } catch (err) {
195
  results.codex = {
web/src/App.tsx CHANGED
@@ -40,7 +40,10 @@ function useUpdateMessage() {
40
  parts.push(`Codex: ${r.codex.error}`);
41
  color = "text-red-500";
42
  } else if (r.codex_update_in_progress) {
43
- parts.push(t("fingerprintUpdated"));
 
 
 
44
  }
45
 
46
  msg = parts.length > 0 ? parts.join(" · ") : t("upToDate");
@@ -71,6 +74,7 @@ function Dashboard() {
71
  checking={update.checking}
72
  updateStatusMsg={update.msg}
73
  updateStatusColor={update.color}
 
74
  />
75
  <main class="flex-grow px-4 md:px-8 lg:px-40 py-8 flex justify-center">
76
  <div class="flex flex-col w-full max-w-[960px] gap-6">
 
40
  parts.push(`Codex: ${r.codex.error}`);
41
  color = "text-red-500";
42
  } else if (r.codex_update_in_progress) {
43
+ parts.push(t("fingerprintUpdating"));
44
+ } else if (r.codex?.version_changed) {
45
+ parts.push(`Codex: v${r.codex.current_version}`);
46
+ color = "text-blue-500";
47
  }
48
 
49
  msg = parts.length > 0 ? parts.join(" · ") : t("upToDate");
 
74
  checking={update.checking}
75
  updateStatusMsg={update.msg}
76
  updateStatusColor={update.color}
77
+ version={update.status?.proxy.version ?? null}
78
  />
79
  <main class="flex-grow px-4 md:px-8 lg:px-40 py-8 flex justify-center">
80
  <div class="flex flex-col w-full max-w-[960px] gap-6">
web/src/components/AccountCard.tsx CHANGED
@@ -143,7 +143,7 @@ export function AccountCard({ account, index, onDelete, proxies, onProxyChange }
143
  onChange={(e) =>
144
  onProxyChange(account.id, (e.target as HTMLSelectElement).value)
145
  }
146
- class="text-xs px-2 py-1 rounded-md border border-gray-200 dark:border-border-dark bg-transparent focus:outline-none focus:ring-1 focus:ring-primary cursor-pointer"
147
  >
148
  <option value="global">{t("globalDefault")}</option>
149
  <option value="direct">{t("directNoProxy")}</option>
 
143
  onChange={(e) =>
144
  onProxyChange(account.id, (e.target as HTMLSelectElement).value)
145
  }
146
+ class="text-xs px-2 py-1 rounded-md border border-gray-200 dark:border-border-dark bg-white dark:bg-bg-dark text-slate-700 dark:text-text-main focus:outline-none focus:ring-1 focus:ring-primary cursor-pointer"
147
  >
148
  <option value="global">{t("globalDefault")}</option>
149
  <option value="direct">{t("directNoProxy")}</option>
web/src/components/Header.tsx CHANGED
@@ -19,9 +19,10 @@ interface HeaderProps {
19
  checking: boolean;
20
  updateStatusMsg: string | null;
21
  updateStatusColor: string;
 
22
  }
23
 
24
- export function Header({ onAddAccount, onCheckUpdate, checking, updateStatusMsg, updateStatusColor }: HeaderProps) {
25
  const { lang, toggleLang, t } = useI18n();
26
  const { isDark, toggle: toggleTheme } = useTheme();
27
 
@@ -49,6 +50,9 @@ export function Header({ onAddAccount, onCheckUpdate, checking, updateStatusMsg,
49
  <span class="invisible col-start-1 row-start-1">Server Online</span>
50
  <span class="col-start-1 row-start-1">{t("serverOnline")}</span>
51
  </span>
 
 
 
52
  </div>
53
  {/* Star on GitHub */}
54
  <a
@@ -77,9 +81,12 @@ export function Header({ onAddAccount, onCheckUpdate, checking, updateStatusMsg,
77
  </button>
78
  {/* Update status message */}
79
  {updateStatusMsg && !checking && (
80
- <span class={`hidden sm:inline text-xs font-medium ${updateStatusColor}`}>
 
 
 
81
  {updateStatusMsg}
82
- </span>
83
  )}
84
  {/* Language Toggle */}
85
  <button
 
19
  checking: boolean;
20
  updateStatusMsg: string | null;
21
  updateStatusColor: string;
22
+ version: string | null;
23
  }
24
 
25
+ export function Header({ onAddAccount, onCheckUpdate, checking, updateStatusMsg, updateStatusColor, version }: HeaderProps) {
26
  const { lang, toggleLang, t } = useI18n();
27
  const { isDark, toggle: toggleTheme } = useTheme();
28
 
 
50
  <span class="invisible col-start-1 row-start-1">Server Online</span>
51
  <span class="col-start-1 row-start-1">{t("serverOnline")}</span>
52
  </span>
53
+ {version && (
54
+ <span class="text-[0.65rem] font-mono text-primary/70">v{version}</span>
55
+ )}
56
  </div>
57
  {/* Star on GitHub */}
58
  <a
 
81
  </button>
82
  {/* Update status message */}
83
  {updateStatusMsg && !checking && (
84
+ <button
85
+ onClick={onCheckUpdate}
86
+ class={`hidden sm:inline text-xs font-medium ${updateStatusColor} hover:underline`}
87
+ >
88
  {updateStatusMsg}
89
+ </button>
90
  )}
91
  {/* Language Toggle */}
92
  <button
web/src/components/ProxyPool.tsx CHANGED
@@ -20,7 +20,7 @@ const statusStyles: Record<string, [string, TranslationKey]> = {
20
  ],
21
  };
22
 
23
- const inputCls = "px-3 py-2 text-sm border border-gray-200 dark:border-border-dark rounded-lg bg-transparent focus:outline-none focus:ring-1 focus:ring-primary";
24
 
25
  interface ProxyPoolProps {
26
  proxies: ProxiesState;
 
20
  ],
21
  };
22
 
23
+ const inputCls = "px-3 py-2 text-sm border border-gray-200 dark:border-border-dark rounded-lg bg-white dark:bg-bg-dark text-slate-700 dark:text-text-main focus:outline-none focus:ring-1 focus:ring-primary";
24
 
25
  interface ProxyPoolProps {
26
  proxies: ProxiesState;