You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							691 lines
						
					
					
						
							34 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							691 lines
						
					
					
						
							34 KiB
						
					
					
				| package app | |
| 
 | |
| import ( | |
|     "fmt" | |
|     "github.com/seaweedfs/seaweedfs/weed/admin/dash" | |
| ) | |
| 
 | |
| templ ObjectStoreUsers(data dash.ObjectStoreUsersData) { | |
|     <div class="container-fluid"> | |
|         <!-- Page Header --> | |
|         <div class="d-sm-flex align-items-center justify-content-between mb-4"> | |
|             <div> | |
|                 <h1 class="h3 mb-0 text-gray-800"> | |
|                     <i class="fas fa-users me-2"></i>Object Store Users | |
|                 </h1> | |
|                 <p class="mb-0 text-muted">Manage S3 API users and their access credentials</p> | |
|             </div> | |
|             <div class="d-flex gap-2"> | |
|                 <button type="button" class="btn btn-primary"  | |
|                         data-bs-toggle="modal"  | |
|                         data-bs-target="#createUserModal"> | |
|                     <i class="fas fa-plus me-1"></i>Create User | |
|                 </button> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Summary Cards --> | |
|         <div class="row mb-4"> | |
|             <div class="col-xl-3 col-md-6 mb-4"> | |
|                 <div class="card border-left-primary shadow h-100 py-2"> | |
|                     <div class="card-body"> | |
|                         <div class="row no-gutters align-items-center"> | |
|                             <div class="col mr-2"> | |
|                                 <div class="text-xs font-weight-bold text-primary text-uppercase mb-1"> | |
|                                     Total Users | |
|                                 </div> | |
|                                 <div class="h5 mb-0 font-weight-bold text-gray-800"> | |
|                                     {fmt.Sprintf("%d", data.TotalUsers)} | |
|                                 </div> | |
|                             </div> | |
|                             <div class="col-auto"> | |
|                                 <i class="fas fa-users fa-2x text-gray-300"></i> | |
|                             </div> | |
|                         </div> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
| 
 | |
|             <div class="col-xl-3 col-md-6 mb-4"> | |
|                 <div class="card border-left-success shadow h-100 py-2"> | |
|                     <div class="card-body"> | |
|                         <div class="row no-gutters align-items-center"> | |
|                             <div class="col mr-2"> | |
|                                 <div class="text-xs font-weight-bold text-success text-uppercase mb-1"> | |
|                                     Total Users | |
|                                 </div> | |
|                                 <div class="h5 mb-0 font-weight-bold text-gray-800"> | |
|                                     {fmt.Sprintf("%d", len(data.Users))} | |
|                                 </div> | |
|                             </div> | |
|                             <div class="col-auto"> | |
|                                 <i class="fas fa-user-check fa-2x text-gray-300"></i> | |
|                             </div> | |
|                         </div> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
| 
 | |
|             <div class="col-xl-3 col-md-6 mb-4"> | |
|                 <div class="card border-left-info shadow h-100 py-2"> | |
|                     <div class="card-body"> | |
|                         <div class="row no-gutters align-items-center"> | |
|                             <div class="col mr-2"> | |
|                                 <div class="text-xs font-weight-bold text-info text-uppercase mb-1"> | |
|                                     Last Updated | |
|                                 </div> | |
|                                 <div class="h6 mb-0 font-weight-bold text-gray-800"> | |
|                                     {data.LastUpdated.Format("15:04")} | |
|                                 </div> | |
|                             </div> | |
|                             <div class="col-auto"> | |
|                                 <i class="fas fa-clock fa-2x text-gray-300"></i> | |
|                             </div> | |
|                         </div> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Users Table --> | |
|         <div class="row"> | |
|             <div class="col-12"> | |
|                 <div class="card shadow mb-4"> | |
|                     <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between"> | |
|                         <h6 class="m-0 font-weight-bold text-primary"> | |
|                             <i class="fas fa-users me-2"></i>Object Store Users | |
|                         </h6> | |
|                         <div class="dropdown no-arrow"> | |
|                             <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> | |
|                                 <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i> | |
|                             </a> | |
|                             <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in"> | |
|                                 <div class="dropdown-header">Actions:</div> | |
|                                 <a class="dropdown-item" href="#" onclick="exportUsers()"> | |
|                                     <i class="fas fa-download me-2"></i>Export List | |
|                                 </a> | |
|                             </div> | |
|                         </div> | |
|                     </div> | |
|                     <div class="card-body"> | |
|                         <div class="table-responsive"> | |
|                             <table class="table table-hover" width="100%" cellspacing="0" id="usersTable"> | |
|                                 <thead> | |
|                                     <tr> | |
|                                         <th>Username</th> | |
|                                         <th>Email</th> | |
|                                         <th>Access Key</th> | |
|                                         <th>Actions</th> | |
|                                     </tr> | |
|                                 </thead> | |
|                                 <tbody> | |
|                                     for _, user := range data.Users { | |
|                                         <tr> | |
|                                             <td> | |
|                                                 <div class="d-flex align-items-center"> | |
|                                                     <i class="fas fa-user me-2 text-muted"></i> | |
|                                                     <strong>{user.Username}</strong> | |
|                                                 </div> | |
|                                             </td> | |
|                                             <td>{user.Email}</td> | |
|                                             <td> | |
|                                                 <code class="text-muted">{user.AccessKey}</code> | |
|                                             </td> | |
|                                             <td> | |
|                                                 <div class="btn-group btn-group-sm" role="group"> | |
|                                                     <button type="button" class="btn btn-outline-info"  | |
|                                                             data-action="show-user-details" data-username={ user.Username }> | |
|                                                         <i class="fas fa-info-circle"></i> | |
|                                                     </button> | |
|                                                     <button type="button" class="btn btn-outline-primary"  | |
|                                                             data-action="edit-user" data-username={ user.Username }> | |
|                                                         <i class="fas fa-edit"></i> | |
|                                                     </button> | |
|                                                     <button type="button" class="btn btn-outline-secondary"  | |
|                                                             data-action="manage-access-keys" data-username={ user.Username }> | |
|                                                         <i class="fas fa-key"></i> | |
|                                                     </button> | |
|                                                     <button type="button" class="btn btn-outline-danger"  | |
|                                                             data-action="delete-user" data-username={ user.Username }> | |
|                                                         <i class="fas fa-trash"></i> | |
|                                                     </button> | |
|                                                 </div> | |
|                                             </td> | |
|                                         </tr> | |
|                                     } | |
|                                     if len(data.Users) == 0 { | |
|                                         <tr> | |
|                                             <td colspan="4" class="text-center text-muted py-4"> | |
|                                                 <i class="fas fa-users fa-3x mb-3 text-muted"></i> | |
|                                                 <div> | |
|                                                     <h5>No users found</h5> | |
|                                                     <p>Create your first object store user to get started.</p> | |
|                                                 </div> | |
|                                             </td> | |
|                                         </tr> | |
|                                     } | |
|                                 </tbody> | |
|                             </table> | |
|                         </div> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Last Updated --> | |
|         <div class="row"> | |
|             <div class="col-12"> | |
|                 <small class="text-muted"> | |
|                     <i class="fas fa-clock me-1"></i> | |
|                     Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")} | |
|                 </small> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <!-- Create User Modal --> | |
|     <div class="modal fade" id="createUserModal" tabindex="-1" role="dialog"> | |
|         <div class="modal-dialog" role="document"> | |
|             <div class="modal-content"> | |
|                 <div class="modal-header"> | |
|                     <h5 class="modal-title"> | |
|                         <i class="fas fa-user-plus me-2"></i>Create New User | |
|                     </h5> | |
|                     <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
|                 </div> | |
|                 <div class="modal-body"> | |
|                     <form id="createUserForm"> | |
|                         <div class="mb-3"> | |
|                             <label for="username" class="form-label">Username *</label> | |
|                             <input type="text" class="form-control" id="username" name="username" required> | |
|                         </div> | |
|                         <div class="mb-3"> | |
|                             <label for="email" class="form-label">Email</label> | |
|                             <input type="email" class="form-control" id="email" name="email"> | |
|                         </div> | |
|                         <div class="mb-3"> | |
|                             <label for="actions" class="form-label">Permissions</label> | |
|                             <select multiple class="form-control" id="actions" name="actions" size="10"> | |
|                                 <option value="Admin">Admin (Full Access)</option> | |
|                                 <option value="Read">Read</option> | |
|                                 <option value="Write">Write</option> | |
|                                 <option value="List">List</option> | |
|                                 <option value="Tagging">Tagging</option> | |
|                                 <optgroup label="Object Lock Permissions"> | |
|                                     <option value="BypassGovernanceRetention">Bypass Governance Retention</option> | |
|                                     <option value="GetObjectRetention">Get Object Retention</option> | |
|                                     <option value="PutObjectRetention">Put Object Retention</option> | |
|                                     <option value="GetObjectLegalHold">Get Object Legal Hold</option> | |
|                                     <option value="PutObjectLegalHold">Put Object Legal Hold</option> | |
|                                     <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option> | |
|                                     <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option> | |
|                                 </optgroup> | |
|                             </select> | |
|                             <small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small> | |
|                         </div> | |
|                         <div class="mb-3 form-check"> | |
|                             <input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked> | |
|                             <label class="form-check-label" for="generateKey"> | |
|                                 Generate access key automatically | |
|                             </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" onclick="handleCreateUser()">Create User</button> | |
|                 </div> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <!-- Edit User Modal --> | |
|     <div class="modal fade" id="editUserModal" tabindex="-1" role="dialog"> | |
|         <div class="modal-dialog" role="document"> | |
|             <div class="modal-content"> | |
|                 <div class="modal-header"> | |
|                     <h5 class="modal-title"> | |
|                         <i class="fas fa-user-edit me-2"></i>Edit User | |
|                     </h5> | |
|                     <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
|                 </div> | |
|                 <div class="modal-body"> | |
|                     <form id="editUserForm"> | |
|                         <input type="hidden" id="editUsername" name="username"> | |
|                         <div class="mb-3"> | |
|                             <label for="editEmail" class="form-label">Email</label> | |
|                             <input type="email" class="form-control" id="editEmail" name="email"> | |
|                         </div> | |
|                         <div class="mb-3"> | |
|                             <label for="editActions" class="form-label">Permissions</label> | |
|                             <select multiple class="form-control" id="editActions" name="actions" size="10"> | |
|                                 <option value="Admin">Admin (Full Access)</option> | |
|                                 <option value="Read">Read</option> | |
|                                 <option value="Write">Write</option> | |
|                                 <option value="List">List</option> | |
|                                 <option value="Tagging">Tagging</option> | |
|                                 <optgroup label="Object Lock Permissions"> | |
|                                     <option value="BypassGovernanceRetention">Bypass Governance Retention</option> | |
|                                     <option value="GetObjectRetention">Get Object Retention</option> | |
|                                     <option value="PutObjectRetention">Put Object Retention</option> | |
|                                     <option value="GetObjectLegalHold">Get Object Legal Hold</option> | |
|                                     <option value="PutObjectLegalHold">Put Object Legal Hold</option> | |
|                                     <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option> | |
|                                     <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option> | |
|                                 </optgroup> | |
|                             </select> | |
|                         </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" onclick="handleUpdateUser()">Update User</button> | |
|                 </div> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <!-- User Details Modal --> | |
|     <div class="modal fade" id="userDetailsModal" tabindex="-1" role="dialog"> | |
|         <div class="modal-dialog modal-lg" role="document"> | |
|             <div class="modal-content"> | |
|                 <div class="modal-header"> | |
|                     <h5 class="modal-title"> | |
|                         <i class="fas fa-user me-2"></i>User Details | |
|                     </h5> | |
|                     <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
|                 </div> | |
|                 <div class="modal-body" id="userDetailsContent"> | |
|                     <!-- Content will be loaded dynamically --> | |
|                 </div> | |
|                 <div class="modal-footer"> | |
|                     <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
|                 </div> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <!-- Access Keys Management Modal --> | |
|     <div class="modal fade" id="accessKeysModal" tabindex="-1" role="dialog"> | |
|         <div class="modal-dialog modal-lg" role="document"> | |
|             <div class="modal-content"> | |
|                 <div class="modal-header"> | |
|                     <h5 class="modal-title"> | |
|                         <i class="fas fa-key me-2"></i>Manage Access Keys | |
|                     </h5> | |
|                     <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
|                 </div> | |
|                 <div class="modal-body"> | |
|                     <div class="d-flex justify-content-between align-items-center mb-3"> | |
|                         <h6>Access Keys for <span id="accessKeysUsername"></span></h6> | |
|                         <button type="button" class="btn btn-primary btn-sm" onclick="createAccessKey()"> | |
|                             <i class="fas fa-plus me-1"></i>Create New Key | |
|                         </button> | |
|                     </div> | |
|                     <div id="accessKeysContent"> | |
|                         <!-- Content will be loaded dynamically --> | |
|                     </div> | |
|                 </div> | |
|                 <div class="modal-footer"> | |
|                     <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
|                 </div> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <!-- JavaScript for user management --> | |
|     <script> | |
|         document.addEventListener('DOMContentLoaded', function() { | |
|             // Event delegation for user action buttons | |
|             document.addEventListener('click', function(e) { | |
|                 const button = e.target.closest('[data-action]'); | |
|                 if (!button) return; | |
|                  | |
|                 const action = button.getAttribute('data-action'); | |
|                 const username = button.getAttribute('data-username'); | |
|                  | |
|                 switch (action) { | |
|                     case 'show-user-details': | |
|                         showUserDetails(username); | |
|                         break; | |
|                     case 'edit-user': | |
|                         editUser(username); | |
|                         break; | |
|                     case 'manage-access-keys': | |
|                         manageAccessKeys(username); | |
|                         break; | |
|                     case 'delete-user': | |
|                         deleteUser(username); | |
|                         break; | |
|                 } | |
|             }); | |
|         }); | |
| 
 | |
|         // Show user details modal | |
|         async function showUserDetails(username) { | |
|             try { | |
|                 const response = await fetch(`/api/users/${username}`); | |
|                 if (response.ok) { | |
|                     const user = await response.json(); | |
|                     document.getElementById('userDetailsContent').innerHTML = createUserDetailsContent(user); | |
|                     const modal = new bootstrap.Modal(document.getElementById('userDetailsModal')); | |
|                     modal.show(); | |
|                 } else { | |
|                     showErrorMessage('Failed to load user details'); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error loading user details:', error); | |
|                 showErrorMessage('Failed to load user details'); | |
|             } | |
|         } | |
| 
 | |
|         // Edit user function | |
|         async function editUser(username) { | |
|             try { | |
|                 const response = await fetch(`/api/users/${username}`); | |
|                 if (response.ok) { | |
|                     const user = await response.json(); | |
|                      | |
|                     // Populate edit form | |
|                     document.getElementById('editUsername').value = username; | |
|                     document.getElementById('editEmail').value = user.email || ''; | |
|                      | |
|                     // Set selected actions | |
|                     const actionsSelect = document.getElementById('editActions'); | |
|                     Array.from(actionsSelect.options).forEach(option => { | |
|                         option.selected = user.actions && user.actions.includes(option.value); | |
|                     }); | |
|                      | |
|                     // Show modal | |
|                     const modal = new bootstrap.Modal(document.getElementById('editUserModal')); | |
|                     modal.show(); | |
|                 } else { | |
|                     showErrorMessage('Failed to load user details'); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error loading user:', error); | |
|                 showErrorMessage('Failed to load user details'); | |
|             } | |
|         } | |
| 
 | |
|         // Manage access keys function | |
|         async function manageAccessKeys(username) { | |
|             try { | |
|                 const response = await fetch(`/api/users/${username}`); | |
|                 if (response.ok) { | |
|                     const user = await response.json(); | |
|                     document.getElementById('accessKeysUsername').textContent = username; | |
|                     document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user); | |
|                     const modal = new bootstrap.Modal(document.getElementById('accessKeysModal')); | |
|                     modal.show(); | |
|                 } else { | |
|                     showErrorMessage('Failed to load access keys'); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error loading access keys:', error); | |
|                 showErrorMessage('Failed to load access keys'); | |
|             } | |
|         } | |
| 
 | |
|         // Delete user function | |
|         async function deleteUser(username) { | |
|             if (confirm(`Are you sure you want to delete user "${username}"? This action cannot be undone.`)) { | |
|                 try { | |
|                     const response = await fetch(`/api/users/${username}`, { | |
|                         method: 'DELETE' | |
|                     }); | |
|                      | |
|                     if (response.ok) { | |
|                         showSuccessMessage('User deleted successfully'); | |
|                         setTimeout(() => window.location.reload(), 1000); | |
|                     } else { | |
|                         const error = await response.json(); | |
|                         showErrorMessage('Failed to delete user: ' + (error.error || 'Unknown error')); | |
|                     } | |
|                 } catch (error) { | |
|                     console.error('Error deleting user:', error); | |
|                     showErrorMessage('Failed to delete user: ' + error.message); | |
|                 } | |
|             } | |
|         } | |
| 
 | |
|         // Handle create user form submission | |
|         async function handleCreateUser() { | |
|             const form = document.getElementById('createUserForm'); | |
|             const formData = new FormData(form); | |
|              | |
|             const userData = { | |
|                 username: formData.get('username'), | |
|                 email: formData.get('email'), | |
|                 actions: Array.from(document.getElementById('actions').selectedOptions).map(option => option.value), | |
|                 generate_key: document.getElementById('generateKey').checked | |
|             }; | |
|              | |
|             try { | |
|                 const response = await fetch('/api/users', { | |
|                     method: 'POST', | |
|                     headers: { | |
|                         'Content-Type': 'application/json', | |
|                     }, | |
|                     body: JSON.stringify(userData) | |
|                 }); | |
|                  | |
|                 if (response.ok) { | |
|                     const result = await response.json(); | |
|                     showSuccessMessage('User created successfully'); | |
|                      | |
|                     // Show the created access key if generated | |
|                     if (result.user && result.user.access_key) { | |
|                         showNewAccessKeyModal(result.user); | |
|                     } | |
|                      | |
|                     // Close modal and refresh page | |
|                     const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal')); | |
|                     modal.hide(); | |
|                     form.reset(); | |
|                     setTimeout(() => window.location.reload(), 1000); | |
|                 } else { | |
|                     const error = await response.json(); | |
|                     showErrorMessage('Failed to create user: ' + (error.error || 'Unknown error')); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error creating user:', error); | |
|                 showErrorMessage('Failed to create user: ' + error.message); | |
|             } | |
|         } | |
| 
 | |
|         // Handle update user form submission | |
|         async function handleUpdateUser() { | |
|             const username = document.getElementById('editUsername').value; | |
|             const formData = new FormData(document.getElementById('editUserForm')); | |
|              | |
|             const userData = { | |
|                 email: formData.get('email'), | |
|                 actions: Array.from(document.getElementById('editActions').selectedOptions).map(option => option.value) | |
|             }; | |
|              | |
|             try { | |
|                 const response = await fetch(`/api/users/${username}`, { | |
|                     method: 'PUT', | |
|                     headers: { | |
|                         'Content-Type': 'application/json', | |
|                     }, | |
|                     body: JSON.stringify(userData) | |
|                 }); | |
|                  | |
|                 if (response.ok) { | |
|                     showSuccessMessage('User updated successfully'); | |
|                      | |
|                     // Close modal and refresh page | |
|                     const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal')); | |
|                     modal.hide(); | |
|                     setTimeout(() => window.location.reload(), 1000); | |
|                 } else { | |
|                     const error = await response.json(); | |
|                     showErrorMessage('Failed to update user: ' + (error.error || 'Unknown error')); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error updating user:', error); | |
|                 showErrorMessage('Failed to update user: ' + error.message); | |
|             } | |
|         } | |
| 
 | |
|         // Create user details content | |
|         function createUserDetailsContent(user) { | |
|             var detailsHtml = '<div class="row">'; | |
|             detailsHtml += '<div class="col-md-6">'; | |
|             detailsHtml += '<h6 class="text-muted">Basic Information</h6>'; | |
|             detailsHtml += '<table class="table table-sm">'; | |
|             detailsHtml += '<tr><td><strong>Username:</strong></td><td>' + escapeHtml(user.username) + '</td></tr>'; | |
|             detailsHtml += '<tr><td><strong>Email:</strong></td><td>' + escapeHtml(user.email || 'Not set') + '</td></tr>'; | |
|             detailsHtml += '</table>'; | |
|             detailsHtml += '</div>'; | |
|             detailsHtml += '<div class="col-md-6">'; | |
|                          detailsHtml += '<h6 class="text-muted">Permissions</h6>'; | |
|              detailsHtml += '<div class="mb-3">'; | |
|              if (user.actions && user.actions.length > 0) { | |
|                  detailsHtml += user.actions.map(function(action) { | |
|                      return '<span class="badge bg-info me-1">' + action + '</span>'; | |
|                  }).join(''); | |
|              } else { | |
|                  detailsHtml += '<span class="text-muted">No permissions assigned</span>'; | |
|              } | |
|              detailsHtml += '</div>'; | |
|              detailsHtml += '<h6 class="text-muted">Access Keys</h6>'; | |
|              if (user.access_keys && user.access_keys.length > 0) { | |
|                  detailsHtml += '<div class="mb-2">'; | |
|                  user.access_keys.forEach(function(key) { | |
|                      detailsHtml += '<div><code class="text-muted">' + key.access_key + '</code></div>'; | |
|                  }); | |
|                  detailsHtml += '</div>'; | |
|              } else { | |
|                  detailsHtml += '<p class="text-muted">No access keys</p>'; | |
|              } | |
|             detailsHtml += '</div>'; | |
|             detailsHtml += '</div>'; | |
|             return detailsHtml; | |
|         } | |
| 
 | |
|         // Create access keys content | |
|         function createAccessKeysContent(user) { | |
|             if (!user.access_keys || user.access_keys.length === 0) { | |
|                 return '<p class="text-muted">No access keys available</p>'; | |
|             } | |
|              | |
|             var keysHtml = '<div class="table-responsive">'; | |
|             keysHtml += '<table class="table table-sm">'; | |
|             keysHtml += '<thead><tr><th>Access Key</th><th>Status</th><th>Actions</th></tr></thead>'; | |
|             keysHtml += '<tbody>'; | |
|              | |
|             user.access_keys.forEach(function(key) { | |
|                 keysHtml += '<tr>'; | |
|                 keysHtml += '<td><code>' + key.access_key + '</code></td>'; | |
|                 keysHtml += '<td><span class="badge bg-success">Active</span></td>'; | |
|                 keysHtml += '<td>'; | |
|                 keysHtml += '<button class="btn btn-outline-danger btn-sm" onclick="deleteAccessKey(\'' + user.username + '\', \'' + key.access_key + '\')">'; | |
|                 keysHtml += '<i class="fas fa-trash"></i> Delete'; | |
|                 keysHtml += '</button>'; | |
|                 keysHtml += '</td>'; | |
|                 keysHtml += '</tr>'; | |
|             }); | |
|              | |
|             keysHtml += '</tbody>'; | |
|             keysHtml += '</table>'; | |
|             keysHtml += '</div>'; | |
|             return keysHtml; | |
|         } | |
| 
 | |
|         // Create new access key | |
|         async function createAccessKey() { | |
|             const username = document.getElementById('accessKeysUsername').textContent; | |
|              | |
|             try { | |
|                 const response = await fetch(`/api/users/${username}/access-keys`, { | |
|                     method: 'POST', | |
|                     headers: { | |
|                         'Content-Type': 'application/json', | |
|                     }, | |
|                     body: JSON.stringify({}) | |
|                 }); | |
|                  | |
|                 if (response.ok) { | |
|                     const result = await response.json(); | |
|                     showSuccessMessage('Access key created successfully'); | |
|                      | |
|                     // Refresh access keys display | |
|                     const userResponse = await fetch(`/api/users/${username}`); | |
|                     if (userResponse.ok) { | |
|                         const user = await userResponse.json(); | |
|                         document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user); | |
|                     } | |
|                 } else { | |
|                     const error = await response.json(); | |
|                     showErrorMessage('Failed to create access key: ' + (error.error || 'Unknown error')); | |
|                 } | |
|             } catch (error) { | |
|                 console.error('Error creating access key:', error); | |
|                 showErrorMessage('Failed to create access key: ' + error.message); | |
|             } | |
|         } | |
| 
 | |
|         // Delete access key | |
|         async function deleteAccessKey(username, accessKey) { | |
|             if (confirm('Are you sure you want to delete this access key?')) { | |
|                 try { | |
|                     const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, { | |
|                         method: 'DELETE' | |
|                     }); | |
|                      | |
|                     if (response.ok) { | |
|                         showSuccessMessage('Access key deleted successfully'); | |
|                          | |
|                         // Refresh access keys display | |
|                         const userResponse = await fetch(`/api/users/${username}`); | |
|                         if (userResponse.ok) { | |
|                             const user = await userResponse.json(); | |
|                             document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user); | |
|                         } | |
|                     } else { | |
|                         const error = await response.json(); | |
|                         showErrorMessage('Failed to delete access key: ' + (error.error || 'Unknown error')); | |
|                     } | |
|                 } catch (error) { | |
|                     console.error('Error deleting access key:', error); | |
|                     showErrorMessage('Failed to delete access key: ' + error.message); | |
|                 } | |
|             } | |
|         } | |
| 
 | |
|         // Show new access key modal (when user is created with generated key) | |
|         function showNewAccessKeyModal(user) { | |
|             // Create a simple alert for now - could be enhanced with a dedicated modal | |
|             var message = 'New user created!\n\n'; | |
|             message += 'Username: ' + user.username + '\n'; | |
|             message += 'Access Key: ' + user.access_key + '\n'; | |
|             message += 'Secret Key: ' + user.secret_key + '\n\n'; | |
|             message += 'Please save these credentials securely.'; | |
|             alert(message); | |
|         } | |
| 
 | |
|         // Utility functions | |
|         function showSuccessMessage(message) { | |
|             // Simple implementation - could be enhanced with toast notifications | |
|             alert('Success: ' + message); | |
|         } | |
| 
 | |
|         function showErrorMessage(message) { | |
|             // Simple implementation - could be enhanced with toast notifications | |
|             alert('Error: ' + message); | |
|         } | |
| 
 | |
|         function escapeHtml(text) { | |
|             if (!text) return ''; | |
|             const div = document.createElement('div'); | |
|             div.textContent = text; | |
|             return div.innerHTML; | |
|         } | |
|     </script> | |
| } | |
| 
 | |
| // Helper functions for template | |
|   |