ai-servers/llm-gateway/internal/dashboard/templates/partials/dashboard.html

128 lines
4.4 KiB
HTML

{{define "content"}}
<div hx-ext="sse" sse-connect="/api/events" hx-get="/dashboard" hx-trigger="sse:refresh" hx-target="#content" hx-swap="innerHTML">
<div class="page-header">
<h1>Dashboard</h1>
</div>
<div class="cards">
{{with .Summary.Today}}
<div class="card"><div class="label">Requests Today</div><div class="value">{{.Requests}}</div></div>
<div class="card"><div class="label">Cost Today</div><div class="value green">{{formatCost .CostUSD}}</div></div>
<div class="card"><div class="label">Tokens Today</div><div class="value blue">{{addInt .InputTokens .OutputTokens}}</div><div class="sub">{{.InputTokens}} in / {{.OutputTokens}} out</div></div>
<div class="card"><div class="label">Errors Today</div><div class="value {{if gt .Errors 0}}red{{end}}">{{.Errors}}</div></div>
<div class="card"><div class="label">Cache Hits</div><div class="value">{{.CachedHits}}</div></div>
{{end}}
{{with .Summary.Week}}
<div class="card"><div class="label">Cost (7d)</div><div class="value green">{{formatCost .CostUSD}}</div></div>
{{end}}
</div>
<div class="tabs">
<button class="active" onclick="loadTimeseries('24h', this)">24h</button>
<button onclick="loadTimeseries('7d', this)">7d</button>
<button onclick="loadTimeseries('30d', this)">30d</button>
</div>
<div class="section">
<h2>Requests & Cost</h2>
<canvas id="chart" height="200"></canvas>
</div>
{{if .Models}}
<div class="section">
<h2>Models</h2>
<table>
<thead><tr><th>Model</th><th>Requests</th><th>Tokens (in/out)</th><th>Cost</th><th>Avg Latency</th></tr></thead>
<tbody>
{{range .Models}}
<tr>
<td>{{.Model}}</td>
<td>{{.Requests}}</td>
<td>{{.InputTokens}} / {{.OutputTokens}}</td>
<td class="green">{{formatCost .CostUSD}}</td>
<td>{{printf "%.0f" .AvgLatencyMS}}ms</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
{{if .Providers}}
<div class="section">
<h2>Providers</h2>
<table>
<thead><tr><th>Provider</th><th>Requests</th><th>Success</th><th>Errors</th><th>Avg Latency</th><th>Cost</th></tr></thead>
<tbody>
{{range .Providers}}
<tr>
<td>{{.Provider}}</td>
<td>{{.Requests}}</td>
<td class="green">{{.Successes}}</td>
<td class="red">{{.Errors}}</td>
<td>{{printf "%.0f" .AvgLatencyMS}}ms</td>
<td class="green">{{formatCost .CostUSD}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
{{if .TokenStats}}
<div class="section">
<h2>API Token Usage</h2>
<table>
<thead><tr><th>Token</th><th>Requests</th><th>Tokens (in/out)</th><th>Cost</th></tr></thead>
<tbody>
{{range .TokenStats}}
<tr>
<td>{{.TokenName}}</td>
<td>{{.Requests}}</td>
<td>{{.InputTokens}} / {{.OutputTokens}}</td>
<td class="green">{{formatCost .CostUSD}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
<script>
var _chart;
function loadTimeseries(period, btn) {
document.querySelectorAll('.tabs button').forEach(function(b) { b.classList.remove('active'); });
if (btn) btn.classList.add('active');
else document.querySelector('.tabs button').classList.add('active');
fetch('/api/stats/timeseries?period=' + period, {credentials: 'same-origin'})
.then(function(r) { return r.json(); })
.then(function(data) {
var labels = (data||[]).map(function(d) { return d.bucket; });
var requests = (data||[]).map(function(d) { return d.requests; });
var costs = (data||[]).map(function(d) { return d.cost_usd; });
if (_chart) _chart.destroy();
_chart = new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: labels,
datasets: [
{ label: 'Requests', data: requests, backgroundColor: '#3b82f680', yAxisID: 'y' },
{ label: 'Cost ($)', data: costs, type: 'line', borderColor: '#4ade80', backgroundColor: '#4ade8020', yAxisID: 'y1', tension: 0.3 }
]
},
options: {
responsive: true,
interaction: { mode: 'index', intersect: false },
scales: {
y: { position: 'left', ticks: { color: '#94a3b8' }, grid: { color: '#1e293b' } },
y1: { position: 'right', ticks: { color: '#4ade80' }, grid: { display: false } },
x: { ticks: { color: '#94a3b8', maxRotation: 45 }, grid: { color: '#1e293b' } }
},
plugins: { legend: { labels: { color: '#e2e8f0' } } }
}
});
}).catch(function(){});
}
loadTimeseries('24h');
</script>
</div>
{{end}}