From 0e392e18b492884e25f7d18dc281c8c99d75b3b9 Mon Sep 17 00:00:00 2001 From: kqjy Date: Tue, 24 Mar 2026 15:15:03 +0800 Subject: [PATCH] Hide ghost details in object panel when preview fails to load --- app/errors.py | 10 ++- static/js/bucket-detail-main.js | 106 ++++++++++++++++++++++++++++---- templates/bucket_detail.html | 3 +- 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/app/errors.py b/app/errors.py index b2a4079..049187d 100644 --- a/app/errors.py +++ b/app/errors.py @@ -175,13 +175,21 @@ def handle_app_error(error: AppError) -> Response: def handle_rate_limit_exceeded(e: RateLimitExceeded) -> Response: g.s3_error_code = "SlowDown" + if request.path.startswith("/ui") or request.path.startswith("/buckets"): + wants_json = ( + request.is_json or + request.headers.get("X-Requested-With") == "XMLHttpRequest" or + "application/json" in request.accept_mimetypes.values() + ) + if wants_json: + return jsonify({"success": False, "error": {"code": "SlowDown", "message": "Please reduce your request rate."}}), 429 error = Element("Error") SubElement(error, "Code").text = "SlowDown" SubElement(error, "Message").text = "Please reduce your request rate." SubElement(error, "Resource").text = request.path SubElement(error, "RequestId").text = getattr(g, "request_id", "") xml_bytes = tostring(error, encoding="utf-8") - return Response(xml_bytes, status=429, mimetype="application/xml") + return Response(xml_bytes, status="429 Too Many Requests", mimetype="application/xml") def register_error_handlers(app): diff --git a/static/js/bucket-detail-main.js b/static/js/bucket-detail-main.js index 9b48d21..f35f46f 100644 --- a/static/js/bucket-detail-main.js +++ b/static/js/bucket-detail-main.js @@ -99,6 +99,8 @@ const previewMetadataList = document.getElementById('preview-metadata-list'); const previewPlaceholder = document.getElementById('preview-placeholder'); const previewPlaceholderDefault = previewPlaceholder ? previewPlaceholder.innerHTML : ''; + const previewErrorAlert = document.getElementById('preview-error-alert'); + const previewDetailsMeta = document.getElementById('preview-details-meta'); const previewImage = document.getElementById('preview-image'); const previewVideo = document.getElementById('preview-video'); const previewAudio = document.getElementById('preview-audio'); @@ -1990,6 +1992,34 @@ previewPlaceholder.classList.remove('d-none'); }; + let previewFailed = false; + + const handlePreviewError = () => { + previewFailed = true; + if (downloadButton) { + downloadButton.classList.add('disabled'); + downloadButton.removeAttribute('href'); + } + if (presignButton) presignButton.disabled = true; + if (generatePresignButton) generatePresignButton.disabled = true; + if (previewDetailsMeta) previewDetailsMeta.classList.add('d-none'); + if (previewMetadata) previewMetadata.classList.add('d-none'); + const tagsPanel = document.getElementById('preview-tags'); + if (tagsPanel) tagsPanel.classList.add('d-none'); + const versionPanel = document.getElementById('version-panel'); + if (versionPanel) versionPanel.classList.add('d-none'); + if (previewErrorAlert) { + previewErrorAlert.textContent = 'Unable to load object \u2014 it may have been deleted, or the server returned an error.'; + previewErrorAlert.classList.remove('d-none'); + } + }; + + const clearPreviewError = () => { + previewFailed = false; + if (previewErrorAlert) previewErrorAlert.classList.add('d-none'); + if (previewDetailsMeta) previewDetailsMeta.classList.remove('d-none'); + }; + async function fetchMetadata(metadataUrl) { if (!metadataUrl) return null; try { @@ -2011,6 +2041,7 @@ previewPanel.classList.remove('d-none'); activeRow = row; renderMetadata(null); + clearPreviewError(); previewKey.textContent = row.dataset.key; previewSize.textContent = formatBytes(Number(row.dataset.size)); @@ -2036,25 +2067,69 @@ if (previewUrl && lower.match(/\.(png|jpg|jpeg|gif|webp|svg|ico|bmp)$/)) { previewPlaceholder.innerHTML = '
Loading preview\u2026
'; const currentRow = row; - previewImage.onload = () => { - if (activeRow !== currentRow) return; - previewImage.classList.remove('d-none'); - previewPlaceholder.classList.add('d-none'); - }; - previewImage.onerror = () => { - if (activeRow !== currentRow) return; - previewPlaceholder.innerHTML = '
Failed to load preview
'; - }; - previewImage.src = previewUrl; + fetch(previewUrl) + .then((r) => { + if (activeRow !== currentRow) return; + if (!r.ok) { + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); + return; + } + return r.blob(); + }) + .then((blob) => { + if (!blob || activeRow !== currentRow) return; + const url = URL.createObjectURL(blob); + previewImage.onload = () => { + if (activeRow !== currentRow) { URL.revokeObjectURL(url); return; } + previewImage.classList.remove('d-none'); + previewPlaceholder.classList.add('d-none'); + }; + previewImage.onerror = () => { + if (activeRow !== currentRow) { URL.revokeObjectURL(url); return; } + URL.revokeObjectURL(url); + previewPlaceholder.innerHTML = '
Failed to load preview
'; + }; + previewImage.src = url; + }) + .catch(() => { + if (activeRow !== currentRow) return; + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); + }); } else if (previewUrl && lower.match(/\.(mp4|webm|ogv|mov|avi|mkv)$/)) { + const currentRow = row; + previewVideo.onerror = () => { + if (activeRow !== currentRow) return; + previewVideo.classList.add('d-none'); + previewPlaceholder.classList.remove('d-none'); + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); + }; previewVideo.src = previewUrl; previewVideo.classList.remove('d-none'); previewPlaceholder.classList.add('d-none'); } else if (previewUrl && lower.match(/\.(mp3|wav|flac|ogg|aac|m4a|wma)$/)) { + const currentRow = row; + previewAudio.onerror = () => { + if (activeRow !== currentRow) return; + previewAudio.classList.add('d-none'); + previewPlaceholder.classList.remove('d-none'); + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); + }; previewAudio.src = previewUrl; previewAudio.classList.remove('d-none'); previewPlaceholder.classList.add('d-none'); } else if (previewUrl && lower.match(/\.(pdf)$/)) { + const currentRow = row; + previewIframe.onerror = () => { + if (activeRow !== currentRow) return; + previewIframe.classList.add('d-none'); + previewPlaceholder.classList.remove('d-none'); + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); + }; previewIframe.src = previewUrl; previewIframe.style.minHeight = '500px'; previewIframe.classList.remove('d-none'); @@ -2079,14 +2154,17 @@ }) .catch(() => { if (activeRow !== currentRow) return; - previewText.textContent = 'Failed to load preview'; + previewText.classList.add('d-none'); + previewPlaceholder.classList.remove('d-none'); + previewPlaceholder.innerHTML = '
Failed to load preview
'; + handlePreviewError(); }); } const metadataUrl = row.dataset.metadataUrl; if (metadataUrl) { const metadata = await fetchMetadata(metadataUrl); - if (activeRow === row) { + if (activeRow === row && !previewFailed) { renderMetadata(metadata); } } @@ -3993,6 +4071,10 @@ const loadObjectTags = async (row) => { if (!row || !previewTagsPanel) return; + if (previewFailed) { + previewTagsPanel.classList.add('d-none'); + return; + } const tagsUrl = row.dataset.tagsUrl; if (!tagsUrl) { previewTagsPanel.classList.add('d-none'); diff --git a/templates/bucket_detail.html b/templates/bucket_detail.html index d6ad4f3..f913880 100644 --- a/templates/bucket_detail.html +++ b/templates/bucket_detail.html @@ -257,7 +257,8 @@ Share Link -
+ +
Last modified