forked from Mirrors/Dispatcharr
Fixes logo uploads
This commit is contained in:
parent
bd1831e226
commit
8e2309ac58
6 changed files with 119 additions and 22 deletions
|
|
@ -1172,6 +1172,16 @@ class LogoViewSet(viewsets.ModelViewSet):
|
|||
)
|
||||
|
||||
file = request.FILES["file"]
|
||||
|
||||
# Validate file
|
||||
try:
|
||||
from dispatcharr.utils import validate_logo_file
|
||||
validate_logo_file(file)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
file_name = file.name
|
||||
file_path = os.path.join("/data/logos", file_name)
|
||||
|
||||
|
|
@ -1187,8 +1197,10 @@ class LogoViewSet(viewsets.ModelViewSet):
|
|||
},
|
||||
)
|
||||
|
||||
# Use get_serializer to ensure proper context
|
||||
serializer = self.get_serializer(logo)
|
||||
return Response(
|
||||
LogoSerializer(logo, context={'request': request}).data,
|
||||
serializer.data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ def json_success_response(data=None, status=200):
|
|||
|
||||
def validate_logo_file(file):
|
||||
"""Validate uploaded logo file size and MIME type."""
|
||||
valid_mime_types = ["image/jpeg", "image/png", "image/gif"]
|
||||
valid_mime_types = ["image/jpeg", "image/png", "image/gif", "image/webp"]
|
||||
if file.content_type not in valid_mime_types:
|
||||
raise ValidationError("Unsupported file type. Allowed types: JPEG, PNG, GIF.")
|
||||
if file.size > 2 * 1024 * 1024:
|
||||
raise ValidationError("File too large. Max 2MB.")
|
||||
raise ValidationError("Unsupported file type. Allowed types: JPEG, PNG, GIF, WebP.")
|
||||
if file.size > 5 * 1024 * 1024: # Increased to 5MB
|
||||
raise ValidationError("File too large. Max 5MB.")
|
||||
|
||||
|
||||
def get_client_ip(request):
|
||||
|
|
|
|||
|
|
@ -209,10 +209,10 @@ export default class API {
|
|||
API.getAllChannelIds(API.lastQueryParams),
|
||||
]);
|
||||
|
||||
useChannelsTable
|
||||
useChannelsTableStore
|
||||
.getState()
|
||||
.queryChannels(response, API.lastQueryParams);
|
||||
useChannelsTable.getState().setAllQueryIds(ids);
|
||||
useChannelsTableStore.getState().setAllQueryIds(ids);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
|
|
@ -1252,16 +1252,48 @@ export default class API {
|
|||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await request(`${host}/api/channels/logos/upload/`, {
|
||||
// Add timeout handling for file uploads
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
|
||||
|
||||
const response = await fetch(`${host}/api/channels/logos/upload/`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
Authorization: `Bearer ${await API.getAuthToken()}`,
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
useChannelsStore.getState().addLogo(response);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
return response;
|
||||
if (!response.ok) {
|
||||
const error = new Error(`HTTP error! Status: ${response.status}`);
|
||||
let errorBody = await response.text();
|
||||
|
||||
try {
|
||||
errorBody = JSON.parse(errorBody);
|
||||
} catch (e) {
|
||||
// If parsing fails, leave errorBody as the raw text
|
||||
}
|
||||
|
||||
error.status = response.status;
|
||||
error.response = response;
|
||||
error.body = errorBody;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
useChannelsStore.getState().addLogo(result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e.name === 'AbortError') {
|
||||
const timeoutError = new Error('Upload timed out. Please try again.');
|
||||
timeoutError.code = 'NETWORK_ERROR';
|
||||
throw timeoutError;
|
||||
}
|
||||
errorNotification('Failed to upload logo', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
Image,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { ListOrdered, SquarePlus, SquareX, X } from 'lucide-react';
|
||||
import useEPGsStore from '../../store/epgs';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
|
|
@ -84,10 +85,28 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => {
|
|||
|
||||
const handleLogoChange = async (files) => {
|
||||
if (files.length === 1) {
|
||||
const retval = await API.uploadLogo(files[0]);
|
||||
await fetchLogos();
|
||||
setLogoPreview(retval.cache_url);
|
||||
formik.setFieldValue('logo_id', retval.id);
|
||||
const file = files[0];
|
||||
|
||||
// Validate file size on frontend first
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
// 5MB
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'File too large. Maximum size is 5MB.',
|
||||
color: 'red',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const retval = await API.uploadLogo(file);
|
||||
await fetchLogos();
|
||||
setLogoPreview(retval.cache_url);
|
||||
formik.setFieldValue('logo_id', retval.id);
|
||||
} catch (error) {
|
||||
console.error('Logo upload failed:', error);
|
||||
// Error notification is already handled in API.uploadLogo
|
||||
}
|
||||
} else {
|
||||
setLogoPreview(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
import { ListOrdered, SquarePlus, SquareX, X } from 'lucide-react';
|
||||
import useEPGsStore from '../../store/epgs';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
|
||||
const ChannelsForm = ({ channel = null, isOpen, onClose }) => {
|
||||
|
|
@ -81,10 +82,28 @@ const ChannelsForm = ({ channel = null, isOpen, onClose }) => {
|
|||
|
||||
const handleLogoChange = async (files) => {
|
||||
if (files.length === 1) {
|
||||
const retval = await API.uploadLogo(files[0]);
|
||||
await fetchLogos();
|
||||
setLogoPreview(retval.cache_url);
|
||||
formik.setFieldValue('logo_id', retval.id);
|
||||
const file = files[0];
|
||||
|
||||
// Validate file size on frontend first
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
// 5MB
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'File too large. Maximum size is 5MB.',
|
||||
color: 'red',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const retval = await API.uploadLogo(file);
|
||||
await fetchLogos();
|
||||
setLogoPreview(retval.cache_url);
|
||||
formik.setFieldValue('logo_id', retval.id);
|
||||
} catch (error) {
|
||||
console.error('Logo upload failed:', error);
|
||||
// Error notification is already handled in API.uploadLogo
|
||||
}
|
||||
} else {
|
||||
setLogoPreview(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ const LogoForm = ({ logo = null, isOpen, onClose }) => {
|
|||
onClose();
|
||||
} catch (error) {
|
||||
let errorMessage = logo ? 'Failed to update logo' : 'Failed to create logo';
|
||||
|
||||
|
||||
// Handle specific timeout errors
|
||||
if (error.code === 'NETWORK_ERROR' || error.message?.includes('timeout')) {
|
||||
errorMessage = 'Request timed out. Please try again.';
|
||||
}
|
||||
|
||||
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: errorMessage,
|
||||
|
|
@ -85,6 +85,17 @@ const LogoForm = ({ logo = null, isOpen, onClose }) => {
|
|||
if (files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
|
||||
// Validate file size on frontend first
|
||||
if (file.size > 5 * 1024 * 1024) { // 5MB
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'File too large. Maximum size is 5MB.',
|
||||
color: 'red',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
|
|
@ -102,12 +113,16 @@ const LogoForm = ({ logo = null, isOpen, onClose }) => {
|
|||
});
|
||||
} catch (error) {
|
||||
let errorMessage = 'Failed to upload logo';
|
||||
|
||||
|
||||
// Handle specific timeout errors
|
||||
if (error.code === 'NETWORK_ERROR' || error.message?.includes('timeout')) {
|
||||
errorMessage = 'Upload timed out. Please try again.';
|
||||
} else if (error.status === 413) {
|
||||
errorMessage = 'File too large. Please choose a smaller file.';
|
||||
} else if (error.body?.error) {
|
||||
errorMessage = error.body.error;
|
||||
}
|
||||
|
||||
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: errorMessage,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue