Files
function/templates/dashboard/settings/api_keys.html
2025-12-02 17:02:17 +11:00

189 lines
12 KiB
HTML

{% extends 'dashboard.html' %}
{% block page %}
<div class="p-6 max-w-4xl mx-auto">
<!-- Settings Navigation -->
<div class="mb-6 border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8">
<a hx-get="{{ url_for('settings.api_keys') }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true"
class="border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 py-4 px-1 text-sm font-medium cursor-pointer">
API Keys
</a>
<a hx-get="{{ url_for('settings.export') }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Export Data
</a>
<a hx-get="{{ url_for('settings.database_schema') }}" hx-target="#container" hx-swap="innerHTML"
hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Database Schema
</a>
<a hx-get="{{ url_for('settings.login_history') }}" hx-target="#container" hx-swap="innerHTML"
hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Login History
</a>
<a hx-get="{{ url_for('settings.account') }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true"
class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer">
Account
</a>
</nav>
</div>
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">API Keys</h1>
<button onclick="document.getElementById('create-key-modal').showModal()"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Generate New Key
</button>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Name
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Key Prefix
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Scopes
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Rate Limit
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Created
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Last Used
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Delete</span>
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{% for key in api_keys %}
<tr id="key-row-{{ key.id }}">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">{{
key.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ key.key[:8]
}}...</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
<div class="text-sm text-gray-900 dark:text-white">
{% if key.scopes == ['*'] %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">Full
Access</span>
{% else %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">{{
key.scopes|length }} Scopes</span>
{% endif %}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900 dark:text-white">
{% if key.rate_limit_count %}
{{ key.rate_limit_count }} / {{ key.rate_limit_period }}
{% else %}
<span class="text-gray-500 dark:text-gray-400">Unlimited</span>
{% endif %}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{
key.created_at.strftime('%Y-%m-%d') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{
key.last_used_at.strftime('%Y-%m-%d %H:%M') if key.last_used_at else 'Never' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button hx-delete="{{ url_for('settings.delete_api_key', key_id=key.id) }}"
hx-target="#key-row-{{ key.id }}" hx-swap="outerHTML"
hx-confirm="Are you sure you want to revoke this key?"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">Revoke</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500 dark:text-gray-400">No API keys
found. Generate one to get started.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Create Key Modal -->
<dialog id="create-key-modal" class="p-0 rounded-lg shadow-xl backdrop:bg-gray-900/50">
<div class="bg-white dark:bg-gray-800 w-full max-w-md p-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">Generate New API Key</h3>
<form action="{{ url_for('settings.create_api_key') }}" method="POST">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
<input type="text" name="name" required placeholder="e.g., CI/CD Pipeline"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white">
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Scopes</label>
<div class="space-y-2">
<label class="flex items-center">
<input type="checkbox" name="scopes" value="*" checked
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">Full Access (*)</span>
</label>
<div class="mt-3 border-t pt-3">
<p class="text-xs font-medium text-gray-500 mb-2">Or limit to specific functions:</p>
<div class="max-h-40 overflow-y-auto space-y-2 pl-1">
{% for function in functions %}
<label class="flex items-center">
<input type="checkbox" name="scopes" value="function:{{ function.id }}"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">{{ function.name }}</span>
</label>
{% endfor %}
</div>
</div>
</div>
<p class="mt-1 text-xs text-gray-500">Select "Full Access" or specific functions.</p>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Rate Limit
(Optional)</label>
<div class="flex space-x-4">
<div class="w-1/2">
<input type="number" name="rate_limit_count" placeholder="Requests count (e.g. 100)" min="1"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white">
</div>
<div class="w-1/2">
<select name="rate_limit_period"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white">
<option value="">No Limit</option>
<option value="minute">Per Minute</option>
<option value="hour">Per Hour</option>
<option value="day">Per Day</option>
</select>
</div>
</div>
</div>
<div class="flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('create-key-modal').close()"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600">Cancel</button>
<button type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Generate</button>
</div>
</form>
</div>
</dialog>
{% endblock %}