- 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
134 lines
3.2 KiB
HTML
134 lines
3.2 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - OpenRouter Monitor{% endblock %}
|
|
|
|
{% block content %}
|
|
<h1>Dashboard</h1>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="grid">
|
|
<article>
|
|
<header>
|
|
<h3>Total Requests</h3>
|
|
</header>
|
|
<p style="font-size: 2rem; font-weight: bold;">{{ stats.total_requests | default(0) }}</p>
|
|
</article>
|
|
|
|
<article>
|
|
<header>
|
|
<h3>Total Cost</h3>
|
|
</header>
|
|
<p style="font-size: 2rem; font-weight: bold;">${{ stats.total_cost | default(0) | round(2) }}</p>
|
|
</article>
|
|
|
|
<article>
|
|
<header>
|
|
<h3>API Keys</h3>
|
|
</header>
|
|
<p style="font-size: 2rem; font-weight: bold;">{{ stats.api_keys_count | default(0) }}</p>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Charts -->
|
|
<div class="grid">
|
|
<article>
|
|
<header>
|
|
<h3>Usage Over Time</h3>
|
|
</header>
|
|
<canvas id="usageChart" height="200"></canvas>
|
|
</article>
|
|
|
|
<article>
|
|
<header>
|
|
<h3>Top Models</h3>
|
|
</header>
|
|
<canvas id="modelsChart" height="200"></canvas>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<article>
|
|
<header>
|
|
<h3>Recent Usage</h3>
|
|
</header>
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Model</th>
|
|
<th>Requests</th>
|
|
<th>Cost</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for usage in recent_usage %}
|
|
<tr>
|
|
<td>{{ usage.date }}</td>
|
|
<td>{{ usage.model }}</td>
|
|
<td>{{ usage.requests }}</td>
|
|
<td>${{ usage.cost | round(4) }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="4" style="text-align: center;">No usage data available</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</article>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
// Usage Chart
|
|
const usageCtx = document.getElementById('usageChart').getContext('2d');
|
|
const usageData = {{ chart_data | default({"labels": [], "data": []}) | tojson }};
|
|
|
|
new Chart(usageCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: usageData.labels || [],
|
|
datasets: [{
|
|
label: 'Requests',
|
|
data: usageData.data || [],
|
|
borderColor: '#2563eb',
|
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Models Chart
|
|
const modelsCtx = document.getElementById('modelsChart').getContext('2d');
|
|
const modelsData = {{ models_data | default({"labels": [], "data": []}) | tojson }};
|
|
|
|
new Chart(modelsCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: modelsData.labels || [],
|
|
datasets: [{
|
|
data: modelsData.data || [],
|
|
backgroundColor: [
|
|
'#2563eb',
|
|
'#10b981',
|
|
'#f59e0b',
|
|
'#ef4444',
|
|
'#8b5cf6'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|