Merge pull request #31996 from barredterra/public-file-warning

feat: warn when uploading public files
This commit is contained in:
Akhil Narang 2025-04-22 12:23:32 +05:30 committed by GitHub
commit 43a8dbfcbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 118 additions and 82 deletions

View file

@ -1,69 +1,82 @@
<template>
<div class="file-preview">
<div class="file-icon">
<img v-if="is_image" :src="src" :alt="file.name" />
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')"></div>
</div>
<div>
<div>
<a class="flex" :href="file.doc.file_url" v-if="file.doc" target="_blank">
<span class="file-name">{{ file.name }}</span>
</a>
<span class="file-name" v-else>{{ file.name }}</span>
<div class="file-preview-outline">
<div class="file-preview">
<div class="file-icon">
<img v-if="is_image" :src="src" :alt="file.name" />
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')"></div>
</div>
<div>
<div>
<a class="flex" :href="file.doc.file_url" v-if="file.doc" target="_blank">
<span class="file-name">{{ file.name }}</span>
</a>
<span class="file-name" v-else>{{ file.name }}</span>
</div>
<div>
<span class="file-size">
{{ file_size }}
</span>
</div>
<div>
<span class="file-size">
{{ file_size }}
</span>
</div>
<div class="flex config-area">
<label v-if="allow_toggle_optimize" class="frappe-checkbox"
><input
type="checkbox"
:checked="optimize"
@change="emit('toggle_optimize')"
/>{{ __("Optimize") }}</label
>
<label v-if="allow_toggle_private" class="frappe-checkbox"
><input
type="checkbox"
:checked="file.private"
@change="emit('toggle_private')"
/>{{ __("Private") }}</label
>
<div class="flex config-area">
<label v-if="allow_toggle_optimize" class="frappe-checkbox"
><input
type="checkbox"
:checked="optimize"
@change="emit('toggle_optimize')"
/>{{ __("Optimize") }}</label
>
<label v-if="allow_toggle_private" class="frappe-checkbox"
><input
type="checkbox"
:checked="file.private"
@change="emit('toggle_private')"
/>{{ __("Private") }}</label
>
</div>
</div>
<div>
<span v-if="file.error_message" class="file-error text-danger">
{{ file.error_message }}
</span>
<div class="file-actions">
<ProgressRing
v-show="file.uploading && !uploaded && !file.failed"
primary="var(--primary-color)"
secondary="var(--gray-200)"
:radius="24"
:progress="progress"
:stroke="3"
/>
<div v-if="uploaded" v-html="frappe.utils.icon('solid-success', 'lg')"></div>
<div v-if="file.failed" v-html="frappe.utils.icon('solid-error', 'lg')"></div>
<div class="file-action-buttons">
<button
v-if="is_cropable"
class="btn btn-crop muted"
@click="emit('toggle_image_cropper')"
v-html="frappe.utils.icon('crop', 'md')"
></button>
<button
v-if="!uploaded && !file.uploading && !file.failed"
class="btn muted"
@click="emit('remove')"
v-html="frappe.utils.icon('delete', 'md')"
></button>
</div>
</div>
</div>
<div class="file-actions">
<ProgressRing
v-show="file.uploading && !uploaded && !file.failed"
primary="var(--primary-color)"
secondary="var(--gray-200)"
:radius="24"
:progress="progress"
:stroke="3"
/>
<div v-if="uploaded" v-html="frappe.utils.icon('solid-success', 'lg')"></div>
<div v-if="file.failed" v-html="frappe.utils.icon('solid-error', 'lg')"></div>
<div class="file-action-buttons">
<button
v-if="is_cropable"
class="btn btn-crop muted"
@click="emit('toggle_image_cropper')"
v-html="frappe.utils.icon('crop', 'md')"
></button>
<button
v-if="!uploaded && !file.uploading && !file.failed"
class="btn muted"
@click="emit('remove')"
v-html="frappe.utils.icon('delete', 'md')"
></button>
<div style="width: 100%">
<div v-if="file.error_message" class="alert alert-danger mb-0 mt-2" role="alert">
{{ file.error_message }}
</div>
<div
v-if="!file.private && !file.error_message"
class="alert alert-warning mb-0"
role="alert"
>
{{
__(
"This file is public and can be accessed by anyone, even without logging in. Mark it private to limit access."
)
}}
</div>
</div>
</div>
@ -114,6 +127,9 @@ let allow_toggle_optimize = computed(() => {
!props.file.failed
);
});
let allow_toggle_private = computed(() => {
return props.allow_toggle_private && !uploaded.value && !props.file.failed;
});
let is_cropable = computed(() => {
let croppable_types = ["image/jpeg", "image/png"];
return (
@ -144,24 +160,30 @@ onMounted(() => {
</script>
<style scoped>
.file-preview-outline {
padding: 0.75rem;
border: 1px solid transparent;
display: flex;
flex-direction: column;
}
.file-preview {
display: flex;
align-items: center;
padding: 0.75rem;
border: 1px solid transparent;
flex-direction: row;
}
.file-preview + .file-preview {
.file-preview-outline + .file-preview-outline {
border-top-color: var(--border-color);
}
.file-preview:hover {
.file-preview-outline:hover {
background-color: var(--bg-color);
border-color: var(--dark-border-color);
border-radius: var(--border-radius);
}
.file-preview:hover + .file-preview {
.file-preview-outline:hover + .file-preview-outline {
border-top-color: transparent;
}
@ -242,9 +264,4 @@ onMounted(() => {
.config-area {
gap: 0.5rem;
}
.file-error {
font-size: var(--text-sm);
font-weight: var(--text-bold);
}
</style>

View file

@ -595,23 +595,22 @@ function upload_file(file, i) {
}
} else if (xhr.status === 403) {
file.failed = true;
let response = JSON.parse(xhr.responseText);
file.error_message = `Not permitted. ${response._error_message || ""}.`;
try {
// Append server messages which are useful hint for perm issues
let server_messages = JSON.parse(response._server_messages);
server_messages.forEach((m) => {
m = JSON.parse(m);
file.error_message += `\n ${m.message} `;
});
} catch (e) {
console.warning("Failed to parse server message", e);
let response = parse_error_response(xhr.responseText);
file.error_message = `Not permitted. ${response.error_message || ""}.`;
if (response.server_messages.length) {
file.error_message += `\n${response.server_messages.join("\n")}`;
}
} else if (xhr.status === 413) {
file.failed = true;
file.error_message = "Size exceeds the maximum allowed file size.";
} else if (xhr.status === 417) {
// regular frappe.throw() in backend
file.failed = true;
file.error_message = null;
let response = parse_error_response(xhr.responseText);
if (response.server_messages.length) {
file.error_message = response.server_messages.join("\n");
}
} else {
file.failed = true;
file.error_message =
@ -679,6 +678,26 @@ function upload_file(file, i) {
xhr.send(form_data);
});
}
function parse_error_response(response_text) {
let response = JSON.parse(response_text);
let error_message = response._error_message;
let server_messages = [];
try {
server_messages.push(
...JSON.parse(response._server_messages).map((m) => {
let parsed = JSON.parse(m);
return parsed.message;
})
);
} catch (e) {
console.warning("Failed to parse server message", e);
}
return {
error_message,
server_messages,
};
}
function capture_image() {
const capture = new frappe.ui.Capture({
animate: false,