Compare commits

..

2 Commits

Author SHA1 Message Date
hkfires
aa1dedc932 feat(ui): mark runtime-only auth files and disable actions 2025-11-07 22:02:29 +08:00
hkfires
61e75eee97 feat(auth-files): add AI Studio and Gemini CLI with filename masking 2025-11-07 21:46:46 +08:00
4 changed files with 71 additions and 20 deletions

60
app.js
View File

@@ -3370,17 +3370,13 @@ class CLIProxyManager {
if (fileStats.success === 0 && fileStats.failure === 0) {
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 = [];
// 规则1尝试完整描述脱敏
const match = nameWithoutExt.match(/^([^@]+@[^-]+)-(.+)$/);
if (match) {
const email = match[1]; // mikiunameina@gmail.com
const projectName = match[2]; // ethereal-advice-465201-t0
const email = match[1];
const projectName = match[2];
// 组合成完整的描述格式
const fullDescription = `${email} (${projectName})`;
@@ -3398,6 +3394,12 @@ class CLIProxyManager {
possibleSources.push(maskedPersonalId);
}
// 规则3AI Studio 特殊处理 - 对完整文件名脱敏
if (nameWithoutExt.startsWith('aistudio-')) {
const maskedFullName = this.maskApiKey(nameWithoutExt);
possibleSources.push(maskedFullName);
}
// 查找第一个有统计数据的匹配
for (const source of possibleSources) {
if (stats[source] && (stats[source].success > 0 || stats[source].failure > 0)) {
@@ -3418,6 +3420,12 @@ class CLIProxyManager {
case 'gemini':
typeDisplayKey = 'auth_files.type_gemini';
break;
case 'gemini-cli':
typeDisplayKey = 'auth_files.type_gemini-cli';
break;
case 'aistudio':
typeDisplayKey = 'auth_files.type_aistudio';
break;
case 'claude':
typeDisplayKey = 'auth_files.type_claude';
break;
@@ -3436,8 +3444,28 @@ class CLIProxyManager {
}
const typeBadge = `<span class="file-type-badge ${fileType}">${i18n.t(typeDisplayKey)}</span>`;
// 检查是否为 runtime-only 文件
const isRuntimeOnly = file.runtime_only === true;
// 生成操作按钮 HTMLruntime-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 `
<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-title">${typeBadge}${file.name}</div>
<div class="item-meta">
@@ -3453,17 +3481,7 @@ class CLIProxyManager {
<i class="fas fa-times-circle"></i> ${i18n.t('stats.failure')}: ${fileStats.failure}
</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>
${actionsHtml}
</div>
</div>
</div>
@@ -3487,6 +3505,8 @@ class CLIProxyManager {
{ type: 'all', labelKey: 'auth_files.filter_all' },
{ type: 'qwen', labelKey: 'auth_files.filter_qwen' },
{ 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: 'codex', labelKey: 'auth_files.filter_codex' },
{ type: 'iflow', labelKey: 'auth_files.filter_iflow' },
@@ -4648,7 +4668,7 @@ class CLIProxyManager {
try {
const response = await this.makeRequest('/usage');
const usage = response?.usage || null;
if (!usage) {
return {};
}
@@ -4665,7 +4685,7 @@ class CLIProxyManager {
details.forEach(detail => {
const source = detail.source;
if (!source) return;
if (!sourceStats[source]) {
sourceStats[source] = {
success: 0,

View File

@@ -225,6 +225,8 @@ const i18n = {
'auth_files.filter_all': '全部',
'auth_files.filter_qwen': 'Qwen',
'auth_files.filter_gemini': 'Gemini',
'auth_files.filter_gemini-cli': 'GeminiCLI',
'auth_files.filter_aistudio': 'AIStudio',
'auth_files.filter_claude': 'Claude',
'auth_files.filter_codex': 'Codex',
'auth_files.filter_iflow': 'iFlow',
@@ -232,6 +234,8 @@ const i18n = {
'auth_files.filter_unknown': '其他',
'auth_files.type_qwen': 'Qwen',
'auth_files.type_gemini': 'Gemini',
'auth_files.type_gemini-cli': 'GeminiCLI',
'auth_files.type_aistudio': 'AIStudio',
'auth_files.type_claude': 'Claude',
'auth_files.type_codex': 'Codex',
'auth_files.type_iflow': 'iFlow',
@@ -667,6 +671,8 @@ const i18n = {
'auth_files.filter_all': 'All',
'auth_files.filter_qwen': 'Qwen',
'auth_files.filter_gemini': 'Gemini',
'auth_files.filter_gemini-cli': 'GeminiCLI',
'auth_files.filter_aistudio': 'AIStudio',
'auth_files.filter_claude': 'Claude',
'auth_files.filter_codex': 'Codex',
'auth_files.filter_iflow': 'iFlow',
@@ -674,6 +680,8 @@ const i18n = {
'auth_files.filter_unknown': 'Other',
'auth_files.type_qwen': 'Qwen',
'auth_files.type_gemini': 'Gemini',
'auth_files.type_gemini-cli': 'GeminiCLI',
'auth_files.type_aistudio': 'AIStudio',
'auth_files.type_claude': 'Claude',
'auth_files.type_codex': 'Codex',
'auth_files.type_iflow': 'iFlow',

View File

@@ -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" 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-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="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>

View File

@@ -1683,6 +1683,27 @@ input:checked+.slider:before {
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 {
/* 默认不限制高度,动态设置 */
min-height: 0;