Spaces:
Running
Running
| {% extends "base.html" %} | |
| {% block title %}Admin Dashboard{% endblock %} | |
| {% block content %} | |
| <div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8"> | |
| <div class="mb-8"> | |
| <h1 class="text-2xl font-bold text-gray-900 dark:text-white">Admin Dashboard</h1> | |
| <p class="text-gray-600 dark:text-gray-400">Manage users and credits</p> | |
| </div> | |
| <div class="mb-8 grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg"> | |
| <div class="px-4 py-5 sm:px-6"> | |
| <h3 class="text-lg font-medium text-gray-900 dark:text-white">User Statistics</h3> | |
| </div> | |
| <div class="px-4 py-5 sm:p-6"> | |
| <dl> | |
| <div class="mb-4"> | |
| <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Users</dt> | |
| <dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white">{{ total_users }}</dd> | |
| </div> | |
| <div class="mb-4"> | |
| <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Admin Users</dt> | |
| <dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white">{{ admin_count }}</dd> | |
| </div> | |
| <div> | |
| <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Credits Issued</dt> | |
| <dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white">{{ total_credits }}</dd> | |
| </div> | |
| </dl> | |
| </div> | |
| </div> | |
| <div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg md:col-span-2"> | |
| <div class="px-4 py-5 sm:px-6"> | |
| <h3 class="text-lg font-medium text-gray-900 dark:text-white">Quick Actions</h3> | |
| </div> | |
| <div class="px-4 py-5 sm:p-6"> | |
| <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> | |
| <button id="addCreditsAllBtn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"> | |
| Add Credits to All Users | |
| </button> | |
| <button id="addNewUserBtn" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"> | |
| Add New User | |
| </button> | |
| <button id="clearSessionsBtn" class="bg-yellow-600 text-white px-4 py-2 rounded-md hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2"> | |
| Clear Expired Sessions | |
| </button> | |
| <button id="resetCreditsBtn" class="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"> | |
| Reset All Credits | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-md"> | |
| <div class="px-4 py-5 sm:px-6 flex justify-between items-center"> | |
| <h3 class="text-lg font-medium text-gray-900 dark:text-white">User Management</h3> | |
| <div class="relative"> | |
| <input type="text" id="userSearch" placeholder="Search users..." class="border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"> | |
| </div> | |
| </div> | |
| <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-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"> | |
| User | |
| </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"> | |
| Role | |
| </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"> | |
| Credits | |
| </th> | |
| <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"> | |
| Actions | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700" id="userTable"> | |
| {% for user in users %} | |
| <tr class="user-row" data-user-id="{{ user.id }}"> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div> | |
| <div class="text-sm font-medium text-gray-900 dark:text-white"> | |
| {{ user.username }} | |
| </div> | |
| <div class="text-sm text-gray-500 dark:text-gray-400"> | |
| {{ user.email }} | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {% if user.role == 'admin' %}bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-100{% else %}bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100{% endif %}"> | |
| {{ user.role }} | |
| </span> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> | |
| <span class="credits-display">{{ user.credits }}</span> | |
| <div class="edit-credits hidden"> | |
| <input type="number" class="credits-input w-20 border border-gray-300 dark:border-gray-600 rounded p-1 dark:bg-gray-700 dark:text-white" value="{{ user.credits }}"> | |
| <button class="save-credits ml-2 text-xs bg-blue-600 text-white px-2 py-1 rounded">Save</button> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | |
| <button class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 edit-credits-btn"> | |
| Edit Credits | |
| </button> | |
| <span class="mx-2 text-gray-300 dark:text-gray-600">|</span> | |
| <button class="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 delete-user-btn" {% if user.role == 'admin' %}disabled{% endif %}> | |
| {% if user.role == 'admin' %}Admin{% else %}Delete{% endif %} | |
| </button> | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Modal for adding credits to all users --> | |
| <div id="addCreditsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4"> | |
| <h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Add Credits to All Users</h3> | |
| <div class="mb-4"> | |
| <label for="creditsAmount" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Credits to add:</label> | |
| <input type="number" id="creditsAmount" class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 dark:bg-gray-700 dark:text-white" value="100"> | |
| </div> | |
| <div class="flex justify-end space-x-4"> | |
| <button id="cancelAddCredits" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"> | |
| Cancel | |
| </button> | |
| <button id="confirmAddCredits" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"> | |
| Add Credits | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| // Search functionality | |
| document.getElementById('userSearch').addEventListener('input', function(e) { | |
| const searchTerm = e.target.value.toLowerCase(); | |
| const rows = document.querySelectorAll('.user-row'); | |
| rows.forEach(row => { | |
| const username = row.querySelector('.text-sm.font-medium').textContent.toLowerCase(); | |
| const email = row.querySelector('.text-sm.text-gray-500').textContent.toLowerCase(); | |
| if (username.includes(searchTerm) || email.includes(searchTerm)) { | |
| row.style.display = ''; | |
| } else { | |
| row.style.display = 'none'; | |
| } | |
| }); | |
| }); | |
| // Edit credits functionality | |
| document.querySelectorAll('.edit-credits-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const row = this.closest('tr'); | |
| const creditsDisplay = row.querySelector('.credits-display'); | |
| const editCredits = row.querySelector('.edit-credits'); | |
| creditsDisplay.classList.toggle('hidden'); | |
| editCredits.classList.toggle('hidden'); | |
| if (!editCredits.classList.contains('hidden')) { | |
| editCredits.querySelector('input').focus(); | |
| } | |
| }); | |
| }); | |
| // Save credits functionality | |
| document.querySelectorAll('.save-credits').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const row = this.closest('tr'); | |
| const userId = row.dataset.userId; | |
| const newCredits = parseFloat(row.querySelector('.credits-input').value); | |
| const creditsDisplay = row.querySelector('.credits-display'); | |
| const editCredits = row.querySelector('.edit-credits'); | |
| fetch('/api/admin/update-credits', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| user_id: userId, | |
| credits: newCredits | |
| }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| creditsDisplay.textContent = newCredits; | |
| creditsDisplay.classList.toggle('hidden'); | |
| editCredits.classList.toggle('hidden'); | |
| showToast(data.message, 'success'); | |
| } else { | |
| showToast('Error: ' + data.error, 'error'); | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| showToast('Failed to update credits', 'error'); | |
| }); | |
| }); | |
| }); | |
| // Modal functionality | |
| const addCreditsModal = document.getElementById('addCreditsModal'); | |
| document.getElementById('addCreditsAllBtn').addEventListener('click', function() { | |
| addCreditsModal.classList.remove('hidden'); | |
| addCreditsModal.classList.add('flex'); | |
| }); | |
| document.getElementById('cancelAddCredits').addEventListener('click', function() { | |
| addCreditsModal.classList.add('hidden'); | |
| addCreditsModal.classList.remove('flex'); | |
| }); | |
| // Close modal when clicking outside | |
| addCreditsModal.addEventListener('click', function(e) { | |
| if (e.target === addCreditsModal) { | |
| addCreditsModal.classList.add('hidden'); | |
| addCreditsModal.classList.remove('flex'); | |
| } | |
| }); | |
| </script> | |
| {% endblock %} |