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
-