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

104 lines
4.4 KiB
HTML

{{define "content"}}
<div class="page-header">
<h1>API Tokens</h1>
<button class="btn btn-sm btn-primary" onclick="showCreateTokenModal()">Create Token</button>
</div>
<div id="new-token-display" style="display:none; margin-bottom:16px;">
<div class="success-msg">Token created! Copy the key below - it won't be shown again.</div>
<div class="token-key"><code id="new-token-key"></code><button class="copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('new-token-key').textContent)">Copy</button></div>
</div>
<div class="section">
<table>
<thead><tr><th>Name</th><th>Prefix</th><th>Rate Limit</th><th>Budget</th><th>Created</th><th>Last Used</th><th></th></tr></thead>
<tbody id="tokens-tbody">
{{range .Tokens}}
<tr>
<td>{{.Name}}</td>
<td><code>{{.KeyPrefix}}...</code></td>
<td>{{if eq .RateLimitRPM 0}}unlimited{{else}}{{.RateLimitRPM}} rpm{{end}}</td>
<td>{{if gt .DailyBudgetUSD 0.0}}${{printf "%.2f" .DailyBudgetUSD}}{{else}}unlimited{{end}}</td>
<td>{{formatTime .CreatedAt}}</td>
<td>{{if gt .LastUsedAt 0}}{{formatTime .LastUsedAt}}{{else}}never{{end}}</td>
<td><button class="btn btn-sm btn-danger" onclick="deleteToken({{.ID}})">Revoke</button></td>
</tr>
{{else}}
<tr><td colspan="7" style="color:#64748b;text-align:center;padding:20px;">No API tokens yet. Create one to get started.</td></tr>
{{end}}
</tbody>
</table>
</div>
<!-- Create Token Modal -->
<div id="modal-create-token" class="modal-overlay">
<div class="modal">
<h2>Create API Token</h2>
<div id="create-token-error"></div>
<form onsubmit="doCreateToken(event)">
<div class="form-group">
<label>Token Name</label>
<input type="text" id="token-name" required placeholder="e.g. my-app">
</div>
<div class="form-group">
<label>Rate Limit (requests/min, 0 = unlimited)</label>
<input type="number" id="token-rpm" value="0" min="0">
</div>
<div class="form-group">
<label>Daily Budget (USD, 0 = unlimited)</label>
<input type="number" id="token-budget" value="0" min="0" step="0.01">
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal()">Cancel</button>
<button type="submit" class="btn btn-primary" style="width:auto">Create</button>
</div>
</form>
</div>
</div>
<script>
function showCreateTokenModal() {
document.getElementById('new-token-display').style.display = 'none';
document.getElementById('create-token-error').innerHTML = '';
document.getElementById('token-name').value = '';
document.getElementById('token-rpm').value = '0';
document.getElementById('token-budget').value = '0';
document.getElementById('modal-create-token').classList.add('show');
}
function closeModal() {
document.getElementById('modal-create-token').classList.remove('show');
}
document.getElementById('modal-create-token').addEventListener('click', function(e) {
if (e.target === this) closeModal();
});
async function doCreateToken(e) {
e.preventDefault();
try {
var resp = await fetch('/api/tokens', {
method: 'POST', credentials: 'same-origin',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: document.getElementById('token-name').value,
rate_limit_rpm: parseInt(document.getElementById('token-rpm').value),
daily_budget_usd: parseFloat(document.getElementById('token-budget').value)
})
});
var data = await resp.json();
if (!resp.ok) { document.getElementById('create-token-error').innerHTML = '<div class="error-msg">' + (data.error||'Failed') + '</div>'; return; }
closeModal();
document.getElementById('new-token-key').textContent = data.key;
document.getElementById('new-token-display').style.display = 'block';
// Reload tokens partial
htmx.ajax('GET', '/tokens', {target: '#content', swap: 'innerHTML'});
} catch (e) { document.getElementById('create-token-error').innerHTML = '<div class="error-msg">' + e.message + '</div>'; }
}
async function deleteToken(id) {
if (!confirm('Revoke this API token? This cannot be undone.')) return;
try {
var resp = await fetch('/api/tokens/' + id, { method: 'DELETE', credentials: 'same-origin' });
if (!resp.ok) { var d = await resp.json(); alert(d.error||'Failed'); return; }
htmx.ajax('GET', '/tokens', {target: '#content', swap: 'innerHTML'});
} catch (e) { alert(e.message); }
}
</script>
{{end}}