ui: scope regex preview to the M3U account

- Scope regex previews to the M3U account being edited

- Empty-state message extended from "No streams in this group yet." to "No streams in this group yet. Run an M3U refresh first to populate streams."
This commit is contained in:
None 2026-05-03 16:53:57 -05:00
parent ed91121f2e
commit 983a6675c5
3 changed files with 26 additions and 4 deletions

View file

@ -311,6 +311,19 @@ class StreamViewSet(viewsets.ModelViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
# Group names are not unique across M3U accounts (two providers
# can both publish a "Sports" group). Scope to the calling
# account so the sample reflects only the user's edits.
m3u_account_id = request.query_params.get("m3u_account_id")
if m3u_account_id is not None:
try:
m3u_account_id = int(m3u_account_id)
except (TypeError, ValueError):
return Response(
{"detail": "m3u_account_id must be an integer"},
status=status.HTTP_400_BAD_REQUEST,
)
find_pat = request.query_params.get("find") or None
replace_pat = request.query_params.get("replace") or ""
match_pat = request.query_params.get("match") or None
@ -358,6 +371,8 @@ class StreamViewSet(viewsets.ModelViewSet):
# separate COUNT lets the client surface scan_limit_hit when
# the preview covers only a sample.
base_qs = Stream.objects.filter(channel_group__name=group_name)
if m3u_account_id is not None:
base_qs = base_qs.filter(m3u_account_id=m3u_account_id)
names_iter = base_qs.values_list("name", flat=True)[:SCAN_CAP]
total_in_group = base_qs.count()

View file

@ -316,12 +316,15 @@ export default class API {
// any combination can be supplied per call.
static async getStreamsRegexPreview(
channelGroupName,
{ find, replace, match, exclude, limit = 10, signal } = {}
{ find, replace, match, exclude, limit = 10, signal, m3uAccountId } = {}
) {
try {
const params = new URLSearchParams({
channel_group: channelGroupName,
});
if (m3uAccountId !== undefined && m3uAccountId !== null) {
params.set('m3u_account_id', String(m3uAccountId));
}
if (find) params.set('find', find);
if (replace !== undefined && replace !== null) {
params.set('replace', replace);

View file

@ -325,7 +325,10 @@ const LiveGroupFilter = ({
setRegexPreviewState((prev) => ({ ...prev, [groupId]: emptyState }));
return;
}
const cacheKey = `${groupId}|${find}|${replace}|${match}|${exclude}`;
// Account ID in the cache key so previews stay correct when the
// user switches between accounts that share a group name.
const accountId = playlist?.id ?? '';
const cacheKey = `${accountId}|${groupId}|${find}|${replace}|${match}|${exclude}`;
const cached = regexPreviewCacheRef.current[cacheKey];
if (cached) {
cancelPending();
@ -364,6 +367,7 @@ const LiveGroupFilter = ({
exclude: exclude || undefined,
limit: 10,
signal: controller.signal,
m3uAccountId: playlist?.id,
});
} catch (e) {
if (e?.name === 'AbortError') return;
@ -838,7 +842,7 @@ const LiveGroupFilter = ({
{result && !result.error && result.matches.length === 0 && (
<Text size="xs" c="dimmed">
{result.total_in_group === 0
? 'No streams in this group yet.'
? 'No streams in this group yet. Run an M3U refresh first to populate streams.'
: 'No streams matched this pattern.'}
</Text>
)}
@ -913,7 +917,7 @@ const LiveGroupFilter = ({
{result && !result.error && result.matches.length === 0 && (
<Text size="xs" c="dimmed">
{result.total_in_group === 0
? 'No streams in this group yet.'
? 'No streams in this group yet. Run an M3U refresh first to populate streams.'
: emptyText}
</Text>
)}