From 6a9b5282cd55567349b81594e7cb754da69dab71 Mon Sep 17 00:00:00 2001 From: Adrian Mace <5071859+adrianmace@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:39:30 +1100 Subject: [PATCH 01/26] fix: allow all IPv6 CIDRs by default This change ensures that by default, IPv6 clients can connect to the service unless explicitly denied. Fixes #593 --- dispatcharr/utils.py | 2 +- frontend/src/pages/Settings.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dispatcharr/utils.py b/dispatcharr/utils.py index 260515fc..56243b7a 100644 --- a/dispatcharr/utils.py +++ b/dispatcharr/utils.py @@ -44,7 +44,7 @@ def network_access_allowed(request, settings_key): cidrs = ( network_access[settings_key].split(",") if settings_key in network_access - else ["0.0.0.0/0"] + else ["0.0.0.0/0", "::/0"] ) network_allowed = False diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 10f6f5a2..5c25897a 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -278,7 +278,7 @@ const SettingsPage = () => { const networkAccessForm = useForm({ mode: 'controlled', initialValues: Object.keys(NETWORK_ACCESS_OPTIONS).reduce((acc, key) => { - acc[key] = '0.0.0.0/0,::0/0'; + acc[key] = '0.0.0.0/0,::/0'; return acc; }, {}), validate: Object.keys(NETWORK_ACCESS_OPTIONS).reduce((acc, key) => { @@ -358,7 +358,7 @@ const SettingsPage = () => { ); networkAccessForm.setValues( Object.keys(NETWORK_ACCESS_OPTIONS).reduce((acc, key) => { - acc[key] = networkAccessSettings[key] || '0.0.0.0/0,::0/0'; + acc[key] = networkAccessSettings[key] || '0.0.0.0/0,::/0'; return acc; }, {}) ); From 43949c3ef432d14fb9add2c7dca56aa3709959a6 Mon Sep 17 00:00:00 2001 From: 3l3m3nt Date: Sun, 30 Nov 2025 19:30:47 +1300 Subject: [PATCH 02/26] Added IPv6 port bind to nginx.conf --- docker/nginx.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/nginx.conf b/docker/nginx.conf index 5e754d20..020bc99a 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -3,6 +3,7 @@ proxy_cache_path /app/logo_cache levels=1:2 keys_zone=logo_cache:10m server { listen NGINX_PORT; + listen [::]:NGINX_PORT; proxy_connect_timeout 75; proxy_send_timeout 300; From 641dcfc21e40cb05d42123f0276ebcc705c46608 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sun, 30 Nov 2025 19:20:25 +0000 Subject: [PATCH 03/26] Add sorting functionality to Group and M3U columns in Streams table - Added m3u_account__name to backend ordering_fields in StreamViewSet - Implemented field mapping in frontend to convert column IDs to backend field names - Added sort buttons to both Group and M3U columns with proper icons - Sort buttons show current sort state (ascending/descending/none) - Maintains consistent UX with existing Name column sorting --- apps/channels/api_views.py | 2 +- .../src/components/tables/StreamsTable.jsx | 87 ++++++++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index bc920537..eccc5028 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -124,7 +124,7 @@ class StreamViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_class = StreamFilter search_fields = ["name", "channel_group__name"] - ordering_fields = ["name", "channel_group__name"] + ordering_fields = ["name", "channel_group__name", "m3u_account__name"] ordering = ["-name"] def get_permissions(self): diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index d309552c..a0ae1f5e 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -385,7 +385,14 @@ const StreamsTable = () => { // Apply sorting if (sorting.length > 0) { - const sortField = sorting[0].id; + const columnId = sorting[0].id; + // Map frontend column IDs to backend field names + const fieldMapping = { + name: 'name', + group: 'channel_group__name', + m3u: 'm3u_account__name', + }; + const sortField = fieldMapping[columnId] || columnId; const sortDirection = sorting[0].desc ? '-' : ''; params.append('ordering', `${sortDirection}${sortField}`); } @@ -747,41 +754,57 @@ const StreamsTable = () => { case 'group': return ( - - - + + + + +
+ {React.createElement(sortingIcon, { + onClick: () => onSortingChange('group'), + size: 14, + })} +
+
); case 'm3u': return ( - - ({ + label: playlist.name, + value: `${playlist.id}`, + }))} + variant="unstyled" + className="table-input-header" + /> + +
+ {React.createElement(sortingIcon, { + onClick: () => onSortingChange('m3u'), + size: 14, + })} +
+ ); } }; From cf08e54bd822e38e42779e7702d774807a92571f Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Dec 2025 18:11:58 +0000 Subject: [PATCH 04/26] Fix sorting functionality for Group and M3U columns - Add missing header properties to group and m3u columns - Fix layout issues with sort buttons (proper flex layout, remove blocking onClick) - Fix sorting state initialization (use boolean instead of empty string) - Fix sorting comparison operators (use strict equality) - Fix 3rd click behavior to return to default sort instead of clearing - Map frontend column IDs to backend field names for proper API requests --- .../src/components/tables/StreamsTable.jsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index a0ae1f5e..ca3ee21f 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -182,7 +182,7 @@ const StreamsTable = () => { const [pageCount, setPageCount] = useState(0); const [paginationString, setPaginationString] = useState(''); const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([{ id: 'name', desc: '' }]); + const [sorting, setSorting] = useState([{ id: 'name', desc: true }]); const [selectedStreamIds, setSelectedStreamIds] = useState([]); // Channel numbering modal state @@ -298,6 +298,7 @@ const StreamsTable = () => { ), }, { + header: 'Group', id: 'group', accessorFn: (row) => channelGroups[row.channel_group] @@ -319,6 +320,7 @@ const StreamsTable = () => { ), }, { + header: 'M3U', id: 'm3u', size: columnSizing.m3u || 150, accessorFn: (row) => @@ -698,8 +700,8 @@ const StreamsTable = () => { const sortField = sorting[0]?.id; const sortDirection = sorting[0]?.desc; - if (sortField == column) { - if (sortDirection == false) { + if (sortField === column) { + if (sortDirection === false) { setSorting([ { id: column, @@ -707,7 +709,8 @@ const StreamsTable = () => { }, ]); } else { - setSorting([]); + // Reset to default sort (name descending) instead of clearing + setSorting([{ id: 'name', desc: true }]); } } else { setSorting([ @@ -754,8 +757,8 @@ const StreamsTable = () => { case 'group': return ( - - + + { clearable /> -
+
{React.createElement(sortingIcon, { onClick: () => onSortingChange('group'), size: 14, @@ -780,8 +783,8 @@ const StreamsTable = () => { case 'm3u': return ( - - + + ({ - label: playlist.name, - value: `${playlist.id}`, - }))} - variant="unstyled" - className="table-input-header" - /> - -
- {React.createElement(sortingIcon, { - onClick: () => onSortingChange('m3u'), + +