Add comprehensive database integration, authentication, and admin dashboard

This commit introduces major enhancements to Talk2Me:

## Database Integration
- PostgreSQL support with SQLAlchemy ORM
- Redis integration for caching and real-time analytics
- Automated database initialization scripts
- Migration support infrastructure

## User Authentication System
- JWT-based API authentication
- Session-based web authentication
- API key authentication for programmatic access
- User roles and permissions (admin/user)
- Login history and session tracking
- Rate limiting per user with customizable limits

## Admin Dashboard
- Real-time analytics and monitoring
- User management interface (create, edit, delete users)
- System health monitoring
- Request/error tracking
- Language pair usage statistics
- Performance metrics visualization

## Key Features
- Dual authentication support (token + user accounts)
- Graceful fallback for missing services
- Non-blocking analytics middleware
- Comprehensive error handling
- Session management with security features

## Bug Fixes
- Fixed rate limiting bypass for admin routes
- Added missing email validation method
- Improved error handling for missing database tables
- Fixed session-based authentication for API endpoints

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-06-03 18:21:56 -06:00
parent d818ec7d73
commit fa951c3141
41 changed files with 10120 additions and 325 deletions

75
admin/templates/base.html Normal file
View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Talk2Me Admin Dashboard{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('admin.static', filename='css/admin.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('admin.dashboard') }}">
<i class="fas fa-language"></i> Talk2Me Admin
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.dashboard') }}">
<i class="fas fa-tachometer-alt"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.users') }}">
<i class="fas fa-users"></i> Users
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="exportData('all')">
<i class="fas fa-download"></i> Export Data
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.logout') }}">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="container-fluid mt-5 pt-3">
{% block content %}{% endblock %}
</main>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery (for AJAX) -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<!-- Custom JS -->
<script src="{{ url_for('admin.static', filename='js/admin.js') }}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,277 @@
{% extends "base.html" %}
{% block title %}Dashboard - Talk2Me Admin{% endblock %}
{% block content %}
<!-- Quick Actions -->
<div class="row mb-4">
<div class="col-12">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title">Quick Actions</h5>
<div class="btn-group" role="group">
<a href="{{ url_for('admin.users') }}" class="btn btn-primary">
<i class="fas fa-users"></i> Manage Users
</a>
<button onclick="exportData('all')" class="btn btn-secondary">
<i class="fas fa-download"></i> Export Data
</button>
<button onclick="clearCache()" class="btn btn-warning">
<i class="fas fa-trash"></i> Clear Cache
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Overview Cards -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card text-white bg-primary">
<div class="card-body">
<h5 class="card-title">Total Requests</h5>
<h2 class="card-text" id="total-requests">
<div class="spinner-border spinner-border-sm" role="status"></div>
</h2>
<p class="card-text"><small>Today: <span id="today-requests">-</span></small></p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-white bg-success">
<div class="card-body">
<h5 class="card-title">Active Sessions</h5>
<h2 class="card-text" id="active-sessions">
<div class="spinner-border spinner-border-sm" role="status"></div>
</h2>
<p class="card-text"><small>Live users</small></p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-white bg-warning">
<div class="card-body">
<h5 class="card-title">Error Rate</h5>
<h2 class="card-text" id="error-rate">
<div class="spinner-border spinner-border-sm" role="status"></div>
</h2>
<p class="card-text"><small>Last 24 hours</small></p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-white bg-info">
<div class="card-body">
<h5 class="card-title">Cache Hit Rate</h5>
<h2 class="card-text" id="cache-hit-rate">
<div class="spinner-border spinner-border-sm" role="status"></div>
</h2>
<p class="card-text"><small>Performance metric</small></p>
</div>
</div>
</div>
</div>
<!-- System Health Status -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-heartbeat"></i> System Health</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="d-flex align-items-center">
<i class="fas fa-database fa-2x me-3"></i>
<div>
<h6 class="mb-0">Redis</h6>
<span class="badge" id="redis-status">Checking...</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-center">
<i class="fas fa-server fa-2x me-3"></i>
<div>
<h6 class="mb-0">PostgreSQL</h6>
<span class="badge" id="postgresql-status">Checking...</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-center">
<i class="fas fa-microphone fa-2x me-3"></i>
<div>
<h6 class="mb-0">Whisper/TTS</h6>
<span class="badge" id="ml-status">Checking...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Charts Row 1 -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Request Volume</h5>
<div class="btn-group btn-group-sm float-end" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="updateRequestChart('minute')">Minute</button>
<button type="button" class="btn btn-outline-primary" onclick="updateRequestChart('hour')">Hour</button>
<button type="button" class="btn btn-outline-primary" onclick="updateRequestChart('day')">Day</button>
</div>
</div>
<div class="card-body">
<canvas id="requestChart" height="100"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Language Pairs</h5>
</div>
<div class="card-body">
<canvas id="languageChart" height="200"></canvas>
</div>
</div>
</div>
</div>
<!-- Charts Row 2 -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Operations</h5>
</div>
<div class="card-body">
<canvas id="operationsChart" height="120"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Response Times (ms)</h5>
</div>
<div class="card-body">
<canvas id="responseTimeChart" height="120"></canvas>
</div>
</div>
</div>
</div>
<!-- Error Analysis -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Error Types</h5>
</div>
<div class="card-body">
<canvas id="errorTypeChart" height="150"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Recent Errors</h5>
</div>
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
<div id="recent-errors-list">
<div class="text-center">
<div class="spinner-border" role="status"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Performance Metrics -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Performance Metrics</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Operation</th>
<th>Average (ms)</th>
<th>95th Percentile (ms)</th>
<th>99th Percentile (ms)</th>
</tr>
</thead>
<tbody id="performance-table">
<tr>
<td colspan="4" class="text-center">
<div class="spinner-border" role="status"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Real-time Updates Status -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1000">
<div class="toast" id="update-toast" role="alert">
<div class="toast-header">
<i class="fas fa-sync-alt me-2"></i>
<strong class="me-auto">Real-time Updates</strong>
<small id="last-update">Just now</small>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
<span id="update-status">Connected</span>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Initialize dashboard
$(document).ready(function() {
initializeDashboard();
// Start real-time updates
startRealtimeUpdates();
// Load initial data
loadOverviewStats();
loadRequestChart('minute');
loadOperationStats();
loadErrorStats();
loadPerformanceStats();
// Refresh data periodically
setInterval(loadOverviewStats, 10000); // Every 10 seconds
setInterval(function() {
loadRequestChart(currentTimeframe);
}, 30000); // Every 30 seconds
});
</script>
{% endblock %}

View File

@@ -0,0 +1,120 @@
{% extends "base.html" %}
{% block title %}Dashboard - Talk2Me Admin{% endblock %}
{% block content %}
<!-- Simple Mode Notice -->
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">Simple Mode Active</h4>
<p>The admin dashboard is running in simple mode because Redis and PostgreSQL services are not available.</p>
<hr>
<p class="mb-0">To enable full analytics and monitoring features, please ensure Redis and PostgreSQL are running.</p>
</div>
<!-- Basic Info Cards -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">System Status</h5>
<p class="card-text">
<span class="badge badge-success">Online</span>
</p>
<small class="text-muted">Talk2Me API is running</small>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Admin Access</h5>
<p class="card-text">
<span class="badge badge-primary">Authenticated</span>
</p>
<small class="text-muted">You are logged in as admin</small>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Services</h5>
<p class="card-text">
Redis: <span class="badge badge-secondary">Not configured</span><br>
PostgreSQL: <span class="badge badge-secondary">Not configured</span>
</p>
</div>
</div>
</div>
</div>
<!-- Available Actions -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">Available Actions</h5>
</div>
<div class="card-body">
<p>In simple mode, you can:</p>
<ul>
<li>Access the Talk2Me API with admin privileges</li>
<li>View system health status</li>
<li>Logout from the admin session</li>
</ul>
<p class="mt-3">To enable full features, set up the following services:</p>
<ol>
<li><strong>Redis</strong>: For caching, rate limiting, and session management</li>
<li><strong>PostgreSQL</strong>: For persistent storage of analytics and user data</li>
</ol>
<div class="mt-4">
<a href="/admin/logout" class="btn btn-secondary">Logout</a>
</div>
</div>
</div>
<!-- Setup Instructions -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">Quick Setup Guide</h5>
</div>
<div class="card-body">
<h6>1. Install Redis:</h6>
<pre class="bg-light p-2"><code># Ubuntu/Debian
sudo apt-get install redis-server
sudo systemctl start redis
# macOS
brew install redis
brew services start redis</code></pre>
<h6>2. Install PostgreSQL:</h6>
<pre class="bg-light p-2"><code># Ubuntu/Debian
sudo apt-get install postgresql
sudo systemctl start postgresql
# macOS
brew install postgresql
brew services start postgresql</code></pre>
<h6>3. Configure Environment:</h6>
<pre class="bg-light p-2"><code># Add to .env file
REDIS_URL=redis://localhost:6379/0
DATABASE_URL=postgresql://user:pass@localhost/talk2me</code></pre>
<h6>4. Initialize Database:</h6>
<pre class="bg-light p-2"><code>python init_auth_db.py</code></pre>
<p class="mt-3">After completing these steps, restart the Talk2Me server to enable full admin features.</p>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Simple mode - no need for real-time updates or API calls
console.log('Admin dashboard loaded in simple mode');
</script>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}Admin Login - Talk2Me{% endblock %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6 col-lg-4">
<div class="card shadow">
<div class="card-body">
<h3 class="card-title text-center mb-4">
<i class="fas fa-lock"></i> Admin Login
</h3>
{% if error %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<form method="POST">
<div class="mb-3">
<label for="token" class="form-label">Admin Token</label>
<input type="password" class="form-control" id="token" name="token" required autofocus>
<div class="form-text">Enter your admin access token</div>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-sign-in-alt"></i> Login
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}