mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 13:32:06 +07:00
fix: switch attachment download to secure axios blob fetch
This commit is contained in:
@@ -214,12 +214,36 @@ export default function AdminTicketDetailsClient() {
|
|||||||
{reply.attachments && reply.attachments.length > 0 && (
|
{reply.attachments && reply.attachments.length > 0 && (
|
||||||
<div className="mt-3 space-y-2">
|
<div className="mt-3 space-y-2">
|
||||||
{reply.attachments.map((att: any) => (
|
{reply.attachments.map((att: any) => (
|
||||||
<a
|
<button
|
||||||
key={att.id}
|
key={att.id}
|
||||||
href={att.download_url || getAttachmentUrl(att.file_path)}
|
onClick={async (e) => {
|
||||||
target="_blank"
|
e.preventDefault();
|
||||||
rel="noopener noreferrer"
|
if (att.download_url) {
|
||||||
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors ${
|
try {
|
||||||
|
const response = await axios.get(att.download_url, {
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', att.file_name);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage = parseApiError(err, t("toast_download_failed") || "Download failed");
|
||||||
|
if (err.response?.status === 404) {
|
||||||
|
addToast("File not found on server.", "error");
|
||||||
|
} else {
|
||||||
|
addToast(errorMessage, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.open(getAttachmentUrl(att.file_path), '_blank');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors text-left ${
|
||||||
isMe
|
isMe
|
||||||
? 'bg-white/10 hover:bg-white/20 text-white'
|
? 'bg-white/10 hover:bg-white/20 text-white'
|
||||||
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
|
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
|
||||||
@@ -227,7 +251,7 @@ export default function AdminTicketDetailsClient() {
|
|||||||
>
|
>
|
||||||
<FileText size={14} />
|
<FileText size={14} />
|
||||||
<span className="truncate max-w-[150px]">{att.file_name}</span>
|
<span className="truncate max-w-[150px]">{att.file_name}</span>
|
||||||
</a>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -228,12 +228,41 @@ export default function TicketDetailsClient() {
|
|||||||
{reply.attachments && reply.attachments.length > 0 && (
|
{reply.attachments && reply.attachments.length > 0 && (
|
||||||
<div className="mt-3 space-y-2">
|
<div className="mt-3 space-y-2">
|
||||||
{reply.attachments.map((att: any) => (
|
{reply.attachments.map((att: any) => (
|
||||||
<a
|
<button
|
||||||
key={att.id}
|
key={att.id}
|
||||||
href={att.download_url || getAttachmentUrl(att.file_path)}
|
onClick={async (e) => {
|
||||||
target="_blank"
|
e.preventDefault();
|
||||||
rel="noopener noreferrer"
|
if (att.download_url) {
|
||||||
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors ${
|
try {
|
||||||
|
// Use axios to fetch with auth headers
|
||||||
|
const response = await axios.get(att.download_url, {
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create object URL and trigger download
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', att.file_name);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage = parseApiError(err, t("toast_download_failed") || "Download failed");
|
||||||
|
// If 404, file might actually be missing
|
||||||
|
if (err.response?.status === 404) {
|
||||||
|
addToast("File not found on server.", "error");
|
||||||
|
} else {
|
||||||
|
addToast(errorMessage, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for legacy local files
|
||||||
|
window.open(getAttachmentUrl(att.file_path), '_blank');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors text-left ${
|
||||||
isMe
|
isMe
|
||||||
? 'bg-white/10 hover:bg-white/20 text-white'
|
? 'bg-white/10 hover:bg-white/20 text-white'
|
||||||
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
|
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
|
||||||
@@ -241,7 +270,7 @@ export default function TicketDetailsClient() {
|
|||||||
>
|
>
|
||||||
<FileText size={14} />
|
<FileText size={14} />
|
||||||
<span className="truncate max-w-[150px]">{att.file_name}</span>
|
<span className="truncate max-w-[150px]">{att.file_name}</span>
|
||||||
</a>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user