window.IAMManagement = (function() { 'use strict'; var users = []; var currentUserKey = null; var endpoints = {}; var csrfToken = ''; var iamLocked = false; var policyModal = null; var editUserModal = null; var deleteUserModal = null; var rotateSecretModal = null; var currentRotateKey = null; var currentEditKey = null; var currentDeleteKey = null; var policyTemplates = { full: [{ bucket: '*', actions: ['list', 'read', 'write', 'delete', 'share', 'policy', 'replication', 'iam:list_users', 'iam:*'] }], readonly: [{ bucket: '*', actions: ['list', 'read'] }], writer: [{ bucket: '*', actions: ['list', 'read', 'write'] }] }; function init(config) { users = config.users || []; currentUserKey = config.currentUserKey || null; endpoints = config.endpoints || {}; csrfToken = config.csrfToken || ''; iamLocked = config.iamLocked || false; if (iamLocked) return; initModals(); setupJsonAutoIndent(); setupCopyButtons(); setupPolicyEditor(); setupCreateUserModal(); setupEditUserModal(); setupDeleteUserModal(); setupRotateSecretModal(); setupFormHandlers(); } function initModals() { var policyModalEl = document.getElementById('policyEditorModal'); var editModalEl = document.getElementById('editUserModal'); var deleteModalEl = document.getElementById('deleteUserModal'); var rotateModalEl = document.getElementById('rotateSecretModal'); if (policyModalEl) policyModal = new bootstrap.Modal(policyModalEl); if (editModalEl) editUserModal = new bootstrap.Modal(editModalEl); if (deleteModalEl) deleteUserModal = new bootstrap.Modal(deleteModalEl); if (rotateModalEl) rotateSecretModal = new bootstrap.Modal(rotateModalEl); } function setupJsonAutoIndent() { window.UICore.setupJsonAutoIndent(document.getElementById('policyEditorDocument')); window.UICore.setupJsonAutoIndent(document.getElementById('createUserPolicies')); } function setupCopyButtons() { document.querySelectorAll('.config-copy').forEach(function(button) { button.addEventListener('click', async function() { var targetId = button.dataset.copyTarget; var target = document.getElementById(targetId); if (!target) return; await window.UICore.copyToClipboard(target.innerText, button, 'Copy JSON'); }); }); var secretCopyButton = document.querySelector('[data-secret-copy]'); if (secretCopyButton) { secretCopyButton.addEventListener('click', async function() { var secretInput = document.getElementById('disclosedSecretValue'); if (!secretInput) return; await window.UICore.copyToClipboard(secretInput.value, secretCopyButton, 'Copy'); }); } } function getUserPolicies(accessKey) { var user = users.find(function(u) { return u.access_key === accessKey; }); return user ? JSON.stringify(user.policies, null, 2) : ''; } function applyPolicyTemplate(name, textareaEl) { if (policyTemplates[name] && textareaEl) { textareaEl.value = JSON.stringify(policyTemplates[name], null, 2); } } function setupPolicyEditor() { var userLabelEl = document.getElementById('policyEditorUserLabel'); var userInputEl = document.getElementById('policyEditorUser'); var textareaEl = document.getElementById('policyEditorDocument'); document.querySelectorAll('[data-policy-template]').forEach(function(button) { button.addEventListener('click', function() { applyPolicyTemplate(button.dataset.policyTemplate, textareaEl); }); }); document.querySelectorAll('[data-policy-editor]').forEach(function(button) { button.addEventListener('click', function() { var key = button.getAttribute('data-access-key'); if (!key) return; userLabelEl.textContent = key; userInputEl.value = key; textareaEl.value = getUserPolicies(key); policyModal.show(); }); }); } function setupCreateUserModal() { var createUserPoliciesEl = document.getElementById('createUserPolicies'); document.querySelectorAll('[data-create-policy-template]').forEach(function(button) { button.addEventListener('click', function() { applyPolicyTemplate(button.dataset.createPolicyTemplate, createUserPoliciesEl); }); }); } function setupEditUserModal() { var editUserForm = document.getElementById('editUserForm'); var editUserDisplayName = document.getElementById('editUserDisplayName'); document.querySelectorAll('[data-edit-user]').forEach(function(btn) { btn.addEventListener('click', function() { var key = btn.dataset.editUser; var name = btn.dataset.displayName; currentEditKey = key; editUserDisplayName.value = name; editUserForm.action = endpoints.updateUser.replace('ACCESS_KEY', key); editUserModal.show(); }); }); } function setupDeleteUserModal() { var deleteUserForm = document.getElementById('deleteUserForm'); var deleteUserLabel = document.getElementById('deleteUserLabel'); var deleteSelfWarning = document.getElementById('deleteSelfWarning'); document.querySelectorAll('[data-delete-user]').forEach(function(btn) { btn.addEventListener('click', function() { var key = btn.dataset.deleteUser; currentDeleteKey = key; deleteUserLabel.textContent = key; deleteUserForm.action = endpoints.deleteUser.replace('ACCESS_KEY', key); if (key === currentUserKey) { deleteSelfWarning.classList.remove('d-none'); } else { deleteSelfWarning.classList.add('d-none'); } deleteUserModal.show(); }); }); } function setupRotateSecretModal() { var rotateUserLabel = document.getElementById('rotateUserLabel'); var confirmRotateBtn = document.getElementById('confirmRotateBtn'); var rotateCancelBtn = document.getElementById('rotateCancelBtn'); var rotateDoneBtn = document.getElementById('rotateDoneBtn'); var rotateSecretConfirm = document.getElementById('rotateSecretConfirm'); var rotateSecretResult = document.getElementById('rotateSecretResult'); var newSecretKeyInput = document.getElementById('newSecretKey'); var copyNewSecretBtn = document.getElementById('copyNewSecret'); document.querySelectorAll('[data-rotate-user]').forEach(function(btn) { btn.addEventListener('click', function() { currentRotateKey = btn.dataset.rotateUser; rotateUserLabel.textContent = currentRotateKey; rotateSecretConfirm.classList.remove('d-none'); rotateSecretResult.classList.add('d-none'); confirmRotateBtn.classList.remove('d-none'); rotateCancelBtn.classList.remove('d-none'); rotateDoneBtn.classList.add('d-none'); rotateSecretModal.show(); }); }); if (confirmRotateBtn) { confirmRotateBtn.addEventListener('click', async function() { if (!currentRotateKey) return; window.UICore.setButtonLoading(confirmRotateBtn, true, 'Rotating...'); try { var url = endpoints.rotateSecret.replace('ACCESS_KEY', currentRotateKey); var response = await fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', 'X-CSRFToken': csrfToken } }); if (!response.ok) { var data = await response.json(); throw new Error(data.error || 'Failed to rotate secret'); } var data = await response.json(); newSecretKeyInput.value = data.secret_key; rotateSecretConfirm.classList.add('d-none'); rotateSecretResult.classList.remove('d-none'); confirmRotateBtn.classList.add('d-none'); rotateCancelBtn.classList.add('d-none'); rotateDoneBtn.classList.remove('d-none'); } catch (err) { if (window.showToast) { window.showToast(err.message, 'Error', 'danger'); } rotateSecretModal.hide(); } finally { window.UICore.setButtonLoading(confirmRotateBtn, false); } }); } if (copyNewSecretBtn) { copyNewSecretBtn.addEventListener('click', async function() { await window.UICore.copyToClipboard(newSecretKeyInput.value, copyNewSecretBtn, 'Copy'); }); } if (rotateDoneBtn) { rotateDoneBtn.addEventListener('click', function() { window.location.reload(); }); } } function createUserCardHtml(accessKey, displayName, policies) { var policyBadges = ''; if (policies && policies.length > 0) { policyBadges = policies.map(function(p) { var actionText = p.actions && p.actions.includes('*') ? 'full' : (p.actions ? p.actions.length : 0); return '' + '' + '' + '' + window.UICore.escapeHtml(p.bucket) + '(' + actionText + ')'; }).join(''); } else { policyBadges = 'No policies'; } return '
' + '
' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '
' + window.UICore.escapeHtml(displayName) + '
' + '' + window.UICore.escapeHtml(accessKey) + '' + '
' + '
' + '
' + '
Bucket Permissions
' + '
' + policyBadges + '
' + '' + '
'; } function attachUserCardHandlers(cardElement, accessKey, displayName) { var editBtn = cardElement.querySelector('[data-edit-user]'); if (editBtn) { editBtn.addEventListener('click', function() { currentEditKey = accessKey; document.getElementById('editUserDisplayName').value = displayName; document.getElementById('editUserForm').action = endpoints.updateUser.replace('ACCESS_KEY', accessKey); editUserModal.show(); }); } var deleteBtn = cardElement.querySelector('[data-delete-user]'); if (deleteBtn) { deleteBtn.addEventListener('click', function() { currentDeleteKey = accessKey; document.getElementById('deleteUserLabel').textContent = accessKey; document.getElementById('deleteUserForm').action = endpoints.deleteUser.replace('ACCESS_KEY', accessKey); var deleteSelfWarning = document.getElementById('deleteSelfWarning'); if (accessKey === currentUserKey) { deleteSelfWarning.classList.remove('d-none'); } else { deleteSelfWarning.classList.add('d-none'); } deleteUserModal.show(); }); } var rotateBtn = cardElement.querySelector('[data-rotate-user]'); if (rotateBtn) { rotateBtn.addEventListener('click', function() { currentRotateKey = accessKey; document.getElementById('rotateUserLabel').textContent = accessKey; document.getElementById('rotateSecretConfirm').classList.remove('d-none'); document.getElementById('rotateSecretResult').classList.add('d-none'); document.getElementById('confirmRotateBtn').classList.remove('d-none'); document.getElementById('rotateCancelBtn').classList.remove('d-none'); document.getElementById('rotateDoneBtn').classList.add('d-none'); rotateSecretModal.show(); }); } var policyBtn = cardElement.querySelector('[data-policy-editor]'); if (policyBtn) { policyBtn.addEventListener('click', function() { document.getElementById('policyEditorUserLabel').textContent = accessKey; document.getElementById('policyEditorUser').value = accessKey; document.getElementById('policyEditorDocument').value = getUserPolicies(accessKey); policyModal.show(); }); } } function updateUserCount() { var countEl = document.querySelector('.card-header .text-muted.small'); if (countEl) { var count = document.querySelectorAll('.iam-user-card').length; countEl.textContent = count + ' user' + (count !== 1 ? 's' : '') + ' configured'; } } function setupFormHandlers() { var createUserForm = document.querySelector('#createUserModal form'); if (createUserForm) { createUserForm.addEventListener('submit', function(e) { e.preventDefault(); window.UICore.submitFormAjax(createUserForm, { successMessage: 'User created', onSuccess: function(data) { var modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal')); if (modal) modal.hide(); createUserForm.reset(); var existingAlert = document.querySelector('.alert.alert-info.border-0.shadow-sm'); if (existingAlert) existingAlert.remove(); if (data.secret_key) { var alertHtml = ''; var container = document.querySelector('.page-header'); if (container) { container.insertAdjacentHTML('afterend', alertHtml); document.getElementById('copyNewUserSecret').addEventListener('click', async function() { await window.UICore.copyToClipboard(data.secret_key, this, 'Copy'); }); } } var usersGrid = document.querySelector('.row.g-3'); var emptyState = document.querySelector('.empty-state'); if (emptyState) { var emptyCol = emptyState.closest('.col-12'); if (emptyCol) emptyCol.remove(); if (!usersGrid) { var cardBody = document.querySelector('.card-body.px-4.pb-4'); if (cardBody) { cardBody.innerHTML = '
'; usersGrid = cardBody.querySelector('.row.g-3'); } } } if (usersGrid) { var cardHtml = createUserCardHtml(data.access_key, data.display_name, data.policies); usersGrid.insertAdjacentHTML('beforeend', cardHtml); var newCard = usersGrid.lastElementChild; attachUserCardHandlers(newCard, data.access_key, data.display_name); users.push({ access_key: data.access_key, display_name: data.display_name, policies: data.policies || [] }); updateUserCount(); } } }); }); } var policyEditorForm = document.getElementById('policyEditorForm'); if (policyEditorForm) { policyEditorForm.addEventListener('submit', function(e) { e.preventDefault(); var userInputEl = document.getElementById('policyEditorUser'); var key = userInputEl.value; if (!key) return; var template = policyEditorForm.dataset.actionTemplate; policyEditorForm.action = template.replace('ACCESS_KEY_PLACEHOLDER', key); window.UICore.submitFormAjax(policyEditorForm, { successMessage: 'Policies updated', onSuccess: function(data) { policyModal.hide(); var userCard = document.querySelector('[data-access-key="' + key + '"]'); if (userCard) { var badgeContainer = userCard.closest('.iam-user-card').querySelector('.d-flex.flex-wrap.gap-1'); if (badgeContainer && data.policies) { var badges = data.policies.map(function(p) { return '' + '' + '' + '' + window.UICore.escapeHtml(p.bucket) + '(' + (p.actions.includes('*') ? 'full' : p.actions.length) + ')'; }).join(''); badgeContainer.innerHTML = badges || 'No policies'; } } var userIndex = users.findIndex(function(u) { return u.access_key === key; }); if (userIndex >= 0 && data.policies) { users[userIndex].policies = data.policies; } } }); }); } var editUserForm = document.getElementById('editUserForm'); if (editUserForm) { editUserForm.addEventListener('submit', function(e) { e.preventDefault(); var key = currentEditKey; window.UICore.submitFormAjax(editUserForm, { successMessage: 'User updated', onSuccess: function(data) { editUserModal.hide(); var newName = data.display_name || document.getElementById('editUserDisplayName').value; var editBtn = document.querySelector('[data-edit-user="' + key + '"]'); if (editBtn) { editBtn.setAttribute('data-display-name', newName); var card = editBtn.closest('.iam-user-card'); if (card) { var nameEl = card.querySelector('h6'); if (nameEl) { nameEl.textContent = newName; nameEl.title = newName; } } } var userIndex = users.findIndex(function(u) { return u.access_key === key; }); if (userIndex >= 0) { users[userIndex].display_name = newName; } if (key === currentUserKey) { document.querySelectorAll('.sidebar-user .user-name').forEach(function(el) { var truncated = newName.length > 16 ? newName.substring(0, 16) + '...' : newName; el.textContent = truncated; el.title = newName; }); document.querySelectorAll('.sidebar-user[data-username]').forEach(function(el) { el.setAttribute('data-username', newName); }); } } }); }); } var deleteUserForm = document.getElementById('deleteUserForm'); if (deleteUserForm) { deleteUserForm.addEventListener('submit', function(e) { e.preventDefault(); var key = currentDeleteKey; window.UICore.submitFormAjax(deleteUserForm, { successMessage: 'User deleted', onSuccess: function(data) { deleteUserModal.hide(); if (key === currentUserKey) { window.location.href = '/ui/'; return; } var deleteBtn = document.querySelector('[data-delete-user="' + key + '"]'); if (deleteBtn) { var cardCol = deleteBtn.closest('[class*="col-"]'); if (cardCol) { cardCol.remove(); } } users = users.filter(function(u) { return u.access_key !== key; }); updateUserCount(); } }); }); } } return { init: init }; })();