From f5d4c46c370e5b62a4e83b3044f05ac62e061951 Mon Sep 17 00:00:00 2001 From: Master3395 Date: Sun, 21 Sep 2025 22:06:51 +0200 Subject: [PATCH] Add fetchAPIUsers endpoint and corresponding UI: Implement a new API endpoint to fetch users with API access, including search functionality. Update JavaScript to handle user data loading and searching, and enhance the API access HTML template with a new tab for managing API users. Improve styling and user interaction elements for better usability. --- .../static/userManagment/userManagment.js | 141 +++++++ .../templates/userManagment/apiAccess.html | 366 +++++++++++++++++- userManagment/urls.py | 1 + userManagment/views.py | 73 ++++ 4 files changed, 578 insertions(+), 3 deletions(-) diff --git a/userManagment/static/userManagment/userManagment.js b/userManagment/static/userManagment/userManagment.js index 22b2f7f2c..f898b411e 100644 --- a/userManagment/static/userManagment/userManagment.js +++ b/userManagment/static/userManagment/userManagment.js @@ -1659,6 +1659,147 @@ app.controller('apiAccessCTRL', function ($scope, $http) { }); /* Java script code for api access */ +/* Java script code for api users list */ +app.controller('apiUsersCTRL', function ($scope, $http) { + $scope.apiUsers = []; + $scope.filteredUsers = []; + $scope.searchQuery = ''; + $scope.apiUsersLoading = true; + + $scope.loadAPIUsers = function() { + $scope.apiUsersLoading = false; + + var url = "/users/fetchAPIUsers"; + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.get(url, config).then(loadAPIUsersSuccess, loadAPIUsersError); + }; + + function loadAPIUsersSuccess(response) { + $scope.apiUsersLoading = true; + + if (response.data.status === 1) { + $scope.apiUsers = response.data.users; + $scope.filteredUsers = response.data.users; + + new PNotify({ + title: 'Success!', + text: 'API users loaded successfully', + type: 'success' + }); + } else { + new PNotify({ + title: 'Error!', + text: response.data.error_message, + type: 'error' + }); + } + } + + function loadAPIUsersError(response) { + $scope.apiUsersLoading = true; + new PNotify({ + title: 'Error!', + text: 'Could not load API users. Please refresh the page.', + type: 'error' + }); + } + + $scope.searchUsers = function() { + if (!$scope.searchQuery || $scope.searchQuery.trim() === '') { + $scope.filteredUsers = $scope.apiUsers; + return; + } + + var query = $scope.searchQuery.toLowerCase(); + $scope.filteredUsers = $scope.apiUsers.filter(function(user) { + return user.userName.toLowerCase().includes(query) || + user.firstName.toLowerCase().includes(query) || + user.lastName.toLowerCase().includes(query) || + user.email.toLowerCase().includes(query) || + user.aclName.toLowerCase().includes(query); + }); + }; + + $scope.clearSearch = function() { + $scope.searchQuery = ''; + $scope.filteredUsers = $scope.apiUsers; + }; + + $scope.viewUserDetails = function(user) { + new PNotify({ + title: 'User Details', + text: 'Username: ' + user.userName + '
' + + 'Full Name: ' + user.firstName + ' ' + user.lastName + '
' + + 'Email: ' + user.email + '
' + + 'ACL: ' + user.aclName + '
' + + 'Token Status: ' + user.tokenStatus + '
' + + 'State: ' + user.state, + type: 'info', + styling: 'bootstrap3', + delay: 10000 + }); + }; + + $scope.disableAPI = function(user) { + if (confirm('Are you sure you want to disable API access for ' + user.userName + '?')) { + $scope.apiUsersLoading = false; + + var url = "/users/saveChangesAPIAccess"; + var data = { + accountUsername: user.userName, + access: 'Disable' + }; + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post(url, data, config).then(disableAPISuccess, disableAPIError); + } + }; + + function disableAPISuccess(response) { + $scope.apiUsersLoading = true; + + if (response.data.status === 1) { + // Remove user from the list + $scope.apiUsers = $scope.apiUsers.filter(function(u) { + return u.userName !== response.data.accountUsername; + }); + $scope.filteredUsers = $scope.apiUsers; + + new PNotify({ + title: 'Success!', + text: 'API access disabled for ' + response.data.accountUsername, + type: 'success' + }); + } else { + new PNotify({ + title: 'Error!', + text: response.data.error_message, + type: 'error' + }); + } + } + + function disableAPIError(response) { + $scope.apiUsersLoading = true; + new PNotify({ + title: 'Error!', + text: 'Could not disable API access. Please try again.', + type: 'error' + }); + } + + // Load API users when controller initializes + $scope.loadAPIUsers(); +}); /* Java script code to list table users */ diff --git a/userManagment/templates/userManagment/apiAccess.html b/userManagment/templates/userManagment/apiAccess.html index 30f4d80b8..8dd4baac8 100644 --- a/userManagment/templates/userManagment/apiAccess.html +++ b/userManagment/templates/userManagment/apiAccess.html @@ -166,6 +166,220 @@ .text-muted { color: var(--text-secondary, #8893a7); } + /* Tab Navigation Styles */ + .tab-navigation { + display: flex; + margin-bottom: 20px; + border-bottom: 2px solid var(--border-color, #e8e9ff); + } + .tab-button { + background: none; + border: none; + padding: 15px 25px; + font-size: 16px; + font-weight: 600; + color: var(--text-secondary, #8893a7); + cursor: pointer; + border-bottom: 3px solid transparent; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; + } + .tab-button:hover { + color: var(--accent-color, #5b5fcf); + background: var(--bg-hover, #f8f9ff); + } + .tab-button.active { + color: var(--accent-color, #5b5fcf); + border-bottom-color: var(--accent-color, #5b5fcf); + background: var(--bg-hover, #f8f9ff); + } + .tab-content { + display: none; + } + .tab-content.active { + display: block; + } + + /* Search Container Styles */ + .search-container { + margin-bottom: 25px; + } + .search-box { + position: relative; + max-width: 400px; + } + .search-box i { + position: absolute; + left: 15px; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary, #8893a7); + } + .search-input { + width: 100%; + padding: 12px 45px 12px 45px; + border: 1px solid var(--border-color, #e8e9ff); + border-radius: 8px; + font-size: 16px; + background: var(--bg-secondary, white); + color: var(--text-primary, #2f3640); + transition: all 0.3s ease; + } + .search-input:focus { + border-color: var(--accent-color, #5b5fcf); + box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1); + outline: none; + } + .clear-search { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-secondary, #8893a7); + cursor: pointer; + padding: 5px; + border-radius: 50%; + transition: all 0.3s ease; + } + .clear-search:hover { + background: var(--bg-hover, #f8f9ff); + color: var(--accent-color, #5b5fcf); + } + .search-results-info { + margin-top: 10px; + color: var(--text-secondary, #8893a7); + font-size: 14px; + } + + /* Users Table Styles */ + .users-table-container { + overflow-x: auto; + } + .users-table { + width: 100%; + border-collapse: collapse; + background: var(--bg-secondary, white); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px var(--shadow-color, rgba(0,0,0,0.08)); + } + .users-table th { + background: var(--bg-hover, #f8f9ff); + color: var(--text-primary, #2f3640); + font-weight: 600; + padding: 15px 12px; + text-align: left; + border-bottom: 2px solid var(--border-color, #e8e9ff); + } + .users-table td { + padding: 15px 12px; + border-bottom: 1px solid var(--border-color, #e8e9ff); + vertical-align: middle; + } + .users-table tbody tr:hover { + background: var(--bg-hover, #f8f9ff); + } + + /* Badge Styles */ + .acl-badge { + background: var(--accent-color, #5b5fcf); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + } + + /* Token Status Styles */ + .token-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 500; + } + .token-valid { + color: var(--success-text, #10b981); + } + .token-warning { + color: var(--warning-text, #f59e0b); + } + .token-error { + color: var(--danger-text, #ef4444); + } + .token-status i { + font-size: 8px; + } + + /* User State Styles */ + .user-state { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + } + .state-active { + background: var(--success-bg, #f0fdf4); + color: var(--success-text, #166534); + } + .state-inactive { + background: var(--danger-bg, #fef2f2); + color: var(--danger-text, #991b1b); + } + + /* Action Buttons */ + .action-buttons { + display: flex; + gap: 8px; + } + .btn-action { + background: none; + border: 1px solid var(--border-color, #e8e9ff); + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + color: var(--text-secondary, #8893a7); + } + .btn-action:hover { + background: var(--bg-hover, #f8f9ff); + border-color: var(--accent-color, #5b5fcf); + color: var(--accent-color, #5b5fcf); + } + .btn-view:hover { + color: var(--info-text, #3b82f6); + border-color: var(--info-text, #3b82f6); + } + .btn-disable:hover { + color: var(--danger-text, #ef4444); + border-color: var(--danger-text, #ef4444); + } + + /* Empty State */ + .empty-state { + text-align: center; + padding: 60px 20px; + color: var(--text-secondary, #8893a7); + } + .empty-state i { + font-size: 48px; + margin-bottom: 20px; + color: var(--text-secondary, #8893a7); + } + .empty-state h3 { + margin-bottom: 10px; + color: var(--text-primary, #2f3640); + } + .empty-state p { + margin: 0; + line-height: 1.5; + } + @media (max-width: 768px) { .content-card { padding: 20px; @@ -176,6 +390,17 @@ .section-title { font-size: 1.1rem; } + .tab-button { + padding: 12px 15px; + font-size: 14px; + } + .users-table { + font-size: 14px; + } + .users-table th, + .users-table td { + padding: 10px 8px; + } } @@ -185,10 +410,22 @@ -
+ + +
+ + +
+ + +

{% trans "Configure API Access" %} - + Loading configuration

{% trans "Important Information" %}

@@ -197,7 +434,7 @@
- {% for items in acctNames %} @@ -241,7 +478,130 @@
+ + +
+

+ {% trans "Users with API Access" %} + Loading API users +

+ + +
+ +
+ {{ filteredUsers.length }} {% trans "users found" %} +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Username" %}{% trans "Full Name" %}{% trans "Email" %}{% trans "ACL" %}{% trans "Token Status" %}{% trans "State" %}{% trans "Actions" %}
+ {{ user.userName }} + {{ user.firstName }} {{ user.lastName }}{{ user.email }} + {{ user.aclName }} + + + {{ user.tokenStatus }} + + + + {{ user.state }} + + +
+ + +
+
+ + +
+ +

{% trans "No users with API access found" %}

+

{% trans "No users currently have API access enabled. Use the Configure tab to enable API access for users." %}

+
+ + +
+ +

{% trans "No users found" %}

+

{% trans "No users match your search criteria. Try adjusting your search terms." %}

+
+
+
+ + {% endblock %} \ No newline at end of file diff --git a/userManagment/urls.py b/userManagment/urls.py index fa14ccbcf..6f2660a9d 100644 --- a/userManagment/urls.py +++ b/userManagment/urls.py @@ -25,6 +25,7 @@ urlpatterns = [ path('saveResellerChanges', views.saveResellerChanges, name='saveResellerChanges'), path('apiAccess', views.apiAccess, name='apiAccess'), path('saveChangesAPIAccess', views.saveChangesAPIAccess, name='saveChangesAPIAccess'), + path('fetchAPIUsers', views.fetchAPIUsers, name='fetchAPIUsers'), path('listUsers', views.listUsers, name='listUsers'), path('fetchTableUsers', views.fetchTableUsers, name='fetchTableUsers'), path('controlUserState', views.controlUserState, name='controlUserState'), diff --git a/userManagment/views.py b/userManagment/views.py index a5d26ee6c..3ff4ba6e0 100644 --- a/userManagment/views.py +++ b/userManagment/views.py @@ -3,6 +3,7 @@ from django.shortcuts import render, redirect from django.http import HttpResponse +from django.db import models from loginSystem.views import loadLoginPage from loginSystem.models import Administrator, ACL import json @@ -122,6 +123,78 @@ def saveChangesAPIAccess(request): return HttpResponse(json_data) +def fetchAPIUsers(request): + """ + Fetch all users with API access enabled, with optional search functionality + """ + try: + userID = request.session['userID'] + currentACL = ACLManager.loadedACL(userID) + + if currentACL['admin'] != 1: + finalResponse = {'status': 0, "error_message": "Only administrators are allowed to perform this task."} + json_data = json.dumps(finalResponse) + return HttpResponse(json_data) + + # Get search query if provided + search_query = request.GET.get('search', '').strip() + + # Fetch all users with API access enabled + api_users = Administrator.objects.filter(api=1).select_related('acl') + + # Apply search filter if provided + if search_query: + api_users = api_users.filter( + models.Q(userName__icontains=search_query) | + models.Q(firstName__icontains=search_query) | + models.Q(lastName__icontains=search_query) | + models.Q(email__icontains=search_query) + ) + + # Prepare user data + users_data = [] + for user in api_users: + # Determine token status + token_status = "Valid" + if not user.token or user.token == 'None' or user.token == '': + token_status = "Not Generated" + elif user.token == 'TOKEN_NEEDS_GENERATION': + token_status = "Needs Generation" + + # Get ACL name + acl_name = user.acl.name if user.acl else "Default" + + users_data.append({ + 'id': user.pk, + 'userName': user.userName, + 'firstName': user.firstName, + 'lastName': user.lastName, + 'email': user.email, + 'aclName': acl_name, + 'tokenStatus': token_status, + 'state': user.state, + 'createdDate': user.pk, # Using pk as a proxy for creation order + 'lastLogin': 'N/A' # This would need to be tracked separately + }) + + # Sort by username + users_data.sort(key=lambda x: x['userName'].lower()) + + finalResponse = { + 'status': 1, + 'users': users_data, + 'totalCount': len(users_data) + } + json_data = json.dumps(finalResponse) + return HttpResponse(json_data) + + except Exception as e: + secure_log_error(e, 'fetchAPIUsers', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to fetch API users') + json_data = json.dumps(finalResponse) + return HttpResponse(json_data) + + def submitUserCreation(request): try: