|
<!DOCTYPE html> |
|
<html lang="zh-CN"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<meta http-equiv="refresh" content="60"> |
|
<title>2API 用量统计</title> |
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> |
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> |
|
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap" rel="stylesheet"> |
|
</head> |
|
<body> |
|
<div class="dashboard-wrapper"> |
|
<header class="main-header"> |
|
<div class="header-left"> |
|
<h1><i class="fas fa-chart-line"></i> 2API 监控面板</h1> |
|
</div> |
|
<div class="header-right"> |
|
<div class="time-info"> |
|
<span><i class="fas fa-clock"></i> 最后更新: {{ current_time }}</span> |
|
<span><i class="fas fa-save"></i> 最后保存: {{ stats.last_saved|format_datetime if stats.last_saved != "从未保存" else "从未保存" }}</span> |
|
</div> |
|
<div class="actions"> |
|
<form action="/save_stats" method="post"> |
|
<button type="submit" class="save-button" title="保存统计数据"><i class="fas fa-save"></i></button> |
|
</form> |
|
<button id="refresh-btn" class="refresh-button" title="刷新数据"><i class="fas fa-sync-alt"></i></button> |
|
</div> |
|
</div> |
|
</header> |
|
|
|
<div class="main-content"> |
|
<div class="auto-refresh-bar"> |
|
<div class="refresh-progress"> |
|
<div class="progress-bar" id="refresh-progress-bar" style="width: 100%;"></div> |
|
</div> |
|
<div class="refresh-info"> |
|
<span>数据将在 <span id="countdown">60</span> 秒后自动刷新</span> |
|
</div> |
|
</div> |
|
|
|
|
|
<section id="dashboard" class="dashboard-section active-section"> |
|
<div class="section-header"> |
|
<h2><i class="fas fa-tachometer-alt"></i> 统计概览</h2> |
|
</div> |
|
|
|
<div class="stats-overview"> |
|
<div class="stats-card primary"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-server"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>总请求数</h3> |
|
<div class="stats-number">{{ stats.total_requests|format_number }}</div> |
|
<div class="stats-trend positive"> |
|
<i class="fas fa-arrow-up"></i> |
|
{{ stats.growth_rate|round(2) }}% 今日 |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="stats-card success"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-check-circle"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>成功率</h3> |
|
<div class="stats-number">{{ stats.success_rate }}%</div> |
|
<div class="stats-detail"> |
|
成功: {{ stats.successful_requests|format_number }} / 失败: {{ stats.failed_requests|format_number }} |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="stats-card info"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-bolt"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>平均响应时间</h3> |
|
<div class="stats-number"> |
|
{{ stats.avg_duration|format_duration }} |
|
</div> |
|
<div class="stats-detail"> |
|
最快: {{ stats.min_duration|format_duration }} |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="stats-card warning"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-coins"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>总 Tokens</h3> |
|
<div class="stats-number">{{ stats.total_tokens|format_number }}</div> |
|
<div class="stats-detail"> |
|
提示: {{ stats.total_prompt_tokens|format_number }} / 完成: {{ stats.total_completion_tokens|format_number }} |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="stats-card danger"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-dollar-sign"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>估算成本</h3> |
|
<div class="stats-number"> |
|
${{ stats.total_cost | round(2) }} |
|
</div> |
|
<div class="stats-detail"> |
|
平均: ${{ stats.avg_cost | round(2) }}/请求 |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="stats-card secondary"> |
|
<div class="stats-icon"> |
|
<i class="fas fa-robot"></i> |
|
</div> |
|
<div class="stats-content"> |
|
<h3>模型使用</h3> |
|
<div class="stats-number">{{ stats.model_usage.keys()|list|length }}</div> |
|
<div class="stats-detail"> |
|
{% if stats.top_model %} |
|
最常用: {{ stats.top_model[0] }} ({{ stats.top_model[1] }}次) |
|
{% else %} |
|
暂无模型使用数据 |
|
{% endif %} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="dashboard-charts"> |
|
<div class="chart-row"> |
|
<div class="chart-card"> |
|
<div class="chart-header"> |
|
<h3><i class="fas fa-calendar-day"></i> 每日请求趋势</h3> |
|
</div> |
|
<div class="chart-body"> |
|
<canvas id="dailyChart" |
|
data-labels='{{ stats.daily_usage.keys()|list|tojson }}' |
|
data-values='{{ stats.daily_usage.values()|list|tojson }}'></canvas> |
|
</div> |
|
</div> |
|
|
|
<div class="chart-card"> |
|
<div class="chart-header"> |
|
<h3><i class="fas fa-robot"></i> 模型使用分布</h3> |
|
</div> |
|
<div class="chart-body"> |
|
<canvas id="modelChart" |
|
data-labels='{{ stats.model_usage.keys()|list|tojson }}' |
|
data-values='{{ stats.model_usage.values()|list|tojson }}'></canvas> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</section> |
|
|
|
|
|
<section id="history" class="dashboard-section"> |
|
<div class="section-header"> |
|
<h2><i class="fas fa-history"></i> 请求历史</h2> |
|
<div class="history-actions"> |
|
<div class="search-box"> |
|
<input type="text" id="history-search" placeholder="搜索请求..."> |
|
<i class="fas fa-search"></i> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="table-container"> |
|
<table id="history-table" class="data-table"> |
|
<thead> |
|
<tr> |
|
<th data-sort="id">请求ID <i class="fas fa-sort"></i></th> |
|
<th data-sort="timestamp">时间 <i class="fas fa-sort"></i></th> |
|
<th data-sort="model">模型 <i class="fas fa-sort"></i></th> |
|
<th data-sort="account">账户 <i class="fas fa-sort"></i></th> |
|
<th data-sort="status">状态 <i class="fas fa-sort"></i></th> |
|
<th data-sort="duration">耗时(ms) <i class="fas fa-sort"></i></th> |
|
<th data-sort="total">总Tokens <i class="fas fa-sort"></i></th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
{% for req in stats.request_history|reverse %} |
|
<tr data-model="{{ req.model }}" data-status="{{ 'success' if req.success else 'fail' }}" data-id="{{ req.id }}"> |
|
<td title="{{ req.id }}">{{ req.id[:8] }}...</td> |
|
<td>{{ req.timestamp|format_datetime }}</td> |
|
<td><span class="model-badge small">{{ req.model }}</span></td> |
|
<td title="{{ req.account }}"> |
|
<div class="account-cell"> |
|
<span class="account-avatar small">{{ req.account[0]|upper }}</span> |
|
<span>{{ req.account.split('@')[0] }}</span> |
|
</div> |
|
</td> |
|
<td class="{{ 'success' if req.success else 'fail' }}"> |
|
<span class="status-badge {{ 'success' if req.success else 'fail' }}"> |
|
<i class="fas {{ 'fa-check-circle' if req.success else 'fa-times-circle' }}"></i> |
|
{{ '成功' if req.success else '失败' }} |
|
</span> |
|
</td> |
|
<td>{{ req.duration_ms|format_duration }}</td> |
|
<td>{{ (req.total_tokens if req.total_tokens is defined else req.estimated_total_tokens if req.estimated_total_tokens is defined else '-')|format_number if (req.total_tokens is defined or req.estimated_total_tokens is defined) else '-' }}</td> |
|
</tr> |
|
{% endfor %} |
|
</tbody> |
|
</table> |
|
</div> |
|
<div class="pagination"> |
|
<button id="prev-page" disabled><i class="fas fa-chevron-left"></i> 上一页</button> |
|
<span id="page-info">第 <span id="current-page">1</span> 页,共 <span id="total-pages">1</span> 页</span> |
|
<button id="next-page"><i class="fas fa-chevron-right"></i> 下一页</button> |
|
</div> |
|
</section> |
|
|
|
<footer class="main-footer"> |
|
<div class="footer-content"> |
|
<div class="footer-logo"> |
|
<h3>2API <span>统计面板</span></h3> |
|
</div> |
|
<div class="footer-info"> |
|
<p>© 2025 2API 统计面板 | 版本 1.0.1</p> |
|
<p>数据每60秒自动刷新</p> |
|
</div> |
|
</div> |
|
</footer> |
|
</div> |
|
</div> |
|
|
|
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script> |
|
</body> |
|
</html> |