将项目中所有文档、注释和用户界面中的"供应商"术语统一修改为"提供商",提升术语一致性。同时: - 优化提供商健康检查频率从30分钟调整为10分钟,提高监控及时性 - 新增路由URL动态更新功能,支持不同部署环境的路径适配 - 更新相关样式类名和注释,确保代码与文档术语保持一致
313 lines
No EOL
11 KiB
JavaScript
313 lines
No EOL
11 KiB
JavaScript
// 提供商管理功能模块
|
|
|
|
import { providerStats, updateProviderStats } from './constants.js';
|
|
import { showToast } from './utils.js';
|
|
|
|
// 保存初始服务器时间和运行时间
|
|
let initialServerTime = null;
|
|
let initialUptime = null;
|
|
let initialLoadTime = null;
|
|
|
|
/**
|
|
* 加载系统信息
|
|
*/
|
|
async function loadSystemInfo() {
|
|
try {
|
|
const data = await window.apiClient.get('/system');
|
|
|
|
const nodeVersionEl = document.getElementById('nodeVersion');
|
|
const serverTimeEl = document.getElementById('serverTime');
|
|
const memoryUsageEl = document.getElementById('memoryUsage');
|
|
const uptimeEl = document.getElementById('uptime');
|
|
|
|
if (nodeVersionEl) nodeVersionEl.textContent = data.nodeVersion || '--';
|
|
if (memoryUsageEl) memoryUsageEl.textContent = data.memoryUsage || '--';
|
|
|
|
// 保存初始时间用于本地计算
|
|
if (data.serverTime && data.uptime !== undefined) {
|
|
initialServerTime = new Date(data.serverTime);
|
|
initialUptime = data.uptime;
|
|
initialLoadTime = Date.now();
|
|
}
|
|
|
|
// 初始显示
|
|
if (serverTimeEl) serverTimeEl.textContent = data.serverTime || '--';
|
|
if (uptimeEl) uptimeEl.textContent = data.uptime ? formatUptime(data.uptime) : '--';
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load system info:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新服务器时间和运行时间显示(本地计算)
|
|
*/
|
|
function updateTimeDisplay() {
|
|
if (!initialServerTime || initialUptime === null || !initialLoadTime) {
|
|
return;
|
|
}
|
|
|
|
const serverTimeEl = document.getElementById('serverTime');
|
|
const uptimeEl = document.getElementById('uptime');
|
|
|
|
// 计算经过的秒数
|
|
const elapsedSeconds = Math.floor((Date.now() - initialLoadTime) / 1000);
|
|
|
|
// 更新服务器时间
|
|
if (serverTimeEl) {
|
|
const currentServerTime = new Date(initialServerTime.getTime() + elapsedSeconds * 1000);
|
|
serverTimeEl.textContent = currentServerTime.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false
|
|
});
|
|
}
|
|
|
|
// 更新运行时间
|
|
if (uptimeEl) {
|
|
const currentUptime = initialUptime + elapsedSeconds;
|
|
uptimeEl.textContent = formatUptime(currentUptime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载提供商列表
|
|
*/
|
|
async function loadProviders() {
|
|
try {
|
|
const data = await window.apiClient.get('/providers');
|
|
renderProviders(data);
|
|
} catch (error) {
|
|
console.error('Failed to load providers:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 渲染提供商列表
|
|
* @param {Object} providers - 提供商数据
|
|
*/
|
|
function renderProviders(providers) {
|
|
const container = document.getElementById('providersList');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
// 检查是否有提供商池数据
|
|
const hasProviders = Object.keys(providers).length > 0;
|
|
const statsGrid = document.querySelector('#providers .stats-grid');
|
|
|
|
// 始终显示统计卡片
|
|
if (statsGrid) statsGrid.style.display = 'grid';
|
|
|
|
// 定义所有支持的提供商显示顺序
|
|
const providerDisplayOrder = [
|
|
'gemini-cli-oauth',
|
|
'openai-custom',
|
|
'claude-custom',
|
|
'claude-kiro-oauth',
|
|
'openai-qwen-oauth',
|
|
'openaiResponses-custom'
|
|
];
|
|
|
|
// 获取所有提供商类型并按指定顺序排序
|
|
// 优先显示预定义的所有提供商类型,即使某些提供商没有数据也要显示
|
|
let allProviderTypes;
|
|
if (hasProviders) {
|
|
// 合并预定义类型和实际存在的类型,确保显示所有预定义提供商
|
|
const actualProviderTypes = Object.keys(providers);
|
|
allProviderTypes = [...new Set([...providerDisplayOrder, ...actualProviderTypes])];
|
|
} else {
|
|
allProviderTypes = providerDisplayOrder;
|
|
}
|
|
const sortedProviderTypes = providerDisplayOrder.filter(type => allProviderTypes.includes(type))
|
|
.concat(allProviderTypes.filter(type => !providerDisplayOrder.includes(type)));
|
|
|
|
// 计算总统计
|
|
let totalAccounts = 0;
|
|
let totalHealthy = 0;
|
|
|
|
// 按照排序后的提供商类型渲染
|
|
sortedProviderTypes.forEach((providerType) => {
|
|
const accounts = hasProviders ? providers[providerType] || [] : [];
|
|
const providerDiv = document.createElement('div');
|
|
providerDiv.className = 'provider-item';
|
|
providerDiv.dataset.providerType = providerType;
|
|
providerDiv.style.cursor = 'pointer';
|
|
|
|
const healthyCount = accounts.filter(acc => acc.isHealthy).length;
|
|
const totalCount = accounts.length;
|
|
const usageCount = accounts.reduce((sum, acc) => sum + (acc.usageCount || 0), 0);
|
|
const errorCount = accounts.reduce((sum, acc) => sum + (acc.errorCount || 0), 0);
|
|
|
|
totalAccounts += totalCount;
|
|
totalHealthy += healthyCount;
|
|
|
|
// 更新全局统计变量
|
|
if (!providerStats.providerTypeStats[providerType]) {
|
|
providerStats.providerTypeStats[providerType] = {
|
|
totalAccounts: 0,
|
|
healthyAccounts: 0,
|
|
totalUsage: 0,
|
|
totalErrors: 0,
|
|
lastUpdate: null
|
|
};
|
|
}
|
|
|
|
const typeStats = providerStats.providerTypeStats[providerType];
|
|
typeStats.totalAccounts = totalCount;
|
|
typeStats.healthyAccounts = healthyCount;
|
|
typeStats.totalUsage = usageCount;
|
|
typeStats.totalErrors = errorCount;
|
|
typeStats.lastUpdate = new Date().toISOString();
|
|
|
|
// 为无数据状态设置特殊样式
|
|
const isEmptyState = !hasProviders || totalCount === 0;
|
|
const statusClass = isEmptyState ? 'status-empty' : (healthyCount === totalCount ? 'status-healthy' : 'status-unhealthy');
|
|
const statusIcon = isEmptyState ? 'fa-info-circle' : (healthyCount === totalCount ? 'fa-check-circle' : 'fa-exclamation-triangle');
|
|
const statusText = isEmptyState ? '0/0 节点' : `${healthyCount}/${totalCount} 健康`;
|
|
|
|
providerDiv.innerHTML = `
|
|
<div class="provider-header">
|
|
<div class="provider-name">
|
|
<span class="provider-type-text">${providerType}</span>
|
|
</div>
|
|
<div class="provider-status ${statusClass}">
|
|
<i class="fas fa-${statusIcon}"></i>
|
|
<span>${statusText}</span>
|
|
</div>
|
|
</div>
|
|
<div class="provider-stats">
|
|
<div class="provider-stat">
|
|
<span class="provider-stat-label">总账户</span>
|
|
<span class="provider-stat-value">${totalCount}</span>
|
|
</div>
|
|
<div class="provider-stat">
|
|
<span class="provider-stat-label">健康账户</span>
|
|
<span class="provider-stat-value">${healthyCount}</span>
|
|
</div>
|
|
<div class="provider-stat">
|
|
<span class="provider-stat-label">使用次数</span>
|
|
<span class="provider-stat-value">${usageCount}</span>
|
|
</div>
|
|
<div class="provider-stat">
|
|
<span class="provider-stat-label">错误次数</span>
|
|
<span class="provider-stat-value">${errorCount}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 如果是空状态,添加特殊样式
|
|
if (isEmptyState) {
|
|
providerDiv.classList.add('empty-provider');
|
|
}
|
|
|
|
// 添加点击事件 - 整个提供商组都可以点击
|
|
providerDiv.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
openProviderManager(providerType);
|
|
});
|
|
|
|
container.appendChild(providerDiv);
|
|
});
|
|
|
|
// 更新统计卡片数据
|
|
const activeProviders = hasProviders ? Object.keys(providers).length : 0;
|
|
updateProviderStatsDisplay(activeProviders, totalHealthy, totalAccounts);
|
|
}
|
|
|
|
/**
|
|
* 更新提供商统计信息
|
|
* @param {number} activeProviders - 活跃提供商数
|
|
* @param {number} healthyProviders - 健康提供商数
|
|
* @param {number} totalAccounts - 总账户数
|
|
*/
|
|
function updateProviderStatsDisplay(activeProviders, healthyProviders, totalAccounts) {
|
|
// 更新全局统计变量
|
|
const newStats = {
|
|
activeProviders,
|
|
healthyProviders,
|
|
totalAccounts,
|
|
lastUpdateTime: new Date().toISOString()
|
|
};
|
|
|
|
updateProviderStats(newStats);
|
|
|
|
// 计算总请求数和错误数
|
|
let totalUsage = 0;
|
|
let totalErrors = 0;
|
|
Object.values(providerStats.providerTypeStats).forEach(typeStats => {
|
|
totalUsage += typeStats.totalUsage || 0;
|
|
totalErrors += typeStats.totalErrors || 0;
|
|
});
|
|
|
|
const finalStats = {
|
|
...newStats,
|
|
totalRequests: totalUsage,
|
|
totalErrors: totalErrors
|
|
};
|
|
|
|
updateProviderStats(finalStats);
|
|
|
|
// 修改:根据使用次数统计"活跃提供商"和"活动连接"
|
|
// "活跃提供商":统计有使用次数(usageCount > 0)的提供商类型数量
|
|
let activeProvidersByUsage = 0;
|
|
Object.entries(providerStats.providerTypeStats).forEach(([providerType, typeStats]) => {
|
|
if (typeStats.totalUsage > 0) {
|
|
activeProvidersByUsage++;
|
|
}
|
|
});
|
|
|
|
// "活动连接":统计所有提供商账户的使用次数总和
|
|
const activeConnections = totalUsage;
|
|
|
|
// 更新页面显示
|
|
const activeProvidersEl = document.getElementById('activeProviders');
|
|
const healthyProvidersEl = document.getElementById('healthyProviders');
|
|
const activeConnectionsEl = document.getElementById('activeConnections');
|
|
|
|
if (activeProvidersEl) activeProvidersEl.textContent = activeProvidersByUsage;
|
|
if (healthyProvidersEl) healthyProvidersEl.textContent = healthyProviders;
|
|
if (activeConnectionsEl) activeConnectionsEl.textContent = activeConnections;
|
|
|
|
// 打印调试信息到控制台
|
|
console.log('Provider Stats Updated:', {
|
|
activeProviders,
|
|
activeProvidersByUsage,
|
|
healthyProviders,
|
|
totalAccounts,
|
|
totalUsage,
|
|
totalErrors,
|
|
providerTypeStats: providerStats.providerTypeStats
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 打开提供商管理模态框
|
|
* @param {string} providerType - 提供商类型
|
|
*/
|
|
async function openProviderManager(providerType) {
|
|
try {
|
|
const data = await window.apiClient.get(`/providers/${encodeURIComponent(providerType)}`);
|
|
|
|
showProviderManagerModal(data);
|
|
} catch (error) {
|
|
console.error('Failed to load provider details:', error);
|
|
showToast('加载提供商详情失败', 'error');
|
|
}
|
|
}
|
|
|
|
// 导入工具函数
|
|
import { formatUptime } from './utils.js';
|
|
|
|
export {
|
|
loadSystemInfo,
|
|
updateTimeDisplay,
|
|
loadProviders,
|
|
renderProviders,
|
|
updateProviderStatsDisplay,
|
|
openProviderManager
|
|
}; |