mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
fix: WebUI 密钥用量导出 CSV + 修复 Stats API key 过滤 bug
- api.go: 非 admin 用户过滤改用完整 key;keyNames 映射用完整 key;keys-csv 导出支持 key 查询参数过滤,安全类型断言 - stats.go: 新增 StatsRow 类型和 rows() 函数供导出使用 - server.go: handleStatusAPI 返回当前用户 key(已存在逻辑) - index.html: 密钥用量卡片右上角添加导出 CSV 按钮(与请求记录一致)
This commit is contained in:
@ -473,7 +473,7 @@ const STR = {
|
||||
sortSave:'保存排序', sortReset:'重置', sortAdd:'添加档位', sortSaved:'排序已保存并热重载', sortNoChange:'无变更', sortHintSave:'点击保存排序后生效',
|
||||
sortSource:'源', sortPrio:'优先级 %s', sortEmpty:'该源暂无模型',
|
||||
kpiActive:'活跃请求', kpiReqs:'总请求', kpiOk:'成功率', kpiTokens:'Tokens', kpiLat:'平均延迟', kpiMaxLat:'最大延迟',
|
||||
dashModel:'模型用量', dashSrc:'源用量与延迟', dashKey:'密钥用量', dashRecs:'请求记录', exportCsv:'导出 CSV', expWeek:'近一周', expMonth:'近一月', expYear:'近一年', expRange:'自定义范围', expStart:'开始日期', expEnd:'结束日期', expDownload:'下载',
|
||||
dashModel:'模型用量', dashSrc:'源用量与延迟', dashKey:'密钥用量', dashRecs:'请求记录', exportCsv:'导出 CSV', expWeek:'近一周', expMonth:'近一月', expYear:'近一年', expRange:'自定义范围', expStart:'开始日期', expEnd:'结束日期', expDownload:'下载', expKeysCsv:'导出密钥用量',
|
||||
thModel:'模型', thSrc:'源', thKey:'密钥', thReqs:'请求', thOk:'成功', thErr:'失败',
|
||||
thPrompt:'输入 Tokens', thCompl:'输出 Tokens', thAvgLat:'平均延迟', thMaxLat:'最长延迟',
|
||||
thTime:'时间', thType:'类型', thStatus:'状态', thLatMs:'延迟',
|
||||
@ -635,12 +635,12 @@ async function renderStatus() {
|
||||
const s = await api('/api/status');
|
||||
const base = window._base = s.base_url || location.origin + '/v1';
|
||||
const key = window._key = (s.gateway_keys && s.gateway_keys[0]) || '';
|
||||
const srcRows = s.sources.map(x =>
|
||||
const srcRows = s.sources ? s.sources.map(x =>
|
||||
`<tr><td>${x.live_available ? `<span class="tag tag-green"><i class="net-dot"></i>${t('online')}</span>` : `<span class="tag tag-red" title="${esc(x.last_error || '')}"><i class="net-dot"></i>${t('offline')}</span>`}</td>
|
||||
<td><b>${esc(x.name)}</b></td><td>${esc(x.adapter)}</td>
|
||||
<td><div class="src-models">${x.models.map(m => `<span class="tag tag-blue modelchip" onclick="showModelConfig('${escAttr(x.name)}','${escAttr(m)}')">${esc(m)}</span>`).join('')}</div></td>
|
||||
<td><span class="muted">${esc(x.base_url || '')}</span></td>
|
||||
<td>${x.max_concurrent}</td></tr>`).join('');
|
||||
<td>${x.max_concurrent}</td></tr>`).join('') : '';
|
||||
$('#tab-status').innerHTML = `
|
||||
<div class="kpis" id="kpi-row"></div>
|
||||
<div class="card"><h2>${t('connTitle')}</h2>
|
||||
@ -651,14 +651,14 @@ async function renderStatus() {
|
||||
<label>${t('tModels')}</label>
|
||||
<div class="chips" id="model-chips"></div>
|
||||
</div>
|
||||
<div class="card"><h2>${t('srcTitle')} (${s.sources.length})</h2>
|
||||
${s.sources ? `<div class="card"><h2>${t('srcTitle')} (${s.sources.length})</h2>
|
||||
<div class="tbl-wrap"><table><tr><th>${t('tConn')}</th><th>${t('tName')}</th><th>${t('tAdapter')}</th><th>${t('tModels')}</th><th>${t('tURL')}</th><th>${t('tConc')}</th></tr>${srcRows || `<tr><td colspan="6" class="empty">${t('srcEmpty')}</td></tr>`}</table></div>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
<div class="dash-row">
|
||||
<div class="card"><h2>${t('dashModel')}</h2><div id="tb-model"></div></div>
|
||||
<div class="card"><h2>${t('dashSrc')}</h2><div id="tb-src"></div></div>
|
||||
${s.sources ? `<div class="card"><h2>${t('dashSrc')}</h2><div id="tb-src"></div></div>` : ''}
|
||||
</div>
|
||||
<div class="card"><h2>${t('dashKey')}</h2><div id="tb-key"></div></div>
|
||||
<div class="card"><h2>${t('dashKey')}<span class="grow"></span><button class="ghost small" onclick="openExportModal()">${t('exportCsv')}</button></h2><div id="tb-key"></div></div>
|
||||
<div class="card"><h2><span>${t('dashRecs')}</span><span class="grow"></span><button class="ghost small" onclick="openExportModal()">${t('exportCsv')}</button></h2>
|
||||
<div class="filter-line">
|
||||
<span class="muted">${t('recFilter')}</span>
|
||||
@ -666,10 +666,10 @@ async function renderStatus() {
|
||||
</div>
|
||||
<div class="recs-scroll" id="tb-recs"></div>
|
||||
</div>
|
||||
<div class="card"><h2>${t('adTitle')} (${s.adapters.length})</h2>
|
||||
${s.adapters ? `<div class="card"><h2>${t('adTitle')} (${s.adapters.length})</h2>
|
||||
<div class="tbl-wrap"><table><tr><th>${t('tName')}</th><th>${t('tVersion')}</th></tr>
|
||||
${s.adapters.map(a => `<tr><td>${esc(a.name)}</td><td>${esc(a.version || '')}</td></tr>`).join('')}</table></div>
|
||||
</div>`;
|
||||
</div>` : ''}`;
|
||||
$('#conncfg').textContent = '';
|
||||
showModelConfig(null, '');
|
||||
$('#model-chips').innerHTML = s.models.map(m => `<span class="tag tag-blue modelchip" onclick="showModelConfig(null,'${escAttr(m)}')">${esc(m)}</span>`).join('');
|
||||
@ -700,6 +700,9 @@ function openExportModal() {
|
||||
<div><label>${t('expEnd')}</label><input id="exp-to" type="date" value="${now.toISOString().slice(0, 10)}"></div></div>
|
||||
<p><button onclick="downloadStatsCsvFromForm()">${t('expDownload')}</button>
|
||||
<button class="ghost" onclick="this.closest('#modal-wrap').remove()">${t('mCancel')}</button></p>
|
||||
<hr style="margin:12px 0">
|
||||
<p><button onclick="downloadKeysCsv()">${t('expKeysCsv')}</button></p>
|
||||
<button class="ghost" onclick="this.closest('#modal-wrap').remove()">${t('mCancel')}</button></p>
|
||||
</div>`;
|
||||
document.body.appendChild(wrap);
|
||||
}
|
||||
@ -709,11 +712,9 @@ function downloadStatsCsv(from, to) {
|
||||
location.href = '/api/stats?' + q.toString();
|
||||
const w = $('#modal-wrap'); if (w) w.remove();
|
||||
}
|
||||
function downloadStatsCsvFromForm() {
|
||||
const f = $('#exp-from').value, t0 = $('#exp-to').value;
|
||||
const from = f ? new Date(f + 'T00:00:00').getTime() : 0;
|
||||
const to = t0 ? new Date(t0 + 'T23:59:59').getTime() : Date.now();
|
||||
downloadStatsCsv(from, to);
|
||||
function downloadKeysCsv() {
|
||||
location.href = '/api/stats?export=keys-csv' + (statsKeyF ? '&key=' + encodeURIComponent(statsKeyF) : '');
|
||||
const w = $('#modal-wrap'); if (w) w.remove();
|
||||
}
|
||||
async function paintStats() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user