Fix folder selection not showing delete button in bucket browser
This commit is contained in:
39
app/ui.py
39
app/ui.py
@@ -1063,6 +1063,27 @@ def bulk_delete_objects(bucket_name: str):
|
||||
return _respond(False, f"A maximum of {MAX_KEYS} objects can be deleted per request", status_code=400)
|
||||
|
||||
unique_keys = list(dict.fromkeys(cleaned))
|
||||
|
||||
folder_prefixes = [k for k in unique_keys if k.endswith("/")]
|
||||
if folder_prefixes:
|
||||
try:
|
||||
client = get_session_s3_client()
|
||||
for prefix in folder_prefixes:
|
||||
unique_keys.remove(prefix)
|
||||
paginator = client.get_paginator("list_objects_v2")
|
||||
for page in paginator.paginate(Bucket=bucket_name, Prefix=prefix):
|
||||
for obj in page.get("Contents", []):
|
||||
if obj["Key"] not in unique_keys:
|
||||
unique_keys.append(obj["Key"])
|
||||
except (ClientError, EndpointConnectionError, ConnectionClosedError) as exc:
|
||||
if isinstance(exc, ClientError):
|
||||
err, status = handle_client_error(exc)
|
||||
return _respond(False, err["error"], status_code=status)
|
||||
return _respond(False, "S3 API server is unreachable", status_code=502)
|
||||
|
||||
if not unique_keys:
|
||||
return _respond(False, "No objects found under the selected folders", status_code=400)
|
||||
|
||||
try:
|
||||
_authorize_ui(principal, bucket_name, "delete")
|
||||
except IamError as exc:
|
||||
@@ -1093,13 +1114,17 @@ def bulk_delete_objects(bucket_name: str):
|
||||
else:
|
||||
try:
|
||||
client = get_session_s3_client()
|
||||
objects_to_delete = [{"Key": k} for k in unique_keys]
|
||||
resp = client.delete_objects(
|
||||
Bucket=bucket_name,
|
||||
Delete={"Objects": objects_to_delete, "Quiet": False},
|
||||
)
|
||||
deleted = [d["Key"] for d in resp.get("Deleted", [])]
|
||||
errors = [{"key": e["Key"], "error": e.get("Message", e.get("Code", "Unknown error"))} for e in resp.get("Errors", [])]
|
||||
deleted = []
|
||||
errors = []
|
||||
for i in range(0, len(unique_keys), 1000):
|
||||
batch = unique_keys[i:i + 1000]
|
||||
objects_to_delete = [{"Key": k} for k in batch]
|
||||
resp = client.delete_objects(
|
||||
Bucket=bucket_name,
|
||||
Delete={"Objects": objects_to_delete, "Quiet": False},
|
||||
)
|
||||
deleted.extend(d["Key"] for d in resp.get("Deleted", []))
|
||||
errors.extend({"key": e["Key"], "error": e.get("Message", e.get("Code", "Unknown error"))} for e in resp.get("Errors", []))
|
||||
for key in deleted:
|
||||
_replication_manager().trigger_replication(bucket_name, key, action="delete")
|
||||
except (ClientError, EndpointConnectionError, ConnectionClosedError) as exc:
|
||||
|
||||
@@ -867,6 +867,11 @@
|
||||
const checkbox = row.querySelector('[data-folder-select]');
|
||||
checkbox?.addEventListener('change', (e) => {
|
||||
e.stopPropagation();
|
||||
if (checkbox.checked) {
|
||||
selectedRows.set(folderPath, { key: folderPath, isFolder: true });
|
||||
} else {
|
||||
selectedRows.delete(folderPath);
|
||||
}
|
||||
const folderObjects = allObjects.filter(obj => obj.key.startsWith(folderPath));
|
||||
folderObjects.forEach(obj => {
|
||||
if (checkbox.checked) {
|
||||
@@ -1351,8 +1356,11 @@
|
||||
}
|
||||
if (selectAllCheckbox) {
|
||||
const filesInView = visibleItems.filter(item => item.type === 'file');
|
||||
const total = filesInView.length;
|
||||
const visibleSelectedCount = filesInView.filter(item => selectedRows.has(item.data.key)).length;
|
||||
const foldersInView = visibleItems.filter(item => item.type === 'folder');
|
||||
const total = filesInView.length + foldersInView.length;
|
||||
const fileSelectedCount = filesInView.filter(item => selectedRows.has(item.data.key)).length;
|
||||
const folderSelectedCount = foldersInView.filter(item => selectedRows.has(item.path)).length;
|
||||
const visibleSelectedCount = fileSelectedCount + folderSelectedCount;
|
||||
selectAllCheckbox.disabled = total === 0;
|
||||
selectAllCheckbox.checked = visibleSelectedCount > 0 && visibleSelectedCount === total && total > 0;
|
||||
selectAllCheckbox.indeterminate = visibleSelectedCount > 0 && visibleSelectedCount < total;
|
||||
@@ -1374,8 +1382,12 @@
|
||||
const keys = Array.from(selectedRows.keys());
|
||||
bulkDeleteList.innerHTML = '';
|
||||
if (bulkDeleteCount) {
|
||||
const label = keys.length === 1 ? 'object' : 'objects';
|
||||
bulkDeleteCount.textContent = `${keys.length} ${label} selected`;
|
||||
const folderCount = keys.filter(k => k.endsWith('/')).length;
|
||||
const objectCount = keys.length - folderCount;
|
||||
const parts = [];
|
||||
if (folderCount) parts.push(`${folderCount} folder${folderCount !== 1 ? 's' : ''}`);
|
||||
if (objectCount) parts.push(`${objectCount} object${objectCount !== 1 ? 's' : ''}`);
|
||||
bulkDeleteCount.textContent = `${parts.join(' and ')} selected`;
|
||||
}
|
||||
if (!keys.length) {
|
||||
const empty = document.createElement('li');
|
||||
@@ -3172,6 +3184,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
const foldersInView = visibleItems.filter(item => item.type === 'folder');
|
||||
foldersInView.forEach(item => {
|
||||
if (shouldSelect) {
|
||||
selectedRows.set(item.path, { key: item.path, isFolder: true });
|
||||
} else {
|
||||
selectedRows.delete(item.path);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-folder-select]').forEach(cb => {
|
||||
cb.checked = shouldSelect;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user