Skip to content

Commit d6bd934

Browse files
StuMasonclaude
andcommitted
feat: Add API keys section to admin dashboard
- Display all API keys with rate limit progress bars - Show key scope (user-scoped vs service-level) - Show active/revoked status and last used timestamp - Query API keys in dashboard route Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a6a06cc commit d6bd934

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/polar_flow_server/admin/routes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from polar_flow_server.core.security import token_encryption
2929
from polar_flow_server.models.activity import Activity
3030
from polar_flow_server.models.activity_samples import ActivitySamples
31+
from polar_flow_server.models.api_key import APIKey
3132
from polar_flow_server.models.cardio_load import CardioLoad
3233
from polar_flow_server.models.continuous_hr import ContinuousHeartRate
3334
from polar_flow_server.models.exercise import Exercise
@@ -513,6 +514,11 @@ async def admin_dashboard(
513514
cardio=latest_cardio,
514515
)
515516

517+
# Get API keys data
518+
api_keys_stmt = select(APIKey).order_by(APIKey.created_at.desc())
519+
api_keys_result = await session.execute(api_keys_stmt)
520+
api_keys = api_keys_result.scalars().all()
521+
516522
return Template(
517523
template_name="admin/dashboard.html",
518524
context={
@@ -533,6 +539,7 @@ async def admin_dashboard(
533539
"latest_alertness": latest_alertness,
534540
"sync_interval_hours": settings.sync_interval_hours,
535541
"recovery_status": recovery_status,
542+
"api_keys": api_keys,
536543
"csrf_token": _get_csrf_token(request),
537544
},
538545
)

src/polar_flow_server/templates/admin/dashboard.html

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,91 @@ <h2 class="text-lg font-semibold text-gray-900">Today's Readiness</h2>
177177
</div>
178178
</div>
179179

180+
<!-- API Keys Management -->
181+
<div class="bg-white shadow rounded-lg p-6 mb-6">
182+
<div class="flex items-center justify-between mb-4">
183+
<div>
184+
<h2 class="text-lg font-semibold text-gray-900">API Keys</h2>
185+
<p class="text-sm text-gray-600">Manage API keys for service-to-service authentication</p>
186+
</div>
187+
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
188+
{{ api_keys|length }} key{% if api_keys|length != 1 %}s{% endif %}
189+
</span>
190+
</div>
191+
192+
{% if api_keys %}
193+
<div class="overflow-x-auto">
194+
<table class="min-w-full divide-y divide-gray-200">
195+
<thead class="bg-gray-50">
196+
<tr>
197+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Key</th>
198+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
199+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Scope</th>
200+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rate Limit</th>
201+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
202+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Used</th>
203+
</tr>
204+
</thead>
205+
<tbody class="bg-white divide-y divide-gray-200">
206+
{% for key in api_keys %}
207+
<tr class="{% if not key.is_active %}bg-gray-50 opacity-60{% endif %}">
208+
<td class="px-4 py-3 whitespace-nowrap">
209+
<code class="text-sm font-mono bg-gray-100 px-2 py-1 rounded">{{ key.key_prefix }}...</code>
210+
</td>
211+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">{{ key.name }}</td>
212+
<td class="px-4 py-3 whitespace-nowrap">
213+
{% if key.user_id %}
214+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
215+
User: {{ key.user_id[:12] }}{% if key.user_id|length > 12 %}...{% endif %}
216+
</span>
217+
{% else %}
218+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
219+
Service-level
220+
</span>
221+
{% endif %}
222+
</td>
223+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
224+
<div class="flex items-center">
225+
<div class="w-16 bg-gray-200 rounded-full h-2 mr-2">
226+
<div class="bg-blue-600 h-2 rounded-full" style="width: {{ (key.rate_limit_remaining / key.rate_limit_requests * 100)|round }}%"></div>
227+
</div>
228+
<span>{{ key.rate_limit_remaining }}/{{ key.rate_limit_requests }}</span>
229+
</div>
230+
</td>
231+
<td class="px-4 py-3 whitespace-nowrap">
232+
{% if key.is_active %}
233+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
234+
Active
235+
</span>
236+
{% else %}
237+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
238+
Revoked
239+
</span>
240+
{% endif %}
241+
</td>
242+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
243+
{% if key.last_used_at %}
244+
{{ key.last_used_at.strftime('%Y-%m-%d %H:%M') }}
245+
{% else %}
246+
<span class="text-gray-400">Never</span>
247+
{% endif %}
248+
</td>
249+
</tr>
250+
{% endfor %}
251+
</tbody>
252+
</table>
253+
</div>
254+
{% else %}
255+
<div class="text-center py-8">
256+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
257+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
258+
</svg>
259+
<h3 class="mt-2 text-sm font-medium text-gray-900">No API keys</h3>
260+
<p class="mt-1 text-sm text-gray-500">API keys are created when users connect via OAuth.</p>
261+
</div>
262+
{% endif %}
263+
</div>
264+
180265
<!-- Sync Control -->
181266
<div class="bg-white shadow rounded-lg p-6 mb-6">
182267
<div class="flex items-center justify-between mb-4">

0 commit comments

Comments
 (0)