- Mount static files on /static endpoint - Configure Jinja2Templates with directory structure - Create base template with Pico.css, HTMX, Chart.js - Create all template subdirectories (auth, dashboard, keys, tokens, profile, components) - Create initial CSS and JS files - Add tests for static files and templates configuration Tests: 12 passing Coverage: 100% on new configuration code
136 lines
4.1 KiB
HTML
136 lines
4.1 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Statistics - OpenRouter Monitor{% endblock %}
|
|
|
|
{% block content %}
|
|
<h1>Detailed Statistics</h1>
|
|
|
|
<!-- Filters -->
|
|
<article>
|
|
<header>
|
|
<h3>Filters</h3>
|
|
</header>
|
|
<form action="/stats" method="GET" hx-get="/stats" hx-target="#stats-results" hx-push-url="true">
|
|
<div class="grid">
|
|
<label for="start_date">
|
|
Start Date
|
|
<input type="date" id="start_date" name="start_date" value="{{ filters.start_date }}">
|
|
</label>
|
|
|
|
<label for="end_date">
|
|
End Date
|
|
<input type="date" id="end_date" name="end_date" value="{{ filters.end_date }}">
|
|
</label>
|
|
|
|
<label for="api_key_id">
|
|
API Key
|
|
<select id="api_key_id" name="api_key_id">
|
|
<option value="">All Keys</option>
|
|
{% for key in api_keys %}
|
|
<option value="{{ key.id }}" {% if filters.api_key_id == key.id %}selected{% endif %}>
|
|
{{ key.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
|
|
<label for="model">
|
|
Model
|
|
<input type="text" id="model" name="model" placeholder="gpt-4" value="{{ filters.model }}">
|
|
</label>
|
|
</div>
|
|
|
|
<button type="submit">Apply Filters</button>
|
|
<a href="/stats/export?{{ query_string }}" role="button" class="secondary">Export CSV</a>
|
|
</form>
|
|
</article>
|
|
|
|
<!-- Results -->
|
|
<article id="stats-results">
|
|
<header>
|
|
<h3>Usage Details</h3>
|
|
<p><small>Showing {{ stats|length }} results</small></p>
|
|
</header>
|
|
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>API Key</th>
|
|
<th>Model</th>
|
|
<th>Requests</th>
|
|
<th>Prompt Tokens</th>
|
|
<th>Completion Tokens</th>
|
|
<th>Total Tokens</th>
|
|
<th>Cost</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for stat in stats %}
|
|
<tr>
|
|
<td>{{ stat.date }}</td>
|
|
<td>{{ stat.api_key_name }}</td>
|
|
<td>{{ stat.model }}</td>
|
|
<td>{{ stat.requests }}</td>
|
|
<td>{{ stat.prompt_tokens }}</td>
|
|
<td>{{ stat.completion_tokens }}</td>
|
|
<td>{{ stat.total_tokens }}</td>
|
|
<td>${{ stat.cost | round(4) }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="8" style="text-align: center;">No data found for the selected filters.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
{% if total_pages > 1 %}
|
|
<nav>
|
|
<ul>
|
|
{% if page > 1 %}
|
|
<li>
|
|
<a href="?page={{ page - 1 }}&{{ query_string }}" class="secondary">« Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for p in range(1, total_pages + 1) %}
|
|
<li>
|
|
{% if p == page %}
|
|
<strong>{{ p }}</strong>
|
|
{% else %}
|
|
<a href="?page={{ p }}&{{ query_string }}">{{ p }}</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
|
|
{% if page < total_pages %}
|
|
<li>
|
|
<a href="?page={{ page + 1 }}&{{ query_string }}" class="secondary">Next »</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</article>
|
|
|
|
<!-- Summary -->
|
|
<article>
|
|
<header>
|
|
<h3>Summary</h3>
|
|
</header>
|
|
<div class="grid">
|
|
<div>
|
|
<strong>Total Requests:</strong> {{ summary.total_requests }}
|
|
</div>
|
|
<div>
|
|
<strong>Total Tokens:</strong> {{ summary.total_tokens }}
|
|
</div>
|
|
<div>
|
|
<strong>Total Cost:</strong> ${{ summary.total_cost | round(2) }}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{% endblock %}
|