diff --git a/apps/channels/api_urls.py b/apps/channels/api_urls.py index fc02d7e5..4246373e 100644 --- a/apps/channels/api_urls.py +++ b/apps/channels/api_urls.py @@ -11,6 +11,7 @@ from .api_views import ( UpdateChannelMembershipAPIView, BulkUpdateChannelMembershipAPIView, RecordingViewSet, + GetChannelStreamsAPIView, ) app_name = 'channels' # for DRF routing @@ -27,6 +28,7 @@ urlpatterns = [ # Bulk delete is a single APIView, not a ViewSet path('streams/bulk-delete/', BulkDeleteStreamsAPIView.as_view(), name='bulk_delete_streams'), path('channels/bulk-delete/', BulkDeleteChannelsAPIView.as_view(), name='bulk_delete_channels'), + path('channels//streams/', GetChannelStreamsAPIView.as_view(), name='get_channel_streams'), path('profiles//channels//', UpdateChannelMembershipAPIView.as_view(), name='update_channel_membership'), path('profiles//channels/bulk-update/', BulkUpdateChannelMembershipAPIView.as_view(), name='bulk_update_channel_membership'), ] diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index b406fa07..95ad05a8 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -6,7 +6,7 @@ from rest_framework.decorators import action from rest_framework.parsers import MultiPartParser, FormParser from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, get_list_or_404 from django.db import transaction import os, json, requests @@ -88,6 +88,16 @@ class StreamViewSet(viewsets.ModelViewSet): return qs + def list(self, request, *args, **kwargs): + ids = request.query_params.get('ids', None) + if ids: + ids = ids.split(',') + streams = get_list_or_404(Stream, id__in=ids) + serializer = self.get_serializer(streams, many=True) + return Response(serializer.data) + + return super().list(request, *args, **kwargs) + @action(detail=False, methods=['get'], url_path='ids') def get_ids(self, request, *args, **kwargs): # Get the filtered queryset @@ -169,6 +179,12 @@ class ChannelViewSet(viewsets.ModelViewSet): return qs + def get_serializer_context(self): + context = super().get_serializer_context() + include_streams = self.request.query_params.get('include_streams', 'false') == 'true' + context['include_streams'] = include_streams + return context + @action(detail=False, methods=['get'], url_path='ids') def get_ids(self, request, *args, **kwargs): # Get the filtered queryset @@ -629,6 +645,13 @@ class ChannelProfileViewSet(viewsets.ModelViewSet): serializer_class = ChannelProfileSerializer permission_classes = [IsAuthenticated] +class GetChannelStreamsAPIView(APIView): + def get(self, request, channel_id): + channel = get_object_or_404(Channel, id=channel_id) + streams = channel.streams + serializer = StreamSerializer(streams, many=True) + return Response(serializer.data) + class UpdateChannelMembershipAPIView(APIView): def patch(self, request, profile_id, channel_id): """Enable or disable a channel for a specific group""" diff --git a/apps/channels/serializers.py b/apps/channels/serializers.py index 67386d4e..b1e2af46 100644 --- a/apps/channels/serializers.py +++ b/apps/channels/serializers.py @@ -134,10 +134,7 @@ class ChannelSerializer(serializers.ModelSerializer): required=False ) - streams = serializers.SerializerMethodField() - stream_ids = serializers.PrimaryKeyRelatedField( - queryset=Stream.objects.all(), many=True, write_only=True, required=False - ) + streams = serializers.PrimaryKeyRelatedField(queryset=Stream.objects.all(), many=True, required=False) logo_id = serializers.PrimaryKeyRelatedField( queryset=Logo.objects.all(), @@ -156,23 +153,25 @@ class ChannelSerializer(serializers.ModelSerializer): 'tvg_id', 'epg_data_id', 'streams', - 'stream_ids', 'stream_profile_id', 'uuid', 'logo_id', ] - def get_streams(self, obj): - """Retrieve ordered stream objects for GET requests.""" - ordered_streams = obj.streams.all().order_by('channelstream__order') - return StreamSerializer(ordered_streams, many=True).data + def to_representation(self, instance): + include_streams = self.context.get('include_streams', False) + + if include_streams: + self.fields['streams'] = serializers.SerializerMethodField() + + return super().to_representation(instance) def get_logo(self, obj): return LogoSerializer(obj.logo).data - # def get_stream_ids(self, obj): - # """Retrieve ordered stream IDs for GET requests.""" - # return list(obj.streams.all().order_by('channelstream__order').values_list('id', flat=True)) + def get_streams(self, obj): + """Retrieve ordered stream IDs for GET requests.""" + return StreamSerializer(obj.streams.all().order_by('channelstream__order'), many=True).data def create(self, validated_data): stream_ids = validated_data.pop('streams', []) @@ -185,8 +184,9 @@ class ChannelSerializer(serializers.ModelSerializer): ChannelStream.objects.create(channel=channel, stream_id=stream_id, order=index) return channel + def update(self, instance, validated_data): - stream_ids = validated_data.pop('stream_ids', None) + streams = validated_data.pop('streams', None) # Update standard fields for attr, value in validated_data.items(): @@ -194,12 +194,13 @@ class ChannelSerializer(serializers.ModelSerializer): instance.save() - if stream_ids is not None: + if streams is not None: # Normalize stream IDs normalized_ids = [ stream.id if hasattr(stream, "id") else stream - for stream in stream_ids + for stream in streams ] + print(normalized_ids) # Get current mapping of stream_id -> ChannelStream current_links = { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d8da7f76..af1881ff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,7 +23,6 @@ "@tanstack/react-table": "^8.21.2", "allotment": "^1.20.3", "axios": "^1.8.2", - "clsx": "^2.1.1", "dayjs": "^1.11.13", "formik": "^2.4.6", "hls.js": "^1.5.20", @@ -39,7 +38,6 @@ "react-virtualized": "^9.22.6", "react-virtualized-auto-sizer": "^1.0.26", "react-window": "^1.8.11", - "recharts": "^2.15.1", "video.js": "^8.21.0", "yup": "^1.6.1", "zustand": "^5.0.3" @@ -1102,25 +1100,29 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-color": "*" } @@ -1129,13 +1131,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-time": "*" } @@ -1145,6 +1149,7 @@ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-path": "*" } @@ -1153,13 +1158,15 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/estree": { "version": "1.0.6", @@ -1595,6 +1602,7 @@ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", + "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -1607,6 +1615,7 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -1616,6 +1625,7 @@ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=12" } @@ -1625,6 +1635,7 @@ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -1634,6 +1645,7 @@ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", + "peer": true, "dependencies": { "d3-color": "1 - 3" }, @@ -1646,6 +1658,7 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -1655,6 +1668,7 @@ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", + "peer": true, "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -1671,6 +1685,7 @@ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", + "peer": true, "dependencies": { "d3-path": "^3.1.0" }, @@ -1683,6 +1698,7 @@ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", + "peer": true, "dependencies": { "d3-array": "2 - 3" }, @@ -1695,6 +1711,7 @@ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", + "peer": true, "dependencies": { "d3-time": "1 - 3" }, @@ -1707,6 +1724,7 @@ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -1738,7 +1756,8 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/deep-is": { "version": "0.1.4", @@ -2108,6 +2127,7 @@ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0.0" } @@ -2457,6 +2477,7 @@ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -3206,7 +3227,8 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -3332,6 +3354,7 @@ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", "license": "MIT", + "peer": true, "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", @@ -3456,6 +3479,7 @@ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz", "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==", "license": "MIT", + "peer": true, "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", @@ -3479,6 +3503,7 @@ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", "license": "MIT", + "peer": true, "dependencies": { "decimal.js-light": "^2.4.1" } @@ -3487,7 +3512,8 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regenerator-runtime": { "version": "0.14.1", @@ -3683,7 +3709,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -3837,6 +3864,7 @@ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "license": "MIT AND ISC", + "peer": true, "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", diff --git a/frontend/package.json b/frontend/package.json index 3b287d79..6d946474 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,6 @@ "@tanstack/react-table": "^8.21.2", "allotment": "^1.20.3", "axios": "^1.8.2", - "clsx": "^2.1.1", "dayjs": "^1.11.13", "formik": "^2.4.6", "hls.js": "^1.5.20", @@ -41,7 +40,6 @@ "react-virtualized": "^9.22.6", "react-virtualized-auto-sizer": "^1.0.26", "react-window": "^1.8.11", - "recharts": "^2.15.1", "video.js": "^8.21.0", "yup": "^1.6.1", "zustand": "^5.0.3" diff --git a/frontend/src/api.js b/frontend/src/api.js index e3a1fb8f..b2619427 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -450,9 +450,15 @@ export default class API { } } - static async getStreams() { + static async getStreams(ids = null) { try { - const response = await request(`${host}/api/channels/streams/`); + const params = new URLSearchParams(); + if (ids) { + params.append('ids', ids.join(',')); + } + const response = await request( + `${host}/api/channels/streams/?${params.toString()}` + ); return response; } catch (e) { @@ -460,6 +466,18 @@ export default class API { } } + static async getChannelStreams(id) { + try { + const response = await request( + `${host}/api/channels/channels/${id}/streams/` + ); + + return response; + } catch (e) { + errorNotification('Failed to retrieve channel streams', e); + } + } + static async queryStreams(params) { try { const response = await request( diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index da03dfa3..9a670b59 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -139,7 +139,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => { response = await API.updateChannel({ id: channel.id, ...otherValues, - stream_ids: channelStreams.map((stream) => stream.id), + streams: channelStreams.map((stream) => stream.id), }); } } else { @@ -147,14 +147,14 @@ const Channel = ({ channel = null, isOpen, onClose }) => { response = await API.updateChannel({ id: channel.id, ...formattedValues, - stream_ids: channelStreams.map((stream) => stream.id), + streams: channelStreams.map((stream) => stream.id), }); } } else { // New channel creation - use the standard method response = await API.addChannel({ ...formattedValues, - stream_ids: channelStreams.map((stream) => stream.id), + streams: channelStreams.map((stream) => stream.id), }); } } catch (error) { diff --git a/frontend/src/components/tables/ChannelTableStreams.jsx b/frontend/src/components/tables/ChannelTableStreams.jsx index dae9d2da..e9926b5f 100644 --- a/frontend/src/components/tables/ChannelTableStreams.jsx +++ b/frontend/src/components/tables/ChannelTableStreams.jsx @@ -133,7 +133,7 @@ const ChannelStreams = ({ channel, isExpanded }) => { const newStreamList = data.filter((s) => s.id !== stream.id); await API.updateChannel({ ...channel, - stream_ids: newStreamList.map((s) => s.id), + streams: newStreamList.map((s) => s.id), }); await API.requeryChannels(); }; @@ -203,7 +203,7 @@ const ChannelStreams = ({ channel, isExpanded }) => { const { streams: _, ...channelUpdate } = channel; API.updateChannel({ ...channelUpdate, - stream_ids: retval.map((row) => row.id), + streams: retval.map((row) => row.id), }).then(() => { API.requeryChannels(); }); diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 1525dde2..dfdfbebd 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -198,6 +198,7 @@ const ChannelsTable = ({}) => { const sorting = useChannelsTableStore((s) => s.sorting); const setSorting = useChannelsTableStore((s) => s.setSorting); const totalCount = useChannelsTableStore((s) => s.totalCount); + const setChannelStreams = useChannelsTableStore((s) => s.setChannelStreams); // store/channels const channels = useChannelsStore((s) => s.channels); @@ -264,6 +265,7 @@ const ChannelsTable = ({}) => { const params = new URLSearchParams(); params.append('page', pagination.pageIndex + 1); params.append('page_size', pagination.pageSize); + params.append('include_streams', 'true'); // Apply sorting if (sorting.length > 0) { @@ -277,8 +279,10 @@ const ChannelsTable = ({}) => { if (value) params.append(key, value); }); - const results = await API.queryChannels(params); - const ids = await API.getAllChannelIds(params); + const [results, ids] = await Promise.all([ + await API.queryChannels(params), + await API.getAllChannelIds(params), + ]); setTablePrefs({ pageSize: pagination.pageSize, diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index 43b9a532..b7645e1a 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -52,7 +52,7 @@ const StreamRowActions = ({ handleWatchStream, selectedChannelIds, }) => { - const channelSelectionStreams = useChannelsTableStore( + const channelSelectionStreamIds = useChannelsTableStore( (state) => state.channels.find((chan) => chan.id === selectedChannelIds[0])?.streams ); @@ -71,12 +71,8 @@ const StreamRowActions = ({ const addStreamToChannel = async () => { await API.updateChannel({ id: selectedChannelIds[0], - stream_ids: [ - ...new Set( - channelSelectionStreams - .map((stream) => stream.id) - .concat([row.original.id]) - ), + streams: [ + ...new Set(channelSelectionStreamIds.concat([row.original.id])), ], }); await API.requeryChannels(); @@ -105,10 +101,8 @@ const StreamRowActions = ({ style={{ background: 'none' }} disabled={ selectedChannelIds.length !== 1 || - (channelSelectionStreams && - channelSelectionStreams - .map((stream) => stream.id) - .includes(row.original.id)) + (channelSelectionStreamIds && + channelSelectionStreamIds.includes(row.original.id)) } > @@ -198,7 +192,7 @@ const StreamsTable = ({}) => { const channelGroups = useChannelsStore((s) => s.channelGroups); const selectedChannelIds = useChannelsTableStore((s) => s.selectedChannelIds); const fetchLogos = useChannelsStore((s) => s.fetchLogos); - const channelSelectionStreams = useChannelsTableStore( + const channelSelectionStreamIds = useChannelsTableStore( (state) => state.channels.find((chan) => chan.id === selectedChannelIds[0])?.streams ); @@ -322,11 +316,16 @@ const StreamsTable = ({}) => { }); try { - const result = await API.queryStreams(params); - const ids = await API.getAllStreamIds(params); + const [result, ids, groups] = await Promise.all([ + API.queryStreams(params), + API.getAllStreamIds(params), + API.getStreamGroups(), + ]); + setAllRowIds(ids); setData(result.results); setPageCount(Math.ceil(result.count / pagination.pageSize)); + setGroupOptions(groups); // Calculate the starting and ending item indexes const startItem = pagination.pageIndex * pagination.pageSize + 1; // +1 to start from 1, not 0 @@ -345,9 +344,6 @@ const StreamsTable = ({}) => { console.error('Error fetching data:', error); } - const groups = await API.getStreamGroups(); - setGroupOptions(groups); - setIsLoading(false); }, [pagination, sorting, debouncedFilters]); @@ -388,12 +384,8 @@ const StreamsTable = ({}) => { const addStreamsToChannel = async () => { await API.updateChannel({ id: selectedChannelIds[0], - stream_ids: [ - ...new Set( - channelSelectionStreams - .map((stream) => stream.id) - .concat(selectedStreamIds) - ), + streams: [ + ...new Set(channelSelectionStreamIds.concat(selectedStreamIds)), ], }); await API.requeryChannels(); @@ -540,12 +532,11 @@ const StreamsTable = ({}) => { deleteStream={deleteStream} handleWatchStream={handleWatchStream} selectedChannelIds={selectedChannelIds} - channelSelectionStreams={channelSelectionStreams} /> ); } }, - [selectedChannelIds, channelSelectionStreams] + [selectedChannelIds, channelSelectionStreamIds] ); const table = useTable({