amauricunha commited on
Commit
4a242b7
·
verified ·
1 Parent(s): 4ddaca4

Delete templates/admin.html

Browse files
Files changed (1) hide show
  1. templates/admin.html +0 -1083
templates/admin.html DELETED
@@ -1,1083 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>English Helper - Admin Panel</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
- <style>
10
- .admin-card {
11
- @apply bg-white rounded-lg shadow-md p-6 border border-gray-200;
12
- }
13
- .stat-card {
14
- @apply bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg p-4 shadow-md;
15
- }
16
- .danger-zone {
17
- @apply bg-red-50 border border-red-200 rounded-lg p-4;
18
- }
19
- .success-zone {
20
- @apply bg-green-50 border border-green-200 rounded-lg p-4;
21
- }
22
- .spinner {
23
- border: 3px solid #f3f3f3;
24
- border-top: 3px solid #3498db;
25
- border-radius: 50%;
26
- width: 20px;
27
- height: 20px;
28
- animation: spin 1s linear infinite;
29
- display: inline-block;
30
- margin-right: 10px;
31
- }
32
- @keyframes spin {
33
- 0% { transform: rotate(0deg); }
34
- 100% { transform: rotate(360deg); }
35
- }
36
- .table-container {
37
- max-height: 600px;
38
- overflow-y: auto;
39
- }
40
- </style>
41
- </head>
42
- <body class="bg-gray-100 min-h-screen">
43
- <!-- Admin Login Modal -->
44
- <div id="loginModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
45
- <div class="bg-white rounded-lg p-8 max-w-md w-full mx-4">
46
- <h2 class="text-2xl font-bold mb-6 text-center">🔐 Admin Login</h2>
47
- <form id="adminLoginForm">
48
- <div class="mb-4">
49
- <label class="block text-gray-700 text-sm font-bold mb-2">Username</label>
50
- <input type="text" id="adminUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:border-blue-500" required>
51
- </div>
52
- <div class="mb-6">
53
- <label class="block text-gray-700 text-sm font-bold mb-2">Password</label>
54
- <input type="password" id="adminPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:border-blue-500" required>
55
- </div>
56
- <button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition duration-200">
57
- 🚀 Login
58
- </button>
59
- </form>
60
- <div id="loginError" class="mt-4 text-red-600 text-sm hidden"></div>
61
- </div>
62
- </div>
63
-
64
- <!-- Admin Interface -->
65
- <div id="adminInterface" class="hidden">
66
- <!-- Header -->
67
- <header class="bg-white shadow-md">
68
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
69
- <div class="flex justify-between items-center py-4">
70
- <div class="flex items-center">
71
- <h1 class="text-2xl font-bold text-gray-900">🎓 English Helper Admin</h1>
72
- </div>
73
- <div class="flex items-center space-x-4">
74
- <span id="adminUserInfo" class="text-gray-600"></span>
75
- <button onclick="adminLogout()" class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition duration-200">
76
- 🚪 Logout
77
- </button>
78
- </div>
79
- </div>
80
- </div>
81
- </header>
82
-
83
- <!-- Navigation Tabs -->
84
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-6">
85
- <div class="border-b border-gray-200">
86
- <nav class="-mb-px flex space-x-8">
87
- <button onclick="showTab('dashboard')" class="admin-tab active" data-tab="dashboard">
88
- 📊 Dashboard
89
- </button>
90
- <button onclick="showTab('users')" class="admin-tab" data-tab="users">
91
- 👥 Users
92
- </button>
93
- <button onclick="showTab('database')" class="admin-tab" data-tab="database">
94
- 🗄️ Database
95
- </button>
96
- <button onclick="showTab('tokens')" class="admin-tab" data-tab="tokens">
97
- 🎯 Token Usage
98
- </button>
99
- <button onclick="showTab('system')" class="admin-tab" data-tab="system">
100
- 🖥️ System Health
101
- </button>
102
- </nav>
103
- </div>
104
- </div>
105
-
106
- <!-- Tab Content -->
107
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
108
- <!-- Dashboard Tab -->
109
- <div id="dashboardTab" class="tab-content">
110
- <h2 class="text-2xl font-bold mb-6">📊 System Dashboard</h2>
111
-
112
- <!-- System Stats Cards -->
113
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
114
- <div class="stat-card">
115
- <h3 class="text-lg font-semibold mb-2">👥 Total Users</h3>
116
- <p id="totalUsers" class="text-3xl font-bold">-</p>
117
- <p class="text-sm opacity-80">
118
- <span id="newUsersWeek">-</span> this week
119
- </p>
120
- </div>
121
- <div class="stat-card">
122
- <h3 class="text-lg font-semibold mb-2">📚 Study Sessions</h3>
123
- <p id="totalSessions" class="text-3xl font-bold">-</p>
124
- <p class="text-sm opacity-80">All time</p>
125
- </div>
126
- <div class="stat-card">
127
- <h3 class="text-lg font-semibold mb-2">🎯 API Tokens</h3>
128
- <p id="totalTokens" class="text-3xl font-bold">-</p>
129
- <p class="text-sm opacity-80">
130
- $<span id="estimatedCost">-</span> estimated cost
131
- </p>
132
- </div>
133
- <div class="stat-card">
134
- <h3 class="text-lg font-semibold mb-2">📝 Content Items</h3>
135
- <p id="totalContent" class="text-3xl font-bold">-</p>
136
- <p class="text-sm opacity-80">Articles & flashcards</p>
137
- </div>
138
- </div>
139
-
140
- <!-- Charts Section -->
141
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
142
- <div class="admin-card">
143
- <h3 class="text-lg font-semibold mb-4">📈 User Growth</h3>
144
- <canvas id="userGrowthChart" width="400" height="200"></canvas>
145
- </div>
146
- <div class="admin-card">
147
- <h3 class="text-lg font-semibold mb-4">💰 Token Usage Costs</h3>
148
- <canvas id="tokenCostChart" width="400" height="200"></canvas>
149
- </div>
150
- </div>
151
-
152
- <!-- Recent Activity -->
153
- <div class="admin-card">
154
- <h3 class="text-lg font-semibold mb-4">🔄 Recent Activity</h3>
155
- <div id="recentActivity" class="space-y-2">
156
- <div class="text-center text-gray-500 py-4">Loading recent activity...</div>
157
- </div>
158
- </div>
159
- </div>
160
-
161
- <!-- Users Tab -->
162
- <div id="usersTab" class="tab-content hidden">
163
- <div class="flex justify-between items-center mb-6">
164
- <h2 class="text-2xl font-bold">👥 User Management</h2>
165
- <div class="flex items-center space-x-4">
166
- <input type="text" id="userSearch" placeholder="Search users..." class="px-4 py-2 border rounded-lg">
167
- <button onclick="refreshUsers()" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
168
- 🔄 Refresh
169
- </button>
170
- <button onclick="exportUsers()" class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600">
171
- 📥 Export CSV
172
- </button>
173
- </div>
174
- </div>
175
-
176
- <!-- Users Table -->
177
- <div class="admin-card">
178
- <div class="table-container">
179
- <table class="w-full table-auto">
180
- <thead class="bg-gray-50 sticky top-0">
181
- <tr>
182
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">User</th>
183
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
184
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Activity</th>
185
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
186
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
187
- </tr>
188
- </thead>
189
- <tbody id="usersTableBody">
190
- <tr>
191
- <td colspan="5" class="text-center py-8">
192
- <div class="spinner"></div> Loading users...
193
- </td>
194
- </tr>
195
- </tbody>
196
- </table>
197
- </div>
198
-
199
- <!-- Pagination -->
200
- <div id="usersPagination" class="flex justify-between items-center mt-4 pt-4 border-t">
201
- <div id="usersInfo" class="text-sm text-gray-600"></div>
202
- <div class="flex space-x-2">
203
- <button onclick="previousPage()" id="prevPageBtn" class="px-3 py-1 bg-gray-300 rounded hover:bg-gray-400 disabled:opacity-50" disabled>Previous</button>
204
- <span id="pageInfo" class="px-3 py-1">-</span>
205
- <button onclick="nextPage()" id="nextPageBtn" class="px-3 py-1 bg-gray-300 rounded hover:bg-gray-400 disabled:opacity-50" disabled>Next</button>
206
- </div>
207
- </div>
208
- </div>
209
- </div>
210
-
211
- <!-- Database Tab -->
212
- <div id="databaseTab" class="tab-content hidden">
213
- <h2 class="text-2xl font-bold mb-6">🗄️ Database Overview</h2>
214
-
215
- <div class="admin-card">
216
- <h3 class="text-lg font-semibold mb-4">📋 Database Schema</h3>
217
- <div id="databaseSchema" class="space-y-4">
218
- <div class="text-center text-gray-500 py-4">Loading schema...</div>
219
- </div>
220
- </div>
221
- </div>
222
-
223
- <!-- Token Usage Tab -->
224
- <div id="tokensTab" class="tab-content hidden">
225
- <div class="flex justify-between items-center mb-6">
226
- <h2 class="text-2xl font-bold">🎯 Token Usage Analytics</h2>
227
- <button onclick="exportTokens()" class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600">
228
- 📥 Export Token Data
229
- </button>
230
- </div>
231
-
232
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
233
- <div class="admin-card">
234
- <h3 class="text-lg font-semibold mb-4">📊 Usage by Provider</h3>
235
- <canvas id="providerUsageChart" width="400" height="300"></canvas>
236
- </div>
237
- <div class="admin-card">
238
- <h3 class="text-lg font-semibold mb-4">💰 Cost Breakdown</h3>
239
- <canvas id="costBreakdownChart" width="400" height="300"></canvas>
240
- </div>
241
- </div>
242
- </div>
243
-
244
- <!-- System Health Tab -->
245
- <div id="systemTab" class="tab-content hidden">
246
- <h2 class="text-2xl font-bold mb-6">🖥️ System Health Monitor</h2>
247
-
248
- <!-- System Alerts -->
249
- <div class="admin-card mb-6">
250
- <h3 class="text-lg font-semibold mb-4">⚠️ System Alerts</h3>
251
- <div id="systemAlerts">
252
- <div class="text-center text-gray-500 py-4">Loading alerts...</div>
253
- </div>
254
- </div>
255
-
256
- <!-- System Metrics -->
257
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
258
- <div class="admin-card">
259
- <h4 class="font-semibold mb-3">💾 Memory Usage</h4>
260
- <div id="memoryMetrics">
261
- <div class="text-sm text-gray-500">Loading...</div>
262
- </div>
263
- </div>
264
-
265
- <div class="admin-card">
266
- <h4 class="font-semibold mb-3">💿 Disk Usage</h4>
267
- <div id="diskMetrics">
268
- <div class="text-sm text-gray-500">Loading...</div>
269
- </div>
270
- </div>
271
-
272
- <div class="admin-card">
273
- <h4 class="font-semibold mb-3">🗄️ Database</h4>
274
- <div id="databaseMetrics">
275
- <div class="text-sm text-gray-500">Loading...</div>
276
- </div>
277
- </div>
278
- </div>
279
-
280
- <!-- Error Logs -->
281
- <div class="admin-card">
282
- <h3 class="text-lg font-semibold mb-4">🚨 Recent Errors</h3>
283
- <div id="errorLogs">
284
- <div class="text-center text-gray-500 py-4">Loading error logs...</div>
285
- </div>
286
- </div>
287
- </div>
288
- </div>
289
- </div>
290
-
291
- <!-- User Detail Modal -->
292
- <div id="userDetailModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
293
- <div class="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-screen overflow-y-auto">
294
- <div class="flex justify-between items-center mb-4">
295
- <h3 class="text-xl font-bold">👤 User Details</h3>
296
- <button onclick="closeUserModal()" class="text-gray-500 hover:text-gray-700">✕</button>
297
- </div>
298
- <div id="userDetailContent">
299
- <div class="text-center py-8">Loading user details...</div>
300
- </div>
301
- </div>
302
- </div>
303
-
304
- <!-- Toast Notifications -->
305
- <div id="toastContainer" class="fixed top-4 right-4 z-50"></div>
306
-
307
- <script>
308
- let currentUser = null;
309
- let currentPage = 1;
310
- let usersData = null;
311
-
312
- // Check admin authentication on load
313
- document.addEventListener('DOMContentLoaded', () => {
314
- checkAdminAuth();
315
- });
316
-
317
- async function checkAdminAuth() {
318
- try {
319
- const response = await fetch('/admin/check');
320
- const data = await response.json();
321
-
322
- if (data.authenticated) {
323
- showAdminInterface(data.username);
324
- } else {
325
- showLoginModal();
326
- }
327
- } catch (error) {
328
- console.error('Auth check error:', error);
329
- showLoginModal();
330
- }
331
- }
332
-
333
- function showLoginModal() {
334
- document.getElementById('loginModal').classList.remove('hidden');
335
- document.getElementById('adminInterface').classList.add('hidden');
336
- }
337
-
338
- function showAdminInterface(username) {
339
- document.getElementById('loginModal').classList.add('hidden');
340
- document.getElementById('adminInterface').classList.remove('hidden');
341
- document.getElementById('adminUserInfo').textContent = `Welcome, ${username}`;
342
-
343
- // Load dashboard data
344
- loadDashboard();
345
- }
346
-
347
- // Admin login
348
- document.getElementById('adminLoginForm').addEventListener('submit', async (e) => {
349
- e.preventDefault();
350
-
351
- const username = document.getElementById('adminUsername').value;
352
- const password = document.getElementById('adminPassword').value;
353
- const errorDiv = document.getElementById('loginError');
354
-
355
- try {
356
- const response = await fetch('/admin/login', {
357
- method: 'POST',
358
- headers: {
359
- 'Content-Type': 'application/json',
360
- },
361
- body: JSON.stringify({ username, password })
362
- });
363
-
364
- const data = await response.json();
365
-
366
- if (data.success) {
367
- showAdminInterface(username);
368
- errorDiv.classList.add('hidden');
369
- } else {
370
- errorDiv.textContent = data.error || 'Login failed';
371
- errorDiv.classList.remove('hidden');
372
- }
373
- } catch (error) {
374
- errorDiv.textContent = 'Network error. Please try again.';
375
- errorDiv.classList.remove('hidden');
376
- }
377
- });
378
-
379
- async function adminLogout() {
380
- try {
381
- await fetch('/admin/logout', { method: 'POST' });
382
- showLoginModal();
383
- showToast('Logged out successfully', 'success');
384
- } catch (error) {
385
- console.error('Logout error:', error);
386
- }
387
- }
388
-
389
- // Tab management
390
- function showTab(tabName) {
391
- // Hide all tabs
392
- document.querySelectorAll('.tab-content').forEach(tab => {
393
- tab.classList.add('hidden');
394
- });
395
-
396
- // Remove active class from all tab buttons
397
- document.querySelectorAll('.admin-tab').forEach(btn => {
398
- btn.classList.remove('active', 'border-blue-500', 'text-blue-600');
399
- btn.classList.add('border-transparent', 'text-gray-500');
400
- });
401
-
402
- // Show selected tab
403
- document.getElementById(tabName + 'Tab').classList.remove('hidden');
404
-
405
- // Add active class to selected tab button
406
- const activeBtn = document.querySelector(`[data-tab="${tabName}"]`);
407
- activeBtn.classList.add('active', 'border-blue-500', 'text-blue-600');
408
- activeBtn.classList.remove('border-transparent', 'text-gray-500');
409
-
410
- // Load tab-specific data
411
- if (tabName === 'dashboard') {
412
- loadDashboard();
413
- } else if (tabName === 'users') {
414
- loadUsers();
415
- } else if (tabName === 'database') {
416
- loadDatabaseSchema();
417
- } else if (tabName === 'tokens') {
418
- loadTokenUsage();
419
- } else if (tabName === 'system') {
420
- loadSystemHealth();
421
- }
422
- }
423
-
424
- async function loadDashboard() {
425
- try {
426
- const response = await fetch('/admin/dashboard');
427
- const data = await response.json();
428
-
429
- if (data.success) {
430
- const stats = data.stats;
431
-
432
- // Update stat cards
433
- document.getElementById('totalUsers').textContent = stats.users?.total || 0;
434
- document.getElementById('newUsersWeek').textContent = stats.users?.new_week || 0;
435
- document.getElementById('totalSessions').textContent = stats.activity?.total_sessions || 0;
436
- document.getElementById('totalTokens').textContent = (stats.api_usage?.total_tokens || 0).toLocaleString();
437
- document.getElementById('estimatedCost').textContent = stats.api_usage?.estimated_cost || '0.00';
438
- document.getElementById('totalContent').textContent =
439
- (stats.activity?.total_flashcards || 0) + (stats.activity?.total_articles || 0);
440
-
441
- // Update recent activity
442
- updateRecentActivity(stats.recent_activity || []);
443
-
444
- // Create charts
445
- createCharts(stats);
446
- }
447
- } catch (error) {
448
- console.error('Dashboard loading error:', error);
449
- showToast('Failed to load dashboard', 'error');
450
- }
451
- }
452
-
453
- function updateRecentActivity(activities) {
454
- const container = document.getElementById('recentActivity');
455
-
456
- if (activities.length === 0) {
457
- container.innerHTML = '<div class="text-center text-gray-500 py-4">No recent activity</div>';
458
- return;
459
- }
460
-
461
- container.innerHTML = activities.map(activity => `
462
- <div class="flex items-center justify-between py-2 border-b border-gray-100">
463
- <div>
464
- <span class="font-medium">${activity.user}</span>
465
- <span class="text-gray-600">performed ${activity.activity}</span>
466
- </div>
467
- <span class="text-sm text-gray-500">${formatDate(activity.timestamp)}</span>
468
- </div>
469
- `).join('');
470
- }
471
-
472
- async function loadUsers() {
473
- try {
474
- const response = await fetch(`/admin/users?page=${currentPage}&per_page=20`);
475
- const data = await response.json();
476
-
477
- if (data.success) {
478
- usersData = data.data;
479
- updateUsersTable(usersData.users);
480
- updateUsersPagination(usersData);
481
- }
482
- } catch (error) {
483
- console.error('Users loading error:', error);
484
- showToast('Failed to load users', 'error');
485
- }
486
- }
487
-
488
- function updateUsersTable(users) {
489
- const tbody = document.getElementById('usersTableBody');
490
-
491
- if (users.length === 0) {
492
- tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8">No users found</td></tr>';
493
- return;
494
- }
495
-
496
- tbody.innerHTML = users.map(user => `
497
- <tr class="border-b hover:bg-gray-50">
498
- <td class="px-4 py-3">
499
- <div>
500
- <div class="font-medium">${user.email}</div>
501
- <div class="text-sm text-gray-500">ID: ${user.id}</div>
502
- </div>
503
- </td>
504
- <td class="px-4 py-3 text-sm text-gray-600">
505
- ${formatDate(user.created_at)}
506
- </td>
507
- <td class="px-4 py-3">
508
- <div class="text-sm">
509
- <div>${user.session_count} sessions</div>
510
- <div class="text-gray-500">${user.flashcard_count} cards, ${user.article_count} articles</div>
511
- </div>
512
- </td>
513
- <td class="px-4 py-3">
514
- <span class="px-2 py-1 text-xs rounded-full ${user.email_confirmed ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}">
515
- ${user.email_confirmed ? 'Confirmed' : 'Unconfirmed'}
516
- </span>
517
- </td>
518
- <td class="px-4 py-3">
519
- <div class="flex space-x-2">
520
- <button onclick="viewUser(${user.id})" class="text-blue-600 hover:text-blue-800 text-sm">👁️ View</button>
521
- <button onclick="deleteUser(${user.id}, '${user.email}')" class="text-red-600 hover:text-red-800 text-sm">🗑️ Delete</button>
522
- </div>
523
- </td>
524
- </tr>
525
- `).join('');
526
- }
527
-
528
- function updateUsersPagination(data) {
529
- document.getElementById('usersInfo').textContent =
530
- `Showing ${((data.page - 1) * data.per_page) + 1}-${Math.min(data.page * data.per_page, data.total)} of ${data.total} users`;
531
-
532
- document.getElementById('pageInfo').textContent = `Page ${data.page} of ${data.total_pages}`;
533
-
534
- document.getElementById('prevPageBtn').disabled = data.page <= 1;
535
- document.getElementById('nextPageBtn').disabled = data.page >= data.total_pages;
536
- }
537
-
538
- async function viewUser(userId) {
539
- try {
540
- const response = await fetch(`/admin/users/${userId}`);
541
- const data = await response.json();
542
-
543
- if (data.success) {
544
- showUserModal(data.user);
545
- }
546
- } catch (error) {
547
- console.error('User details error:', error);
548
- showToast('Failed to load user details', 'error');
549
- }
550
- }
551
-
552
- function showUserModal(user) {
553
- const modal = document.getElementById('userDetailModal');
554
- const content = document.getElementById('userDetailContent');
555
-
556
- content.innerHTML = `
557
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
558
- <div>
559
- <h4 class="font-semibold mb-3">📋 Basic Information</h4>
560
- <div class="space-y-2 text-sm">
561
- <div><strong>Email:</strong> ${user.user.email}</div>
562
- <div><strong>ID:</strong> ${user.user.id}</div>
563
- <div><strong>Created:</strong> ${formatDate(user.user.created_at)}</div>
564
- <div><strong>Last Login:</strong> ${user.user.last_login ? formatDate(user.user.last_login) : 'Never'}</div>
565
- <div><strong>Email Confirmed:</strong> ${user.user.email_confirmed ? '✅ Yes' : '❌ No'}</div>
566
- </div>
567
- </div>
568
-
569
- <div>
570
- <h4 class="font-semibold mb-3">⚙️ Settings</h4>
571
- <div class="space-y-2 text-sm">
572
- ${Object.entries(user.settings).map(([key, value]) =>
573
- `<div><strong>${key}:</strong> ${value}</div>`
574
- ).join('')}
575
- </div>
576
- </div>
577
- </div>
578
-
579
- <div class="mt-6">
580
- <h4 class="font-semibold mb-3">📊 Recent Activity</h4>
581
- <div class="max-h-48 overflow-y-auto">
582
- ${user.recent_sessions.map(session => `
583
- <div class="flex justify-between py-2 border-b text-sm">
584
- <span>${session.activity}</span>
585
- <span class="text-gray-500">${formatDate(session.timestamp)}</span>
586
- </div>
587
- `).join('')}
588
- </div>
589
- </div>
590
-
591
- <div class="mt-6">
592
- <h4 class="font-semibold mb-3">🎯 Token Usage</h4>
593
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
594
- ${user.token_usage.map(usage => `
595
- <div class="bg-gray-50 p-3 rounded">
596
- <div class="font-medium">${usage.provider}</div>
597
- <div class="text-sm text-gray-600">
598
- Input: ${usage.input_tokens.toLocaleString()}<br>
599
- Output: ${usage.output_tokens.toLocaleString()}<br>
600
- Calls: ${usage.calls}
601
- </div>
602
- </div>
603
- `).join('')}
604
- </div>
605
- </div>
606
- `;
607
-
608
- modal.classList.remove('hidden');
609
- }
610
-
611
- function closeUserModal() {
612
- document.getElementById('userDetailModal').classList.add('hidden');
613
- }
614
-
615
- async function deleteUser(userId, email) {
616
- if (!confirm(`Are you sure you want to delete user "${email}" and all their data? This action cannot be undone.`)) {
617
- return;
618
- }
619
-
620
- try {
621
- const response = await fetch(`/admin/users/${userId}`, {
622
- method: 'DELETE'
623
- });
624
-
625
- const data = await response.json();
626
-
627
- if (data.success) {
628
- showToast('User deleted successfully', 'success');
629
- loadUsers(); // Refresh the table
630
- } else {
631
- showToast('Failed to delete user', 'error');
632
- }
633
- } catch (error) {
634
- console.error('Delete user error:', error);
635
- showToast('Failed to delete user', 'error');
636
- }
637
- }
638
-
639
- async function loadDatabaseSchema() {
640
- try {
641
- const response = await fetch('/admin/database/schema');
642
- const data = await response.json();
643
-
644
- if (data.success) {
645
- updateDatabaseSchema(data.schema);
646
- }
647
- } catch (error) {
648
- console.error('Schema loading error:', error);
649
- showToast('Failed to load database schema', 'error');
650
- }
651
- }
652
-
653
- function updateDatabaseSchema(schema) {
654
- const container = document.getElementById('databaseSchema');
655
-
656
- container.innerHTML = Object.entries(schema).map(([tableName, tableInfo]) => `
657
- <div class="border rounded-lg p-4">
658
- <div class="flex justify-between items-center mb-3">
659
- <h5 class="font-semibold text-lg">${tableName}</h5>
660
- <span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm">
661
- ${tableInfo.row_count} rows
662
- </span>
663
- </div>
664
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 text-sm">
665
- ${tableInfo.columns.map(col => `
666
- <div class="flex items-center space-x-2">
667
- <span class="font-medium">${col.name}</span>
668
- <span class="text-gray-500">${col.type}</span>
669
- ${col.primary_key ? '<span class="bg-yellow-100 text-yellow-800 px-1 rounded text-xs">PK</span>' : ''}
670
- ${col.not_null ? '<span class="bg-red-100 text-red-800 px-1 rounded text-xs">NOT NULL</span>' : ''}
671
- </div>
672
- `).join('')}
673
- </div>
674
- </div>
675
- `).join('');
676
- }
677
-
678
- async function loadTokenUsage() {
679
- try {
680
- // For now, create sample charts with existing data
681
- await createTokenUsageCharts();
682
- } catch (error) {
683
- console.error('Token usage loading error:', error);
684
- showToast('Failed to load token usage data', 'error');
685
- }
686
- }
687
-
688
- async function createTokenUsageCharts() {
689
- // Provider Usage Chart
690
- const providerCtx = document.getElementById('providerUsageChart').getContext('2d');
691
-
692
- new Chart(providerCtx, {
693
- type: 'bar',
694
- data: {
695
- labels: ['Groq', 'Gemini'],
696
- datasets: [{
697
- label: 'Input Tokens',
698
- data: [150000, 85000],
699
- backgroundColor: 'rgba(59, 130, 246, 0.8)',
700
- borderColor: 'rgba(59, 130, 246, 1)',
701
- borderWidth: 1
702
- }, {
703
- label: 'Output Tokens',
704
- data: [45000, 28000],
705
- backgroundColor: 'rgba(147, 51, 234, 0.8)',
706
- borderColor: 'rgba(147, 51, 234, 1)',
707
- borderWidth: 1
708
- }]
709
- },
710
- options: {
711
- responsive: true,
712
- plugins: {
713
- title: {
714
- display: true,
715
- text: 'Token Usage by Provider'
716
- }
717
- },
718
- scales: {
719
- y: {
720
- beginAtZero: true,
721
- title: {
722
- display: true,
723
- text: 'Tokens'
724
- }
725
- }
726
- }
727
- }
728
- });
729
-
730
- // Cost Breakdown Chart
731
- const costCtx = document.getElementById('costBreakdownChart').getContext('2d');
732
-
733
- new Chart(costCtx, {
734
- type: 'pie',
735
- data: {
736
- labels: ['Conversation', 'Content Analysis', 'Recommendations', 'Study Planning'],
737
- datasets: [{
738
- data: [40, 25, 20, 15],
739
- backgroundColor: [
740
- 'rgba(34, 197, 94, 0.8)',
741
- 'rgba(59, 130, 246, 0.8)',
742
- 'rgba(147, 51, 234, 0.8)',
743
- 'rgba(251, 146, 60, 0.8)'
744
- ],
745
- borderColor: [
746
- 'rgba(34, 197, 94, 1)',
747
- 'rgba(59, 130, 246, 1)',
748
- 'rgba(147, 51, 234, 1)',
749
- 'rgba(251, 146, 60, 1)'
750
- ],
751
- borderWidth: 2
752
- }]
753
- },
754
- options: {
755
- responsive: true,
756
- plugins: {
757
- title: {
758
- display: true,
759
- text: 'Cost Distribution by Operation Type'
760
- },
761
- legend: {
762
- position: 'bottom'
763
- }
764
- }
765
- }
766
- });
767
- }
768
-
769
- function refreshUsers() {
770
- currentPage = 1;
771
- loadUsers();
772
- }
773
-
774
- function previousPage() {
775
- if (currentPage > 1) {
776
- currentPage--;
777
- loadUsers();
778
- }
779
- }
780
-
781
- function nextPage() {
782
- if (usersData && currentPage < usersData.total_pages) {
783
- currentPage++;
784
- loadUsers();
785
- }
786
- }
787
-
788
- function createCharts(stats) {
789
- createUserGrowthChart(stats);
790
- createTokenCostChart(stats);
791
- }
792
-
793
- function createUserGrowthChart(stats) {
794
- const ctx = document.getElementById('userGrowthChart').getContext('2d');
795
-
796
- // Generate sample data for last 30 days
797
- const dates = [];
798
- const userData = [];
799
- const today = new Date();
800
-
801
- for (let i = 29; i >= 0; i--) {
802
- const date = new Date(today);
803
- date.setDate(date.getDate() - i);
804
- dates.push(date.toLocaleDateString());
805
-
806
- // Simulate growth data (would come from real analytics)
807
- userData.push(Math.max(0, stats.users?.total - Math.floor(Math.random() * i * 2)));
808
- }
809
-
810
- new Chart(ctx, {
811
- type: 'line',
812
- data: {
813
- labels: dates,
814
- datasets: [{
815
- label: 'Total Users',
816
- data: userData,
817
- borderColor: 'rgb(59, 130, 246)',
818
- backgroundColor: 'rgba(59, 130, 246, 0.1)',
819
- fill: true,
820
- tension: 0.4
821
- }]
822
- },
823
- options: {
824
- responsive: true,
825
- plugins: {
826
- title: {
827
- display: true,
828
- text: 'User Growth Over Time'
829
- }
830
- },
831
- interaction: {
832
- intersect: false,
833
- },
834
- scales: {
835
- x: {
836
- display: true,
837
- title: {
838
- display: true,
839
- text: 'Date'
840
- }
841
- },
842
- y: {
843
- display: true,
844
- title: {
845
- display: true,
846
- text: 'Users'
847
- }
848
- }
849
- }
850
- }
851
- });
852
- }
853
-
854
- function createTokenCostChart(stats) {
855
- const ctx = document.getElementById('tokenCostChart').getContext('2d');
856
-
857
- // Sample cost data
858
- const providers = ['Groq', 'Gemini'];
859
- const costs = [
860
- (stats.api_usage?.estimated_cost || 0) * 0.6, // Groq portion
861
- (stats.api_usage?.estimated_cost || 0) * 0.4 // Gemini portion
862
- ];
863
-
864
- new Chart(ctx, {
865
- type: 'doughnut',
866
- data: {
867
- labels: providers,
868
- datasets: [{
869
- label: 'Cost ($)',
870
- data: costs,
871
- backgroundColor: [
872
- 'rgba(59, 130, 246, 0.8)',
873
- 'rgba(147, 51, 234, 0.8)'
874
- ],
875
- borderColor: [
876
- 'rgba(59, 130, 246, 1)',
877
- 'rgba(147, 51, 234, 1)'
878
- ],
879
- borderWidth: 2
880
- }]
881
- },
882
- options: {
883
- responsive: true,
884
- plugins: {
885
- title: {
886
- display: true,
887
- text: 'API Costs by Provider'
888
- },
889
- legend: {
890
- position: 'bottom',
891
- }
892
- }
893
- }
894
- });
895
- }
896
-
897
- function exportUsers() {
898
- window.open('/admin/export/users', '_blank');
899
- showToast('Users data export started', 'success');
900
- }
901
-
902
- function exportTokens() {
903
- window.open('/admin/export/tokens', '_blank');
904
- showToast('Token usage data export started', 'success');
905
- }
906
-
907
- async function loadSystemHealth() {
908
- try {
909
- // Load system health metrics
910
- const healthResponse = await fetch('/admin/system/health');
911
- const healthData = await healthResponse.json();
912
-
913
- if (healthData.success) {
914
- updateSystemMetrics(healthData.health);
915
- }
916
-
917
- // Load system alerts
918
- const alertsResponse = await fetch('/admin/system/alerts');
919
- const alertsData = await alertsResponse.json();
920
-
921
- if (alertsData.success) {
922
- updateSystemAlerts(alertsData.alerts);
923
- }
924
- } catch (error) {
925
- console.error('System health loading error:', error);
926
- showToast('Failed to load system health data', 'error');
927
- }
928
- }
929
-
930
- function updateSystemMetrics(health) {
931
- // Memory metrics
932
- if (health.memory) {
933
- const memoryPercent = health.memory.percent || 0;
934
- const memoryUsed = Math.round(health.memory.used / 1024 / 1024 / 1024 * 100) / 100;
935
- const memoryTotal = Math.round(health.memory.total / 1024 / 1024 / 1024 * 100) / 100;
936
-
937
- document.getElementById('memoryMetrics').innerHTML = `
938
- <div class="space-y-2">
939
- <div class="flex justify-between">
940
- <span>Used:</span>
941
- <span>${memoryUsed}GB / ${memoryTotal}GB</span>
942
- </div>
943
- <div class="w-full bg-gray-200 rounded-full h-2">
944
- <div class="bg-blue-600 h-2 rounded-full" style="width: ${memoryPercent}%"></div>
945
- </div>
946
- <div class="text-xs text-gray-500">${memoryPercent.toFixed(1)}% used</div>
947
- </div>
948
- `;
949
- }
950
-
951
- // Disk metrics
952
- if (health.disk) {
953
- const diskPercent = health.disk.percent || 0;
954
- const diskUsed = Math.round(health.disk.used / 1024 / 1024 / 1024 * 100) / 100;
955
- const diskTotal = Math.round(health.disk.total / 1024 / 1024 / 1024 * 100) / 100;
956
-
957
- document.getElementById('diskMetrics').innerHTML = `
958
- <div class="space-y-2">
959
- <div class="flex justify-between">
960
- <span>Used:</span>
961
- <span>${diskUsed}GB / ${diskTotal}GB</span>
962
- </div>
963
- <div class="w-full bg-gray-200 rounded-full h-2">
964
- <div class="bg-green-600 h-2 rounded-full" style="width: ${diskPercent}%"></div>
965
- </div>
966
- <div class="text-xs text-gray-500">${diskPercent.toFixed(1)}% used</div>
967
- </div>
968
- `;
969
- }
970
-
971
- // Database metrics
972
- if (health.database) {
973
- document.getElementById('databaseMetrics').innerHTML = `
974
- <div class="space-y-2">
975
- <div class="flex justify-between">
976
- <span>Size:</span>
977
- <span>${health.database.size_mb}MB</span>
978
- </div>
979
- <div class="flex justify-between">
980
- <span>Uptime:</span>
981
- <span>${health.uptime || 'Unknown'}</span>
982
- </div>
983
- </div>
984
- `;
985
- }
986
-
987
- // Error logs
988
- if (health.recent_errors && health.recent_errors.length > 0) {
989
- document.getElementById('errorLogs').innerHTML = `
990
- <div class="space-y-2 max-h-64 overflow-y-auto">
991
- ${health.recent_errors.map(error => `
992
- <div class="p-2 bg-red-50 border border-red-200 rounded text-sm">
993
- <div class="flex justify-between items-start">
994
- <span class="font-medium text-red-800">${error.level}</span>
995
- <span class="text-red-600 text-xs">${error.timestamp}</span>
996
- </div>
997
- <div class="text-red-700 mt-1">${error.message}</div>
998
- <div class="text-red-600 text-xs mt-1">Module: ${error.module}</div>
999
- </div>
1000
- `).join('')}
1001
- </div>
1002
- `;
1003
- } else {
1004
- document.getElementById('errorLogs').innerHTML = '<div class="text-center text-gray-500 py-4">No recent errors</div>';
1005
- }
1006
- }
1007
-
1008
- function updateSystemAlerts(alerts) {
1009
- const container = document.getElementById('systemAlerts');
1010
-
1011
- if (alerts.length === 0) {
1012
- container.innerHTML = '<div class="text-center text-green-600 py-4">✅ All systems normal</div>';
1013
- return;
1014
- }
1015
-
1016
- container.innerHTML = alerts.map(alert => {
1017
- const bgColor = alert.type === 'error' ? 'bg-red-50 border-red-200' :
1018
- alert.type === 'warning' ? 'bg-yellow-50 border-yellow-200' :
1019
- 'bg-blue-50 border-blue-200';
1020
- const textColor = alert.type === 'error' ? 'text-red-800' :
1021
- alert.type === 'warning' ? 'text-yellow-800' :
1022
- 'text-blue-800';
1023
- const icon = alert.type === 'error' ? '🚨' :
1024
- alert.type === 'warning' ? '⚠️' : 'ℹ️';
1025
-
1026
- return `
1027
- <div class="p-3 ${bgColor} border rounded-lg">
1028
- <div class="flex items-start gap-3">
1029
- <span class="text-lg">${icon}</span>
1030
- <div class="flex-1">
1031
- <div class="font-medium ${textColor}">${alert.message}</div>
1032
- <div class="text-sm ${textColor} opacity-75 mt-1">Action: ${alert.action}</div>
1033
- </div>
1034
- </div>
1035
- </div>
1036
- `;
1037
- }).join('');
1038
- }
1039
-
1040
- function formatDate(dateString) {
1041
- if (!dateString) return 'Never';
1042
- return new Date(dateString).toLocaleDateString() + ' ' + new Date(dateString).toLocaleTimeString();
1043
- }
1044
-
1045
- function showToast(message, type = 'info') {
1046
- const toast = document.createElement('div');
1047
- toast.className = `fixed top-4 right-4 p-4 rounded-lg shadow-md z-50 ${
1048
- type === 'success' ? 'bg-green-500' :
1049
- type === 'error' ? 'bg-red-500' :
1050
- type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'
1051
- } text-white`;
1052
-
1053
- toast.textContent = message;
1054
- document.body.appendChild(toast);
1055
-
1056
- setTimeout(() => {
1057
- toast.remove();
1058
- }, 5000);
1059
- }
1060
-
1061
- // Add CSS for tab styling
1062
- const style = document.createElement('style');
1063
- style.textContent = `
1064
- .admin-tab {
1065
- padding: 0.5rem 1rem;
1066
- border-bottom: 2px solid transparent;
1067
- font-medium: 500;
1068
- text-decoration: none;
1069
- transition: all 0.2s;
1070
- }
1071
- .admin-tab:hover {
1072
- text-decoration: none;
1073
- border-bottom-color: #d1d5db;
1074
- }
1075
- .admin-tab.active {
1076
- border-bottom-color: #3b82f6;
1077
- color: #3b82f6;
1078
- }
1079
- `;
1080
- document.head.appendChild(style);
1081
- </script>
1082
- </body>
1083
- </html>