mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-24 16:17:08 +00:00
admin: keep the copy confirmation in front of the access key modal (#10856)
* admin: raise nested modals above the ones already open Bootstrap gives every modal and every backdrop the same z-index, so a modal opened while another is showing paints behind it and its buttons cannot be clicked. Viewing an access key secret and then copying a field left the confirmation stuck behind the details modal with no way to dismiss it. Give each nested modal, and the backdrop Bootstrap creates for it, a z-index above what is already on screen, and put back the scroll lock that Bootstrap drops as soon as any one of them closes. * admin: confirm clipboard copies with a toast The access key details modal offers three copy buttons, and each one raised a modal that had to be dismissed before the next copy. Confirm with a toast instead, so the credentials stay in view and nothing has to be clicked away.
This commit is contained in:
@@ -634,7 +634,7 @@ function formatDate(date) {
|
||||
function adminCopyToClipboard(text) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showAlert('Copied to clipboard!', 'success');
|
||||
showToast('Copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
fallbackCopyText(text);
|
||||
@@ -660,7 +660,7 @@ function fallbackCopyText(text) {
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
showAlert('Copied to clipboard!', 'success');
|
||||
showToast('Copied to clipboard!');
|
||||
} else {
|
||||
showAlert('Failed to copy to clipboard', 'danger');
|
||||
}
|
||||
@@ -2265,11 +2265,11 @@ function copyFromInput(inputId) {
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
showAlert('Copied to clipboard!', 'success');
|
||||
showToast('Copied to clipboard!');
|
||||
} else {
|
||||
// Try modern clipboard API as fallback
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
showAlert('Copied to clipboard!', 'success');
|
||||
showToast('Copied to clipboard!');
|
||||
}).catch(() => {
|
||||
showAlert('Failed to copy', 'danger');
|
||||
});
|
||||
@@ -2277,7 +2277,7 @@ function copyFromInput(inputId) {
|
||||
} catch (err) {
|
||||
// Try modern clipboard API as fallback
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
showAlert('Copied to clipboard!', 'success');
|
||||
showToast('Copied to clipboard!');
|
||||
}).catch(() => {
|
||||
showAlert('Failed to copy', 'danger');
|
||||
});
|
||||
|
||||
@@ -168,6 +168,36 @@
|
||||
bsModal.show();
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a transient message that does not have to be dismissed
|
||||
* @param {string} message - The message to display
|
||||
* @param {string} type - Bootstrap contextual color, defaults to 'success'
|
||||
*/
|
||||
window.showToast = function (message, type) {
|
||||
let container = document.getElementById('globalToastContainer');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'globalToastContainer';
|
||||
container.className = 'toast-container position-fixed top-0 end-0 p-3';
|
||||
// Above any modal, however deeply they are stacked.
|
||||
container.style.zIndex = '9999';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast align-items-center text-white bg-' + (type || 'success') + ' border-0';
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.innerHTML = '<div class="d-flex"><div class="toast-body">' + escapeHtml(message) + '</div>' +
|
||||
'<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button></div>';
|
||||
container.appendChild(toast);
|
||||
|
||||
toast.addEventListener('hidden.bs.toast', function () {
|
||||
toast.remove();
|
||||
});
|
||||
|
||||
new bootstrap.Toast(toast, { delay: 3000 }).show();
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a confirmation dialog using Bootstrap modal
|
||||
* @param {string} message - The confirmation message
|
||||
@@ -290,6 +320,40 @@
|
||||
return text.replace(/[&<>"']/g, function (m) { return map[m]; });
|
||||
}
|
||||
|
||||
// Bootstrap gives every modal and backdrop the same z-index, so a modal
|
||||
// opened while another one is showing paints behind it and cannot be
|
||||
// clicked. Lift each nested modal, and its backdrop, above what is already
|
||||
// on screen.
|
||||
const MODAL_Z_INDEX = 1055;
|
||||
const MODAL_Z_INDEX_STEP = 20;
|
||||
|
||||
document.addEventListener('show.bs.modal', function (event) {
|
||||
const depth = document.querySelectorAll('.modal.show').length;
|
||||
if (depth === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zIndex = MODAL_Z_INDEX + depth * MODAL_Z_INDEX_STEP;
|
||||
event.target.style.zIndex = zIndex;
|
||||
|
||||
// Bootstrap only creates the backdrop after this event returns.
|
||||
setTimeout(function () {
|
||||
const backdrops = document.querySelectorAll('.modal-backdrop');
|
||||
const backdrop = backdrops[backdrops.length - 1];
|
||||
if (backdrop) {
|
||||
backdrop.style.zIndex = zIndex - 1;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
document.addEventListener('hidden.bs.modal', function (event) {
|
||||
event.target.style.zIndex = '';
|
||||
// Closing any modal releases the scroll lock the others still need.
|
||||
if (document.querySelector('.modal.show')) {
|
||||
document.body.classList.add('modal-open');
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-initialize on DOMContentLoaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', ensureModalsExist);
|
||||
|
||||
Reference in New Issue
Block a user