feat: add sensitive value masking functionality to usage module and update UI for system info localization

This commit is contained in:
Supra4E8C
2025-12-05 18:30:01 +08:00
parent ba6a461a40
commit ac4f310fe8
4 changed files with 64 additions and 6 deletions

View File

@@ -94,7 +94,7 @@ const i18n = {
'nav.usage_stats': '使用统计',
'nav.config_management': '配置管理',
'nav.logs': '日志查看',
'nav.system_info': '管理中心信息',
'nav.system_info': '中心信息',
// 基础设置
'basic_settings.title': '基础设置',

View File

@@ -182,7 +182,7 @@
<i class="fas fa-scroll"></i> <span data-i18n="nav.logs">日志查看</span>
</a></li>
<li data-i18n-tooltip="nav.system_info"><a href="#system-info" class="nav-item" data-section="system-info">
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">管理中心信息</span>
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">中心信息</span>
</a></li>
</ul>
</nav>

View File

@@ -5,6 +5,46 @@ const DEFAULT_CHART_LINE_COUNT = 3;
const MIN_CHART_LINE_COUNT = 1;
const ALL_MODELS_VALUE = 'all';
export function maskUsageSensitiveValue(value) {
if (value === null || value === undefined) {
return '';
}
const raw = typeof value === 'string' ? value : String(value);
if (!raw) {
return '';
}
const maskFn = (this && typeof this.maskApiKey === 'function') ? this.maskApiKey : (v) => v;
let masked = raw;
const queryRegex = /([?&])(api[-_]?key|key|token|access_token|authorization)=([^&#\s]+)/ig;
masked = masked.replace(queryRegex, (full, prefix, keyName, valuePart) => `${prefix}${keyName}=${maskFn(valuePart)}`);
const headerRegex = /(api[-_]?key|key|token|access[-_]?token|authorization)\s*([:=])\s*([A-Za-z0-9._-]+)/ig;
masked = masked.replace(headerRegex, (full, keyName, separator, valuePart) => `${keyName}${separator}${maskFn(valuePart)}`);
const keyLikeRegex = /(sk-[A-Za-z0-9]{6,}|AI[a-zA-Z0-9_-]{6,}|AIza[0-9A-Za-z-_]{8,}|hf_[A-Za-z0-9]{6,}|pk_[A-Za-z0-9]{6,}|rk_[A-Za-z0-9]{6,})/g;
masked = masked.replace(keyLikeRegex, match => maskFn(match));
if (masked === raw) {
const trimmed = raw.trim();
if (trimmed && !/\s/.test(trimmed)) {
const looksLikeKey = /^sk-/i.test(trimmed)
|| /^AI/i.test(trimmed)
|| /^AIza/i.test(trimmed)
|| /^hf_/i.test(trimmed)
|| /^pk_/i.test(trimmed)
|| /^rk_/i.test(trimmed)
|| (!/[\\/]/.test(trimmed) && (/\d/.test(trimmed) || trimmed.length >= 10))
|| trimmed.length >= 24;
if (looksLikeKey) {
return maskFn(trimmed);
}
}
}
return masked;
}
// 获取API密钥的统计信息
export async function getKeyStats(usageData = null) {
try {
@@ -45,7 +85,9 @@ export async function getKeyStats(usageData = null) {
const details = modelEntry.details || [];
details.forEach(detail => {
const source = detail.source;
const source = this.maskUsageSensitiveValue
? this.maskUsageSensitiveValue(detail.source)
: detail.source;
const authIndexKey = normalizeAuthIndex(detail?.auth_index);
const isFailed = detail.failed === true;
@@ -1489,17 +1531,27 @@ export function updateApiStatsTable(data) {
Object.entries(apis).forEach(([endpoint, apiData]) => {
const totalRequests = apiData.total_requests || 0;
const endpointCost = calculateEndpointCost(apiData);
const displayEndpoint = (this.maskUsageSensitiveValue
? this.maskUsageSensitiveValue(endpoint)
: (endpoint ?? '')) || '-';
const safeEndpoint = this.escapeHtml
? this.escapeHtml(displayEndpoint)
: displayEndpoint;
// 构建模型详情
let modelsHtml = '';
if (apiData.models && Object.keys(apiData.models).length > 0) {
modelsHtml = '<div class="model-details">';
Object.entries(apiData.models).forEach(([modelName, modelData]) => {
const maskedModel = (this.maskUsageSensitiveValue
? this.maskUsageSensitiveValue(modelName)
: modelName) || '';
const safeModel = this.escapeHtml ? this.escapeHtml(maskedModel) : maskedModel;
const modelRequests = modelData.total_requests ?? 0;
const modelTokens = this.formatTokensInMillions(modelData.total_tokens ?? 0);
modelsHtml += `
<div class="model-item">
<span class="model-name">${modelName}</span>
<span class="model-name">${safeModel}</span>
<span>${modelRequests} 请求 / ${modelTokens} tokens</span>
</div>
`;
@@ -1509,7 +1561,7 @@ export function updateApiStatsTable(data) {
tableHtml += `
<tr>
<td>${endpoint}</td>
<td>${safeEndpoint}</td>
<td>${totalRequests}</td>
<td>${this.formatTokensInMillions(apiData.total_tokens || 0)}</td>
<td>${hasPrices && endpointCost > 0 ? this.formatUsd(endpointCost) : '--'}</td>
@@ -1526,6 +1578,7 @@ export const usageModule = {
getKeyStats,
loadUsageStats,
updateUsageOverview,
maskUsageSensitiveValue,
getModelNamesFromUsage,
getChartLineMaxCount,
getVisibleChartLineCount,

View File

@@ -804,7 +804,7 @@ body {
display: flex;
flex-direction: column;
background: var(--bg-primary);
min-height: 100%;
min-height: calc(100vh - var(--navbar-height, 69px));
}
/* 顶部导航栏 */
@@ -1036,6 +1036,8 @@ body {
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px 32px;
max-width: 1400px;
width: 100%;
@@ -1046,6 +1048,8 @@ body {
.content-area {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
}
.content-section {
@@ -3528,6 +3532,7 @@ input:checked+.slider:before {
margin-top: 40px;
padding: 24px 0;
border-top: 1px solid var(--border-primary);
flex-shrink: 0;
}
.version-info {