MyFSIO v0.2.2 Release #14

Merged
kqjy merged 17 commits from next into main 2026-01-19 07:12:15 +00:00
2 changed files with 94 additions and 91 deletions
Showing only changes of commit 5522f9ac04 - Show all commits

View File

@@ -1,4 +1,4 @@
(function() { (function () {
'use strict'; 'use strict';
const { formatBytes, escapeHtml, fallbackCopy, setupJsonAutoIndent } = window.BucketDetailUtils || { const { formatBytes, escapeHtml, fallbackCopy, setupJsonAutoIndent } = window.BucketDetailUtils || {
@@ -23,7 +23,7 @@
.replace(/'/g, '''); .replace(/'/g, ''');
}, },
fallbackCopy: () => false, fallbackCopy: () => false,
setupJsonAutoIndent: () => {} setupJsonAutoIndent: () => { }
}; };
setupJsonAutoIndent(document.getElementById('policyDocument')); setupJsonAutoIndent(document.getElementById('policyDocument'));
@@ -323,7 +323,7 @@
const bKey = b.type === 'folder' ? b.path : b.data.key; const bKey = b.type === 'folder' ? b.path : b.data.key;
return aKey.localeCompare(bKey); return aKey.localeCompare(bKey);
}); });
return items; return items;
}; };
@@ -400,14 +400,14 @@
} else { } else {
renderVirtualRows(); renderVirtualRows();
} }
updateFolderViewStatus(); updateFolderViewStatus();
}; };
const updateFolderViewStatus = () => { const updateFolderViewStatus = () => {
const folderViewStatusEl = document.getElementById('folder-view-status'); const folderViewStatusEl = document.getElementById('folder-view-status');
if (!folderViewStatusEl) return; if (!folderViewStatusEl) return;
if (currentPrefix) { if (currentPrefix) {
const folderCount = visibleItems.filter(i => i.type === 'folder').length; const folderCount = visibleItems.filter(i => i.type === 'folder').length;
const fileCount = visibleItems.filter(i => i.type === 'file').length; const fileCount = visibleItems.filter(i => i.type === 'file').length;
@@ -548,7 +548,7 @@
} else if (msg.type === 'done') { } else if (msg.type === 'done') {
streamingComplete = true; streamingComplete = true;
} }
} catch (e) {} } catch (e) { }
} }
flushPendingStreamObjects(); flushPendingStreamObjects();
@@ -687,20 +687,20 @@
selectCheckbox?.addEventListener('change', () => { selectCheckbox?.addEventListener('change', () => {
toggleRowSelection(row, selectCheckbox.checked); toggleRowSelection(row, selectCheckbox.checked);
}); });
if (selectedRows.has(row.dataset.key)) { if (selectedRows.has(row.dataset.key)) {
selectCheckbox.checked = true; selectCheckbox.checked = true;
row.classList.add('table-active'); row.classList.add('table-active');
} }
}); });
const folderRows = document.querySelectorAll('.folder-row'); const folderRows = document.querySelectorAll('.folder-row');
folderRows.forEach(row => { folderRows.forEach(row => {
if (row.dataset.handlersAttached) return; if (row.dataset.handlersAttached) return;
row.dataset.handlersAttached = 'true'; row.dataset.handlersAttached = 'true';
const folderPath = row.dataset.folderPath; const folderPath = row.dataset.folderPath;
const checkbox = row.querySelector('[data-folder-select]'); const checkbox = row.querySelector('[data-folder-select]');
checkbox?.addEventListener('change', (e) => { checkbox?.addEventListener('change', (e) => {
e.stopPropagation(); e.stopPropagation();
@@ -720,7 +720,7 @@
e.stopPropagation(); e.stopPropagation();
navigateToFolder(folderPath); navigateToFolder(folderPath);
}); });
row.addEventListener('click', (e) => { row.addEventListener('click', (e) => {
if (e.target.closest('[data-folder-select]') || e.target.closest('button')) return; if (e.target.closest('[data-folder-select]') || e.target.closest('button')) return;
navigateToFolder(folderPath); navigateToFolder(folderPath);
@@ -750,7 +750,7 @@
threshold: 0 threshold: 0
}); });
containerObserver.observe(scrollSentinel); containerObserver.observe(scrollSentinel);
const viewportObserver = new IntersectionObserver((entries) => { const viewportObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting && hasMoreObjects && !isLoadingObjects) { if (entry.isIntersecting && hasMoreObjects && !isLoadingObjects) {
@@ -781,7 +781,7 @@
if (e.target.closest('[data-delete-object]') || e.target.closest('[data-object-select]') || e.target.closest('a')) { if (e.target.closest('[data-delete-object]') || e.target.closest('[data-object-select]') || e.target.closest('a')) {
return; return;
} }
selectRow(row); selectRow(row);
}); });
} }
@@ -791,14 +791,14 @@
const getFoldersAtPrefix = (prefix) => { const getFoldersAtPrefix = (prefix) => {
const folders = new Set(); const folders = new Set();
const files = []; const files = [];
allObjects.forEach(obj => { allObjects.forEach(obj => {
const key = obj.key; const key = obj.key;
if (!key.startsWith(prefix)) return; if (!key.startsWith(prefix)) return;
const remainder = key.slice(prefix.length); const remainder = key.slice(prefix.length);
const slashIndex = remainder.indexOf('/'); const slashIndex = remainder.indexOf('/');
if (slashIndex === -1) { if (slashIndex === -1) {
files.push(obj); files.push(obj);
@@ -808,7 +808,7 @@
folders.add(prefix + folderName); folders.add(prefix + folderName);
} }
}); });
return { folders: Array.from(folders).sort(), files }; return { folders: Array.from(folders).sort(), files };
}; };
@@ -819,12 +819,12 @@
const renderBreadcrumb = (prefix) => { const renderBreadcrumb = (prefix) => {
if (!folderBreadcrumb) return; if (!folderBreadcrumb) return;
if (!prefix && !hasFolders()) { if (!prefix && !hasFolders()) {
folderBreadcrumb.classList.add('d-none'); folderBreadcrumb.classList.add('d-none');
return; return;
} }
folderBreadcrumb.classList.remove('d-none'); folderBreadcrumb.classList.remove('d-none');
const ol = folderBreadcrumb.querySelector('ol'); const ol = folderBreadcrumb.querySelector('ol');
ol.innerHTML = ''; ol.innerHTML = '';
@@ -859,7 +859,7 @@
accumulated += part + '/'; accumulated += part + '/';
const li = document.createElement('li'); const li = document.createElement('li');
li.className = 'breadcrumb-item'; li.className = 'breadcrumb-item';
if (index === parts.length - 1) { if (index === parts.length - 1) {
li.classList.add('active'); li.classList.add('active');
li.setAttribute('aria-current', 'page'); li.setAttribute('aria-current', 'page');
@@ -892,12 +892,12 @@
const folderName = displayName || folderPath.slice(currentPrefix.length).replace(/\/$/, ''); const folderName = displayName || folderPath.slice(currentPrefix.length).replace(/\/$/, '');
const { count: objectCount, mayHaveMore } = countObjectsInFolder(folderPath); const { count: objectCount, mayHaveMore } = countObjectsInFolder(folderPath);
const countDisplay = mayHaveMore ? `${objectCount}+` : objectCount; const countDisplay = mayHaveMore ? `${objectCount}+` : objectCount;
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.className = 'folder-row'; tr.className = 'folder-row';
tr.dataset.folderPath = folderPath; tr.dataset.folderPath = folderPath;
tr.style.cursor = 'pointer'; tr.style.cursor = 'pointer';
tr.innerHTML = ` tr.innerHTML = `
<td class="text-center align-middle" onclick="event.stopPropagation();"> <td class="text-center align-middle" onclick="event.stopPropagation();">
<input class="form-check-input" type="checkbox" data-folder-select="${escapeHtml(folderPath)}" aria-label="Select folder" /> <input class="form-check-input" type="checkbox" data-folder-select="${escapeHtml(folderPath)}" aria-label="Select folder" />
@@ -922,7 +922,7 @@
</button> </button>
</td> </td>
`; `;
return tr; return tr;
}; };
@@ -947,7 +947,7 @@
const renderObjectsView = () => { const renderObjectsView = () => {
if (!objectsTableBody) return; if (!objectsTableBody) return;
const { folders, files } = getFoldersAtPrefix(currentPrefix); const { folders, files } = getFoldersAtPrefix(currentPrefix);
objectsTableBody.innerHTML = ''; objectsTableBody.innerHTML = '';
@@ -1397,11 +1397,11 @@
const metadata = version.metadata && typeof version.metadata === 'object' ? Object.entries(version.metadata) : []; const metadata = version.metadata && typeof version.metadata === 'object' ? Object.entries(version.metadata) : [];
const metadataHtml = metadata.length const metadataHtml = metadata.length
? `<div class="mt-3"><div class="fw-semibold text-uppercase small">Metadata</div><hr class="my-2"><div class="metadata-stack small">${metadata ? `<div class="mt-3"><div class="fw-semibold text-uppercase small">Metadata</div><hr class="my-2"><div class="metadata-stack small">${metadata
.map( .map(
([key, value]) => ([key, value]) =>
`<div class="metadata-entry"><div class="metadata-key small">${escapeHtml(key)}</div><div class="metadata-value text-break">${escapeHtml(value)}</div></div>` `<div class="metadata-entry"><div class="metadata-key small">${escapeHtml(key)}</div><div class="metadata-value text-break">${escapeHtml(value)}</div></div>`
) )
.join('')}</div></div>` .join('')}</div></div>`
: ''; : '';
const summaryHtml = ` const summaryHtml = `
<div class="small"> <div class="small">
@@ -1673,7 +1673,7 @@
if (!endpoint) { if (!endpoint) {
versionPanel.classList.add('d-none'); versionPanel.classList.add('d-none');
return; return;
} }
versionPanel.classList.remove('d-none'); versionPanel.classList.remove('d-none');
if (!force && versionsCache.has(endpoint)) { if (!force && versionsCache.has(endpoint)) {
renderVersionEntries(versionsCache.get(endpoint), row); renderVersionEntries(versionsCache.get(endpoint), row);
@@ -1913,7 +1913,7 @@
textArea.remove(); textArea.remove();
return success; return success;
}; };
let copied = false; let copied = false;
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
@@ -1928,7 +1928,7 @@
if (!copied) { if (!copied) {
copied = fallbackCopy(presignLink.value); copied = fallbackCopy(presignLink.value);
} }
if (copied) { if (copied) {
copyPresignLink.textContent = 'Copied!'; copyPresignLink.textContent = 'Copied!';
window.setTimeout(() => { window.setTimeout(() => {
@@ -2040,7 +2040,7 @@
uploadCancelled = true; uploadCancelled = true;
activeXHRs.forEach(xhr => { activeXHRs.forEach(xhr => {
try { xhr.abort(); } catch {} try { xhr.abort(); } catch { }
}); });
activeXHRs = []; activeXHRs = [];
@@ -2049,7 +2049,7 @@
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value; const csrfToken = document.querySelector('input[name="csrf_token"]')?.value;
try { try {
await fetch(abortUrl, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken || '' } }); await fetch(abortUrl, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken || '' } });
} catch {} } catch { }
activeMultipartUpload = null; activeMultipartUpload = null;
} }
@@ -2275,7 +2275,7 @@
if (!uploadCancelled) { if (!uploadCancelled) {
try { try {
await fetch(abortUrl, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken || '' } }); await fetch(abortUrl, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken || '' } });
} catch {} } catch { }
} }
activeMultipartUpload = null; activeMultipartUpload = null;
throw err; throw err;
@@ -2588,7 +2588,7 @@
uploadForm.addEventListener('submit', async (event) => { uploadForm.addEventListener('submit', async (event) => {
const files = uploadFileInput.files; const files = uploadFileInput.files;
if (!files || files.length === 0) return; if (!files || files.length === 0) return;
const keyPrefix = (uploadKeyPrefix?.value || '').trim(); const keyPrefix = (uploadKeyPrefix?.value || '').trim();
if (files.length === 1 && !keyPrefix) { if (files.length === 1 && !keyPrefix) {
@@ -2609,7 +2609,7 @@
uploadSubmitBtn.disabled = true; uploadSubmitBtn.disabled = true;
if (uploadBtnText) uploadBtnText.textContent = 'Uploading...'; if (uploadBtnText) uploadBtnText.textContent = 'Uploading...';
} }
await performBulkUpload(Array.from(files)); await performBulkUpload(Array.from(files));
}); });
@@ -2810,7 +2810,7 @@
} }
} }
if (statusAlert) statusAlert.classList.add('d-none'); if (statusAlert) statusAlert.classList.add('d-none');
// Update status badge to show "Paused" with warning styling // Update status badge to show "Paused" with warning styling
if (statusBadge) { if (statusBadge) {
statusBadge.className = 'badge bg-warning-subtle text-warning px-3 py-2'; statusBadge.className = 'badge bg-warning-subtle text-warning px-3 py-2';
@@ -2820,14 +2820,14 @@
</svg> </svg>
<span>Paused (Endpoint Unavailable)</span>`; <span>Paused (Endpoint Unavailable)</span>`;
} }
// Hide the pause button since replication is effectively already paused // Hide the pause button since replication is effectively already paused
if (pauseForm) pauseForm.classList.add('d-none'); if (pauseForm) pauseForm.classList.add('d-none');
} else { } else {
// Hide warning and show success alert // Hide warning and show success alert
if (endpointWarning) endpointWarning.classList.add('d-none'); if (endpointWarning) endpointWarning.classList.add('d-none');
if (statusAlert) statusAlert.classList.remove('d-none'); if (statusAlert) statusAlert.classList.remove('d-none');
// Restore status badge to show "Enabled" // Restore status badge to show "Enabled"
if (statusBadge) { if (statusBadge) {
statusBadge.className = 'badge bg-success-subtle text-success px-3 py-2'; statusBadge.className = 'badge bg-success-subtle text-success px-3 py-2';
@@ -2837,7 +2837,7 @@
</svg> </svg>
<span>Enabled</span>`; <span>Enabled</span>`;
} }
// Show the pause button // Show the pause button
if (pauseForm) pauseForm.classList.remove('d-none'); if (pauseForm) pauseForm.classList.remove('d-none');
} }
@@ -3074,7 +3074,7 @@
const targetBucketInput = document.getElementById('target_bucket'); const targetBucketInput = document.getElementById('target_bucket');
const targetBucketFeedback = document.getElementById('target_bucket_feedback'); const targetBucketFeedback = document.getElementById('target_bucket_feedback');
const validateBucketName = (name) => { const validateBucketName = (name) => {
if (!name) return { valid: false, error: 'Bucket name is required' }; if (!name) return { valid: false, error: 'Bucket name is required' };
if (name.length < 3) return { valid: false, error: 'Bucket name must be at least 3 characters' }; if (name.length < 3) return { valid: false, error: 'Bucket name must be at least 3 characters' };
@@ -3177,7 +3177,7 @@
const loadLifecycleRules = async () => { const loadLifecycleRules = async () => {
if (!lifecycleUrl || !lifecycleRulesBody) return; if (!lifecycleUrl || !lifecycleRulesBody) return;
lifecycleRulesBody.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4"><div class="spinner-border spinner-border-sm me-2" role="status"></div>Loading...</td></tr>'; lifecycleRulesBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted py-4"><div class="spinner-border spinner-border-sm me-2" role="status"></div>Loading...</td></tr>';
try { try {
const resp = await fetch(lifecycleUrl); const resp = await fetch(lifecycleUrl);
const data = await resp.json(); const data = await resp.json();
@@ -3185,19 +3185,20 @@
lifecycleRules = data.rules || []; lifecycleRules = data.rules || [];
renderLifecycleRules(); renderLifecycleRules();
} catch (err) { } catch (err) {
lifecycleRulesBody.innerHTML = `<tr><td colspan="6" class="text-center text-danger py-4">${escapeHtml(err.message)}</td></tr>`; lifecycleRulesBody.innerHTML = `<tr><td colspan="7" class="text-center text-danger py-4">${escapeHtml(err.message)}</td></tr>`;
} }
}; };
const renderLifecycleRules = () => { const renderLifecycleRules = () => {
if (!lifecycleRulesBody) return; if (!lifecycleRulesBody) return;
if (lifecycleRules.length === 0) { if (lifecycleRules.length === 0) {
lifecycleRulesBody.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4">No lifecycle rules configured</td></tr>'; lifecycleRulesBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted py-4">No lifecycle rules configured</td></tr>';
return; return;
} }
lifecycleRulesBody.innerHTML = lifecycleRules.map((rule, idx) => { lifecycleRulesBody.innerHTML = lifecycleRules.map((rule, idx) => {
const expiration = rule.Expiration?.Days ? `${rule.Expiration.Days}d` : '-'; const expiration = rule.Expiration?.Days ? `${rule.Expiration.Days}d` : '-';
const noncurrent = rule.NoncurrentVersionExpiration?.NoncurrentDays ? `${rule.NoncurrentVersionExpiration.NoncurrentDays}d` : '-'; const noncurrent = rule.NoncurrentVersionExpiration?.NoncurrentDays ? `${rule.NoncurrentVersionExpiration.NoncurrentDays}d` : '-';
const abortMpu = rule.AbortIncompleteMultipartUpload?.DaysAfterInitiation ? `${rule.AbortIncompleteMultipartUpload.DaysAfterInitiation}d` : '-';
const statusClass = rule.Status === 'Enabled' ? 'bg-success' : 'bg-secondary'; const statusClass = rule.Status === 'Enabled' ? 'bg-success' : 'bg-secondary';
return `<tr> return `<tr>
<td><code class="small">${escapeHtml(rule.ID || '')}</code></td> <td><code class="small">${escapeHtml(rule.ID || '')}</code></td>
@@ -3205,6 +3206,7 @@
<td><span class="badge ${statusClass}">${escapeHtml(rule.Status)}</span></td> <td><span class="badge ${statusClass}">${escapeHtml(rule.Status)}</span></td>
<td class="small">${expiration}</td> <td class="small">${expiration}</td>
<td class="small">${noncurrent}</td> <td class="small">${noncurrent}</td>
<td class="small">${abortMpu}</td>
<td class="text-end"> <td class="text-end">
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary" onclick="editLifecycleRule(${idx})" title="Edit rule"> <button class="btn btn-outline-secondary" onclick="editLifecycleRule(${idx})" title="Edit rule">
@@ -3490,7 +3492,7 @@
}); });
}); });
document.getElementById('objects-table')?.addEventListener('show.bs.dropdown', function(e) { document.getElementById('objects-table')?.addEventListener('show.bs.dropdown', function (e) {
const dropdown = e.target.closest('.dropdown'); const dropdown = e.target.closest('.dropdown');
const menu = dropdown?.querySelector('.dropdown-menu'); const menu = dropdown?.querySelector('.dropdown-menu');
const btn = e.target; const btn = e.target;
@@ -3789,18 +3791,18 @@
var form = document.getElementById(formId); var form = document.getElementById(formId);
if (!form) return; if (!form) return;
form.addEventListener('submit', function(e) { form.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
window.UICore.submitFormAjax(form, { window.UICore.submitFormAjax(form, {
successMessage: options.successMessage || 'Operation completed', successMessage: options.successMessage || 'Operation completed',
onSuccess: function(data) { onSuccess: function (data) {
if (options.onSuccess) options.onSuccess(data); if (options.onSuccess) options.onSuccess(data);
if (options.closeModal) { if (options.closeModal) {
var modal = bootstrap.Modal.getInstance(document.getElementById(options.closeModal)); var modal = bootstrap.Modal.getInstance(document.getElementById(options.closeModal));
if (modal) modal.hide(); if (modal) modal.hide();
} }
if (options.reload) { if (options.reload) {
setTimeout(function() { location.reload(); }, 500); setTimeout(function () { location.reload(); }, 500);
} }
} }
}); });
@@ -3855,11 +3857,11 @@
var newForm = document.getElementById('enableVersioningForm'); var newForm = document.getElementById('enableVersioningForm');
if (newForm) { if (newForm) {
newForm.setAttribute('action', window.BucketDetailConfig?.endpoints?.versioning || ''); newForm.setAttribute('action', window.BucketDetailConfig?.endpoints?.versioning || '');
newForm.addEventListener('submit', function(e) { newForm.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
window.UICore.submitFormAjax(newForm, { window.UICore.submitFormAjax(newForm, {
successMessage: 'Versioning enabled', successMessage: 'Versioning enabled',
onSuccess: function() { onSuccess: function () {
updateVersioningBadge(true); updateVersioningBadge(true);
updateVersioningCard(true); updateVersioningCard(true);
} }
@@ -3949,7 +3951,7 @@
'<p class="mb-0 small">No bucket policy is attached. Access is controlled by IAM policies only.</p></div>'; '<p class="mb-0 small">No bucket policy is attached. Access is controlled by IAM policies only.</p></div>';
} }
} }
document.querySelectorAll('.preset-btn').forEach(function(btn) { document.querySelectorAll('.preset-btn').forEach(function (btn) {
btn.classList.remove('active'); btn.classList.remove('active');
if (btn.dataset.preset === preset) btn.classList.add('active'); if (btn.dataset.preset === preset) btn.classList.add('active');
}); });
@@ -3963,7 +3965,7 @@
interceptForm('enableVersioningForm', { interceptForm('enableVersioningForm', {
successMessage: 'Versioning enabled', successMessage: 'Versioning enabled',
onSuccess: function(data) { onSuccess: function (data) {
updateVersioningBadge(true); updateVersioningBadge(true);
updateVersioningCard(true); updateVersioningCard(true);
} }
@@ -3972,7 +3974,7 @@
interceptForm('suspendVersioningForm', { interceptForm('suspendVersioningForm', {
successMessage: 'Versioning suspended', successMessage: 'Versioning suspended',
closeModal: 'suspendVersioningModal', closeModal: 'suspendVersioningModal',
onSuccess: function(data) { onSuccess: function (data) {
updateVersioningBadge(false); updateVersioningBadge(false);
updateVersioningCard(false); updateVersioningCard(false);
} }
@@ -3980,36 +3982,36 @@
interceptForm('encryptionForm', { interceptForm('encryptionForm', {
successMessage: 'Encryption settings saved', successMessage: 'Encryption settings saved',
onSuccess: function(data) { onSuccess: function (data) {
updateEncryptionCard(data.enabled !== false, data.algorithm || 'AES256'); updateEncryptionCard(data.enabled !== false, data.algorithm || 'AES256');
} }
}); });
interceptForm('quotaForm', { interceptForm('quotaForm', {
successMessage: 'Quota settings saved', successMessage: 'Quota settings saved',
onSuccess: function(data) { onSuccess: function (data) {
updateQuotaCard(data.has_quota, data.max_bytes, data.max_objects); updateQuotaCard(data.has_quota, data.max_bytes, data.max_objects);
} }
}); });
interceptForm('bucketPolicyForm', { interceptForm('bucketPolicyForm', {
successMessage: 'Bucket policy saved', successMessage: 'Bucket policy saved',
onSuccess: function(data) { onSuccess: function (data) {
var policyModeEl = document.getElementById('policyMode'); var policyModeEl = document.getElementById('policyMode');
var policyPresetEl = document.getElementById('policyPreset'); var policyPresetEl = document.getElementById('policyPreset');
var preset = policyModeEl && policyModeEl.value === 'delete' ? 'private' : var preset = policyModeEl && policyModeEl.value === 'delete' ? 'private' :
(policyPresetEl?.value || 'custom'); (policyPresetEl?.value || 'custom');
updatePolicyCard(preset !== 'private', preset); updatePolicyCard(preset !== 'private', preset);
} }
}); });
var deletePolicyForm = document.getElementById('deletePolicyForm'); var deletePolicyForm = document.getElementById('deletePolicyForm');
if (deletePolicyForm) { if (deletePolicyForm) {
deletePolicyForm.addEventListener('submit', function(e) { deletePolicyForm.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
window.UICore.submitFormAjax(deletePolicyForm, { window.UICore.submitFormAjax(deletePolicyForm, {
successMessage: 'Bucket policy deleted', successMessage: 'Bucket policy deleted',
onSuccess: function(data) { onSuccess: function (data) {
var modal = bootstrap.Modal.getInstance(document.getElementById('deletePolicyModal')); var modal = bootstrap.Modal.getInstance(document.getElementById('deletePolicyModal'));
if (modal) modal.hide(); if (modal) modal.hide();
updatePolicyCard(false, 'private'); updatePolicyCard(false, 'private');
@@ -4022,13 +4024,13 @@
var disableEncBtn = document.getElementById('disableEncryptionBtn'); var disableEncBtn = document.getElementById('disableEncryptionBtn');
if (disableEncBtn) { if (disableEncBtn) {
disableEncBtn.addEventListener('click', function() { disableEncBtn.addEventListener('click', function () {
var form = document.getElementById('encryptionForm'); var form = document.getElementById('encryptionForm');
if (!form) return; if (!form) return;
document.getElementById('encryptionAction').value = 'disable'; document.getElementById('encryptionAction').value = 'disable';
window.UICore.submitFormAjax(form, { window.UICore.submitFormAjax(form, {
successMessage: 'Encryption disabled', successMessage: 'Encryption disabled',
onSuccess: function(data) { onSuccess: function (data) {
document.getElementById('encryptionAction').value = 'enable'; document.getElementById('encryptionAction').value = 'enable';
updateEncryptionCard(false, null); updateEncryptionCard(false, null);
} }
@@ -4038,13 +4040,13 @@
var removeQuotaBtn = document.getElementById('removeQuotaBtn'); var removeQuotaBtn = document.getElementById('removeQuotaBtn');
if (removeQuotaBtn) { if (removeQuotaBtn) {
removeQuotaBtn.addEventListener('click', function() { removeQuotaBtn.addEventListener('click', function () {
var form = document.getElementById('quotaForm'); var form = document.getElementById('quotaForm');
if (!form) return; if (!form) return;
document.getElementById('quotaAction').value = 'remove'; document.getElementById('quotaAction').value = 'remove';
window.UICore.submitFormAjax(form, { window.UICore.submitFormAjax(form, {
successMessage: 'Quota removed', successMessage: 'Quota removed',
onSuccess: function(data) { onSuccess: function (data) {
document.getElementById('quotaAction').value = 'set'; document.getElementById('quotaAction').value = 'set';
updateQuotaCard(false, null, null); updateQuotaCard(false, null, null);
} }
@@ -4058,39 +4060,39 @@
fetch(window.location.pathname + '?tab=replication', { fetch(window.location.pathname + '?tab=replication', {
headers: { 'X-Requested-With': 'XMLHttpRequest' } headers: { 'X-Requested-With': 'XMLHttpRequest' }
}) })
.then(function(resp) { return resp.text(); }) .then(function (resp) { return resp.text(); })
.then(function(html) { .then(function (html) {
var parser = new DOMParser(); var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html'); var doc = parser.parseFromString(html, 'text/html');
var newPane = doc.getElementById('replication-pane'); var newPane = doc.getElementById('replication-pane');
if (newPane) { if (newPane) {
replicationPane.innerHTML = newPane.innerHTML; replicationPane.innerHTML = newPane.innerHTML;
initReplicationForms(); initReplicationForms();
initReplicationStats(); initReplicationStats();
} }
}) })
.catch(function(err) { .catch(function (err) {
console.error('Failed to reload replication pane:', err); console.error('Failed to reload replication pane:', err);
}); });
} }
function initReplicationForms() { function initReplicationForms() {
document.querySelectorAll('form[action*="replication"]').forEach(function(form) { document.querySelectorAll('form[action*="replication"]').forEach(function (form) {
if (form.dataset.ajaxBound) return; if (form.dataset.ajaxBound) return;
form.dataset.ajaxBound = 'true'; form.dataset.ajaxBound = 'true';
var actionInput = form.querySelector('input[name="action"]'); var actionInput = form.querySelector('input[name="action"]');
if (!actionInput) return; if (!actionInput) return;
var action = actionInput.value; var action = actionInput.value;
form.addEventListener('submit', function(e) { form.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
var msg = action === 'pause' ? 'Replication paused' : var msg = action === 'pause' ? 'Replication paused' :
action === 'resume' ? 'Replication resumed' : action === 'resume' ? 'Replication resumed' :
action === 'delete' ? 'Replication disabled' : action === 'delete' ? 'Replication disabled' :
action === 'create' ? 'Replication configured' : 'Operation completed'; action === 'create' ? 'Replication configured' : 'Operation completed';
window.UICore.submitFormAjax(form, { window.UICore.submitFormAjax(form, {
successMessage: msg, successMessage: msg,
onSuccess: function(data) { onSuccess: function (data) {
var modal = bootstrap.Modal.getInstance(document.getElementById('disableReplicationModal')); var modal = bootstrap.Modal.getInstance(document.getElementById('disableReplicationModal'));
if (modal) modal.hide(); if (modal) modal.hide();
reloadReplicationPane(); reloadReplicationPane();
@@ -4112,14 +4114,14 @@
var bytesEl = statsContainer.querySelector('[data-stat="bytes"]'); var bytesEl = statsContainer.querySelector('[data-stat="bytes"]');
fetch(statusEndpoint) fetch(statusEndpoint)
.then(function(resp) { return resp.json(); }) .then(function (resp) { return resp.json(); })
.then(function(data) { .then(function (data) {
if (syncedEl) syncedEl.textContent = data.objects_synced || 0; if (syncedEl) syncedEl.textContent = data.objects_synced || 0;
if (pendingEl) pendingEl.textContent = data.objects_pending || 0; if (pendingEl) pendingEl.textContent = data.objects_pending || 0;
if (orphanedEl) orphanedEl.textContent = data.objects_orphaned || 0; if (orphanedEl) orphanedEl.textContent = data.objects_orphaned || 0;
if (bytesEl) bytesEl.textContent = formatBytes(data.bytes_synced || 0); if (bytesEl) bytesEl.textContent = formatBytes(data.bytes_synced || 0);
}) })
.catch(function(err) { .catch(function (err) {
console.error('Failed to load replication stats:', err); console.error('Failed to load replication stats:', err);
}); });
} }
@@ -4129,10 +4131,10 @@
var deleteBucketForm = document.getElementById('deleteBucketForm'); var deleteBucketForm = document.getElementById('deleteBucketForm');
if (deleteBucketForm) { if (deleteBucketForm) {
deleteBucketForm.addEventListener('submit', function(e) { deleteBucketForm.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
window.UICore.submitFormAjax(deleteBucketForm, { window.UICore.submitFormAjax(deleteBucketForm, {
onSuccess: function() { onSuccess: function () {
sessionStorage.setItem('flashMessage', JSON.stringify({ title: 'Bucket deleted', variant: 'success' })); sessionStorage.setItem('flashMessage', JSON.stringify({ title: 'Bucket deleted', variant: 'success' }));
window.location.href = window.BucketDetailConfig?.endpoints?.bucketsOverview || '/ui/buckets'; window.location.href = window.BucketDetailConfig?.endpoints?.bucketsOverview || '/ui/buckets';
} }

View File

@@ -1560,12 +1560,13 @@
<th>Status</th> <th>Status</th>
<th>Expiration</th> <th>Expiration</th>
<th>Noncurrent</th> <th>Noncurrent</th>
<th>Abort MPU</th>
<th class="text-end">Actions</th> <th class="text-end">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody id="lifecycle-rules-body"> <tbody id="lifecycle-rules-body">
<tr> <tr>
<td colspan="6" class="text-center text-muted py-4"> <td colspan="7" class="text-center text-muted py-4">
<div class="spinner-border spinner-border-sm me-2" role="status"></div> <div class="spinner-border spinner-border-sm me-2" role="status"></div>
Loading... Loading...
</td> </td>