Fix user management interface

- Remove 'user created successfully' alert after user creation
- Replace edit user placeholder alert with fully functional edit modal
- Add update_user() method to AuthManager with validation
- Add update_user() method to DashboardAPI class
- Add PUT /api/users/{user_id} route for user updates
- Edit modal includes form validation and error handling
- Pre-populates current user data in edit form
- Supports updating username, email, password (optional), and admin status
- Validates username/email uniqueness before saving
- Automatically refreshes user list after successful updates

User management interface is now fully functional with create, edit, and delete operations.
parent 7ab1fbf6
......@@ -334,6 +334,36 @@ class DashboardAPI:
logger.error(f"User creation error: {e}")
return {"error": str(e)}
def update_user(self, user_id: int, username: str = None, email: str = None,
password: str = None, is_admin: bool = None) -> Dict[str, Any]:
"""Update user (admin only)"""
try:
from .auth import AuthManager
# Get auth manager from Flask g context
auth_manager = g.get('auth_manager')
if not auth_manager:
return {"error": "Auth manager not available"}
user = auth_manager.update_user(user_id, username, email, password, is_admin)
if user:
return {
"success": True,
"user": {
"id": user["id"],
"username": user["username"],
"email": user["email"],
"is_admin": user["is_admin"]
}
}
else:
return {"error": "Failed to update user"}
except Exception as e:
logger.error(f"User update error: {e}")
return {"error": str(e)}
def delete_user(self, user_id: int) -> Dict[str, Any]:
"""Delete user (admin only)"""
try:
......
......@@ -144,6 +144,56 @@ class AuthManager:
logger.error(f"User creation error: {e}")
return None
def update_user(self, user_id: int, username: str = None, email: str = None,
password: str = None, is_admin: bool = None) -> Optional[Dict[str, Any]]:
"""Update user information"""
try:
user = self.db_manager.get_user_by_id(user_id)
if not user:
logger.warning(f"User update failed: user not found - {user_id}")
return None
# Check for username conflicts if changing username
if username and username != user.username:
existing_user = self.db_manager.get_user_by_username(username)
if existing_user and existing_user.id != user_id:
logger.warning(f"User update failed: username already exists - {username}")
return None
user.username = username
# Check for email conflicts if changing email
if email and email != user.email:
existing_email = self.db_manager.get_user_by_email(email)
if existing_email and existing_email.id != user_id:
logger.warning(f"User update failed: email already exists - {email}")
return None
user.email = email
# Update password if provided
if password:
user.password_hash = self.hash_password(password)
# Update admin status if provided
if is_admin is not None:
user.is_admin = is_admin
# Update timestamp
user.updated_at = datetime.utcnow()
# Save user
updated_user_data = self.db_manager.save_user(user)
if updated_user_data:
logger.info(f"User updated successfully: {user.username}")
return updated_user_data
else:
logger.error(f"Failed to save updated user: {user_id}")
return None
except Exception as e:
logger.error(f"User update error: {e}")
return None
def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
"""Change user password"""
try:
......
......@@ -428,6 +428,26 @@ def create_user():
return jsonify({"error": str(e)}), 500
@api_bp.route('/users/<int:user_id>', methods=['PUT'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
@api_bp.auth_manager.require_admin if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def update_user(user_id):
"""Update user"""
try:
data = request.get_json() or {}
username = data.get('username')
email = data.get('email')
password = data.get('password')
is_admin = data.get('is_admin')
result = api_bp.api.update_user(user_id, username, email, password, is_admin)
return jsonify(result)
except Exception as e:
logger.error(f"API update user error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/users/<int:user_id>', methods=['DELETE'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
@api_bp.auth_manager.require_admin if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
......
......@@ -134,7 +134,119 @@
// Edit user
function editUser(userId) {
alert(`Edit user ${userId} - This would open the user editor`);
// Get user data first
const userRow = document.querySelector(`[data-id="${userId}"]`).closest('tr');
const cells = userRow.querySelectorAll('td');
const currentUsername = cells[0].textContent;
const currentEmail = cells[1].textContent;
const currentIsAdmin = cells[2].textContent === 'Administrator';
// Create edit modal dynamically
const editModal = `
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editUserModalLabel">Edit User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="edit-user-form">
<div class="mb-3">
<label for="edit-username" class="form-label">Username</label>
<input type="text" class="form-control" id="edit-username" value="${currentUsername}" required>
</div>
<div class="mb-3">
<label for="edit-email" class="form-label">Email</label>
<input type="email" class="form-control" id="edit-email" value="${currentEmail}" required>
</div>
<div class="mb-3">
<label for="edit-password" class="form-label">New Password (leave empty to keep current)</label>
<input type="password" class="form-control" id="edit-password">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="edit-is-admin" ${currentIsAdmin ? 'checked' : ''}>
<label class="form-check-label" for="edit-is-admin">
Administrator
</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save-user-btn">Save Changes</button>
</div>
</div>
</div>
</div>
`;
// Remove existing edit modal if present
const existingModal = document.getElementById('editUserModal');
if (existingModal) {
existingModal.remove();
}
// Add modal to page
document.body.insertAdjacentHTML('beforeend', editModal);
// Show modal
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show();
// Add save button event listener
document.getElementById('save-user-btn').addEventListener('click', function() {
const username = document.getElementById('edit-username').value;
const email = document.getElementById('edit-email').value;
const password = document.getElementById('edit-password').value;
const isAdmin = document.getElementById('edit-is-admin').checked;
if (!username || !email) {
alert('Username and email are required');
return;
}
const updateData = {
username: username,
email: email,
is_admin: isAdmin
};
// Only include password if it's provided
if (password) {
updateData.password = password;
}
// Update user via API
fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Close modal
modal.hide();
// Refresh user list
loadUsers();
} else {
alert('Failed to update user: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error updating user:', error);
alert('Error updating user: ' + error.message);
});
});
// Clean up modal when closed
document.getElementById('editUserModal').addEventListener('hidden.bs.modal', function () {
this.remove();
});
}
// Delete user
......@@ -193,8 +305,6 @@
.then(response => response.json())
.then(data => {
if (data.success) {
alert('User created successfully');
// Reset form
document.getElementById('create-user-form').reset();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment