Fixes logo uploads

This commit is contained in:
SergeantPanda 2025-07-17 21:02:50 -05:00
parent bd1831e226
commit 8e2309ac58
6 changed files with 119 additions and 22 deletions

View file

@ -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,
)

View file

@ -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):

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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,