Add login history to settings

This commit is contained in:
Peter Stockings
2025-12-02 16:08:40 +11:00
parent ab7079f87e
commit 4227be5a80
7 changed files with 182 additions and 1 deletions

View File

@@ -18,6 +18,11 @@
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>
</nav>
</div>

View File

@@ -18,6 +18,11 @@
class="border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 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>
</nav>
</div>

View File

@@ -18,6 +18,11 @@
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>
</nav>
</div>

View File

@@ -0,0 +1,115 @@
{% extends 'dashboard.html' %}
{% block page %}
<div class="p-6 max-w-6xl 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-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">
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-blue-500 text-blue-600 dark:text-blue-400 py-4 px-1 text-sm font-medium cursor-pointer">
Login History
</a>
</nav>
</div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Login History</h1>
<p class="text-gray-600 dark:text-gray-400">View your recent login activity and security events</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow">
{% if history %}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Date & Time
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
IP Address
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Browser / Device
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Status
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{% for entry in history %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
<div>{{ entry.login_time.strftime('%b %d, %Y') }}</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
{{ entry.login_time.strftime('%I:%M %p') }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{{ entry.ip_address or 'N/A' }}
</td>
<td class="px-6 py-4 text-sm text-gray-900 dark:text-white">
<div class="max-w-xs truncate" title="{{ entry.user_agent }}">
{{ entry.user_agent or 'Unknown' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if entry.success %}
<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">
Success
</span>
{% else %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
title="{{ entry.failure_reason }}">
Failed
</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-8 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No login history</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Your login activity will appear here
</p>
</div>
{% endif %}
</div>
{% if history %}
<div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
<p>Showing last {{ history|length }} login{% if history|length != 1 %}s{% endif %}. Login history is kept for
security purposes.</p>
</div>
{% endif %}
</div>
{% endblock %}