mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
9 Commits
v6.0.2.102
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90cd8df1ae | ||
|
|
7d8444c435 | ||
|
|
1883ae52ac | ||
|
|
47d4ebbeac | ||
|
|
ef9836d71d | ||
|
|
955ee2f29b | ||
|
|
abf3fc4557 | ||
|
|
1e72cc6b5a | ||
|
|
24639a7016 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '6.0.2'
|
||||
majorVersion: '6.0.4'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -67,6 +67,7 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||
monitored,
|
||||
status,
|
||||
path,
|
||||
titleSlug,
|
||||
overview,
|
||||
statistics = {} as Statistics,
|
||||
images,
|
||||
@@ -141,7 +142,9 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||
<div className={styles.content}>
|
||||
<div className={styles.poster}>
|
||||
<div className={styles.posterContainer}>
|
||||
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
|
||||
{isSelectMode ? (
|
||||
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
|
||||
) : null}
|
||||
|
||||
{status === 'deleted' ? (
|
||||
<div className={styles.deleted} title={translate('Deleted')} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { SyntheticEvent, useCallback, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import { MOVIE_SEARCH, REFRESH_MOVIE } from 'Commands/commandNames';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
@@ -70,6 +69,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
monitored,
|
||||
status,
|
||||
images,
|
||||
titleSlug,
|
||||
tmdbId,
|
||||
imdbId,
|
||||
youTubeTrailerId,
|
||||
@@ -142,30 +142,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
setIsDeleteMovieModalOpen(false);
|
||||
}, [setIsDeleteMovieModalOpen]);
|
||||
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
|
||||
const onSelectPress = useCallback(
|
||||
(event: SyntheticEvent<HTMLElement, MouseEvent>) => {
|
||||
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
|
||||
window.open(`/movie/${tmdbId}`, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
const shiftKey = event.nativeEvent.shiftKey;
|
||||
|
||||
selectDispatch({
|
||||
type: 'toggleSelected',
|
||||
id: movieId,
|
||||
isSelected: !selectState.selectedState[movieId],
|
||||
shiftKey,
|
||||
});
|
||||
},
|
||||
[movieId, selectState.selectedState, selectDispatch, tmdbId]
|
||||
);
|
||||
|
||||
const link = `/movie/${tmdbId}`;
|
||||
|
||||
const linkProps = isSelectMode ? { onPress: onSelectPress } : { to: link };
|
||||
const link = `/movie/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
@@ -175,7 +152,9 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer} title={title}>
|
||||
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
|
||||
{isSelectMode ? (
|
||||
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
|
||||
) : null}
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<SpinnerIconButton
|
||||
@@ -220,7 +199,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
<div className={styles.deleted} title={translate('Deleted')} />
|
||||
) : null}
|
||||
|
||||
<Link className={styles.link} style={elementStyle} {...linkProps}>
|
||||
<Link className={styles.link} style={elementStyle} to={link}>
|
||||
<MoviePoster
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.checkContainer {
|
||||
|
||||
@@ -7,15 +7,23 @@ import styles from './MovieIndexPosterSelect.css';
|
||||
|
||||
interface MovieIndexPosterSelectProps {
|
||||
movieId: number;
|
||||
titleSlug: string;
|
||||
}
|
||||
|
||||
function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
|
||||
const { movieId } = props;
|
||||
function MovieIndexPosterSelect({
|
||||
movieId,
|
||||
titleSlug,
|
||||
}: MovieIndexPosterSelectProps) {
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
const isSelected = selectState.selectedState[movieId];
|
||||
|
||||
const onSelectPress = useCallback(
|
||||
(event: SyntheticEvent<HTMLElement, PointerEvent>) => {
|
||||
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
|
||||
window.open(`${window.Radarr.urlBase}/movie/${titleSlug}`, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
const shiftKey = event.nativeEvent.shiftKey;
|
||||
|
||||
selectDispatch({
|
||||
@@ -25,7 +33,7 @@ function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
|
||||
shiftKey,
|
||||
});
|
||||
},
|
||||
[movieId, isSelected, selectDispatch]
|
||||
[movieId, titleSlug, isSelected, selectDispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,9 +18,19 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import {
|
||||
clearMovieFiles,
|
||||
fetchMovieFiles,
|
||||
} from 'Store/Actions/movieFileActions';
|
||||
import {
|
||||
clearQueueDetails,
|
||||
fetchQueueDetails,
|
||||
} from 'Store/Actions/queueActions';
|
||||
import {
|
||||
batchToggleCutoffUnmetMovies,
|
||||
clearCutoffUnmet,
|
||||
@@ -35,6 +45,8 @@ import { CheckInputChanged } from 'typings/inputs';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import getFilterValue from 'Utilities/Filter/getFilterValue';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import {
|
||||
registerPagePopulator,
|
||||
unregisterPagePopulator,
|
||||
@@ -108,6 +120,8 @@ function CutoffUnmet() {
|
||||
const isSearchingForMovies =
|
||||
isSearchingForAllMovies || isSearchingForSelectedMovies;
|
||||
|
||||
const previousItems = usePrevious(items);
|
||||
|
||||
const handleSelectAllChange = useCallback(
|
||||
({ value }: CheckInputChanged) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
@@ -204,6 +218,8 @@ function CutoffUnmet() {
|
||||
|
||||
return () => {
|
||||
dispatch(clearCutoffUnmet());
|
||||
dispatch(clearQueueDetails());
|
||||
dispatch(clearMovieFiles());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
@@ -223,6 +239,21 @@ function CutoffUnmet() {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousItems || hasDifferentItems(items, previousItems)) {
|
||||
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
|
||||
const movieFileIds = selectUniqueIds<Movie, number>(items, 'movieFileId');
|
||||
|
||||
if (movieIds.length) {
|
||||
dispatch(fetchQueueDetails({ movieIds }));
|
||||
}
|
||||
|
||||
if (movieFileIds.length) {
|
||||
dispatch(fetchMovieFiles({ movieFileIds }));
|
||||
}
|
||||
}
|
||||
}, [items, previousItems, dispatch]);
|
||||
|
||||
return (
|
||||
<PageContent title={translate('CutoffUnmet')}>
|
||||
<PageToolbar>
|
||||
|
||||
@@ -18,10 +18,16 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import {
|
||||
clearQueueDetails,
|
||||
fetchQueueDetails,
|
||||
} from 'Store/Actions/queueActions';
|
||||
import {
|
||||
batchToggleMissingMovies,
|
||||
clearMissing,
|
||||
@@ -36,6 +42,8 @@ import { CheckInputChanged } from 'typings/inputs';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import getFilterValue from 'Utilities/Filter/getFilterValue';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import {
|
||||
registerPagePopulator,
|
||||
unregisterPagePopulator,
|
||||
@@ -112,6 +120,8 @@ function Missing() {
|
||||
const isSearchingForMovies =
|
||||
isSearchingForAllMovies || isSearchingForSelectedMovies;
|
||||
|
||||
const previousItems = usePrevious(items);
|
||||
|
||||
const handleSelectAllChange = useCallback(
|
||||
({ value }: CheckInputChanged) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
@@ -216,6 +226,7 @@ function Missing() {
|
||||
|
||||
return () => {
|
||||
dispatch(clearMissing());
|
||||
dispatch(clearQueueDetails());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
@@ -235,6 +246,16 @@ function Missing() {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousItems || hasDifferentItems(items, previousItems)) {
|
||||
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
|
||||
|
||||
if (movieIds.length) {
|
||||
dispatch(fetchQueueDetails({ movieIds }));
|
||||
}
|
||||
}
|
||||
}, [items, previousItems, dispatch]);
|
||||
|
||||
return (
|
||||
<PageContent title={translate('Missing')}>
|
||||
<PageToolbar>
|
||||
|
||||
@@ -424,8 +424,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
_logger.Debug(ex, "qbitTorrent authentication failed.");
|
||||
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
|
||||
}
|
||||
@@ -437,7 +437,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
if (response.Content != "Ok.")
|
||||
if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
|
||||
{
|
||||
// returns "Fails." on bad login
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
<PackageReference Include="Polly" Version="8.6.0" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.4.112" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.20" />
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Core" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="6.2.0" />
|
||||
<PackageReference Include="FluentValidation" Version="9.5.4" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using DryIoc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -32,6 +33,7 @@ using Radarr.Http.ClientSchema;
|
||||
using Radarr.Http.ErrorManagement;
|
||||
using Radarr.Http.Frontend;
|
||||
using Radarr.Http.Middleware;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
@@ -60,8 +62,11 @@ namespace NzbDrone.Host
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
options.KnownNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));
|
||||
});
|
||||
|
||||
services.AddRouting(options => options.LowercaseUrls = true);
|
||||
|
||||
Reference in New Issue
Block a user