| <!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/chartjs-plugin-datalabels@2.0.0"></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> |