new ui updates, primarily streams table, new api endpoints to efficiently get ids and stream groups

This commit is contained in:
dekzter 2025-03-09 17:35:59 -04:00
parent 7aa30f2678
commit c05e769f09
6 changed files with 100 additions and 36 deletions

View file

@ -6,7 +6,6 @@ from .api_views import (
ChannelGroupViewSet,
BulkDeleteStreamsAPIView,
BulkDeleteChannelsAPIView,
StreamIDsAPIView,
)
app_name = 'channels' # for DRF routing
@ -20,7 +19,6 @@ 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('streams/ids/', StreamIDsAPIView.as_view(), name='stream_ids'),
]
urlpatterns += router.urls

View file

@ -32,13 +32,6 @@ class StreamFilter(django_filters.FilterSet):
model = Stream
fields = ['name', 'group_name', 'm3u_account', 'm3u_account_name', 'm3u_account_is_active']
class StreamIDsAPIView(APIView):
permission_classes = [IsAuthenticated] # Enforce authentication if needed
def get(self, request, *args, **kwargs):
stream_ids = Stream.objects.values_list('id', flat=True)
return Response(list(stream_ids))
# ─────────────────────────────────────────────────────────
# 1) Stream API (CRUD)
# ─────────────────────────────────────────────────────────
@ -66,6 +59,26 @@ class StreamViewSet(viewsets.ModelViewSet):
qs = qs.filter(channels__isnull=True)
return qs
@action(detail=False, methods=['get'], url_path='ids')
def get_ids(self, request, *args, **kwargs):
# Get the filtered queryset
queryset = self.get_queryset()
# Apply filtering, search, and ordering
queryset = self.filter_queryset(queryset)
# Return only the IDs from the queryset
stream_ids = queryset.values_list('id', flat=True)
# Return the response with the list of IDs
return Response(list(stream_ids))
@action(detail=False, methods=['get'], url_path='groups')
def get_groups(self, request, *args, **kwargs):
group_names = Stream.objects.exclude(group_name__isnull=True).exclude(group_name="").order_by().values_list('group_name', flat=True).distinct()
# Return the response with the list of unique group names
return Response(list(group_names))
# ─────────────────────────────────────────────────────────
# 2) Channel Group Management (CRUD)

View file

@ -296,6 +296,18 @@ export default class API {
return retval;
}
static async getStreamGroups() {
const response = await fetch(`${host}/api/channels/streams/groups/`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await API.getAuthToken()}`,
},
});
const retval = await response.json();
return retval;
}
static async addStream(values) {
const response = await fetch(`${host}/api/channels/streams/`, {
method: 'POST',

View file

@ -43,7 +43,7 @@ import logo from '../../images/logo.png';
import useVideoStore from '../../store/useVideoStore';
import useSettingsStore from '../../store/settings';
import usePlaylistsStore from '../../store/playlists';
import { Tv2, ScreenShare, Scroll } from 'lucide-react';
import { Tv2, ScreenShare, Scroll, SquareMinus, Pencil } from 'lucide-react';
import { styled, useTheme } from '@mui/material/styles';
import ghostImage from '../../images/ghost.svg';
@ -308,9 +308,10 @@ const ChannelsTable = ({}) => {
size="small"
sx={{ width: 300 }}
clearOnEscape
onChange={(event, newValue) =>
handleFilterChange(column.id, newValue)
}
onChange={(event, newValue) => {
event.stopPropagation();
handleFilterChange(column.id, newValue);
}}
renderInput={(params) => (
<TextField
{...params}
@ -351,7 +352,7 @@ const ChannelsTable = ({}) => {
},
},
],
[channelGroupOptions]
[channelGroupOptions, filterValues]
);
// Access the row virtualizer instance (optional)
@ -590,7 +591,7 @@ const ChannelsTable = ({}) => {
}}
sx={{ py: 0, px: 0.5 }}
>
<EditIcon fontSize="small" />
<Pencil size="18" />
</IconButton>
</Tooltip>
@ -601,7 +602,7 @@ const ChannelsTable = ({}) => {
onClick={() => deleteChannel(row.original.id)}
sx={{ py: 0, px: 0.5 }}
>
<DeleteIcon fontSize="small" />
<SquareMinus size="18" />
</IconButton>
</Tooltip>

View file

@ -29,6 +29,7 @@ import StreamForm from '../forms/Stream';
import usePlaylistsStore from '../../store/playlists';
import useChannelsStore from '../../store/channels';
import { useDebounce } from '../../utils';
import { SquarePlus, ListPlus } from 'lucide-react';
const StreamsTable = ({}) => {
/**
@ -38,7 +39,6 @@ const StreamsTable = ({}) => {
const [stream, setStream] = useState(null);
const [modalOpen, setModalOpen] = useState(false);
const [moreActionsAnchorEl, setMoreActionsAnchorEl] = useState(null);
const [filterValues, setFilterValues] = useState({});
const [groupOptions, setGroupOptions] = useState([]);
const [m3uOptions, setM3uOptions] = useState([]);
const [actionsOpenRow, setActionsOpenRow] = useState(null);
@ -91,7 +91,7 @@ const StreamsTable = ({}) => {
variant="standard"
name="name"
label="Name"
value={filters[column.id]}
value={filters.name || ''}
onClick={(e) => e.stopPropagation()}
onChange={handleFilterChange}
size="small"
@ -131,11 +131,12 @@ const StreamsTable = ({}) => {
disablePortal
options={groupOptions}
size="small"
sx={{ width: 300 }}
// sx={{ width: 300 }}
clearOnEscape
onChange={(event, newValue) =>
handleFilterChange(column.id, newValue)
}
onChange={(e, value) => {
e.stopPropagation();
handleGroupChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
@ -162,13 +163,17 @@ const StreamsTable = ({}) => {
Header: ({ column }) => (
<Autocomplete
disablePortal
options={m3uOptions}
options={playlists.map((playlist) => ({
label: playlist.name,
value: playlist.id,
}))}
size="small"
sx={{ width: 300 }}
// sx={{ width: 300 }}
clearOnEscape
onChange={(event, newValue) =>
handleFilterChange(column.id, newValue)
}
onChange={(e, value) => {
e.stopPropagation();
handleM3UChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
@ -188,7 +193,7 @@ const StreamsTable = ({}) => {
),
},
],
[playlists, groupOptions, m3uOptions, filters]
[playlists, groupOptions, filters]
);
/**
@ -202,6 +207,22 @@ const StreamsTable = ({}) => {
}));
};
const handleGroupChange = (value) => {
console.log(value);
setFilters((prev) => ({
...prev,
group_name: value ? value.value : '',
}));
};
const handleM3UChange = (value) => {
console.log(value);
setFilters((prev) => ({
...prev,
m3u_account: value ? value.value : '',
}));
};
const fetchData = useCallback(async () => {
setIsLoading(true);
@ -241,9 +262,16 @@ const StreamsTable = ({}) => {
console.error('Error fetching data:', error);
}
const groups = await API.getStreamGroups();
setGroupOptions(groups);
setIsLoading(false);
}, [pagination, sorting, debouncedFilters]);
useEffect(() => {
console.log(pagination);
}, [pagination]);
// Fallback: Individual creation (optional)
const createChannelFromStream = async (stream) => {
await API.createChannelFromStream({
@ -363,6 +391,16 @@ const StreamsTable = ({}) => {
setRowSelection(newSelection);
};
const onPaginationChange = (updater) => {
const newPagination = updater(pagination);
if (JSON.stringify(newPagination) === JSON.stringify(pagination)) {
// Prevent infinite re-render when there are no results
return;
}
setPagination(updater);
};
const table = useMaterialReactTable({
...TableHelper.defaultProperties,
columns,
@ -375,7 +413,7 @@ const StreamsTable = ({}) => {
manualSorting: true,
enableBottomToolbar: true,
enableStickyHeader: true,
onPaginationChange: setPagination,
onPaginationChange: onPaginationChange,
onSortingChange: setSorting,
rowCount: rowCount,
enableRowSelection: true,
@ -386,7 +424,9 @@ const StreamsTable = ({}) => {
onChange: onSelectAllChange,
},
onRowSelectionChange: onRowSelectionChange,
onSortingChange: setSorting,
initialState: {
density: 'compact',
},
state: {
isLoading: isLoading,
sorting,
@ -411,7 +451,7 @@ const StreamsTable = ({}) => {
.includes(row.original.id))
}
>
<PlaylistAddIcon fontSize="small" />
<ListPlus size="18" fontSize="small" />
</IconButton>
</Tooltip>
@ -422,7 +462,7 @@ const StreamsTable = ({}) => {
onClick={() => createChannelFromStream(row.original)}
sx={{ py: 0, px: 0.5 }}
>
<AddIcon fontSize="small" />
<SquarePlus size="18" fontSize="small" />
</IconButton>
</Tooltip>
@ -482,7 +522,7 @@ const StreamsTable = ({}) => {
variant="contained"
onClick={() => editStream()}
>
<AddIcon fontSize="small" />
<SquarePlus size="18" fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Delete Streams">
@ -526,7 +566,7 @@ const StreamsTable = ({}) => {
*/
useEffect(() => {
fetchData();
}, [pagination, debouncedFilters]);
}, [fetchData]);
useEffect(() => {
if (typeof window !== 'undefined') {

View file

@ -18,7 +18,7 @@ export default {
},
muiTableBodyCellProps: {
sx: {
padding: '6px',
py: 0,
borderColor: '#444',
color: '#E0E0E0',
fontSize: '0.85rem',
@ -26,7 +26,7 @@ export default {
},
muiTableHeadCellProps: {
sx: {
padding: '6px',
py: 0,
color: '#CFCFCF',
backgroundColor: '#383A3F',
borderColor: '#444',