mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
Compare commits
5 Commits
c6fabcb6bc
...
v0.5.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a05145bf48 | ||
|
|
1007dbbf44 | ||
|
|
3046375b3c | ||
|
|
48956aa0a7 | ||
|
|
d3db2680cf |
58
app.js
58
app.js
@@ -229,13 +229,25 @@ class CLIProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLocalHostname(hostname = (typeof window !== 'undefined' ? window.location.hostname : '')) {
|
||||||
|
const host = (hostname || '').toLowerCase();
|
||||||
|
return host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
||||||
|
}
|
||||||
|
|
||||||
|
isIflowOAuthAllowed(hostname = (typeof window !== 'undefined' ? window.location.hostname : '')) {
|
||||||
|
const host = (hostname || '').toLowerCase();
|
||||||
|
// iFlow OAuth 仅允许在本机回环地址访问
|
||||||
|
return host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
||||||
|
}
|
||||||
|
|
||||||
// 检查主机名并隐藏 OAuth 登录框
|
// 检查主机名并隐藏 OAuth 登录框
|
||||||
checkHostAndHideOAuth() {
|
checkHostAndHideOAuth() {
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
const isLocalhost = this.isLocalHostname(hostname);
|
||||||
|
const isIflowOAuthAllowed = this.isIflowOAuthAllowed(hostname);
|
||||||
|
|
||||||
if (!isLocalhost) {
|
if (!isLocalhost) {
|
||||||
// 隐藏所有 OAuth 登录卡片
|
// 隐藏所有 OAuth 登录卡片(除了 iFlow, 因为它有 Cookie 登录功能可远程使用)
|
||||||
OAUTH_CARD_IDS.forEach(cardId => {
|
OAUTH_CARD_IDS.forEach(cardId => {
|
||||||
const card = document.getElementById(cardId);
|
const card = document.getElementById(cardId);
|
||||||
if (card) {
|
if (card) {
|
||||||
@@ -247,17 +259,44 @@ class CLIProxyManager {
|
|||||||
const oauthCardElements = document.querySelectorAll('.card');
|
const oauthCardElements = document.querySelectorAll('.card');
|
||||||
oauthCardElements.forEach(card => {
|
oauthCardElements.forEach(card => {
|
||||||
const cardText = card.textContent || '';
|
const cardText = card.textContent || '';
|
||||||
|
// 不再隐藏包含 'iFlow' 的卡片
|
||||||
if (cardText.includes('Codex OAuth') ||
|
if (cardText.includes('Codex OAuth') ||
|
||||||
cardText.includes('Anthropic OAuth') ||
|
cardText.includes('Anthropic OAuth') ||
|
||||||
cardText.includes('Antigravity OAuth') ||
|
cardText.includes('Antigravity OAuth') ||
|
||||||
cardText.includes('Gemini CLI OAuth') ||
|
cardText.includes('Gemini CLI OAuth') ||
|
||||||
cardText.includes('Qwen OAuth') ||
|
cardText.includes('Qwen OAuth')) {
|
||||||
cardText.includes('iFlow OAuth')) {
|
|
||||||
card.style.display = 'none';
|
card.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`当前主机名: ${hostname},已隐藏 OAuth 登录框`);
|
console.log(`当前主机名: ${hostname},已隐藏 OAuth 登录框(保留 iFlow Cookie 登录)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isIflowOAuthAllowed) {
|
||||||
|
// 对于 iFlow card, 仅在本机允许 OAuth,其余情况只保留 Cookie 登录
|
||||||
|
const iflowCard = document.getElementById('iflow-oauth-card');
|
||||||
|
if (iflowCard) {
|
||||||
|
const oauthContent = document.getElementById('iflow-oauth-content');
|
||||||
|
const oauthButton = document.getElementById('iflow-oauth-btn');
|
||||||
|
const oauthStatus = document.getElementById('iflow-oauth-status');
|
||||||
|
const oauthUrlGroup = document.getElementById('iflow-oauth-url')?.closest('.form-group');
|
||||||
|
const oauthHint = iflowCard.querySelector('[data-i18n="auth_login.iflow_oauth_hint"]');
|
||||||
|
|
||||||
|
if (oauthContent) oauthContent.style.display = 'none';
|
||||||
|
if (oauthButton) oauthButton.style.display = 'none';
|
||||||
|
if (oauthStatus) {
|
||||||
|
oauthStatus.textContent = i18n.t('auth_login.iflow_oauth_local_only');
|
||||||
|
oauthStatus.style.display = 'block';
|
||||||
|
oauthStatus.style.color = 'var(--warning-text)';
|
||||||
|
}
|
||||||
|
if (oauthUrlGroup) oauthUrlGroup.style.display = 'none';
|
||||||
|
if (oauthHint) oauthHint.style.display = 'none';
|
||||||
|
|
||||||
|
// 保持整个 card 可见, 因为 Cookie 登录部分仍然可用
|
||||||
|
iflowCard.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`当前主机名: ${hostname},iFlow OAuth 已限制为本机访问,仅保留 Cookie 登录`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,7 +1050,8 @@ function exposeManagerInstance(instance) {
|
|||||||
function setupSiteLogo() {
|
function setupSiteLogo() {
|
||||||
const img = document.getElementById('site-logo');
|
const img = document.getElementById('site-logo');
|
||||||
const loginImg = document.getElementById('login-logo');
|
const loginImg = document.getElementById('login-logo');
|
||||||
if (!img && !loginImg) return;
|
const favicon = document.getElementById('favicon-link');
|
||||||
|
if (!img && !loginImg && !favicon) return;
|
||||||
|
|
||||||
const inlineLogo = typeof window !== 'undefined' ? window.__INLINE_LOGO__ : null;
|
const inlineLogo = typeof window !== 'undefined' ? window.__INLINE_LOGO__ : null;
|
||||||
if (inlineLogo) {
|
if (inlineLogo) {
|
||||||
@@ -1023,6 +1063,9 @@ function setupSiteLogo() {
|
|||||||
loginImg.src = inlineLogo;
|
loginImg.src = inlineLogo;
|
||||||
loginImg.style.display = 'inline-block';
|
loginImg.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
|
if (favicon) {
|
||||||
|
favicon.href = inlineLogo;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,6 +1087,9 @@ function setupSiteLogo() {
|
|||||||
loginImg.src = test.src;
|
loginImg.src = test.src;
|
||||||
loginImg.style.display = 'inline-block';
|
loginImg.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
|
if (favicon) {
|
||||||
|
favicon.href = test.src;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
test.onerror = () => {
|
test.onerror = () => {
|
||||||
idx++;
|
idx++;
|
||||||
|
|||||||
2
i18n.js
2
i18n.js
@@ -431,6 +431,7 @@ const i18n = {
|
|||||||
'auth_login.iflow_oauth_title': 'iFlow OAuth',
|
'auth_login.iflow_oauth_title': 'iFlow OAuth',
|
||||||
'auth_login.iflow_oauth_button': '开始 iFlow 登录',
|
'auth_login.iflow_oauth_button': '开始 iFlow 登录',
|
||||||
'auth_login.iflow_oauth_hint': '通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。',
|
'auth_login.iflow_oauth_hint': '通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。',
|
||||||
|
'auth_login.iflow_oauth_local_only': 'iFlow OAuth 仅在本机 (127.0.0.1) 访问时可用,请使用 Cookie 登录。',
|
||||||
'auth_login.iflow_oauth_url_label': '授权链接:',
|
'auth_login.iflow_oauth_url_label': '授权链接:',
|
||||||
'auth_login.iflow_open_link': '打开链接',
|
'auth_login.iflow_open_link': '打开链接',
|
||||||
'auth_login.iflow_copy_link': '复制链接',
|
'auth_login.iflow_copy_link': '复制链接',
|
||||||
@@ -1097,6 +1098,7 @@ const i18n = {
|
|||||||
'auth_login.iflow_oauth_title': 'iFlow OAuth',
|
'auth_login.iflow_oauth_title': 'iFlow OAuth',
|
||||||
'auth_login.iflow_oauth_button': 'Start iFlow Login',
|
'auth_login.iflow_oauth_button': 'Start iFlow Login',
|
||||||
'auth_login.iflow_oauth_hint': 'Login to iFlow service through OAuth flow, automatically obtain and save authentication files.',
|
'auth_login.iflow_oauth_hint': 'Login to iFlow service through OAuth flow, automatically obtain and save authentication files.',
|
||||||
|
'auth_login.iflow_oauth_local_only': 'iFlow OAuth is only available from 127.0.0.1 (local machine); please use Cookie login remotely.',
|
||||||
'auth_login.iflow_oauth_url_label': 'Authorization URL:',
|
'auth_login.iflow_oauth_url_label': 'Authorization URL:',
|
||||||
'auth_login.iflow_open_link': 'Open Link',
|
'auth_login.iflow_open_link': 'Open Link',
|
||||||
'auth_login.iflow_copy_link': 'Copy Link',
|
'auth_login.iflow_copy_link': 'Copy Link',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title data-i18n="title.login">CLI Proxy API Management Center</title>
|
<title data-i18n="title.login">CLI Proxy API Management Center</title>
|
||||||
|
<link rel="icon" type="image/x-icon" id="favicon-link">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||||
|
|||||||
@@ -1275,9 +1275,25 @@ export async function refreshOpenAIModelDiscovery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(context.endpoint, {
|
let response;
|
||||||
headers: context.headers || {}
|
let usedSimpleRequest = false;
|
||||||
});
|
|
||||||
|
try {
|
||||||
|
// 首先尝试正常的带自定义headers的请求
|
||||||
|
response = await fetch(context.endpoint, {
|
||||||
|
headers: context.headers || {}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// 如果fetch失败(通常是CORS预检失败),尝试简单GET请求
|
||||||
|
console.warn('Normal fetch failed, trying simple GET request:', error);
|
||||||
|
usedSimpleRequest = true;
|
||||||
|
response = await fetch(context.endpoint, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'omit'
|
||||||
|
// 不发送自定义headers,避免触发OPTIONS预检
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`${response.status} ${response.statusText}`);
|
throw new Error(`${response.status} ${response.statusText}`);
|
||||||
@@ -1298,6 +1314,10 @@ export async function refreshOpenAIModelDiscovery() {
|
|||||||
if (!models.length) {
|
if (!models.length) {
|
||||||
this.setOpenAIModelDiscoveryStatus(i18n.t('ai_providers.openai_models_fetch_empty'), 'warning');
|
this.setOpenAIModelDiscoveryStatus(i18n.t('ai_providers.openai_models_fetch_empty'), 'warning');
|
||||||
} else {
|
} else {
|
||||||
|
if (usedSimpleRequest) {
|
||||||
|
// 如果使用了简单请求,提示用户
|
||||||
|
console.info('Models fetched using simple request (without custom headers)');
|
||||||
|
}
|
||||||
this.setOpenAIModelDiscoveryStatus('', 'info');
|
this.setOpenAIModelDiscoveryStatus('', 'info');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -723,6 +723,17 @@ export const oauthModule = {
|
|||||||
|
|
||||||
// 开始 iFlow OAuth 流程
|
// 开始 iFlow OAuth 流程
|
||||||
async startIflowOAuth() {
|
async startIflowOAuth() {
|
||||||
|
if (!this.isIflowOAuthAllowed()) {
|
||||||
|
const statusEl = document.getElementById('iflow-oauth-status');
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.textContent = i18n.t('auth_login.iflow_oauth_local_only');
|
||||||
|
statusEl.style.display = 'block';
|
||||||
|
statusEl.style.color = 'var(--warning-text)';
|
||||||
|
}
|
||||||
|
this.showNotification(i18n.t('auth_login.iflow_oauth_local_only'), 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.makeRequest('/iflow-auth-url?is_webui=1');
|
const response = await this.makeRequest('/iflow-auth-url?is_webui=1');
|
||||||
const authUrl = response.url;
|
const authUrl = response.url;
|
||||||
|
|||||||
@@ -111,14 +111,14 @@ export const REQUEST_TIMEOUT_MS = 30 * 1000;
|
|||||||
/**
|
/**
|
||||||
* OAuth 卡片元素 ID 列表
|
* OAuth 卡片元素 ID 列表
|
||||||
* 用于根据主机环境隐藏/显示不同的 OAuth 选项
|
* 用于根据主机环境隐藏/显示不同的 OAuth 选项
|
||||||
|
* 注意: iflow-oauth-card 不在此列表中,因为它包含Cookie登录功能,该功能可在远程使用
|
||||||
*/
|
*/
|
||||||
export const OAUTH_CARD_IDS = [
|
export const OAUTH_CARD_IDS = [
|
||||||
'codex-oauth-card',
|
'codex-oauth-card',
|
||||||
'anthropic-oauth-card',
|
'anthropic-oauth-card',
|
||||||
'antigravity-oauth-card',
|
'antigravity-oauth-card',
|
||||||
'gemini-cli-oauth-card',
|
'gemini-cli-oauth-card',
|
||||||
'qwen-oauth-card',
|
'qwen-oauth-card'
|
||||||
'iflow-oauth-card'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3057,8 +3057,9 @@ input:checked+.slider:before {
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: var(--navbar-height, 69px);
|
top: 0;
|
||||||
height: calc(100vh - var(--navbar-height, 69px));
|
height: 100vh;
|
||||||
|
padding-top: calc(var(--navbar-height, 69px) + 20px);
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
z-index: 150;
|
z-index: 150;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
@@ -3110,7 +3111,7 @@ input:checked+.slider:before {
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: var(--navbar-height, 69px);
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-navbar-title {
|
.top-navbar-title {
|
||||||
|
|||||||
Reference in New Issue
Block a user