unify StreamsTable refresh with channels requery

This commit is contained in:
Jeffrey C 2026-01-14 09:48:40 -06:00
parent 77067d4a1d
commit 29395f0538
No known key found for this signature in database
GPG key ID: A68C7782CDAC89E3
5 changed files with 152 additions and 71 deletions

View file

@ -755,6 +755,7 @@ export const WebsocketProvider = ({ children }) => {
// Refresh the channels table to show new channels
try {
await API.requeryChannels();
await API.requeryStreams();
await useChannelsStore.getState().fetchChannels();
await fetchChannelProfiles();
console.log('Channels refreshed after bulk creation');

View file

@ -10,6 +10,7 @@ import useStreamProfilesStore from './store/streamProfiles';
import useSettingsStore from './store/settings';
import { notifications } from '@mantine/notifications';
import useChannelsTableStore from './store/channelsTable';
import useStreamsTableStore from './store/streamsTable';
import useUsersStore from './store/users';
// If needed, you can set a base host or keep it empty if relative requests
@ -380,6 +381,7 @@ export default class API {
});
useChannelsStore.getState().removeChannels([id]);
await API.requeryStreams();
} catch (e) {
errorNotification('Failed to delete channel', e);
}
@ -394,6 +396,7 @@ export default class API {
});
useChannelsStore.getState().removeChannels(channel_ids);
await API.requeryStreams();
} catch (e) {
errorNotification('Failed to delete channels', e);
}
@ -447,6 +450,9 @@ export default class API {
);
useChannelsStore.getState().updateChannel(response);
if (Object.prototype.hasOwnProperty.call(payload, 'streams')) {
await API.requeryStreams();
}
return response;
} catch (e) {
errorNotification('Failed to update channel', e);
@ -630,6 +636,7 @@ export default class API {
useChannelsStore.getState().addChannel(response);
}
await API.requeryStreams();
return response;
} catch (e) {
errorNotification('Failed to create channel', e);
@ -705,6 +712,46 @@ export default class API {
}
}
static async queryStreamsTable(params) {
try {
API.lastStreamQueryParams = params;
const response = await request(
`${host}/api/channels/streams/?${params.toString()}`
);
useStreamsTableStore.getState().queryStreams(response, params);
return response;
} catch (e) {
errorNotification('Failed to fetch streams', e);
}
}
static async requeryStreams() {
if (!API.lastStreamQueryParams) {
return null;
}
try {
const [response, ids] = await Promise.all([
request(
`${host}/api/channels/streams/?${API.lastStreamQueryParams.toString()}`
),
API.getAllStreamIds(API.lastStreamQueryParams),
]);
useStreamsTableStore
.getState()
.queryStreams(response, API.lastStreamQueryParams);
useStreamsTableStore.getState().setAllQueryIds(ids);
return response;
} catch (e) {
errorNotification('Failed to fetch streams', e);
}
}
static async getAllStreamIds(params) {
try {
const response = await request(
@ -738,6 +785,7 @@ export default class API {
useStreamsStore.getState().addStream(response);
}
await API.requeryStreams();
return response;
} catch (e) {
errorNotification('Failed to add stream', e);
@ -756,6 +804,7 @@ export default class API {
useStreamsStore.getState().updateStream(response);
}
await API.requeryStreams();
return response;
} catch (e) {
errorNotification('Failed to update stream', e);
@ -769,6 +818,7 @@ export default class API {
});
useStreamsStore.getState().removeStreams([id]);
await API.requeryStreams();
} catch (e) {
errorNotification('Failed to delete stream', e);
}
@ -782,6 +832,7 @@ export default class API {
});
useStreamsStore.getState().removeStreams(ids);
await API.requeryStreams();
} catch (e) {
errorNotification('Failed to delete streams', e);
}

View file

@ -167,6 +167,7 @@ const ChannelStreams = ({ channel, isExpanded }) => {
streams: newStreamList.map((s) => s.id),
});
await API.requeryChannels();
await API.requeryStreams();
};
// Create M3U account map for quick lookup

View file

@ -46,7 +46,6 @@ import {
MultiSelect,
useMantineTheme,
UnstyledButton,
LoadingOverlay,
Skeleton,
Modal,
NumberInput,
@ -61,7 +60,7 @@ import { CustomTable, useTable } from './CustomTable';
import useLocalStorage from '../../hooks/useLocalStorage';
import ConfirmationDialog from '../ConfirmationDialog';
import CreateChannelModal from '../modals/CreateChannelModal';
import { useWebSocket } from '../../WebSocket';
import useStreamsTableStore from '../../store/streamsTable';
const StreamRowActions = ({
theme,
@ -71,7 +70,6 @@ const StreamRowActions = ({
handleWatchStream,
selectedChannelIds,
createChannelFromStream,
fetchData,
}) => {
const [tableSize, _] = useLocalStorage('table-size', 'default');
const channelSelectionStreams = useChannelsTableStore(
@ -89,7 +87,6 @@ const StreamRowActions = ({
],
});
await API.requeryChannels();
await fetchData({ showLoader: false });
};
const onEdit = useCallback(() => {
@ -182,23 +179,18 @@ const StreamRowActions = ({
const StreamsTable = ({ onReady }) => {
const theme = useMantineTheme();
const hasSignaledReady = useRef(false);
const [, , websocketEvent] = useWebSocket();
const hasFetchedOnce = useRef(false);
/**
* useState
*/
const [allRowIds, setAllRowIds] = useState([]);
const [stream, setStream] = useState(null);
const [modalOpen, setModalOpen] = useState(false);
const [groupOptions, setGroupOptions] = useState([]);
const [initialDataCount, setInitialDataCount] = useState(null);
const [data, setData] = useState([]); // Holds fetched data
const [pageCount, setPageCount] = useState(0);
const [paginationString, setPaginationString] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [sorting, setSorting] = useState([{ id: 'name', desc: false }]);
const [selectedStreamIds, setSelectedStreamIds] = useState([]);
// Channel creation modal state (bulk)
const [channelNumberingModalOpen, setChannelNumberingModalOpen] =
@ -230,10 +222,6 @@ const StreamsTable = ({ onReady }) => {
'streams-page-size',
50
);
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: storedPageSize,
});
const [filters, setFilters] = useState({
name: '',
channel_group: '',
@ -246,15 +234,12 @@ const StreamsTable = ({ onReady }) => {
);
const debouncedFilters = useDebounce(filters, 500, () => {
// Reset to first page whenever filters change to avoid "Invalid page" errors
setPagination((prev) => ({
...prev,
setPagination({
...pagination,
pageIndex: 0,
}));
});
});
// Add state to track if stream groups are loaded
const [groupsLoaded, setGroupsLoaded] = useState(false);
const navigate = useNavigate();
/**
@ -277,6 +262,20 @@ const StreamsTable = ({ onReady }) => {
const showVideo = useVideoStore((s) => s.showVideo);
const [tableSize, _] = useLocalStorage('table-size', 'default');
const data = useStreamsTableStore((s) => s.streams);
const pageCount = useStreamsTableStore((s) => s.pageCount);
const totalCount = useStreamsTableStore((s) => s.totalCount);
const allRowIds = useStreamsTableStore((s) => s.allQueryIds);
const setAllRowIds = useStreamsTableStore((s) => s.setAllQueryIds);
const pagination = useStreamsTableStore((s) => s.pagination);
const setPagination = useStreamsTableStore((s) => s.setPagination);
const sorting = useStreamsTableStore((s) => s.sorting);
const setSorting = useStreamsTableStore((s) => s.setSorting);
const selectedStreamIds = useStreamsTableStore((s) => s.selectedStreamIds);
const setSelectedStreamIds = useStreamsTableStore(
(s) => s.setSelectedStreamIds
);
// Warnings store for "remember choice" functionality
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
@ -401,16 +400,6 @@ const StreamsTable = ({ onReady }) => {
setIsLoading(true);
}
// Ensure we have channel groups first (if not already loaded)
if (!groupsLoaded && Object.keys(channelGroups).length === 0) {
try {
await fetchChannelGroups();
setGroupsLoaded(true);
} catch (error) {
console.error('Error fetching channel groups:', error);
}
}
const params = new URLSearchParams();
params.append('page', pagination.pageIndex + 1);
params.append('page_size', pagination.pageSize);
@ -436,30 +425,18 @@ const StreamsTable = ({ onReady }) => {
try {
const [result, ids, groups] = await Promise.all([
API.queryStreams(params),
API.queryStreamsTable(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
const endItem = Math.min(
(pagination.pageIndex + 1) * pagination.pageSize,
result.count
);
if (initialDataCount === null) {
setInitialDataCount(result.count);
}
// Generate the string
setPaginationString(`${startItem} to ${endItem} of ${result.count}`);
// Signal that initial data load is complete
if (!hasSignaledReady.current && onReady) {
hasSignaledReady.current = true;
@ -469,18 +446,11 @@ const StreamsTable = ({ onReady }) => {
console.error('Error fetching data:', error);
}
hasFetchedOnce.current = true;
if (showLoader) {
setIsLoading(false);
}
}, [
pagination,
sorting,
debouncedFilters,
groupsLoaded,
channelGroups,
fetchChannelGroups,
onReady,
]);
}, [pagination, sorting, debouncedFilters, onReady]);
// Bulk creation: create channels from selected streams asynchronously
const createChannelsFromStreams = async () => {
@ -554,9 +524,7 @@ const StreamsTable = ({ onReady }) => {
// Clear selection since the task has started
setSelectedStreamIds([]);
// Note: This is a background task, so fetchData may not show updated data immediately
// The actual update will occur when the WebSocket event fires on task completion
await fetchData({ showLoader: false });
// Note: This is a background task, so the update happens on WebSocket completion
} catch (error) {
console.error('Error starting bulk channel creation:', error);
// Error notifications will be handled by WebSocket
@ -616,7 +584,6 @@ const StreamsTable = ({ onReady }) => {
setDeleting(true);
try {
await API.deleteStream(id);
fetchData({ showLoader: false });
// Clear the selection for the deleted stream
setSelectedStreamIds([]);
table.setSelectedTableIds([]);
@ -642,7 +609,6 @@ const StreamsTable = ({ onReady }) => {
setDeleting(true);
try {
await API.deleteStreams(selectedStreamIds);
fetchData({ showLoader: false });
setSelectedStreamIds([]);
table.setSelectedTableIds([]);
} finally {
@ -654,7 +620,7 @@ const StreamsTable = ({ onReady }) => {
const closeStreamForm = () => {
setStream(null);
setModalOpen(false);
fetchData({ showLoader: false });
API.requeryStreams();
};
// Single channel creation functions
@ -724,7 +690,6 @@ const StreamsTable = ({ onReady }) => {
await API.requeryChannels();
// const fetchLogos = useChannelsStore.getState().fetchLogos;
// fetchLogos();
await fetchData({ showLoader: false });
};
// Handle confirming the single channel numbering modal
@ -767,7 +732,6 @@ const StreamsTable = ({ onReady }) => {
],
});
await API.requeryChannels();
await fetchData({ showLoader: false });
};
const onRowSelectionChange = (updatedIds) => {
@ -928,7 +892,6 @@ const StreamsTable = ({ onReady }) => {
handleWatchStream={handleWatchStream}
selectedChannelIds={selectedChannelIds}
createChannelFromStream={createChannelFromStream}
fetchData={fetchData}
/>
);
}
@ -940,7 +903,6 @@ const StreamsTable = ({ onReady }) => {
editStream,
deleteStream,
handleWatchStream,
fetchData,
]
);
@ -958,6 +920,10 @@ const StreamsTable = ({ onReady }) => {
manualSorting: true,
manualFiltering: true,
enableRowSelection: true,
state: {
pagination,
sorting,
},
headerCellRenderFns: {
name: renderHeaderCell,
group: renderHeaderCell,
@ -985,15 +951,29 @@ const StreamsTable = ({ onReady }) => {
}, [fetchData]);
useEffect(() => {
const eventType = websocketEvent?.data?.type;
if (
eventType === 'channels_created' ||
(eventType === 'bulk_channel_creation_progress' &&
websocketEvent?.data?.status === 'completed')
) {
fetchData({ showLoader: false });
if (Object.keys(channelGroups).length > 0) {
return;
}
}, [websocketEvent, fetchData]);
const loadGroups = async () => {
try {
await fetchChannelGroups();
} catch (error) {
console.error('Error fetching channel groups:', error);
}
};
loadGroups();
}, [channelGroups, fetchChannelGroups]);
useEffect(() => {
const startItem = pagination.pageIndex * pagination.pageSize + 1;
const endItem = Math.min(
(pagination.pageIndex + 1) * pagination.pageSize,
totalCount
);
setPaginationString(`${startItem} to ${endItem} of ${totalCount}`);
}, [pagination.pageIndex, pagination.pageSize, totalCount]);
return (
<>
@ -1201,7 +1181,6 @@ const StreamsTable = ({ onReady }) => {
borderRadius: 'var(--mantine-radius-default)',
}}
>
<LoadingOverlay visible={isLoading} />
<CustomTable table={table} />
</Box>

View file

@ -0,0 +1,49 @@
import { create } from 'zustand';
const useStreamsTableStore = create((set) => ({
streams: [],
pageCount: 0,
totalCount: 0,
sorting: [{ id: 'name', desc: false }],
pagination: {
pageIndex: 0,
pageSize:
JSON.parse(localStorage.getItem('streams-page-size')) || 50,
},
selectedStreamIds: [],
allQueryIds: [],
queryStreams: ({ results, count }, params) => {
set(() => ({
streams: results,
totalCount: count,
pageCount: Math.ceil(count / params.get('page_size')),
}));
},
setAllQueryIds: (allQueryIds) => {
set(() => ({
allQueryIds,
}));
},
setSelectedStreamIds: (selectedStreamIds) => {
set(() => ({
selectedStreamIds,
}));
},
setPagination: (pagination) => {
set(() => ({
pagination,
}));
},
setSorting: (sorting) => {
set(() => ({
sorting,
}));
},
}));
export default useStreamsTableStore;