diff --git a/index.html b/index.html index 1bc9508..0f8be67 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - cli-proxy-webui-react + CLI Proxy API Management Center
diff --git a/logo.jpg b/logo.jpg new file mode 100644 index 0000000..f701a6c Binary files /dev/null and b/logo.jpg differ diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 593777f..35b6c2e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -431,7 +431,8 @@ "iflow_cookie_result_email": "Account", "iflow_cookie_result_expired": "Expires At", "iflow_cookie_result_path": "Saved Path", - "iflow_cookie_result_type": "Type" + "iflow_cookie_result_type": "Type", + "remote_access_disabled": "This login method is not available for remote access. Please access from localhost." }, "usage_stats": { "title": "Usage Statistics", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index da0c137..a0681f9 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -431,7 +431,8 @@ "iflow_cookie_result_email": "账号", "iflow_cookie_result_expired": "过期时间", "iflow_cookie_result_path": "保存路径", - "iflow_cookie_result_type": "类型" + "iflow_cookie_result_type": "类型", + "remote_access_disabled": "远程访问不支持此登录方式,请从本地 (localhost) 访问" }, "usage_stats": { "title": "使用统计", diff --git a/src/pages/OAuthPage.tsx b/src/pages/OAuthPage.tsx index 7967c70..05b132f 100644 --- a/src/pages/OAuthPage.tsx +++ b/src/pages/OAuthPage.tsx @@ -1,9 +1,11 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; +import { Input } from '@/components/ui/Input'; import { useNotificationStore } from '@/stores'; -import { oauthApi, type OAuthProvider } from '@/services/api/oauth'; +import { oauthApi, type OAuthProvider, type IFlowCookieAuthResponse } from '@/services/api/oauth'; +import { isLocalhost } from '@/utils/connection'; interface ProviderState { url?: string; @@ -13,6 +15,13 @@ interface ProviderState { polling?: boolean; } +interface IFlowCookieState { + cookie: string; + loading: boolean; + result?: IFlowCookieAuthResponse; + error?: string; +} + const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabelKey: string }[] = [ { id: 'codex', titleKey: 'auth_login.codex_oauth_title', hintKey: 'auth_login.codex_oauth_hint', urlLabelKey: 'auth_login.codex_oauth_url_label' }, { id: 'anthropic', titleKey: 'auth_login.anthropic_oauth_title', hintKey: 'auth_login.anthropic_oauth_hint', urlLabelKey: 'auth_login.anthropic_oauth_url_label' }, @@ -26,8 +35,12 @@ export function OAuthPage() { const { t } = useTranslation(); const { showNotification } = useNotificationStore(); const [states, setStates] = useState>({} as Record); + const [iflowCookie, setIflowCookie] = useState({ cookie: '', loading: false }); const timers = useRef>({}); + // 检测是否为本地访问 + const isLocal = useMemo(() => isLocalhost(window.location.hostname), []); + useEffect(() => { return () => { Object.values(timers.current).forEach((timer) => window.clearInterval(timer)); @@ -103,22 +116,58 @@ export function OAuthPage() { } }; + const submitIflowCookie = async () => { + const cookie = iflowCookie.cookie.trim(); + if (!cookie) { + showNotification(t('auth_login.iflow_cookie_required'), 'warning'); + return; + } + setIflowCookie((prev) => ({ ...prev, loading: true, error: undefined, result: undefined })); + try { + const res = await oauthApi.iflowCookieAuth(cookie); + if (res.status === 'ok') { + setIflowCookie((prev) => ({ ...prev, loading: false, result: res })); + showNotification(t('auth_login.iflow_cookie_status_success'), 'success'); + } else { + setIflowCookie((prev) => ({ ...prev, loading: false, error: res.error })); + showNotification(`${t('auth_login.iflow_cookie_status_error')} ${res.error || ''}`, 'error'); + } + } catch (err: any) { + setIflowCookie((prev) => ({ ...prev, loading: false, error: err?.message })); + showNotification(`${t('auth_login.iflow_cookie_start_error')} ${err?.message || ''}`, 'error'); + } + }; + return (
{PROVIDERS.map((provider) => { const state = states[provider.id] || {}; + // 非本地访问时禁用所有 OAuth 登录方式 + const isDisabled = !isLocal; return ( - startAuth(provider.id)} loading={state.polling}> - {t('common.login')} - - } + style={isDisabled ? { opacity: 0.6, pointerEvents: 'none' } : undefined} > -
{t(provider.hintKey)}
- {state.url && ( + startAuth(provider.id)} + loading={state.polling} + disabled={isDisabled} + > + {t('common.login')} + + } + > +
{t(provider.hintKey)}
+ {isDisabled && ( +
+ {t('auth_login.remote_access_disabled')} +
+ )} + {!isDisabled && state.url && (
{t(provider.urlLabelKey)}
{state.url}
@@ -136,18 +185,77 @@ export function OAuthPage() {
)} -
- {state.status === 'success' - ? t('auth_login.codex_oauth_status_success') - : state.status === 'error' - ? `${t('auth_login.codex_oauth_status_error')} ${state.error || ''}` - : state.status === 'waiting' - ? t('auth_login.codex_oauth_status_waiting') - : t('common.info')} -
+ {!isDisabled && ( +
+ {state.status === 'success' + ? t('auth_login.codex_oauth_status_success') + : state.status === 'error' + ? `${t('auth_login.codex_oauth_status_error')} ${state.error || ''}` + : state.status === 'waiting' + ? t('auth_login.codex_oauth_status_waiting') + : t('common.info')} +
+ )} + ); })} + + {/* iFlow Cookie 登录 */} + + {t('auth_login.iflow_cookie_button')} + + } + > +
{t('auth_login.iflow_cookie_hint')}
+
+ + setIflowCookie((prev) => ({ ...prev, cookie: e.target.value }))} + placeholder={t('auth_login.iflow_cookie_placeholder')} + /> +
+ {iflowCookie.error && ( +
+ {t('auth_login.iflow_cookie_status_error')} {iflowCookie.error} +
+ )} + {iflowCookie.result && iflowCookie.result.status === 'ok' && ( +
+
{t('auth_login.iflow_cookie_result_title')}
+
+ {iflowCookie.result.email && ( +
+ {t('auth_login.iflow_cookie_result_email')} + {iflowCookie.result.email} +
+ )} + {iflowCookie.result.expired && ( +
+ {t('auth_login.iflow_cookie_result_expired')} + {iflowCookie.result.expired} +
+ )} + {iflowCookie.result.saved_path && ( +
+ {t('auth_login.iflow_cookie_result_path')} + {iflowCookie.result.saved_path} +
+ )} + {iflowCookie.result.type && ( +
+ {t('auth_login.iflow_cookie_result_type')} + {iflowCookie.result.type} +
+ )} +
+
+ )} +
); } diff --git a/src/services/api/oauth.ts b/src/services/api/oauth.ts index cba7c85..762cbd3 100644 --- a/src/services/api/oauth.ts +++ b/src/services/api/oauth.ts @@ -17,6 +17,15 @@ export interface OAuthStartResponse { state?: string; } +export interface IFlowCookieAuthResponse { + status: 'ok' | 'error'; + error?: string; + saved_path?: string; + email?: string; + expired?: string; + type?: string; +} + const WEBUI_SUPPORTED: OAuthProvider[] = ['codex', 'anthropic', 'antigravity', 'gemini-cli', 'iflow']; export const oauthApi = { @@ -28,5 +37,9 @@ export const oauthApi = { getAuthStatus: (state: string) => apiClient.get<{ status: 'ok' | 'wait' | 'error'; error?: string }>(`/get-auth-status`, { params: { state } - }) + }), + + /** iFlow cookie 认证 */ + iflowCookieAuth: (cookie: string) => + apiClient.post('/iflow-auth-url', { cookie }) };