|
|
@@ -0,0 +1,433 @@
|
|
|
+// Admin Panel JavaScript
|
|
|
+let currentUsers = [];
|
|
|
+let filteredUsers = [];
|
|
|
+
|
|
|
+// Check if user has admin role on page load
|
|
|
+document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ initializeTheme();
|
|
|
+ checkAdminAccess();
|
|
|
+ loadUsers();
|
|
|
+});
|
|
|
+
|
|
|
+// Theme toggle functionality (reused from main.js)
|
|
|
+function toggleTheme() {
|
|
|
+ const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
|
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
|
+
|
|
|
+ document.documentElement.setAttribute('data-theme', newTheme);
|
|
|
+ localStorage.setItem('theme', newTheme);
|
|
|
+}
|
|
|
+
|
|
|
+function initializeTheme() {
|
|
|
+ const savedTheme = localStorage.getItem('theme') || 'light';
|
|
|
+ document.documentElement.setAttribute('data-theme', savedTheme);
|
|
|
+}
|
|
|
+
|
|
|
+// Check admin access
|
|
|
+function checkAdminAccess() {
|
|
|
+ const currentUser = getCurrentUser();
|
|
|
+
|
|
|
+ if (!currentUser || !hasAdminRole(currentUser.roles)) {
|
|
|
+ showMessage('Access denied. Admin privileges required.', 'error');
|
|
|
+ setTimeout(() => {
|
|
|
+ window.location.href = 'index.html';
|
|
|
+ }, 2000);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// Get current user from localStorage
|
|
|
+function getCurrentUser() {
|
|
|
+ try {
|
|
|
+ const userData = localStorage.getItem('userData');
|
|
|
+ if (!userData) return null;
|
|
|
+ return JSON.parse(userData);
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Error parsing user data:', e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Check if user has admin role
|
|
|
+function hasAdminRole(roles) {
|
|
|
+ return Array.isArray(roles) && roles.includes('ADMIN');
|
|
|
+}
|
|
|
+
|
|
|
+// Load users from API
|
|
|
+async function loadUsers() {
|
|
|
+ showLoading(true);
|
|
|
+ hideEmptyState();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const token = localStorage.getItem('authToken');
|
|
|
+ if (!token) {
|
|
|
+ throw new Error('No authentication token found');
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await fetch(API_BASE_URL + '/users', {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ if (response.status === 401 || response.status === 403) {
|
|
|
+ throw new Error('Access denied. Admin privileges required.');
|
|
|
+ }
|
|
|
+ throw new Error(`Failed to load users: ${response.status} ${response.statusText}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const users = await response.json();
|
|
|
+
|
|
|
+ // Transform API response to match our display format
|
|
|
+ const transformedUsers = users.map(user => ({
|
|
|
+ id: user.id,
|
|
|
+ username: user.username,
|
|
|
+ email: user.email,
|
|
|
+ roles: user.roles,
|
|
|
+ status: user.active ? 'active' : 'inactive',
|
|
|
+ lastLogin: null // Not provided by API
|
|
|
+ }));
|
|
|
+
|
|
|
+ currentUsers = transformedUsers;
|
|
|
+ filteredUsers = [...currentUsers];
|
|
|
+ displayUsers(filteredUsers);
|
|
|
+ updateStats(transformedUsers);
|
|
|
+ showMessage('Users loaded successfully', 'success');
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading users:', error);
|
|
|
+ if (error.message.includes('Access denied') || error.message.includes('Admin privileges')) {
|
|
|
+ showMessage('Access denied. Admin privileges required.', 'error');
|
|
|
+ setTimeout(() => {
|
|
|
+ window.location.href = 'index.html';
|
|
|
+ }, 2000);
|
|
|
+ } else {
|
|
|
+ showMessage('Failed to load users. Please try again.', 'error');
|
|
|
+ }
|
|
|
+ showEmptyState();
|
|
|
+ } finally {
|
|
|
+ showLoading(false);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Display users in table
|
|
|
+function displayUsers(users) {
|
|
|
+ const tableBody = document.getElementById('usersTableBody');
|
|
|
+
|
|
|
+ if (users.length === 0) {
|
|
|
+ showEmptyState();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ tableBody.innerHTML = users.map(user => `
|
|
|
+ <tr class="user-row" data-user-id="${user.id}">
|
|
|
+ <td>
|
|
|
+ <div class="user-info">
|
|
|
+ <strong class="username">${user.username}</strong>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <span class="email">${user.email || 'No email'}</span>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div class="roles">
|
|
|
+ ${user.roles.map(role => `<span class="role-badge ${role.toLowerCase()}">${role}</span>`).join('')}
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <span class="status-badge ${user.status}">${user.status}</span>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <span class="last-login">${user.lastLogin ? formatDate(user.lastLogin) : 'Not available'}</span>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <div class="action-buttons">
|
|
|
+ <button class="action-btn small" onclick="viewUser('${user.id}')" title="View Details">
|
|
|
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
|
|
+ <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="action-btn small secondary" onclick="editUser('${user.id}')" title="Edit User">
|
|
|
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
|
|
+ <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `).join('');
|
|
|
+}
|
|
|
+
|
|
|
+// Update statistics
|
|
|
+function updateStats(users) {
|
|
|
+ const totalUsers = users.length;
|
|
|
+ const activeUsers = users.filter(u => u.status === 'active').length;
|
|
|
+ const adminUsers = users.filter(u => u.roles.includes('ADMIN')).length;
|
|
|
+
|
|
|
+ document.getElementById('totalUsers').textContent = totalUsers;
|
|
|
+ document.getElementById('activeUsers').textContent = activeUsers;
|
|
|
+ document.getElementById('adminUsers').textContent = adminUsers;
|
|
|
+}
|
|
|
+
|
|
|
+// Filter users based on search input
|
|
|
+function filterUsers() {
|
|
|
+ const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
|
+
|
|
|
+ filteredUsers = currentUsers.filter(user =>
|
|
|
+ user.username.toLowerCase().includes(searchTerm) ||
|
|
|
+ user.id.toLowerCase().includes(searchTerm) ||
|
|
|
+ (user.email && user.email.toLowerCase().includes(searchTerm)) ||
|
|
|
+ user.roles.some(role => role.toLowerCase().includes(searchTerm)) ||
|
|
|
+ user.status.toLowerCase().includes(searchTerm)
|
|
|
+ );
|
|
|
+
|
|
|
+ displayUsers(filteredUsers);
|
|
|
+}
|
|
|
+
|
|
|
+// User action functions
|
|
|
+function viewUser(userId) {
|
|
|
+ const user = currentUsers.find(u => u.id === userId);
|
|
|
+ if (user) {
|
|
|
+ const details = [
|
|
|
+ `ID: ${user.id}`,
|
|
|
+ `Username: ${user.username}`,
|
|
|
+ `Email: ${user.email || 'No email'}`,
|
|
|
+ `Roles: ${user.roles.join(', ')}`,
|
|
|
+ `Status: ${user.status}`,
|
|
|
+ `Last Login: ${user.lastLogin ? formatDate(user.lastLogin) : 'Not available'}`
|
|
|
+ ];
|
|
|
+ alert(`User Details:\n\n${details.join('\n')}`);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function editUser(userId) {
|
|
|
+ const user = currentUsers.find(u => u.id === userId);
|
|
|
+ if (user) {
|
|
|
+ openEditUserModal(user);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Edit User Modal Functions
|
|
|
+let currentEditUserId = null;
|
|
|
+
|
|
|
+function openEditUserModal(user) {
|
|
|
+ currentEditUserId = user.id;
|
|
|
+ const modal = document.getElementById('editUserModal');
|
|
|
+
|
|
|
+ // Populate form with user data
|
|
|
+ document.getElementById('editUserEmail').value = user.email || '';
|
|
|
+ document.getElementById('editUserActive').checked = user.status === 'active';
|
|
|
+
|
|
|
+ // Clear all role checkboxes first
|
|
|
+ document.querySelectorAll('input[name="role"]').forEach(checkbox => {
|
|
|
+ checkbox.checked = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Check the roles the user has
|
|
|
+ user.roles.forEach(role => {
|
|
|
+ const checkbox = document.querySelector(`input[name="role"][value="${role}"]`);
|
|
|
+ if (checkbox) {
|
|
|
+ checkbox.checked = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Clear any previous messages
|
|
|
+ clearEditUserMessage();
|
|
|
+
|
|
|
+ // Show modal
|
|
|
+ modal.style.display = 'flex';
|
|
|
+
|
|
|
+ // Focus on email input
|
|
|
+ setTimeout(() => {
|
|
|
+ document.getElementById('editUserEmail').focus();
|
|
|
+ }, 100);
|
|
|
+}
|
|
|
+
|
|
|
+function closeEditUserModal() {
|
|
|
+ const modal = document.getElementById('editUserModal');
|
|
|
+ modal.style.display = 'none';
|
|
|
+ currentEditUserId = null;
|
|
|
+
|
|
|
+ // Reset form
|
|
|
+ document.getElementById('editUserForm').reset();
|
|
|
+ clearEditUserMessage();
|
|
|
+}
|
|
|
+
|
|
|
+function clearEditUserMessage() {
|
|
|
+ const messageEl = document.getElementById('editUserMessage');
|
|
|
+ messageEl.style.display = 'none';
|
|
|
+ messageEl.textContent = '';
|
|
|
+ messageEl.className = 'modal-message';
|
|
|
+}
|
|
|
+
|
|
|
+function showEditUserMessage(message, type) {
|
|
|
+ const messageEl = document.getElementById('editUserMessage');
|
|
|
+ messageEl.textContent = message;
|
|
|
+ messageEl.className = `modal-message ${type}`;
|
|
|
+ messageEl.style.display = 'block';
|
|
|
+}
|
|
|
+
|
|
|
+async function handleEditUserSubmit(event) {
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+ if (!currentEditUserId) {
|
|
|
+ showEditUserMessage('Error: No user selected for editing', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const formData = new FormData(event.target);
|
|
|
+ const email = formData.get('email').trim();
|
|
|
+ const isActive = formData.has('isActive');
|
|
|
+
|
|
|
+ // Get selected roles
|
|
|
+ const selectedRoles = [];
|
|
|
+ document.querySelectorAll('input[name="role"]:checked').forEach(checkbox => {
|
|
|
+ selectedRoles.push(checkbox.value);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Validation
|
|
|
+ if (selectedRoles.length === 0) {
|
|
|
+ showEditUserMessage('Please select at least one role', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (email && !isValidEmail(email)) {
|
|
|
+ showEditUserMessage('Please enter a valid email address', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Prepare request data
|
|
|
+ const requestData = {
|
|
|
+ roles: selectedRoles,
|
|
|
+ email: email || null,
|
|
|
+ isActive: isActive
|
|
|
+ };
|
|
|
+
|
|
|
+ // Update UI
|
|
|
+ const saveBtn = document.getElementById('saveUserBtn');
|
|
|
+ const originalText = saveBtn.textContent;
|
|
|
+ saveBtn.textContent = 'Saving...';
|
|
|
+ saveBtn.disabled = true;
|
|
|
+ clearEditUserMessage();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const token = localStorage.getItem('authToken');
|
|
|
+ if (!token) {
|
|
|
+ throw new Error('No authentication token found');
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE_URL}/users/${currentEditUserId}`, {
|
|
|
+ method: 'PUT',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify(requestData)
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ if (response.status === 401 || response.status === 403) {
|
|
|
+ throw new Error('Access denied. Admin privileges required.');
|
|
|
+ }
|
|
|
+
|
|
|
+ let errorMessage;
|
|
|
+ try {
|
|
|
+ const errorData = await response.json();
|
|
|
+ errorMessage = errorData.message || `Update failed: ${response.status} ${response.statusText}`;
|
|
|
+ } catch (parseError) {
|
|
|
+ errorMessage = `Update failed: ${response.status} ${response.statusText}`;
|
|
|
+ }
|
|
|
+ throw new Error(errorMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ showEditUserMessage('User updated successfully!', 'success');
|
|
|
+
|
|
|
+ // Refresh the users list to show updated data
|
|
|
+ setTimeout(() => {
|
|
|
+ loadUsers();
|
|
|
+ closeEditUserModal();
|
|
|
+ }, 1500);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error updating user:', error);
|
|
|
+ showEditUserMessage(error.message, 'error');
|
|
|
+ } finally {
|
|
|
+ saveBtn.textContent = originalText;
|
|
|
+ saveBtn.disabled = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function isValidEmail(email) {
|
|
|
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
+ return emailRegex.test(email);
|
|
|
+}
|
|
|
+
|
|
|
+// Close modal when clicking outside of it
|
|
|
+document.addEventListener('click', function(event) {
|
|
|
+ const modal = document.getElementById('editUserModal');
|
|
|
+ if (event.target === modal) {
|
|
|
+ closeEditUserModal();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// Close modal with Escape key
|
|
|
+document.addEventListener('keydown', function(event) {
|
|
|
+ if (event.key === 'Escape') {
|
|
|
+ const modal = document.getElementById('editUserModal');
|
|
|
+ if (modal.style.display === 'flex') {
|
|
|
+ closeEditUserModal();
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// Utility functions
|
|
|
+function formatUserId(id) {
|
|
|
+ // Format UUID for display (show first 8 characters)
|
|
|
+ return id.substring(0, 8) + '...';
|
|
|
+}
|
|
|
+
|
|
|
+function formatDate(dateString) {
|
|
|
+ if (!dateString) return 'Never';
|
|
|
+ const date = new Date(dateString);
|
|
|
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
|
|
+}
|
|
|
+
|
|
|
+function showLoading(show) {
|
|
|
+ const indicator = document.getElementById('loadingIndicator');
|
|
|
+ indicator.style.display = show ? 'block' : 'none';
|
|
|
+}
|
|
|
+
|
|
|
+function showEmptyState() {
|
|
|
+ document.getElementById('emptyState').style.display = 'block';
|
|
|
+}
|
|
|
+
|
|
|
+function hideEmptyState() {
|
|
|
+ document.getElementById('emptyState').style.display = 'none';
|
|
|
+}
|
|
|
+
|
|
|
+function showMessage(message, type = 'info') {
|
|
|
+ const messageContainer = document.getElementById('messageContainer');
|
|
|
+ messageContainer.textContent = message;
|
|
|
+ messageContainer.className = `message ${type}`;
|
|
|
+ messageContainer.style.display = 'block';
|
|
|
+
|
|
|
+ // Auto-hide message after 3 seconds for success messages
|
|
|
+ if (type === 'success') {
|
|
|
+ setTimeout(() => {
|
|
|
+ messageContainer.style.display = 'none';
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Export functions for potential use in other scripts
|
|
|
+window.adminAPI = {
|
|
|
+ loadUsers,
|
|
|
+ filterUsers,
|
|
|
+ viewUser,
|
|
|
+ editUser,
|
|
|
+ toggleTheme
|
|
|
+};
|