diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py
index 2cbde99b..43f0eca6 100644
--- a/apps/channels/api_views.py
+++ b/apps/channels/api_views.py
@@ -588,40 +588,47 @@ class ChannelViewSet(viewsets.ModelViewSet):
channel_group = stream.channel_group
name = request.data.get("name")
+
+
if name is None:
name = stream.name
# Check if client provided a channel_number; if not, auto-assign one.
stream_custom_props = stream.custom_properties or {}
-
- channel_number = None
- if "tvg-chno" in stream_custom_props:
- channel_number = float(stream_custom_props["tvg-chno"])
- elif "channel-number" in stream_custom_props:
- channel_number = float(stream_custom_props["channel-number"])
- elif "num" in stream_custom_props:
- channel_number = float(stream_custom_props["num"])
+ channel_number = request.data.get("channel_number")
if channel_number is None:
- provided_number = request.data.get("channel_number")
- if provided_number is None:
- channel_number = Channel.get_next_available_channel_number()
- else:
- try:
- channel_number = float(provided_number)
- except ValueError:
- return Response(
- {"error": "channel_number must be an integer."},
- status=status.HTTP_400_BAD_REQUEST,
- )
- # If the provided number is already used, return an error.
- if Channel.objects.filter(channel_number=channel_number).exists():
- return Response(
- {
- "error": f"Channel number {channel_number} is already in use. Please choose a different number."
- },
- status=status.HTTP_400_BAD_REQUEST,
- )
+ # Channel number not provided by client, check stream properties or auto-assign
+ if "tvg-chno" in stream_custom_props:
+ channel_number = float(stream_custom_props["tvg-chno"])
+ elif "channel-number" in stream_custom_props:
+ channel_number = float(stream_custom_props["channel-number"])
+ elif "num" in stream_custom_props:
+ channel_number = float(stream_custom_props["num"])
+ elif channel_number == 0:
+ # Special case: 0 means ignore provider numbers and auto-assign
+ channel_number = None
+
+ if channel_number is None:
+ # Still None, auto-assign the next available channel number
+ channel_number = Channel.get_next_available_channel_number()
+
+
+ try:
+ channel_number = float(channel_number)
+ except ValueError:
+ return Response(
+ {"error": "channel_number must be an integer."},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
+ # If the provided number is already used, return an error.
+ if Channel.objects.filter(channel_number=channel_number).exists():
+ return Response(
+ {
+ "error": f"Channel number {channel_number} is already in use. Please choose a different number."
+ },
+ status=status.HTTP_400_BAD_REQUEST,
+ )
# Get the tvc_guide_stationid from custom properties if it exists
tvc_guide_stationid = None
if "tvc-guide-stationid" in stream_custom_props:
diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx
index 0214c7cd..ef944bbd 100644
--- a/frontend/src/components/tables/StreamsTable.jsx
+++ b/frontend/src/components/tables/StreamsTable.jsx
@@ -59,6 +59,7 @@ const StreamRowActions = ({
deleteStream,
handleWatchStream,
selectedChannelIds,
+ createChannelFromStream,
}) => {
const [tableSize, _] = useLocalStorage('table-size', 'default');
const channelSelectionStreams = useChannelsTableStore(
@@ -67,23 +68,6 @@ const StreamRowActions = ({
);
const fetchLogos = useLogosStore((s) => s.fetchLogos);
- const createChannelFromStream = async () => {
- const selectedChannelProfileId =
- useChannelsStore.getState().selectedProfileId;
-
- await API.createChannelFromStream({
- name: row.original.name,
- channel_number: null,
- stream_id: row.original.id,
- // Only pass channel_profile_ids if a specific profile is selected (not "All")
- ...(selectedChannelProfileId !== '0' && {
- channel_profile_ids: selectedChannelProfileId,
- }),
- });
- await API.requeryChannels();
- fetchLogos();
- };
-
const addStreamToChannel = async () => {
await API.updateChannel({
id: selectedChannelIds[0],
@@ -145,7 +129,7 @@ const StreamRowActions = ({
size={iconSize}
color={theme.tailwind.green[5]}
variant="transparent"
- onClick={createChannelFromStream}
+ onClick={() => createChannelFromStream(row.original)}
>
@@ -209,6 +193,13 @@ const StreamsTable = () => {
const [customStartNumber, setCustomStartNumber] = useState(1);
const [rememberChoice, setRememberChoice] = useState(false);
+ // Single channel numbering modal state
+ const [singleChannelModalOpen, setSingleChannelModalOpen] = useState(false);
+ const [singleChannelMode, setSingleChannelMode] = useState('provider'); // 'provider', 'auto', or 'specific'
+ const [specificChannelNumber, setSpecificChannelNumber] = useState(1);
+ const [rememberSingleChoice, setRememberSingleChoice] = useState(false);
+ const [currentStreamForChannel, setCurrentStreamForChannel] = useState(null);
+
// const [allRowsSelected, setAllRowsSelected] = useState(false);
// Add local storage for page size
@@ -534,6 +525,80 @@ const StreamsTable = () => {
fetchData();
};
+ // Single channel creation functions
+ const createChannelFromStream = async (stream) => {
+ // Check if user has suppressed the single channel numbering dialog
+ const actionKey = 'single-channel-numbering-choice';
+ if (isWarningSuppressed(actionKey)) {
+ // Use the remembered settings or default to 'provider' mode
+ const savedMode =
+ localStorage.getItem('single-channel-numbering-mode') || 'provider';
+ const savedChannelNumber =
+ localStorage.getItem('single-channel-numbering-specific') || '1';
+
+ const channelNumberValue =
+ savedMode === 'provider'
+ ? null
+ : savedMode === 'auto'
+ ? 0
+ : Number(savedChannelNumber);
+
+ await executeSingleChannelCreation(stream, channelNumberValue);
+ } else {
+ // Show the modal to let user choose
+ setCurrentStreamForChannel(stream);
+ setSingleChannelModalOpen(true);
+ }
+ };
+
+ // Separate function to actually execute single channel creation
+ const executeSingleChannelCreation = async (stream, channelNumber = null) => {
+ const selectedChannelProfileId =
+ useChannelsStore.getState().selectedProfileId;
+
+ await API.createChannelFromStream({
+ name: stream.name,
+ channel_number: channelNumber,
+ stream_id: stream.id,
+ // Only pass channel_profile_ids if a specific profile is selected (not "All")
+ ...(selectedChannelProfileId !== '0' && {
+ channel_profile_ids: selectedChannelProfileId,
+ }),
+ });
+ await API.requeryChannels();
+ const fetchLogos = useChannelsStore.getState().fetchLogos;
+ fetchLogos();
+ };
+
+ // Handle confirming the single channel numbering modal
+ const handleSingleChannelNumberingConfirm = async () => {
+ // Save the choice if user wants to remember it
+ if (rememberSingleChoice) {
+ suppressWarning('single-channel-numbering-choice');
+ localStorage.setItem('single-channel-numbering-mode', singleChannelMode);
+ if (singleChannelMode === 'specific') {
+ localStorage.setItem(
+ 'single-channel-numbering-specific',
+ specificChannelNumber.toString()
+ );
+ }
+ }
+
+ // Convert mode to API value
+ const channelNumberValue =
+ singleChannelMode === 'provider'
+ ? null
+ : singleChannelMode === 'auto'
+ ? 0
+ : Number(specificChannelNumber);
+
+ setSingleChannelModalOpen(false);
+ await executeSingleChannelCreation(
+ currentStreamForChannel,
+ channelNumberValue
+ );
+ };
+
const addStreamsToChannel = async () => {
await API.updateChannel({
id: selectedChannelIds[0],
@@ -689,6 +754,7 @@ const StreamsTable = () => {
deleteStream={deleteStream}
handleWatchStream={handleWatchStream}
selectedChannelIds={selectedChannelIds}
+ createChannelFromStream={createChannelFromStream}
/>
);
}
@@ -1037,6 +1103,78 @@ const StreamsTable = () => {
+
+ {/* Single Channel Numbering Modal */}
+ setSingleChannelModalOpen(false)}
+ title="Channel Number Assignment"
+ size="md"
+ centered
+ >
+
+
+ Choose how to assign the channel number for "
+ {currentStreamForChannel?.name}":
+
+
+
+
+
+
+
+
+
+
+ {singleChannelMode === 'specific' && (
+
+ )}
+
+
+ setRememberSingleChoice(event.currentTarget.checked)
+ }
+ label="Remember this choice and don't ask again"
+ />
+
+
+
+
+
+
+
>
);
};