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
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa1dedc932 | ||
|
|
61e75eee97 | ||
|
|
3a2d96725f | ||
|
|
8283e99909 |
108
app.js
108
app.js
@@ -2315,6 +2315,30 @@ class CLIProxyManager {
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容服务端返回的数组结构
|
||||||
|
normalizeArrayResponse(data, key) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (data && Array.isArray(data[key])) {
|
||||||
|
return data[key];
|
||||||
|
}
|
||||||
|
if (data && Array.isArray(data.items)) {
|
||||||
|
return data.items;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造Codex配置,保持未展示的字段
|
||||||
|
buildCodexConfig(apiKey, baseUrl, proxyUrl, original = {}) {
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
'api-key': apiKey,
|
||||||
|
'base-url': baseUrl || '',
|
||||||
|
'proxy-url': proxyUrl || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 显示添加API密钥模态框
|
// 显示添加API密钥模态框
|
||||||
showAddApiKeyModal() {
|
showAddApiKeyModal() {
|
||||||
const modal = document.getElementById('modal');
|
const modal = document.getElementById('modal');
|
||||||
@@ -2695,15 +2719,9 @@ class CLIProxyManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.makeRequest('/codex-api-key');
|
const data = await this.makeRequest('/codex-api-key');
|
||||||
const currentKeys = data['codex-api-key'] || [];
|
const currentKeys = this.normalizeArrayResponse(data, 'codex-api-key').map(item => ({ ...item }));
|
||||||
|
|
||||||
const newConfig = { 'api-key': apiKey };
|
const newConfig = this.buildCodexConfig(apiKey, baseUrl, proxyUrl);
|
||||||
if (baseUrl) {
|
|
||||||
newConfig['base-url'] = baseUrl;
|
|
||||||
}
|
|
||||||
if (proxyUrl) {
|
|
||||||
newConfig['proxy-url'] = proxyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentKeys.push(newConfig);
|
currentKeys.push(newConfig);
|
||||||
|
|
||||||
@@ -2761,14 +2779,16 @@ class CLIProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newConfig = { 'api-key': apiKey };
|
const listResponse = await this.makeRequest('/codex-api-key');
|
||||||
if (baseUrl) {
|
const currentList = this.normalizeArrayResponse(listResponse, 'codex-api-key');
|
||||||
newConfig['base-url'] = baseUrl;
|
|
||||||
}
|
if (!Array.isArray(currentList) || index < 0 || index >= currentList.length) {
|
||||||
if (proxyUrl) {
|
throw new Error('Invalid codex configuration index');
|
||||||
newConfig['proxy-url'] = proxyUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const original = currentList[index] ? { ...currentList[index] } : {};
|
||||||
|
const newConfig = this.buildCodexConfig(apiKey, baseUrl, proxyUrl, original);
|
||||||
|
|
||||||
await this.makeRequest('/codex-api-key', {
|
await this.makeRequest('/codex-api-key', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify({ index, value: newConfig })
|
body: JSON.stringify({ index, value: newConfig })
|
||||||
@@ -3350,17 +3370,13 @@ class CLIProxyManager {
|
|||||||
if (fileStats.success === 0 && fileStats.failure === 0) {
|
if (fileStats.success === 0 && fileStats.failure === 0) {
|
||||||
const nameWithoutExt = file.name.replace(/\.[^/.]+$/, ""); // 去掉扩展名
|
const nameWithoutExt = file.name.replace(/\.[^/.]+$/, ""); // 去掉扩展名
|
||||||
|
|
||||||
// 后端有两种脱敏规则,都要尝试:
|
|
||||||
// 规则1:完整描述脱敏 - mikiunameina@gmail.com (ethereal-advice-465201-t0) -> 脱敏 -> miki...-t0)
|
|
||||||
// 规则2:直接整体脱敏 - mikiunameina@gmail.com-ethereal-advice-465201-t0 -> 脱敏 -> ???
|
|
||||||
|
|
||||||
const possibleSources = [];
|
const possibleSources = [];
|
||||||
|
|
||||||
// 规则1:尝试完整描述脱敏
|
// 规则1:尝试完整描述脱敏
|
||||||
const match = nameWithoutExt.match(/^([^@]+@[^-]+)-(.+)$/);
|
const match = nameWithoutExt.match(/^([^@]+@[^-]+)-(.+)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const email = match[1]; // mikiunameina@gmail.com
|
const email = match[1];
|
||||||
const projectName = match[2]; // ethereal-advice-465201-t0
|
const projectName = match[2];
|
||||||
|
|
||||||
// 组合成完整的描述格式
|
// 组合成完整的描述格式
|
||||||
const fullDescription = `${email} (${projectName})`;
|
const fullDescription = `${email} (${projectName})`;
|
||||||
@@ -3378,6 +3394,12 @@ class CLIProxyManager {
|
|||||||
possibleSources.push(maskedPersonalId);
|
possibleSources.push(maskedPersonalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 规则3:AI Studio 特殊处理 - 对完整文件名脱敏
|
||||||
|
if (nameWithoutExt.startsWith('aistudio-')) {
|
||||||
|
const maskedFullName = this.maskApiKey(nameWithoutExt);
|
||||||
|
possibleSources.push(maskedFullName);
|
||||||
|
}
|
||||||
|
|
||||||
// 查找第一个有统计数据的匹配
|
// 查找第一个有统计数据的匹配
|
||||||
for (const source of possibleSources) {
|
for (const source of possibleSources) {
|
||||||
if (stats[source] && (stats[source].success > 0 || stats[source].failure > 0)) {
|
if (stats[source] && (stats[source].success > 0 || stats[source].failure > 0)) {
|
||||||
@@ -3398,6 +3420,12 @@ class CLIProxyManager {
|
|||||||
case 'gemini':
|
case 'gemini':
|
||||||
typeDisplayKey = 'auth_files.type_gemini';
|
typeDisplayKey = 'auth_files.type_gemini';
|
||||||
break;
|
break;
|
||||||
|
case 'gemini-cli':
|
||||||
|
typeDisplayKey = 'auth_files.type_gemini-cli';
|
||||||
|
break;
|
||||||
|
case 'aistudio':
|
||||||
|
typeDisplayKey = 'auth_files.type_aistudio';
|
||||||
|
break;
|
||||||
case 'claude':
|
case 'claude':
|
||||||
typeDisplayKey = 'auth_files.type_claude';
|
typeDisplayKey = 'auth_files.type_claude';
|
||||||
break;
|
break;
|
||||||
@@ -3416,8 +3444,28 @@ class CLIProxyManager {
|
|||||||
}
|
}
|
||||||
const typeBadge = `<span class="file-type-badge ${fileType}">${i18n.t(typeDisplayKey)}</span>`;
|
const typeBadge = `<span class="file-type-badge ${fileType}">${i18n.t(typeDisplayKey)}</span>`;
|
||||||
|
|
||||||
|
// 检查是否为 runtime-only 文件
|
||||||
|
const isRuntimeOnly = file.runtime_only === true;
|
||||||
|
|
||||||
|
// 生成操作按钮 HTML,runtime-only 文件显示虚拟标记
|
||||||
|
const actionsHtml = isRuntimeOnly ? `
|
||||||
|
<div class="item-actions">
|
||||||
|
<span class="virtual-auth-badge">虚拟认证文件</span>
|
||||||
|
</div>` : `
|
||||||
|
<div class="item-actions" data-filename="${file.name}">
|
||||||
|
<button class="btn-small btn-info" data-action="showDetails" title="详细信息">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn-small btn-primary" data-action="download" title="下载">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn-small btn-danger" data-action="delete" title="删除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="file-item" data-file-type="${fileType}">
|
<div class="file-item" data-file-type="${fileType}" ${isRuntimeOnly ? 'data-runtime-only="true"' : ''}>
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="item-title">${typeBadge}${file.name}</div>
|
<div class="item-title">${typeBadge}${file.name}</div>
|
||||||
<div class="item-meta">
|
<div class="item-meta">
|
||||||
@@ -3433,17 +3481,7 @@ class CLIProxyManager {
|
|||||||
<i class="fas fa-times-circle"></i> ${i18n.t('stats.failure')}: ${fileStats.failure}
|
<i class="fas fa-times-circle"></i> ${i18n.t('stats.failure')}: ${fileStats.failure}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions" data-filename="${file.name}">
|
${actionsHtml}
|
||||||
<button class="btn-small btn-info" data-action="showDetails" title="详细信息">
|
|
||||||
<i class="fas fa-info-circle"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn-small btn-primary" data-action="download" title="下载">
|
|
||||||
<i class="fas fa-download"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn-small btn-danger" data-action="delete" title="删除">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3467,6 +3505,8 @@ class CLIProxyManager {
|
|||||||
{ type: 'all', labelKey: 'auth_files.filter_all' },
|
{ type: 'all', labelKey: 'auth_files.filter_all' },
|
||||||
{ type: 'qwen', labelKey: 'auth_files.filter_qwen' },
|
{ type: 'qwen', labelKey: 'auth_files.filter_qwen' },
|
||||||
{ type: 'gemini', labelKey: 'auth_files.filter_gemini' },
|
{ type: 'gemini', labelKey: 'auth_files.filter_gemini' },
|
||||||
|
{ type: 'gemini-cli', labelKey: 'auth_files.filter_gemini-cli' },
|
||||||
|
{ type: 'aistudio', labelKey: 'auth_files.filter_aistudio' },
|
||||||
{ type: 'claude', labelKey: 'auth_files.filter_claude' },
|
{ type: 'claude', labelKey: 'auth_files.filter_claude' },
|
||||||
{ type: 'codex', labelKey: 'auth_files.filter_codex' },
|
{ type: 'codex', labelKey: 'auth_files.filter_codex' },
|
||||||
{ type: 'iflow', labelKey: 'auth_files.filter_iflow' },
|
{ type: 'iflow', labelKey: 'auth_files.filter_iflow' },
|
||||||
@@ -4628,7 +4668,7 @@ class CLIProxyManager {
|
|||||||
try {
|
try {
|
||||||
const response = await this.makeRequest('/usage');
|
const response = await this.makeRequest('/usage');
|
||||||
const usage = response?.usage || null;
|
const usage = response?.usage || null;
|
||||||
|
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -4645,7 +4685,7 @@ class CLIProxyManager {
|
|||||||
details.forEach(detail => {
|
details.forEach(detail => {
|
||||||
const source = detail.source;
|
const source = detail.source;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
if (!sourceStats[source]) {
|
if (!sourceStats[source]) {
|
||||||
sourceStats[source] = {
|
sourceStats[source] = {
|
||||||
success: 0,
|
success: 0,
|
||||||
|
|||||||
8
i18n.js
8
i18n.js
@@ -225,6 +225,8 @@ const i18n = {
|
|||||||
'auth_files.filter_all': '全部',
|
'auth_files.filter_all': '全部',
|
||||||
'auth_files.filter_qwen': 'Qwen',
|
'auth_files.filter_qwen': 'Qwen',
|
||||||
'auth_files.filter_gemini': 'Gemini',
|
'auth_files.filter_gemini': 'Gemini',
|
||||||
|
'auth_files.filter_gemini-cli': 'GeminiCLI',
|
||||||
|
'auth_files.filter_aistudio': 'AIStudio',
|
||||||
'auth_files.filter_claude': 'Claude',
|
'auth_files.filter_claude': 'Claude',
|
||||||
'auth_files.filter_codex': 'Codex',
|
'auth_files.filter_codex': 'Codex',
|
||||||
'auth_files.filter_iflow': 'iFlow',
|
'auth_files.filter_iflow': 'iFlow',
|
||||||
@@ -232,6 +234,8 @@ const i18n = {
|
|||||||
'auth_files.filter_unknown': '其他',
|
'auth_files.filter_unknown': '其他',
|
||||||
'auth_files.type_qwen': 'Qwen',
|
'auth_files.type_qwen': 'Qwen',
|
||||||
'auth_files.type_gemini': 'Gemini',
|
'auth_files.type_gemini': 'Gemini',
|
||||||
|
'auth_files.type_gemini-cli': 'GeminiCLI',
|
||||||
|
'auth_files.type_aistudio': 'AIStudio',
|
||||||
'auth_files.type_claude': 'Claude',
|
'auth_files.type_claude': 'Claude',
|
||||||
'auth_files.type_codex': 'Codex',
|
'auth_files.type_codex': 'Codex',
|
||||||
'auth_files.type_iflow': 'iFlow',
|
'auth_files.type_iflow': 'iFlow',
|
||||||
@@ -667,6 +671,8 @@ const i18n = {
|
|||||||
'auth_files.filter_all': 'All',
|
'auth_files.filter_all': 'All',
|
||||||
'auth_files.filter_qwen': 'Qwen',
|
'auth_files.filter_qwen': 'Qwen',
|
||||||
'auth_files.filter_gemini': 'Gemini',
|
'auth_files.filter_gemini': 'Gemini',
|
||||||
|
'auth_files.filter_gemini-cli': 'GeminiCLI',
|
||||||
|
'auth_files.filter_aistudio': 'AIStudio',
|
||||||
'auth_files.filter_claude': 'Claude',
|
'auth_files.filter_claude': 'Claude',
|
||||||
'auth_files.filter_codex': 'Codex',
|
'auth_files.filter_codex': 'Codex',
|
||||||
'auth_files.filter_iflow': 'iFlow',
|
'auth_files.filter_iflow': 'iFlow',
|
||||||
@@ -674,6 +680,8 @@ const i18n = {
|
|||||||
'auth_files.filter_unknown': 'Other',
|
'auth_files.filter_unknown': 'Other',
|
||||||
'auth_files.type_qwen': 'Qwen',
|
'auth_files.type_qwen': 'Qwen',
|
||||||
'auth_files.type_gemini': 'Gemini',
|
'auth_files.type_gemini': 'Gemini',
|
||||||
|
'auth_files.type_gemini-cli': 'GeminiCLI',
|
||||||
|
'auth_files.type_aistudio': 'AIStudio',
|
||||||
'auth_files.type_claude': 'Claude',
|
'auth_files.type_claude': 'Claude',
|
||||||
'auth_files.type_codex': 'Codex',
|
'auth_files.type_codex': 'Codex',
|
||||||
'auth_files.type_iflow': 'iFlow',
|
'auth_files.type_iflow': 'iFlow',
|
||||||
|
|||||||
@@ -427,6 +427,8 @@
|
|||||||
<button class="filter-btn active" data-type="all" data-i18n-text="auth_files.filter_all">All</button>
|
<button class="filter-btn active" data-type="all" data-i18n-text="auth_files.filter_all">All</button>
|
||||||
<button class="filter-btn" data-type="qwen" data-i18n-text="auth_files.filter_qwen">Qwen</button>
|
<button class="filter-btn" data-type="qwen" data-i18n-text="auth_files.filter_qwen">Qwen</button>
|
||||||
<button class="filter-btn" data-type="gemini" data-i18n-text="auth_files.filter_gemini">Gemini</button>
|
<button class="filter-btn" data-type="gemini" data-i18n-text="auth_files.filter_gemini">Gemini</button>
|
||||||
|
<button class="filter-btn" data-type="gemini-cli" data-i18n-text="auth_files.filter_gemini-cli">GeminiCLI</button>
|
||||||
|
<button class="filter-btn" data-type="aistudio" data-i18n-text="auth_files.filter_aistudio">AIStudio</button>
|
||||||
<button class="filter-btn" data-type="claude" data-i18n-text="auth_files.filter_claude">Claude</button>
|
<button class="filter-btn" data-type="claude" data-i18n-text="auth_files.filter_claude">Claude</button>
|
||||||
<button class="filter-btn" data-type="codex" data-i18n-text="auth_files.filter_codex">Codex</button>
|
<button class="filter-btn" data-type="codex" data-i18n-text="auth_files.filter_codex">Codex</button>
|
||||||
<button class="filter-btn" data-type="iflow" data-i18n-text="auth_files.filter_iflow">iFlow</button>
|
<button class="filter-btn" data-type="iflow" data-i18n-text="auth_files.filter_iflow">iFlow</button>
|
||||||
|
|||||||
38
styles.css
38
styles.css
@@ -1683,6 +1683,27 @@ input:checked+.slider:before {
|
|||||||
border: 1px dashed #666666;
|
border: 1px dashed #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 虚拟认证文件标记样式 */
|
||||||
|
.virtual-auth-badge {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.25);
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .virtual-auth-badge {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
|
||||||
|
box-shadow: 0 2px 4px rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.provider-list {
|
.provider-list {
|
||||||
/* 默认不限制高度,动态设置 */
|
/* 默认不限制高度,动态设置 */
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -1751,6 +1772,22 @@ input:checked+.slider:before {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 认证文件项的底部布局优化 - 确保状态标签和操作按钮垂直对齐 */
|
||||||
|
.file-item .item-footer {
|
||||||
|
align-items: center; /* 垂直居中对齐 */
|
||||||
|
gap: 12px; /* 增加状态标签和按钮之间的间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item .item-stats {
|
||||||
|
align-items: center; /* 状态徽章垂直居中 */
|
||||||
|
margin: 0; /* 移除默认外边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item .item-actions {
|
||||||
|
align-items: center; /* 操作按钮垂直居中 */
|
||||||
|
gap: 6px; /* 按钮之间适当间距 */
|
||||||
|
}
|
||||||
|
|
||||||
/* API Keys 和 AI Providers 的按钮组 - 绝对定位到右侧垂直居中 */
|
/* API Keys 和 AI Providers 的按钮组 - 绝对定位到右侧垂直居中 */
|
||||||
.key-item .item-actions,
|
.key-item .item-actions,
|
||||||
.provider-item .item-actions {
|
.provider-item .item-actions {
|
||||||
@@ -3642,6 +3679,7 @@ input:checked+.slider:before {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 统计徽章基础样式 */
|
/* 统计徽章基础样式 */
|
||||||
|
|||||||
Reference in New Issue
Block a user