diff --git a/CHANGELOG.md b/CHANGELOG.md index e363135f..4716c250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed - -- nginx now gracefully handles hosts without IPv6 support by automatically disabling IPv6 binding at startup -- XtreamCodes EPG API now returns correct date/time format for start/end fields and proper string types for timestamps and channel_id - ## [0.14.0] - 2025-12-09 ### Added diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 40063245..eccc5028 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -8,7 +8,6 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from django.shortcuts import get_object_or_404, get_list_or_404 from django.db import transaction -from django.db.models import Q import os, json, requests, logging from apps.accounts.permissions import ( Authenticated, @@ -420,36 +419,10 @@ class ChannelViewSet(viewsets.ModelViewSet): group_names = channel_group.split(",") qs = qs.filter(channel_group__name__in=group_names) - filters = {} - q_filters = Q() - - channel_profile_id = self.request.query_params.get("channel_profile_id") - show_disabled_param = self.request.query_params.get("show_disabled", None) - only_streamless = self.request.query_params.get("only_streamless", None) - - if channel_profile_id: - try: - profile_id_int = int(channel_profile_id) - filters["channelprofilemembership__channel_profile_id"] = profile_id_int - - if show_disabled_param is None: - filters["channelprofilemembership__enabled"] = True - except (ValueError, TypeError): - # Ignore invalid profile id values - pass - - if only_streamless: - q_filters &= Q(streams__isnull=True) - if self.request.user.user_level < 10: - filters["user_level__lte"] = self.request.user.user_level + qs = qs.filter(user_level__lte=self.request.user.user_level) - if filters: - qs = qs.filter(**filters) - if q_filters: - qs = qs.filter(q_filters) - - return qs.distinct() + return qs def get_serializer_context(self): context = super().get_serializer_context() diff --git a/apps/output/views.py b/apps/output/views.py index 3a8406cb..bc2bace5 100644 --- a/apps/output/views.py +++ b/apps/output/views.py @@ -2316,18 +2316,18 @@ def xc_get_epg(request, user, short=False): "epg_id": f"{epg_id}", "title": base64.b64encode(title.encode()).decode(), "lang": "", - "start": start.strftime("%Y-%m-%d %H:%M:%S"), - "end": end.strftime("%Y-%m-%d %H:%M:%S"), + "start": start.strftime("%Y%m%d%H%M%S"), + "end": end.strftime("%Y%m%d%H%M%S"), "description": base64.b64encode(description.encode()).decode(), - "channel_id": str(channel_num_int), - "start_timestamp": str(int(start.timestamp())), - "stop_timestamp": str(int(end.timestamp())), + "channel_id": channel_num_int, + "start_timestamp": int(start.timestamp()), + "stop_timestamp": int(end.timestamp()), "stream_id": f"{channel_id}", } if short == False: program_output["now_playing"] = 1 if start <= django_timezone.now() <= end else 0 - program_output["has_archive"] = 0 + program_output["has_archive"] = "0" output['epg_listings'].append(program_output) diff --git a/docker/init/03-init-dispatcharr.sh b/docker/init/03-init-dispatcharr.sh index da7d4484..5fbef23d 100644 --- a/docker/init/03-init-dispatcharr.sh +++ b/docker/init/03-init-dispatcharr.sh @@ -29,16 +29,8 @@ if [ "$(id -u)" = "0" ] && [ -d "/app" ]; then chown $PUID:$PGID /app fi fi -# Configure nginx port -sed -i "s/NGINX_PORT/${DISPATCHARR_PORT}/g" /etc/nginx/sites-enabled/default -# Configure nginx based on IPv6 availability -if ip -6 addr show | grep -q "inet6"; then - echo "✅ IPv6 is available, enabling IPv6 in nginx" -else - echo "⚠️ IPv6 not available, disabling IPv6 in nginx" - sed -i '/listen \[::\]:/d' /etc/nginx/sites-enabled/default -fi +sed -i "s/NGINX_PORT/${DISPATCHARR_PORT}/g" /etc/nginx/sites-enabled/default # NOTE: mac doesn't run as root, so only manage permissions # if this script is running as root diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 7f82140f..9b9958f7 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -68,7 +68,7 @@ const epgUrlBase = `${window.location.protocol}//${window.location.host}/output/ const hdhrUrlBase = `${window.location.protocol}//${window.location.host}/hdhr`; const ChannelEnabledSwitch = React.memo( - ({ rowId, selectedProfileId, selectedTableIds, setSelectedTableIds }) => { + ({ rowId, selectedProfileId, selectedTableIds }) => { // Directly extract the channels set once to avoid re-renders on every change. const isEnabled = useChannelsStore( useCallback( @@ -79,20 +79,16 @@ const ChannelEnabledSwitch = React.memo( ) ); - const handleToggle = async () => { + const handleToggle = () => { if (selectedTableIds.length > 1) { - await API.updateProfileChannels( + API.updateProfileChannels( selectedTableIds, selectedProfileId, !isEnabled ); } else { - await API.updateProfileChannel(rowId, selectedProfileId, !isEnabled); + API.updateProfileChannel(rowId, selectedProfileId, !isEnabled); } - - setSelectedTableIds([]); - - return API.requeryChannels(); }; return ( @@ -293,9 +289,6 @@ const ChannelsTable = ({}) => { const [selectedProfile, setSelectedProfile] = useState( profiles[selectedProfileId] ); - const [showDisabled, setShowDisabled] = useState(true); - const [showOnlyStreamlessChannels, setShowOnlyStreamlessChannels] = - useState(false); const [paginationString, setPaginationString] = useState(''); const [filters, setFilters] = useState({ @@ -376,15 +369,6 @@ const ChannelsTable = ({}) => { params.append('page', pagination.pageIndex + 1); params.append('page_size', pagination.pageSize); params.append('include_streams', 'true'); - if (selectedProfileId !== '0') { - params.append('channel_profile_id', selectedProfileId); - } - if (showDisabled === true) { - params.append('show_disabled', true); - } - if (showOnlyStreamlessChannels === true) { - params.append('only_streamless', true); - } // Apply sorting if (sorting.length > 0) { @@ -417,14 +401,7 @@ const ChannelsTable = ({}) => { pageSize: pagination.pageSize, }); setAllRowIds(ids); - }, [ - pagination, - sorting, - debouncedFilters, - showDisabled, - selectedProfileId, - showOnlyStreamlessChannels, - ]); + }, [pagination, sorting, debouncedFilters]); const stopPropagation = useCallback((e) => { e.stopPropagation(); @@ -751,7 +728,6 @@ const ChannelsTable = ({}) => { rowId={row.original.id} selectedProfileId={selectedProfileId} selectedTableIds={table.getState().selectedTableIds} - setSelectedTableIds={table.setSelectedTableIds} /> ); }, @@ -1350,10 +1326,6 @@ const ChannelsTable = ({}) => { deleteChannels={deleteChannels} selectedTableIds={table.selectedTableIds} table={table} - showDisabled={showDisabled} - setShowDisabled={setShowDisabled} - showOnlyStreamlessChannels={showOnlyStreamlessChannels} - setShowOnlyStreamlessChannels={setShowOnlyStreamlessChannels} /> {/* Table or ghost empty state inside Paper */} diff --git a/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx b/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx index 460ab12a..b7e04d7d 100644 --- a/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx +++ b/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx @@ -12,22 +12,20 @@ import { Text, TextInput, Tooltip, + UnstyledButton, useMantineTheme, } from '@mantine/core'; import { ArrowDown01, Binary, + Check, CircleCheck, + Ellipsis, EllipsisVertical, SquareMinus, SquarePen, SquarePlus, Settings, - Eye, - EyeOff, - Filter, - Square, - SquareCheck, } from 'lucide-react'; import API from '../../../api'; import { notifications } from '@mantine/notifications'; @@ -104,10 +102,6 @@ const ChannelTableHeader = ({ editChannel, deleteChannels, selectedTableIds, - showDisabled, - setShowDisabled, - showOnlyStreamlessChannels, - setShowOnlyStreamlessChannels, }) => { const theme = useMantineTheme(); @@ -214,14 +208,6 @@ const ChannelTableHeader = ({ ); }; - const toggleShowDisabled = () => { - setShowDisabled(!showDisabled); - }; - - const toggleShowOnlyStreamlessChannels = () => { - setShowOnlyStreamlessChannels(!showOnlyStreamlessChannels); - }; - return ( @@ -250,41 +236,6 @@ const ChannelTableHeader = ({ }} > - - - - - - - : - } - disabled={selectedProfileId === '0'} - > - - {showDisabled ? 'Hide Disabled' : 'Show Disabled'} - - - - - ) : ( - - ) - } - > - Only Empty Channels - - - -