mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-04 21:39:49 +00:00
add error state filtering
This commit is contained in:
@@ -48,6 +48,7 @@ class DownloadListQuerySerializer(
|
|||||||
channel = serializers.CharField(required=False, help_text="channel ID")
|
channel = serializers.CharField(required=False, help_text="channel ID")
|
||||||
page = serializers.IntegerField(required=False)
|
page = serializers.IntegerField(required=False)
|
||||||
q = serializers.CharField(required=False, help_text="Search Query")
|
q = serializers.CharField(required=False, help_text="Search Query")
|
||||||
|
error = serializers.BooleanField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
class DownloadListQueueDeleteQuerySerializer(serializers.Serializer):
|
class DownloadListQueueDeleteQuerySerializer(serializers.Serializer):
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ class DownloadApiListView(ApiBaseView):
|
|||||||
if search_query:
|
if search_query:
|
||||||
must_list.append({"match_phrase_prefix": {"title": search_query}})
|
must_list.append({"match_phrase_prefix": {"title": search_query}})
|
||||||
|
|
||||||
|
if validated_data.get("error") is not None:
|
||||||
|
operator = "must" if validated_data["error"] else "must_not"
|
||||||
|
must_list.append(
|
||||||
|
{"bool": {operator: [{"exists": {"field": "message"}}]}}
|
||||||
|
)
|
||||||
|
|
||||||
self.data["query"] = {"bool": {"must": must_list}}
|
self.data["query"] = {"bool": {"must": must_list}}
|
||||||
|
|
||||||
self.get_document_list(request)
|
self.get_document_list(request)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const loadDownloadQueue = async (
|
|||||||
page: number,
|
page: number,
|
||||||
channelId: string | null,
|
channelId: string | null,
|
||||||
vid_type: string | null,
|
vid_type: string | null,
|
||||||
|
errorFilterFromUrl: string | null,
|
||||||
showIgnored: boolean,
|
showIgnored: boolean,
|
||||||
search: string,
|
search: string,
|
||||||
) => {
|
) => {
|
||||||
@@ -14,6 +15,7 @@ const loadDownloadQueue = async (
|
|||||||
if (channelId) searchParams.append('channel', channelId);
|
if (channelId) searchParams.append('channel', channelId);
|
||||||
if (vid_type) searchParams.append('vid_type', vid_type);
|
if (vid_type) searchParams.append('vid_type', vid_type);
|
||||||
if (search) searchParams.append('q', encodeURIComponent(search));
|
if (search) searchParams.append('q', encodeURIComponent(search));
|
||||||
|
if (errorFilterFromUrl !== null) searchParams.append('error', errorFilterFromUrl);
|
||||||
searchParams.append('filter', showIgnored ? 'ignore' : 'pending');
|
searchParams.append('filter', showIgnored ? 'ignore' : 'pending');
|
||||||
|
|
||||||
const endpoint = `/api/download/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
const endpoint = `/api/download/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import iconAdd from '/img/icon-add.svg';
|
|||||||
import iconSubstract from '/img/icon-substract.svg';
|
import iconSubstract from '/img/icon-substract.svg';
|
||||||
import iconGridView from '/img/icon-gridview.svg';
|
import iconGridView from '/img/icon-gridview.svg';
|
||||||
import iconListView from '/img/icon-listview.svg';
|
import iconListView from '/img/icon-listview.svg';
|
||||||
import iconSearch from '/img/icon-search.svg';
|
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
import { useOutletContext, useSearchParams } from 'react-router-dom';
|
import { useOutletContext, useSearchParams } from 'react-router-dom';
|
||||||
import { ConfigType } from './Home';
|
import { ConfigType } from './Home';
|
||||||
@@ -59,13 +58,13 @@ const Download = () => {
|
|||||||
const channelFilterFromUrl = searchParams.get('channel');
|
const channelFilterFromUrl = searchParams.get('channel');
|
||||||
const ignoredOnlyParam = searchParams.get('ignored');
|
const ignoredOnlyParam = searchParams.get('ignored');
|
||||||
const vidTypeFilterFromUrl = searchParams.get('vid-type');
|
const vidTypeFilterFromUrl = searchParams.get('vid-type');
|
||||||
|
const errorFilterFromUrl = searchParams.get('error');
|
||||||
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [showHiddenForm, setShowHiddenForm] = useState(false);
|
const [showHiddenForm, setShowHiddenForm] = useState(false);
|
||||||
const [addAsAutoStart, setAddAsAutoStart] = useState(false);
|
const [addAsAutoStart, setAddAsAutoStart] = useState(false);
|
||||||
const [addAsFlat, setAddAsFlat] = useState(false);
|
const [addAsFlat, setAddAsFlat] = useState(false);
|
||||||
const [showQueueActions, setShowQueueActions] = useState(false);
|
const [showQueueActions, setShowQueueActions] = useState(false);
|
||||||
const [showSearchInput, setShowSearchInput] = useState(false);
|
|
||||||
const [searchInput, setSearchInput] = useState('');
|
const [searchInput, setSearchInput] = useState('');
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [downloadPending, setDownloadPending] = useState(false);
|
const [downloadPending, setDownloadPending] = useState(false);
|
||||||
@@ -120,6 +119,7 @@ const Download = () => {
|
|||||||
currentPage,
|
currentPage,
|
||||||
channelFilterFromUrl,
|
channelFilterFromUrl,
|
||||||
vidTypeFilterFromUrl,
|
vidTypeFilterFromUrl,
|
||||||
|
errorFilterFromUrl,
|
||||||
showIgnored,
|
showIgnored,
|
||||||
searchInput,
|
searchInput,
|
||||||
);
|
);
|
||||||
@@ -140,7 +140,14 @@ const Download = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRefresh(true);
|
setRefresh(true);
|
||||||
}, [channelFilterFromUrl, vidTypeFilterFromUrl, currentPage, showIgnored, searchInput]);
|
}, [
|
||||||
|
channelFilterFromUrl,
|
||||||
|
vidTypeFilterFromUrl,
|
||||||
|
errorFilterFromUrl,
|
||||||
|
currentPage,
|
||||||
|
showIgnored,
|
||||||
|
searchInput,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -160,11 +167,6 @@ const Download = () => {
|
|||||||
setRefresh(true);
|
setRefresh(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShowSearchInput = () => {
|
|
||||||
if (showSearchInput) setSearchInput('');
|
|
||||||
setShowSearchInput(!showSearchInput);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<title>TA | Downloads</title>
|
<title>TA | Downloads</title>
|
||||||
@@ -318,74 +320,8 @@ const Download = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="view-icons">
|
<div className="view-icons">
|
||||||
{showSearchInput && (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
value={searchInput}
|
|
||||||
onChange={e => setSearchInput(e.target.value)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<img
|
|
||||||
src={iconSearch}
|
|
||||||
alt="search-icon"
|
|
||||||
title="Search"
|
|
||||||
onClick={handleShowSearchInput}
|
|
||||||
/>
|
|
||||||
<select
|
|
||||||
name="vid_type_filter"
|
|
||||||
id="vid_type_filter"
|
|
||||||
value={vidTypeFilterFromUrl || 'all'}
|
|
||||||
onChange={async event => {
|
|
||||||
const value = event.currentTarget.value;
|
|
||||||
const params = searchParams;
|
|
||||||
if (value !== 'all') {
|
|
||||||
params.set('vid-type', value);
|
|
||||||
} else {
|
|
||||||
params.delete('vid-type');
|
|
||||||
}
|
|
||||||
setSearchParams(params);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="all">all types</option>
|
|
||||||
<option value="videos">Videos</option>
|
|
||||||
<option value="streams">Streams</option>
|
|
||||||
<option value="shorts">Shorts</option>
|
|
||||||
</select>
|
|
||||||
{channelAggsList && channelAggsList.length > 1 && (
|
|
||||||
<select
|
|
||||||
name="channel_filter"
|
|
||||||
id="channel_filter"
|
|
||||||
value={channelFilterFromUrl || 'all'}
|
|
||||||
onChange={async event => {
|
|
||||||
const value = event.currentTarget.value;
|
|
||||||
|
|
||||||
const params = searchParams;
|
|
||||||
if (value !== 'all') {
|
|
||||||
params.set('channel', value);
|
|
||||||
} else {
|
|
||||||
params.delete('channel');
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(params);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="all">all channels</option>
|
|
||||||
{channelAggsList.map(channel => {
|
|
||||||
const [name, id] = channel.key;
|
|
||||||
const count = channel.doc_count;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<option key={id} value={id}>
|
|
||||||
{name} ({count})
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
<Button onClick={() => setShowQueueActions(!showQueueActions)}>
|
<Button onClick={() => setShowQueueActions(!showQueueActions)}>
|
||||||
{showQueueActions ? 'Hide Actions' : 'Show Actions'}
|
{showQueueActions ? 'Hide Advanced' : 'Show Advanced'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isGridView && (
|
{isGridView && (
|
||||||
@@ -449,6 +385,85 @@ const Download = () => {
|
|||||||
</h3>
|
</h3>
|
||||||
{showQueueActions && (
|
{showQueueActions && (
|
||||||
<div className="settings-group">
|
<div className="settings-group">
|
||||||
|
<h3>Search & Filter</h3>
|
||||||
|
<select
|
||||||
|
name="vid_type_filter"
|
||||||
|
id="vid_type_filter"
|
||||||
|
value={vidTypeFilterFromUrl || 'all'}
|
||||||
|
onChange={async event => {
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
const params = searchParams;
|
||||||
|
if (value !== 'all') {
|
||||||
|
params.set('vid-type', value);
|
||||||
|
} else {
|
||||||
|
params.delete('vid-type');
|
||||||
|
}
|
||||||
|
setSearchParams(params);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="all">all types</option>
|
||||||
|
<option value="videos">Videos</option>
|
||||||
|
<option value="streams">Streams</option>
|
||||||
|
<option value="shorts">Shorts</option>
|
||||||
|
</select>
|
||||||
|
{channelAggsList && channelAggsList.length > 1 && (
|
||||||
|
<select
|
||||||
|
name="channel_filter"
|
||||||
|
id="channel_filter"
|
||||||
|
value={channelFilterFromUrl || 'all'}
|
||||||
|
onChange={async event => {
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
|
||||||
|
const params = searchParams;
|
||||||
|
if (value !== 'all') {
|
||||||
|
params.set('channel', value);
|
||||||
|
} else {
|
||||||
|
params.delete('channel');
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams(params);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="all">all channels</option>
|
||||||
|
{channelAggsList.map(channel => {
|
||||||
|
const [name, id] = channel.key;
|
||||||
|
const count = channel.doc_count;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<option key={id} value={id}>
|
||||||
|
{name} ({count})
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
<select
|
||||||
|
name="error_filter"
|
||||||
|
id="error_filter"
|
||||||
|
value={errorFilterFromUrl || 'all'}
|
||||||
|
onChange={async event => {
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
const params = searchParams;
|
||||||
|
if (value !== 'all') {
|
||||||
|
params.set('error', value);
|
||||||
|
} else {
|
||||||
|
params.delete('error');
|
||||||
|
}
|
||||||
|
setSearchParams(params);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="all">all error state</option>
|
||||||
|
<option value="true">has error</option>
|
||||||
|
<option value="false">has no error</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchInput}
|
||||||
|
onChange={e => setSearchInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
{searchInput && <Button onClick={() => setSearchInput('')}>Clear</Button>}
|
||||||
|
|
||||||
<h3>Bulk actions</h3>
|
<h3>Bulk actions</h3>
|
||||||
<p>
|
<p>
|
||||||
Applied filtered by status <i>'{showIgnoredFilter}'</i>
|
Applied filtered by status <i>'{showIgnoredFilter}'</i>
|
||||||
|
|||||||
Reference in New Issue
Block a user