MyFSIO v0.2.2 Release #14
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user