mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b83a760873 | |||
| 22ab50f76d | |||
| 66758ca006 | |||
| e7d7bc79f4 | |||
| cfccb4f9c3 | |||
| 9312f17041 | |||
| 8192c22910 | |||
| 0b1d6b677a | |||
| d666df0189 | |||
| 10d8f345c1 | |||
| fb720b8714 | |||
| e8131b5791 | |||
| 4f793f6b93 | |||
| 4215c21c94 | |||
| 6913789adc | |||
| 09e0c40792 | |||
| baff805551 | |||
| c885fe43cd | |||
| 464a777722 | |||
| 89e5999c85 | |||
| b6fa332550 | |||
| 05f262dc0a | |||
| 699b765ee9 | |||
| 84beba2383 | |||
| 62eceb9148 | |||
| f46070d4b0 | |||
| 73979c416a | |||
| 348e8f9c27 | |||
| 38bdb5a75d | |||
| 5e4c51e2f7 | |||
| 99a65246a9 | |||
| 598ce9a9d2 | |||
| 42d6b9e703 | |||
| 8f595838aa | |||
| 3d9d7d3582 | |||
| 77cf28bd78 | |||
| 2fb1b8af20 | |||
| af1f389f8e | |||
| b5334da253 | |||
| 68b3904382 | |||
| c8b09b9e29 | |||
| d910fc42ab | |||
| a6db8bfe0e | |||
| 2033d7e411 | |||
| 4a04e54ceb | |||
| d57a9ab9b0 | |||
| d333204194 | |||
| c3676f8d33 | |||
| 932356be61 | |||
| 5b1b2a2d67 | |||
| c362e8c467 | |||
| 67c00a8cc7 | |||
| 27a086dfff | |||
| 8ee0df9c65 | |||
| da30b55902 | |||
| c7226fc85f | |||
| 84f22dbadc | |||
| 06a53ef9ca | |||
| b5ef0cda1e | |||
| 1b1290efac | |||
| dcbc3ea3f8 | |||
| 9a7b2cb818 | |||
| f9cba39f0a | |||
| 6b6ff4fe76 | |||
| 05d0fe2da6 | |||
| 7aab2b49e2 | |||
| 8887df92ed | |||
| 9ee651d6c0 | |||
| 5544e169a6 | |||
| 11d83165e5 | |||
| 9e6d1c581c | |||
| 66e20a0aec | |||
| e639b36283 | |||
| c9f4fb141f | |||
| 29a43fc2fd | |||
| f9454b5b5a | |||
| 9aa6d47349 | |||
| e09946d946 | |||
| c9c5429120 | |||
| ed7bd6c66d | |||
| c88fe7cae8 | |||
| 68642579d0 | |||
| f061d70d38 | |||
| fd4a609f51 | |||
| 9957f734a5 | |||
| 695b8b2ae1 | |||
| 420824b279 | |||
| badc2567c3 | |||
| c8c81927d9 | |||
| f9df843789 | |||
| 3cd39d4ee8 | |||
| 8a39ef4c56 | |||
| ba1195fc1b | |||
| 7656142db4 | |||
| 74c3b45ef8 | |||
| f7368d3d09 | |||
| 5d8e2300f2 | |||
| 1fb54c0da5 | |||
| 5a9a6e593b | |||
| 2d5fc655c0 | |||
| cfcc9a5856 |
@@ -0,0 +1,13 @@
|
|||||||
|
// This file is used to open the backend and frontend in the same workspace, which is necessary as
|
||||||
|
// the frontend has vscode settings that are distinct from the backend
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": ".."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../frontend"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
|
||||||
|
{
|
||||||
|
"name": "Readarr",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"nodeGypDependencies": true,
|
||||||
|
"version": "16",
|
||||||
|
"nvmVersion": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [8787],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": ["esbenp.prettier-vscode"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
@@ -120,11 +120,13 @@ _artifacts
|
|||||||
_rawPackage/
|
_rawPackage/
|
||||||
_dotTrace*
|
_dotTrace*
|
||||||
_tests/
|
_tests/
|
||||||
|
_temp*
|
||||||
*.Result.xml
|
*.Result.xml
|
||||||
coverage*.xml
|
coverage*.xml
|
||||||
coverage*.json
|
coverage*.json
|
||||||
setup/Output/
|
setup/Output/
|
||||||
*.~is
|
*.~is
|
||||||
|
.mono
|
||||||
|
|
||||||
# .NET Core
|
# .NET Core
|
||||||
project.lock.json
|
project.lock.json
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"ms-dotnettools.csdevkit",
|
||||||
|
"ms-vscode-remote.remote-containers"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
|
||||||
|
"name": "Run Readarr",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build dotnet",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/_output/net6.0/Readarr",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build dotnet",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"-restore",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln",
|
||||||
|
"-p:GenerateFullPaths=true",
|
||||||
|
"-p:Configuration=Debug",
|
||||||
|
"-p:Platform=Posix",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln",
|
||||||
|
"-property:GenerateFullPaths=true",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+13
-18
@@ -9,18 +9,18 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.3.22'
|
majorVersion: '0.4.2'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.417'
|
dotnetVersion: '6.0.427'
|
||||||
nodeVersion: '20.X'
|
nodeVersion: '20.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-20.04'
|
||||||
macImage: 'macOS-11'
|
macImage: 'macOS-13'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branches:
|
branches:
|
||||||
@@ -166,10 +166,10 @@ stages:
|
|||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -1075,10 +1075,10 @@ stages:
|
|||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -1102,7 +1102,7 @@ stages:
|
|||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@2
|
||||||
env:
|
env:
|
||||||
SONAR_SCANNER_OPTS: ''
|
SONAR_SCANNER_OPTS: ''
|
||||||
inputs:
|
inputs:
|
||||||
@@ -1114,7 +1114,7 @@ stages:
|
|||||||
cliProjectName: 'ReadarrUI'
|
cliProjectName: 'ReadarrUI'
|
||||||
cliProjectVersion: '$(readarrVersion)'
|
cliProjectVersion: '$(readarrVersion)'
|
||||||
cliSources: './frontend'
|
cliSources: './frontend'
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@2
|
||||||
|
|
||||||
- job: Api_Docs
|
- job: Api_Docs
|
||||||
displayName: API Docs
|
displayName: API Docs
|
||||||
@@ -1190,7 +1190,7 @@ stages:
|
|||||||
submodules: true
|
submodules: true
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@2
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
inputs:
|
inputs:
|
||||||
SonarCloud: 'SonarCloud'
|
SonarCloud: 'SonarCloud'
|
||||||
@@ -1208,21 +1208,16 @@ stages:
|
|||||||
./build.sh --backend -f net6.0 -r win-x64
|
./build.sh --backend -f net6.0 -r win-x64
|
||||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||||
displayName: Coverage Unit Tests
|
displayName: Coverage Unit Tests
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@2
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
displayName: Publish SonarCloud Results
|
displayName: Publish SonarCloud Results
|
||||||
- task: reportgenerator@4
|
- task: reportgenerator@5
|
||||||
displayName: Generate Coverage Report
|
displayName: Generate Coverage Report
|
||||||
inputs:
|
inputs:
|
||||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||||
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
||||||
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
||||||
- task: PublishCodeCoverageResults@1
|
publishCodeCoverageResults: true
|
||||||
displayName: Publish Coverage Report
|
|
||||||
inputs:
|
|
||||||
codeCoverageTool: 'cobertura'
|
|
||||||
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
|
|
||||||
reportDirectory: './CoverageResults/combined/'
|
|
||||||
|
|
||||||
- stage: Report_Out
|
- stage: Report_Out
|
||||||
dependsOn:
|
dependsOn:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FRAMEWORK="net6.0"
|
||||||
PLATFORM=$1
|
PLATFORM=$1
|
||||||
|
|
||||||
if [ "$PLATFORM" = "Windows" ]; then
|
if [ "$PLATFORM" = "Windows" ]; then
|
||||||
@@ -21,15 +25,21 @@ slnFile=src/Readarr.sln
|
|||||||
|
|
||||||
platform=Posix
|
platform=Posix
|
||||||
|
|
||||||
|
if [ "$PLATFORM" = "Windows" ]; then
|
||||||
|
application=Readarr.Console.dll
|
||||||
|
else
|
||||||
|
application=Readarr.dll
|
||||||
|
fi
|
||||||
|
|
||||||
dotnet clean $slnFile -c Debug
|
dotnet clean $slnFile -c Debug
|
||||||
dotnet clean $slnFile -c Release
|
dotnet clean $slnFile -c Release
|
||||||
|
|
||||||
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||||
|
|
||||||
dotnet new tool-manifest
|
dotnet new tool-manifest
|
||||||
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli
|
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||||
|
|
||||||
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 &
|
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &
|
||||||
|
|
||||||
sleep 45
|
sleep 45
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": "explicit"
|
||||||
},
|
},
|
||||||
|
|
||||||
"typescript.preferences.quoteStyle": "single",
|
"typescript.preferences.quoteStyle": "single",
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ module.exports = (env) => {
|
|||||||
output: {
|
output: {
|
||||||
path: distFolder,
|
path: distFolder,
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
filename: '[name]-[contenthash].js',
|
filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
|
||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ module.exports = (env) => {
|
|||||||
|
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'Content/styles.css',
|
filename: 'Content/styles.css',
|
||||||
chunkFilename: 'Content/[id]-[chunkhash].css'
|
chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
@@ -202,7 +202,7 @@ module.exports = (env) => {
|
|||||||
options: {
|
options: {
|
||||||
importLoaders: 1,
|
importLoaders: 1,
|
||||||
modules: {
|
modules: {
|
||||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
|
|||||||
import Logs from 'System/Logs/Logs';
|
import Logs from 'System/Logs/Logs';
|
||||||
import Status from 'System/Status/Status';
|
import Status from 'System/Status/Status';
|
||||||
import Tasks from 'System/Tasks/Tasks';
|
import Tasks from 'System/Tasks/Tasks';
|
||||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
import Updates from 'System/Updates/Updates';
|
||||||
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
|
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
|
||||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||||
@@ -247,7 +247,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/system/updates"
|
path="/system/updates"
|
||||||
component={UpdatesConnector}
|
component={Updates}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import AuthorsAppState from './AuthorsAppState';
|
import AuthorsAppState from './AuthorsAppState';
|
||||||
import CommandAppState from './CommandAppState';
|
import CommandAppState from './CommandAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
|
import SystemAppState from './SystemAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
|
|
||||||
interface FilterBuilderPropOption {
|
interface FilterBuilderPropOption {
|
||||||
@@ -35,10 +36,24 @@ export interface CustomFilter {
|
|||||||
filers: PropertyFilter[];
|
filers: PropertyFilter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppSectionState {
|
||||||
|
isConnected: boolean;
|
||||||
|
isReconnecting: boolean;
|
||||||
|
version: string;
|
||||||
|
prevVersion?: string;
|
||||||
|
dimensions: {
|
||||||
|
isSmallScreen: boolean;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
|
app: AppSectionState;
|
||||||
authors: AuthorsAppState;
|
authors: AuthorsAppState;
|
||||||
commands: CommandAppState;
|
commands: CommandAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
|
system: SystemAppState;
|
||||||
tags: TagsAppState;
|
tags: TagsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionItemState,
|
||||||
AppSectionSaveState,
|
AppSectionSaveState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
@@ -7,13 +8,16 @@ import ImportList from 'typings/ImportList';
|
|||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
import IndexerFlag from 'typings/IndexerFlag';
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import { UiSettings } from 'typings/UiSettings';
|
import General from 'typings/Settings/General';
|
||||||
|
import UiSettings from 'typings/Settings/UiSettings';
|
||||||
|
|
||||||
export interface DownloadClientAppState
|
export interface DownloadClientAppState
|
||||||
extends AppSectionState<DownloadClient>,
|
extends AppSectionState<DownloadClient>,
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
AppSectionSaveState {}
|
AppSectionSaveState {}
|
||||||
|
|
||||||
|
export type GeneralAppState = AppSectionItemState<General>;
|
||||||
|
|
||||||
export interface ImportListAppState
|
export interface ImportListAppState
|
||||||
extends AppSectionState<ImportList>,
|
extends AppSectionState<ImportList>,
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
@@ -33,11 +37,12 @@ export type UiSettingsAppState = AppSectionState<UiSettings>;
|
|||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
|
general: GeneralAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
indexerFlags: IndexerFlagSettingsAppState;
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
uiSettings: UiSettingsAppState;
|
ui: UiSettingsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAppState;
|
export default SettingsAppState;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import SystemStatus from 'typings/SystemStatus';
|
||||||
|
import Update from 'typings/Update';
|
||||||
|
import AppSectionState, { AppSectionItemState } from './AppSectionState';
|
||||||
|
|
||||||
|
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||||
|
export type UpdateAppState = AppSectionState<Update>;
|
||||||
|
|
||||||
|
interface SystemAppState {
|
||||||
|
updates: UpdateAppState;
|
||||||
|
status: SystemStatusAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemAppState;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.input {
|
|
||||||
composes: input from '~Components/Form/TextInput.css';
|
|
||||||
|
|
||||||
font-family: $passwordFamily;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
import styles from './PasswordInput.css';
|
|
||||||
|
|
||||||
// Prevent a user from copying (or cutting) the password from the input
|
// Prevent a user from copying (or cutting) the password from the input
|
||||||
function onCopy(e) {
|
function onCopy(e) {
|
||||||
@@ -13,17 +11,14 @@ function PasswordInput(props) {
|
|||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
{...props}
|
{...props}
|
||||||
|
type="password"
|
||||||
onCopy={onCopy}
|
onCopy={onCopy}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordInput.propTypes = {
|
PasswordInput.propTypes = {
|
||||||
className: PropTypes.string.isRequired
|
...TextInput.props
|
||||||
};
|
|
||||||
|
|
||||||
PasswordInput.defaultProps = {
|
|
||||||
className: styles.input
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasswordInput;
|
export default PasswordInput;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
|
||||||
|
|
||||||
.isDisabled {
|
&.isDisabled {
|
||||||
color: var(--disabledColor);
|
color: var(--disabledColor);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
|
|||||||
import ColorImpairedContext from 'App/ColorImpairedContext';
|
import ColorImpairedContext from 'App/ColorImpairedContext';
|
||||||
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
||||||
import SignalRConnector from 'Components/SignalRConnector';
|
import SignalRConnector from 'Components/SignalRConnector';
|
||||||
|
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
|
||||||
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
||||||
import PageHeader from './Header/PageHeader';
|
import PageHeader from './Header/PageHeader';
|
||||||
import PageSidebar from './Sidebar/PageSidebar';
|
import PageSidebar from './Sidebar/PageSidebar';
|
||||||
@@ -75,6 +76,7 @@ class Page extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
|
authenticationEnabled,
|
||||||
onSidebarToggle,
|
onSidebarToggle,
|
||||||
onSidebarVisibleChange
|
onSidebarVisibleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -108,6 +110,10 @@ class Page extends Component {
|
|||||||
isOpen={this.state.isConnectionLostModalOpen}
|
isOpen={this.state.isConnectionLostModalOpen}
|
||||||
onModalClose={this.onConnectionLostModalClose}
|
onModalClose={this.onConnectionLostModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AuthenticationRequiredModal
|
||||||
|
isOpen={!authenticationEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ColorImpairedContext.Provider>
|
</ColorImpairedContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -123,6 +129,7 @@ Page.propTypes = {
|
|||||||
isUpdated: PropTypes.bool.isRequired,
|
isUpdated: PropTypes.bool.isRequired,
|
||||||
isDisconnected: PropTypes.bool.isRequired,
|
isDisconnected: PropTypes.bool.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
authenticationEnabled: PropTypes.bool.isRequired,
|
||||||
onResize: PropTypes.func.isRequired,
|
onResize: PropTypes.func.isRequired,
|
||||||
onSidebarToggle: PropTypes.func.isRequired,
|
onSidebarToggle: PropTypes.func.isRequired,
|
||||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import ErrorPage from './ErrorPage';
|
import ErrorPage from './ErrorPage';
|
||||||
import LoadingPage from './LoadingPage';
|
import LoadingPage from './LoadingPage';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
@@ -153,18 +154,21 @@ function createMapStateToProps() {
|
|||||||
selectErrors,
|
selectErrors,
|
||||||
selectAppProps,
|
selectAppProps,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
createSystemStatusSelector(),
|
||||||
(
|
(
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
errors,
|
errors,
|
||||||
app,
|
app,
|
||||||
dimensions
|
dimensions,
|
||||||
|
systemStatus
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
...errors,
|
...errors,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
authenticationEnabled: systemStatus.authentication !== 'none',
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ class SignalRConnector extends Component {
|
|||||||
handleWantedCutoff = (body) => {
|
handleWantedCutoff = (body) => {
|
||||||
if (body.action === 'updated') {
|
if (body.action === 'updated') {
|
||||||
this.props.dispatchUpdateItem({
|
this.props.dispatchUpdateItem({
|
||||||
section: 'cutoffUnmet',
|
section: 'wanted.cutoffUnmet',
|
||||||
updateOnly: true,
|
updateOnly: true,
|
||||||
...body.resource
|
...body.resource
|
||||||
});
|
});
|
||||||
@@ -263,7 +263,7 @@ class SignalRConnector extends Component {
|
|||||||
handleWantedMissing = (body) => {
|
handleWantedMissing = (body) => {
|
||||||
if (body.action === 'updated') {
|
if (body.action === 'updated') {
|
||||||
this.props.dispatchUpdateItem({
|
this.props.dispatchUpdateItem({
|
||||||
section: 'missing',
|
section: 'wanted.missing',
|
||||||
updateOnly: true,
|
updateOnly: true,
|
||||||
...body.resource
|
...body.resource
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,14 +25,3 @@
|
|||||||
font-family: 'Ubuntu Mono';
|
font-family: 'Ubuntu Mono';
|
||||||
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
|
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* text-security-disc
|
|
||||||
*/
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-family: 'text-security-disc';
|
|
||||||
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnBackgroundClick={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AuthenticationRequiredModalContentConnector
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModal;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.authRequiredAlert {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'input': string;
|
'authRequiredAlert': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AuthenticationRequiredModalContent.css';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModalContent(props) {
|
||||||
|
const {
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
onSavePress,
|
||||||
|
dispatchFetchStatus
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
passwordConfirmation
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
|
|
||||||
|
const didMount = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSaving && didMount.current) {
|
||||||
|
dispatchFetchStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
didMount.current = true;
|
||||||
|
}, [isSaving, dispatchFetchStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent
|
||||||
|
showCloseButton={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<ModalHeader>
|
||||||
|
{translate('AuthenticationRequired')}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Alert
|
||||||
|
className={styles.authRequiredAlert}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
>
|
||||||
|
{translate('AuthenticationRequiredWarning')}
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error ?
|
||||||
|
<div>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationMethod"
|
||||||
|
values={authenticationMethodOptions}
|
||||||
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
|
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
||||||
|
helpLink="https://wiki.servarr.com/readarr/faq#forced-authentication"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationMethod}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText={translate('AuthenticationRequiredHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Username')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="username"
|
||||||
|
onChange={onInputChange}
|
||||||
|
helpTextWarning={username?.value ? undefined : translate('AuthenticationRequiredUsernameHelpTextWarning')}
|
||||||
|
{...username}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Password')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="password"
|
||||||
|
onChange={onInputChange}
|
||||||
|
helpTextWarning={password?.value ? undefined : translate('AuthenticationRequiredPasswordHelpTextWarning')}
|
||||||
|
{...password}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="passwordConfirmation"
|
||||||
|
onChange={onInputChange}
|
||||||
|
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
|
||||||
|
{...passwordConfirmation}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isPopulated && !error ? <LoadingIndicator /> : null
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!authenticationEnabled}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
{translate('Save')}
|
||||||
|
</SpinnerButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContent.propTypes = {
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModalContent;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
|
||||||
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
|
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
|
||||||
|
|
||||||
|
const SECTION = 'general';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createSettingsSectionSelector(SECTION),
|
||||||
|
(sectionSettings) => {
|
||||||
|
return {
|
||||||
|
...sectionSettings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchClearPendingChanges: clearPendingChanges,
|
||||||
|
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings: saveGeneralSettings,
|
||||||
|
dispatchFetchGeneralSettings: fetchGeneralSettings,
|
||||||
|
dispatchFetchStatus: fetchStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthenticationRequiredModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchGeneralSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.dispatchSetGeneralSettingsValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.dispatchSaveGeneralSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
dispatchFetchGeneralSettings,
|
||||||
|
dispatchSetGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthenticationRequiredModalContent
|
||||||
|
{...otherProps}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContentConnector.propTypes = {
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
|
||||||
@@ -47,14 +47,16 @@ class InteractiveImportRow extends Component {
|
|||||||
author,
|
author,
|
||||||
book,
|
book,
|
||||||
foreignEditionId,
|
foreignEditionId,
|
||||||
quality
|
quality,
|
||||||
|
size
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
author &&
|
author &&
|
||||||
book != null &&
|
book != null &&
|
||||||
foreignEditionId &&
|
foreignEditionId &&
|
||||||
quality
|
quality &&
|
||||||
|
size > 0
|
||||||
) {
|
) {
|
||||||
this.props.onSelectedChange({ id, value: true });
|
this.props.onSelectedChange({ id, value: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
@@ -7,7 +8,7 @@ import Link from 'Components/Link/Link';
|
|||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector';
|
import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector';
|
||||||
@@ -127,9 +128,16 @@ class AddNewItem extends Component {
|
|||||||
!isFetching && !!error ?
|
!isFetching && !!error ?
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
<div className={styles.helpText}>
|
<div className={styles.helpText}>
|
||||||
Failed to load search results, please try again.
|
{translate('FailedLoadingSearchResults')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Link to="https://wiki.servarr.com/readarr/troubleshooting#invalid-response-received-from-metadata-api">
|
||||||
|
{translate('WhySearchesCouldBeFailing')}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>{getErrorMessage(error)}</div>
|
|
||||||
</div> : null
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,3 +25,8 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--cardCenterBackgroundColor);
|
background-color: var(--cardCenterBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customFormats {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|||||||
+1
@@ -3,6 +3,7 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'addSpecification': string;
|
'addSpecification': string;
|
||||||
'center': string;
|
'center': string;
|
||||||
|
'customFormats': string;
|
||||||
'deleteButton': string;
|
'deleteButton': string;
|
||||||
'rightButtons': string;
|
'rightButtons': string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,69 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|||||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const authenticationMethodOptions = [
|
export const authenticationMethodOptions = [
|
||||||
{ key: 'none', value: 'None' },
|
{
|
||||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
key: 'none',
|
||||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
get value() {
|
||||||
|
return translate('None');
|
||||||
|
},
|
||||||
|
isDisabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'external',
|
||||||
|
get value() {
|
||||||
|
return translate('External');
|
||||||
|
},
|
||||||
|
isHidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'basic',
|
||||||
|
get value() {
|
||||||
|
return translate('AuthBasic');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'forms',
|
||||||
|
get value() {
|
||||||
|
return translate('AuthForm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const authenticationRequiredOptions = [
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabledForLocalAddresses',
|
||||||
|
get value() {
|
||||||
|
return translate('DisabledForLocalAddresses');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const certificateValidationOptions = [
|
const certificateValidationOptions = [
|
||||||
{ key: 'enabled', value: 'Enabled' },
|
{
|
||||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
key: 'enabled',
|
||||||
{ key: 'disabled', value: 'Disabled' }
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabledForLocalAddresses',
|
||||||
|
get value() {
|
||||||
|
return translate('DisabledForLocalAddresses');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class SecuritySettings extends Component {
|
class SecuritySettings extends Component {
|
||||||
@@ -68,8 +121,10 @@ class SecuritySettings extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
authenticationMethod,
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
apiKey,
|
apiKey,
|
||||||
certificateValidation
|
certificateValidation
|
||||||
} = settings;
|
} = settings;
|
||||||
@@ -79,26 +134,40 @@ class SecuritySettings extends Component {
|
|||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Security')}>
|
<FieldSet legend={translate('Security')}>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('Authentication')}</FormLabel>
|
||||||
{translate('Authentication')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
|
helpTextWarning={translate('AuthenticationRequiredWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled &&
|
authenticationEnabled ?
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
||||||
{translate('Username')}
|
|
||||||
</FormLabel>
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText={translate('AuthenticationRequiredHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Username')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
@@ -106,15 +175,14 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...username}
|
{...username}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled &&
|
authenticationEnabled ?
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('Password')}</FormLabel>
|
||||||
{translate('Password')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.PASSWORD}
|
type={inputTypes.PASSWORD}
|
||||||
@@ -122,19 +190,33 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="passwordConfirmation"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...passwordConfirmation}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('ApiKey')}</FormLabel>
|
||||||
{translate('APIKey')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
helpTextWarning={translate('ApiKeyHelpTextWarning')}
|
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||||
buttons={[
|
buttons={[
|
||||||
<ClipboardButton
|
<ClipboardButton
|
||||||
key="copy"
|
key="copy"
|
||||||
@@ -160,9 +242,7 @@ class SecuritySettings extends Component {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>{translate('CertificateValidation')}</FormLabel>
|
||||||
{translate('CertificateValidation')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ function UpdateSettings(props) {
|
|||||||
const {
|
const {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
settings,
|
settings,
|
||||||
isWindows,
|
|
||||||
packageUpdateMechanism,
|
packageUpdateMechanism,
|
||||||
onInputChange
|
onInputChange
|
||||||
} = props;
|
} = props;
|
||||||
@@ -44,10 +43,10 @@ function UpdateSettings(props) {
|
|||||||
value: titleCase(packageUpdateMechanism)
|
value: titleCase(packageUpdateMechanism)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
updateOptions.push({ key: 'builtIn', value: 'Built-In' });
|
updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions.push({ key: 'script', value: 'Script' });
|
updateOptions.push({ key: 'script', value: translate('Script') });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Updates')}>
|
<FieldSet legend={translate('Updates')}>
|
||||||
@@ -60,8 +59,8 @@ function UpdateSettings(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.AUTO_COMPLETE}
|
type={inputTypes.AUTO_COMPLETE}
|
||||||
name="branch"
|
name="branch"
|
||||||
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
|
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
||||||
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
|
helpLink="https://wiki.servarr.com/readarr/settings#updates"
|
||||||
{...branch}
|
{...branch}
|
||||||
values={branchValues}
|
values={branchValues}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@@ -69,62 +68,59 @@ function UpdateSettings(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
<div>
|
||||||
!isWindows &&
|
<FormGroup
|
||||||
<div>
|
advancedSettings={advancedSettings}
|
||||||
<FormGroup
|
isAdvanced={true}
|
||||||
advancedSettings={advancedSettings}
|
size={sizes.MEDIUM}
|
||||||
isAdvanced={true}
|
>
|
||||||
size={sizes.MEDIUM}
|
<FormLabel>{translate('Automatic')}</FormLabel>
|
||||||
>
|
|
||||||
<FormLabel>{translate('Automatic')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="updateAutomatically"
|
name="updateAutomatically"
|
||||||
helpText={translate('UpdateAutomaticallyHelpText')}
|
helpText={translate('UpdateAutomaticallyHelpText')}
|
||||||
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined}
|
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateAutomatically}
|
{...updateAutomatically}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>{translate('Mechanism')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="updateMechanism"
|
||||||
|
values={updateOptions}
|
||||||
|
helpText={translate('UpdateMechanismHelpText')}
|
||||||
|
helpLink="https://wiki.servarr.com/readarr/settings#updates"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...updateMechanism}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
updateMechanism.value === 'script' &&
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>{translate('Mechanism')}</FormLabel>
|
<FormLabel>{translate('ScriptPath')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.TEXT}
|
||||||
name="updateMechanism"
|
name="updateScriptPath"
|
||||||
values={updateOptions}
|
helpText={translate('UpdateScriptPathHelpText')}
|
||||||
helpText={translate('UpdateMechanismHelpText')}
|
|
||||||
helpLink="https://wiki.servarr.com/readarr/faq#how-do-i-update-my-readarr"
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateMechanism}
|
{...updateScriptPath}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
}
|
||||||
{
|
</div>
|
||||||
updateMechanism.value === 'script' &&
|
|
||||||
<FormGroup
|
|
||||||
advancedSettings={advancedSettings}
|
|
||||||
isAdvanced={true}
|
|
||||||
>
|
|
||||||
<FormLabel>{translate('ScriptPath')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TEXT}
|
|
||||||
name="updateScriptPath"
|
|
||||||
helpText={translate('UpdateScriptPathHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...updateScriptPath}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import TagListConnector from 'Components/TagListConnector';
|
|||||||
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
|
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
|
||||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||||
import { SelectStateInputProps } from 'typings/props';
|
import { SelectStateInputProps } from 'typings/props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './ManageImportListsModalRow.css';
|
import styles from './ManageImportListsModalRow.css';
|
||||||
|
|
||||||
interface ManageImportListsModalRowProps {
|
interface ManageImportListsModalRowProps {
|
||||||
@@ -70,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.qualityProfileId}>
|
<TableRowCell className={styles.qualityProfileId}>
|
||||||
{qualityProfile?.name ?? 'None'}
|
{qualityProfile?.name ?? translate('None')}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.metadataProfileId}>
|
<TableRowCell className={styles.metadataProfileId}>
|
||||||
@@ -82,7 +83,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.enableAutomaticAdd}>
|
<TableRowCell className={styles.enableAutomaticAdd}>
|
||||||
{enableAutomaticAdd ? 'Yes' : 'No'}
|
{enableAutomaticAdd ? translate('Yes') : translate('No')}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.tags}>
|
<TableRowCell className={styles.tags}>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@@ -15,11 +14,11 @@ function createMapStateToProps() {
|
|||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
(state) => state.settings.namingExamples,
|
(state) => state.settings.namingExamples,
|
||||||
createSettingsSectionSelector(SECTION),
|
createSettingsSectionSelector(SECTION),
|
||||||
(advancedSettings, examples, sectionSettings) => {
|
(advancedSettings, namingExamples, sectionSettings) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
examples: examples.item,
|
examples: namingExamples.item,
|
||||||
examplesPopulated: !_.isEmpty(examples.item),
|
examplesPopulated: namingExamples.isPopulated,
|
||||||
...sectionSettings
|
...sectionSettings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ class RootFolder extends Component {
|
|||||||
{path}
|
{path}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={qualityProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
|
||||||
{qualityProfile.name}
|
{qualityProfile?.name || translate('None')}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={metadataProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
|
||||||
{metadataProfile.name}
|
{metadataProfile?.name || translate('None')}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
import _ from 'lodash';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import getProviderState from 'Utilities/State/getProviderState';
|
import getProviderState from 'Utilities/State/getProviderState';
|
||||||
import { set } from '../baseActions';
|
import { set } from '../baseActions';
|
||||||
|
|
||||||
const abortCurrentRequests = {};
|
const abortCurrentRequests = {};
|
||||||
|
let lastTestData = null;
|
||||||
|
|
||||||
export function createCancelTestProviderHandler(section) {
|
export function createCancelTestProviderHandler(section) {
|
||||||
return function(getState, payload, dispatch) {
|
return function(getState, payload, dispatch) {
|
||||||
@@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
|
|||||||
return function(getState, payload, dispatch) {
|
return function(getState, payload, dispatch) {
|
||||||
dispatch(set({ section, isTesting: true }));
|
dispatch(set({ section, isTesting: true }));
|
||||||
|
|
||||||
const testData = getProviderState(payload, getState, section);
|
const {
|
||||||
|
queryParams = {},
|
||||||
|
...otherPayload
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const testData = getProviderState({ ...otherPayload }, getState, section);
|
||||||
|
const params = { ...queryParams };
|
||||||
|
|
||||||
|
// If the user is re-testing the same provider without changes
|
||||||
|
// force it to be tested.
|
||||||
|
|
||||||
|
if (_.isEqual(testData, lastTestData)) {
|
||||||
|
params.forceTest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTestData = testData;
|
||||||
|
|
||||||
const ajaxOptions = {
|
const ajaxOptions = {
|
||||||
url: `${url}/test`,
|
url: `${url}/test?${$.param(params, true)}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
@@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
|
|||||||
abortCurrentRequests[section] = abortRequest;
|
abortCurrentRequests[section] = abortRequest;
|
||||||
|
|
||||||
request.done((data) => {
|
request.done((data) => {
|
||||||
|
lastTestData = null;
|
||||||
|
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
isTesting: false,
|
isTesting: false,
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
function createTagsSelector() {
|
function createTagsSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.tags.items,
|
(state: AppState) => state.tags.items,
|
||||||
(tags) => {
|
(tags) => {
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ module.exports = {
|
|||||||
// Families
|
// Families
|
||||||
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||||
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
|
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
|
||||||
passwordFamily: 'text-security-disc',
|
|
||||||
|
|
||||||
// Sizes
|
// Sizes
|
||||||
extraSmallFontSize: '11px',
|
extraSmallFontSize: '11px',
|
||||||
|
|||||||
@@ -6,6 +6,22 @@ import createMultiAuthorsSelector from 'Store/Selectors/createMultiAuthorsSelect
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './QueuedTaskRowNameCell.css';
|
import styles from './QueuedTaskRowNameCell.css';
|
||||||
|
|
||||||
|
function formatTitles(titles: string[]) {
|
||||||
|
if (!titles) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titles.length > 11) {
|
||||||
|
return (
|
||||||
|
<span title={titles.join(', ')}>
|
||||||
|
{titles.slice(0, 10).join(', ')}, {titles.length - 10} more
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{titles.join(', ')}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueuedTaskRowNameCellProps {
|
export interface QueuedTaskRowNameCellProps {
|
||||||
commandName: string;
|
commandName: string;
|
||||||
body: CommandBody;
|
body: CommandBody;
|
||||||
@@ -32,7 +48,7 @@ export default function QueuedTaskRowNameCell(
|
|||||||
<span className={styles.commandName}>
|
<span className={styles.commandName}>
|
||||||
{commandName}
|
{commandName}
|
||||||
{sortedAuthors.length ? (
|
{sortedAuthors.length ? (
|
||||||
<span> - {sortedAuthors.map((a) => a.authorName).join(', ')}</span>
|
<span> - {formatTitles(sortedAuthors.map((a) => a.authorName))}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
|
||||||
import styles from './UpdateChanges.css';
|
|
||||||
|
|
||||||
class UpdateChanges extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
changes
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (changes.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={styles.title}>{title}</div>
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
changes.map((change, index) => {
|
|
||||||
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
|
|
||||||
return `[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(1)})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={index}>
|
|
||||||
<InlineMarkdown data={checkChange} />
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateChanges.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
changes: PropTypes.arrayOf(PropTypes.string)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UpdateChanges;
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
|
import styles from './UpdateChanges.css';
|
||||||
|
|
||||||
|
interface UpdateChangesProps {
|
||||||
|
title: string;
|
||||||
|
changes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateChanges(props: UpdateChangesProps) {
|
||||||
|
const { title, changes } = props;
|
||||||
|
|
||||||
|
if (changes.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueChanges = [...new Set(changes)];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<ul>
|
||||||
|
{uniqueChanges.map((change, index) => {
|
||||||
|
const checkChange = change.replace(
|
||||||
|
/#\d{4,5}\b/g,
|
||||||
|
(match) =>
|
||||||
|
`[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(
|
||||||
|
1
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
<InlineMarkdown data={checkChange} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateChanges;
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component, Fragment } from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
|
||||||
import PageContent from 'Components/Page/PageContent';
|
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import formatDate from 'Utilities/Date/formatDate';
|
|
||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import UpdateChanges from './UpdateChanges';
|
|
||||||
import styles from './Updates.css';
|
|
||||||
|
|
||||||
class Updates extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
currentVersion,
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
updatesError,
|
|
||||||
generalSettingsError,
|
|
||||||
items,
|
|
||||||
isInstallingUpdate,
|
|
||||||
updateMechanism,
|
|
||||||
isDocker,
|
|
||||||
updateMechanismMessage,
|
|
||||||
shortDateFormat,
|
|
||||||
longDateFormat,
|
|
||||||
timeFormat,
|
|
||||||
onInstallLatestPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasError = !!(updatesError || generalSettingsError);
|
|
||||||
const hasUpdates = isPopulated && !hasError && items.length > 0;
|
|
||||||
const noUpdates = isPopulated && !hasError && !items.length;
|
|
||||||
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
|
|
||||||
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
|
|
||||||
|
|
||||||
const externalUpdaterPrefix = 'Unable to update Readarr directly,';
|
|
||||||
const externalUpdaterMessages = {
|
|
||||||
external: 'Readarr is configured to use an external update mechanism',
|
|
||||||
apt: 'use apt to install the update',
|
|
||||||
docker: 'update the docker container to receive the update'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent title={translate('Updates')}>
|
|
||||||
<PageContentBody>
|
|
||||||
{
|
|
||||||
!isPopulated && !hasError &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
noUpdates &&
|
|
||||||
<Alert kind={kinds.INFO}>
|
|
||||||
{translate('NoUpdatesAreAvailable')}
|
|
||||||
</Alert>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
hasUpdateToInstall &&
|
|
||||||
<div className={styles.messageContainer}>
|
|
||||||
{
|
|
||||||
(updateMechanism === 'builtIn' || updateMechanism === 'script') && !isDocker ?
|
|
||||||
<SpinnerButton
|
|
||||||
className={styles.updateAvailable}
|
|
||||||
kind={kinds.PRIMARY}
|
|
||||||
isSpinning={isInstallingUpdate}
|
|
||||||
onPress={onInstallLatestPress}
|
|
||||||
>
|
|
||||||
Install Latest
|
|
||||||
</SpinnerButton> :
|
|
||||||
|
|
||||||
<Fragment>
|
|
||||||
<Icon
|
|
||||||
name={icons.WARNING}
|
|
||||||
kind={kinds.WARNING}
|
|
||||||
size={30}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.message}>
|
|
||||||
{externalUpdaterPrefix} <InlineMarkdown data={updateMechanismMessage || externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external} />
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator
|
|
||||||
className={styles.loading}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
noUpdateToInstall &&
|
|
||||||
<div className={styles.messageContainer}>
|
|
||||||
<Icon
|
|
||||||
className={styles.upToDateIcon}
|
|
||||||
name={icons.CHECK_CIRCLE}
|
|
||||||
size={30}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.message}>
|
|
||||||
The latest version of Readarr is already installed
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator
|
|
||||||
className={styles.loading}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
hasUpdates &&
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
items.map((update) => {
|
|
||||||
const hasChanges = !!update.changes;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={update.version}
|
|
||||||
className={styles.update}
|
|
||||||
>
|
|
||||||
<div className={styles.info}>
|
|
||||||
<div className={styles.version}>{update.version}</div>
|
|
||||||
<div className={styles.space}>—</div>
|
|
||||||
<div
|
|
||||||
className={styles.date}
|
|
||||||
title={formatDateTime(update.releaseDate, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
{formatDate(update.releaseDate, shortDateFormat)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
update.branch === 'master' ?
|
|
||||||
null :
|
|
||||||
<Label
|
|
||||||
className={styles.label}
|
|
||||||
>
|
|
||||||
{update.branch}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
update.version === currentVersion ?
|
|
||||||
<Label
|
|
||||||
className={styles.label}
|
|
||||||
kind={kinds.SUCCESS}
|
|
||||||
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
Currently Installed
|
|
||||||
</Label> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
update.version !== currentVersion && update.installedOn ?
|
|
||||||
<Label
|
|
||||||
className={styles.label}
|
|
||||||
kind={kinds.INVERSE}
|
|
||||||
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
Previously Installed
|
|
||||||
</Label> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
!hasChanges &&
|
|
||||||
<div>
|
|
||||||
{translate('MaintenanceRelease')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
hasChanges &&
|
|
||||||
<div className={styles.changes}>
|
|
||||||
<UpdateChanges
|
|
||||||
title={translate('New')}
|
|
||||||
changes={update.changes.new}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UpdateChanges
|
|
||||||
title={translate('Fixed')}
|
|
||||||
changes={update.changes.fixed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!!updatesError &&
|
|
||||||
<div>
|
|
||||||
Failed to fetch updates
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!!generalSettingsError &&
|
|
||||||
<div>
|
|
||||||
Failed to update settings
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</PageContentBody>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Updates.propTypes = {
|
|
||||||
currentVersion: PropTypes.string.isRequired,
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
updatesError: PropTypes.object,
|
|
||||||
generalSettingsError: PropTypes.object,
|
|
||||||
items: PropTypes.array.isRequired,
|
|
||||||
isInstallingUpdate: PropTypes.bool.isRequired,
|
|
||||||
isDocker: PropTypes.bool.isRequired,
|
|
||||||
updateMechanism: PropTypes.string,
|
|
||||||
updateMechanismMessage: PropTypes.string,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired,
|
|
||||||
onInstallLatestPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Updates;
|
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import * as commandNames from 'Commands/commandNames';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { fetchGeneralSettings } from 'Store/Actions/settingsActions';
|
||||||
|
import { fetchUpdates } from 'Store/Actions/systemActions';
|
||||||
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
import { UpdateMechanism } from 'typings/Settings/General';
|
||||||
|
import formatDate from 'Utilities/Date/formatDate';
|
||||||
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import UpdateChanges from './UpdateChanges';
|
||||||
|
import styles from './Updates.css';
|
||||||
|
|
||||||
|
const VERSION_REGEX = /\d+\.\d+\.\d+\.\d+/i;
|
||||||
|
|
||||||
|
function createUpdatesSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.system.updates,
|
||||||
|
(state: AppState) => state.settings.general,
|
||||||
|
(updates, generalSettings) => {
|
||||||
|
const { error: updatesError, items } = updates;
|
||||||
|
|
||||||
|
const isFetching = updates.isFetching || generalSettings.isFetching;
|
||||||
|
const isPopulated = updates.isPopulated && generalSettings.isPopulated;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
updatesError,
|
||||||
|
generalSettingsError: generalSettings.error,
|
||||||
|
items,
|
||||||
|
updateMechanism: generalSettings.item.updateMechanism,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Updates() {
|
||||||
|
const currentVersion = useSelector((state: AppState) => state.app.version);
|
||||||
|
const { packageUpdateMechanismMessage } = useSelector(
|
||||||
|
createSystemStatusSelector()
|
||||||
|
);
|
||||||
|
const { shortDateFormat, longDateFormat, timeFormat } = useSelector(
|
||||||
|
createUISettingsSelector()
|
||||||
|
);
|
||||||
|
const isInstallingUpdate = useSelector(
|
||||||
|
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE)
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
updatesError,
|
||||||
|
generalSettingsError,
|
||||||
|
items,
|
||||||
|
updateMechanism,
|
||||||
|
} = useSelector(createUpdatesSelector());
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [isMajorUpdateModalOpen, setIsMajorUpdateModalOpen] = useState(false);
|
||||||
|
const hasError = !!(updatesError || generalSettingsError);
|
||||||
|
const hasUpdates = isPopulated && !hasError && items.length > 0;
|
||||||
|
const noUpdates = isPopulated && !hasError && !items.length;
|
||||||
|
|
||||||
|
const externalUpdaterPrefix = translate('UpdateAppDirectlyLoadError');
|
||||||
|
const externalUpdaterMessages: Partial<Record<UpdateMechanism, string>> = {
|
||||||
|
external: translate('ExternalUpdater'),
|
||||||
|
apt: translate('AptUpdater'),
|
||||||
|
docker: translate('DockerUpdater'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isMajorUpdate, hasUpdateToInstall } = useMemo(() => {
|
||||||
|
const majorVersion = parseInt(
|
||||||
|
currentVersion.match(VERSION_REGEX)?.[0] ?? '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
const latestVersion = items[0]?.version;
|
||||||
|
const latestMajorVersion = parseInt(
|
||||||
|
latestVersion?.match(VERSION_REGEX)?.[0] ?? '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMajorUpdate: latestMajorVersion > majorVersion,
|
||||||
|
hasUpdateToInstall: items.some(
|
||||||
|
(update) => update.installable && update.latest
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}, [currentVersion, items]);
|
||||||
|
|
||||||
|
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
|
||||||
|
|
||||||
|
const handleInstallLatestPress = useCallback(() => {
|
||||||
|
if (isMajorUpdate) {
|
||||||
|
setIsMajorUpdateModalOpen(true);
|
||||||
|
} else {
|
||||||
|
dispatch(executeCommand({ name: commandNames.APPLICATION_UPDATE }));
|
||||||
|
}
|
||||||
|
}, [isMajorUpdate, setIsMajorUpdateModalOpen, dispatch]);
|
||||||
|
|
||||||
|
const handleInstallLatestMajorVersionPress = useCallback(() => {
|
||||||
|
setIsMajorUpdateModalOpen(false);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
executeCommand({
|
||||||
|
name: commandNames.APPLICATION_UPDATE,
|
||||||
|
installMajorUpdate: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [setIsMajorUpdateModalOpen, dispatch]);
|
||||||
|
|
||||||
|
const handleCancelMajorVersionPress = useCallback(() => {
|
||||||
|
setIsMajorUpdateModalOpen(false);
|
||||||
|
}, [setIsMajorUpdateModalOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchUpdates());
|
||||||
|
dispatch(fetchGeneralSettings());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent title={translate('Updates')}>
|
||||||
|
<PageContentBody>
|
||||||
|
{isPopulated || hasError ? null : <LoadingIndicator />}
|
||||||
|
|
||||||
|
{noUpdates ? (
|
||||||
|
<Alert kind={kinds.INFO}>{translate('NoUpdatesAreAvailable')}</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{hasUpdateToInstall ? (
|
||||||
|
<div className={styles.messageContainer}>
|
||||||
|
{updateMechanism === 'builtIn' || updateMechanism === 'script' ? (
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isInstallingUpdate}
|
||||||
|
onPress={handleInstallLatestPress}
|
||||||
|
>
|
||||||
|
{translate('InstallLatest')}
|
||||||
|
</SpinnerButton>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
|
||||||
|
|
||||||
|
<div className={styles.message}>
|
||||||
|
{externalUpdaterPrefix}{' '}
|
||||||
|
<InlineMarkdown
|
||||||
|
data={
|
||||||
|
packageUpdateMechanismMessage ||
|
||||||
|
externalUpdaterMessages[updateMechanism] ||
|
||||||
|
externalUpdaterMessages.external
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isFetching ? (
|
||||||
|
<LoadingIndicator className={styles.loading} size={20} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{noUpdateToInstall && (
|
||||||
|
<div className={styles.messageContainer}>
|
||||||
|
<Icon
|
||||||
|
className={styles.upToDateIcon}
|
||||||
|
name={icons.CHECK_CIRCLE}
|
||||||
|
size={30}
|
||||||
|
/>
|
||||||
|
<div className={styles.message}>{translate('OnLatestVersion')}</div>
|
||||||
|
|
||||||
|
{isFetching && (
|
||||||
|
<LoadingIndicator className={styles.loading} size={20} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasUpdates && (
|
||||||
|
<div>
|
||||||
|
{items.map((update) => {
|
||||||
|
return (
|
||||||
|
<div key={update.version} className={styles.update}>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<div className={styles.version}>{update.version}</div>
|
||||||
|
<div className={styles.space}>—</div>
|
||||||
|
<div
|
||||||
|
className={styles.date}
|
||||||
|
title={formatDateTime(
|
||||||
|
update.releaseDate,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatDate(update.releaseDate, shortDateFormat)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{update.branch === 'master' ? null : (
|
||||||
|
<Label className={styles.label}>{update.branch}</Label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{update.version === currentVersion ? (
|
||||||
|
<Label
|
||||||
|
className={styles.label}
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
title={formatDateTime(
|
||||||
|
update.installedOn,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{translate('CurrentlyInstalled')}
|
||||||
|
</Label>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{update.version !== currentVersion && update.installedOn ? (
|
||||||
|
<Label
|
||||||
|
className={styles.label}
|
||||||
|
kind={kinds.INVERSE}
|
||||||
|
title={formatDateTime(
|
||||||
|
update.installedOn,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{translate('PreviouslyInstalled')}
|
||||||
|
</Label>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{update.changes ? (
|
||||||
|
<div>
|
||||||
|
<UpdateChanges
|
||||||
|
title={translate('New')}
|
||||||
|
changes={update.changes.new}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UpdateChanges
|
||||||
|
title={translate('Fixed')}
|
||||||
|
changes={update.changes.fixed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>{translate('MaintenanceRelease')}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{updatesError ? (
|
||||||
|
<Alert kind={kinds.WARNING}>
|
||||||
|
{translate('FailedToFetchUpdates')}
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{generalSettingsError ? (
|
||||||
|
<Alert kind={kinds.DANGER}>
|
||||||
|
{translate('FailedToFetchSettings')}
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isMajorUpdateModalOpen}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
title={translate('InstallMajorVersionUpdate')}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<div>{translate('InstallMajorVersionUpdateMessage')}</div>
|
||||||
|
<div>
|
||||||
|
<InlineMarkdown
|
||||||
|
data={translate('InstallMajorVersionUpdateMessageLink', {
|
||||||
|
domain: 'readarr.com',
|
||||||
|
url: 'https://readarr.com/#downloads',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
confirmLabel={translate('Install')}
|
||||||
|
onConfirm={handleInstallLatestMajorVersionPress}
|
||||||
|
onCancel={handleCancelMajorVersionPress}
|
||||||
|
/>
|
||||||
|
</PageContentBody>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Updates;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
export type UpdateMechanism =
|
||||||
|
| 'builtIn'
|
||||||
|
| 'script'
|
||||||
|
| 'external'
|
||||||
|
| 'apt'
|
||||||
|
| 'docker';
|
||||||
|
|
||||||
|
export default interface General {
|
||||||
|
bindAddress: string;
|
||||||
|
port: number;
|
||||||
|
sslPort: number;
|
||||||
|
enableSsl: boolean;
|
||||||
|
launchBrowser: boolean;
|
||||||
|
authenticationMethod: string;
|
||||||
|
authenticationRequired: string;
|
||||||
|
analyticsEnabled: boolean;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
passwordConfirmation: string;
|
||||||
|
logLevel: string;
|
||||||
|
consoleLogLevel: string;
|
||||||
|
branch: string;
|
||||||
|
apiKey: string;
|
||||||
|
sslCertPath: string;
|
||||||
|
sslCertPassword: string;
|
||||||
|
urlBase: string;
|
||||||
|
instanceName: string;
|
||||||
|
applicationUrl: string;
|
||||||
|
updateAutomatically: boolean;
|
||||||
|
updateMechanism: UpdateMechanism;
|
||||||
|
updateScriptPath: string;
|
||||||
|
proxyEnabled: boolean;
|
||||||
|
proxyType: string;
|
||||||
|
proxyHostname: string;
|
||||||
|
proxyPort: number;
|
||||||
|
proxyUsername: string;
|
||||||
|
proxyPassword: string;
|
||||||
|
proxyBypassFilter: string;
|
||||||
|
proxyBypassLocalAddresses: boolean;
|
||||||
|
certificateValidation: string;
|
||||||
|
backupFolder: string;
|
||||||
|
backupInterval: number;
|
||||||
|
backupRetention: number;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface UiSettings {
|
export default interface UiSettings {
|
||||||
|
theme: 'auto' | 'dark' | 'light';
|
||||||
showRelativeDates: boolean;
|
showRelativeDates: boolean;
|
||||||
shortDateFormat: string;
|
shortDateFormat: string;
|
||||||
longDateFormat: string;
|
longDateFormat: string;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
interface SystemStatus {
|
||||||
|
appData: string;
|
||||||
|
appName: string;
|
||||||
|
authentication: string;
|
||||||
|
branch: string;
|
||||||
|
buildTime: string;
|
||||||
|
instanceName: string;
|
||||||
|
isAdmin: boolean;
|
||||||
|
isDebug: boolean;
|
||||||
|
isDocker: boolean;
|
||||||
|
isLinux: boolean;
|
||||||
|
isNetCore: boolean;
|
||||||
|
isOsx: boolean;
|
||||||
|
isProduction: boolean;
|
||||||
|
isUserInteractive: boolean;
|
||||||
|
isWindows: boolean;
|
||||||
|
migrationVersion: number;
|
||||||
|
mode: string;
|
||||||
|
osName: string;
|
||||||
|
osVersion: string;
|
||||||
|
packageUpdateMechanism: string;
|
||||||
|
packageUpdateMechanismMessage: string;
|
||||||
|
runtimeName: string;
|
||||||
|
runtimeVersion: string;
|
||||||
|
sqliteVersion: string;
|
||||||
|
startTime: string;
|
||||||
|
startupPath: string;
|
||||||
|
urlBase: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemStatus;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export interface Changes {
|
||||||
|
new: string[];
|
||||||
|
fixed: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Update {
|
||||||
|
version: string;
|
||||||
|
branch: string;
|
||||||
|
releaseDate: string;
|
||||||
|
fileName: string;
|
||||||
|
url: string;
|
||||||
|
installed: boolean;
|
||||||
|
installedOn: string;
|
||||||
|
installable: boolean;
|
||||||
|
latest: boolean;
|
||||||
|
changes: Changes | null;
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Update;
|
||||||
+52
-56
@@ -25,34 +25,33 @@
|
|||||||
"defaults"
|
"defaults"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "6.4.0",
|
"@fortawesome/fontawesome-free": "6.6.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "6.6.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "6.6.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "6.6.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.2",
|
||||||
"@microsoft/signalr": "6.0.25",
|
"@microsoft/signalr": "6.0.25",
|
||||||
"@sentry/browser": "7.51.2",
|
"@sentry/browser": "7.119.1",
|
||||||
"@sentry/integrations": "7.51.2",
|
"@sentry/integrations": "7.119.1",
|
||||||
"@types/node": "18.16.16",
|
"@types/node": "20.16.11",
|
||||||
"@types/react": "18.2.7",
|
"@types/react": "18.2.79",
|
||||||
"@types/react-dom": "18.2.4",
|
"@types/react-dom": "18.2.25",
|
||||||
"ansi-colors": "4.1.3",
|
"classnames": "2.5.1",
|
||||||
"classnames": "2.3.2",
|
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"connected-react-router": "6.9.3",
|
"connected-react-router": "6.9.3",
|
||||||
"element-class": "0.2.2",
|
"element-class": "0.2.2",
|
||||||
"filesize": "10.0.7",
|
"filesize": "10.1.6",
|
||||||
"fuse.js": "6.6.2",
|
"fuse.js": "6.6.2",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"jdu": "1.0.0",
|
"jdu": "1.0.0",
|
||||||
"jquery": "3.7.0",
|
"jquery": "3.7.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mobile-detect": "1.4.5",
|
"mobile-detect": "1.4.5",
|
||||||
"moment": "2.29.4",
|
"moment": "2.30.1",
|
||||||
"mousetrap": "1.6.5",
|
"mousetrap": "1.6.5",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"qs": "6.11.1",
|
"qs": "6.13.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-addons-shallow-compare": "15.6.3",
|
"react-addons-shallow-compare": "15.6.3",
|
||||||
"react-async-script": "1.2.0",
|
"react-async-script": "1.2.0",
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
"react-dnd-touch-backend": "14.1.1",
|
"react-dnd-touch-backend": "14.1.1",
|
||||||
"react-document-title": "2.0.3",
|
"react-document-title": "2.0.3",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-focus-lock": "2.5.2",
|
"react-focus-lock": "2.9.4",
|
||||||
"react-google-recaptcha": "2.1.0",
|
"react-google-recaptcha": "2.1.0",
|
||||||
"react-lazyload": "3.2.0",
|
"react-lazyload": "3.2.0",
|
||||||
"react-measure": "2.5.2",
|
"react-measure": "2.5.2",
|
||||||
@@ -73,74 +72,71 @@
|
|||||||
"react-redux": "7.2.4",
|
"react-redux": "7.2.4",
|
||||||
"react-router": "5.2.0",
|
"react-router": "5.2.0",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-slider": "1.3.1",
|
"react-slider": "1.3.3",
|
||||||
"react-tabs": "3.2.2",
|
"react-tabs": "4.3.0",
|
||||||
"react-text-truncate": "0.18.0",
|
"react-text-truncate": "0.19.0",
|
||||||
"react-virtualized": "9.21.1",
|
"react-virtualized": "9.21.1",
|
||||||
"redux": "4.1.0",
|
"redux": "4.2.1",
|
||||||
"redux-actions": "2.6.5",
|
"redux-actions": "2.6.5",
|
||||||
"redux-batched-actions": "0.5.0",
|
"redux-batched-actions": "0.5.0",
|
||||||
"redux-localstorage": "0.4.1",
|
"redux-localstorage": "0.4.1",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.4.2",
|
||||||
"reselect": "4.1.8",
|
"reselect": "4.1.8",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"typescript": "4.9.5"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.22.11",
|
"@babel/core": "7.25.8",
|
||||||
"@babel/eslint-parser": "7.22.11",
|
"@babel/eslint-parser": "7.25.8",
|
||||||
"@babel/plugin-proposal-export-default-from": "7.22.5",
|
"@babel/plugin-proposal-export-default-from": "7.25.8",
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/preset-env": "7.22.15",
|
"@babel/preset-env": "7.25.8",
|
||||||
"@babel/preset-react": "7.22.5",
|
"@babel/preset-react": "7.25.7",
|
||||||
"@babel/preset-typescript": "7.22.11",
|
"@babel/preset-typescript": "7.25.7",
|
||||||
"@types/lodash": "4.14.197",
|
"@types/lodash": "4.14.195",
|
||||||
"@types/react-lazyload": "3.2.1",
|
"@types/react-lazyload": "3.2.3",
|
||||||
"@types/redux-actions": "2.6.2",
|
"@types/redux-actions": "2.6.5",
|
||||||
"@typescript-eslint/eslint-plugin": "6.5.0",
|
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||||
"@typescript-eslint/parser": "6.5.0",
|
"@typescript-eslint/parser": "6.21.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.20",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.2.1",
|
||||||
"babel-plugin-inline-classnames": "2.0.1",
|
"babel-plugin-inline-classnames": "2.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||||
"core-js": "3.32.1",
|
"core-js": "3.38.1",
|
||||||
"css-loader": "6.8.1",
|
"css-loader": "6.8.1",
|
||||||
"css-modules-typescript-loader": "4.0.1",
|
"css-modules-typescript-loader": "4.0.1",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-prettier": "8.8.0",
|
"eslint-config-prettier": "8.10.0",
|
||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-json": "3.1.0",
|
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"eslint-plugin-react": "7.32.2",
|
"eslint-plugin-react": "7.37.1",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
"eslint-plugin-react-hooks": "4.6.2",
|
||||||
"eslint-plugin-simple-import-sort": "10.0.0",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "8.0.0",
|
"filemanager-webpack-plugin": "8.0.0",
|
||||||
"fork-ts-checker-webpack-plugin": "8.0.0",
|
"fork-ts-checker-webpack-plugin": "8.0.0",
|
||||||
"html-webpack-plugin": "5.5.3",
|
"html-webpack-plugin": "5.6.0",
|
||||||
"loader-utils": "^3.2.1",
|
"loader-utils": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"mini-css-extract-plugin": "2.9.1",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.47",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "7.3.0",
|
"postcss-loader": "7.3.0",
|
||||||
"postcss-mixins": "9.0.4",
|
"postcss-mixins": "9.0.4",
|
||||||
"postcss-nested": "6.0.1",
|
"postcss-nested": "6.2.0",
|
||||||
"postcss-simple-vars": "7.0.1",
|
"postcss-simple-vars": "7.0.1",
|
||||||
"postcss-url": "10.1.3",
|
"postcss-url": "10.1.3",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"require-nocache": "1.0.0",
|
"require-nocache": "1.0.0",
|
||||||
"rimraf": "4.4.1",
|
"rimraf": "6.0.1",
|
||||||
"run-sequence": "2.2.1",
|
"style-loader": "3.3.4",
|
||||||
"streamqueue": "1.1.2",
|
|
||||||
"style-loader": "3.3.3",
|
|
||||||
"stylelint": "15.10.3",
|
"stylelint": "15.10.3",
|
||||||
"stylelint-order": "6.0.3",
|
"stylelint-order": "6.0.4",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"ts-loader": "9.4.4",
|
"ts-loader": "9.5.1",
|
||||||
"typescript-plugin-css-modules": "5.0.1",
|
"typescript-plugin-css-modules": "5.0.1",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.88.2",
|
"webpack": "5.95.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-livereload-plugin": "3.0.2",
|
"webpack-livereload-plugin": "3.0.2",
|
||||||
"worker-loader": "3.0.8"
|
"worker-loader": "3.0.8"
|
||||||
|
|||||||
@@ -139,16 +139,46 @@
|
|||||||
</Otherwise>
|
</Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Set architecture to RuntimeInformation.ProcessArchitecture if not specified -->
|
||||||
|
<Choose>
|
||||||
|
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X64'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Architecture>x64</Architecture>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X86'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Architecture>x86</Architecture>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm64'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Architecture>arm64</Architecture>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Architecture>arm</Architecture>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
<Otherwise>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Architecture></Architecture>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(IsWindows)' == 'true' and
|
<PropertyGroup Condition="'$(IsWindows)' == 'true' and
|
||||||
'$(RuntimeIdentifier)' == ''">
|
'$(RuntimeIdentifier)' == ''">
|
||||||
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
|
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-$(Architecture)</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(IsLinux)' == 'true' and
|
<PropertyGroup Condition="'$(IsLinux)' == 'true' and
|
||||||
'$(RuntimeIdentifier)' == ''">
|
'$(RuntimeIdentifier)' == ''">
|
||||||
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
|
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
|
||||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>linux-$(Architecture)</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(IsOSX)' == 'true' and
|
<PropertyGroup Condition="'$(IsOSX)' == 'true' and
|
||||||
|
|||||||
@@ -3,27 +3,28 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
<PackageVersion Include="Dapper" Version="2.0.151" />
|
||||||
|
<PackageVersion Include="Diacritical.Net" Version="1.0.4" />
|
||||||
<PackageVersion Include="DryIoc.dll" Version="5.4.3" />
|
<PackageVersion Include="DryIoc.dll" Version="5.4.3" />
|
||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
<PackageVersion Include="Polly" Version="8.3.1" />
|
<PackageVersion Include="Polly" Version="8.4.2" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="FluentValidation" Version="9.5.4" />
|
<PackageVersion Include="FluentValidation" Version="9.5.4" />
|
||||||
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
||||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.25" />
|
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
||||||
<PackageVersion Include="Moq" Version="4.17.2" />
|
<PackageVersion Include="Moq" Version="4.17.2" />
|
||||||
@@ -33,9 +34,9 @@
|
|||||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
||||||
<PackageVersion Include="NLog" Version="5.1.4" />
|
<PackageVersion Include="NLog" Version="5.1.4" />
|
||||||
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||||
<PackageVersion Include="Npgsql" Version="7.0.6" />
|
<PackageVersion Include="Npgsql" Version="7.0.8" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.14.0" />
|
||||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
||||||
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||||
@@ -44,9 +45,10 @@
|
|||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
|
||||||
|
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||||
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="6.0.9" />
|
<PackageVersion Include="System.Text.Json" Version="6.0.10" />
|
||||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
|
|||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start();
|
_runner.Start(true);
|
||||||
|
|
||||||
driver.Url = "http://localhost:8787";
|
driver.Url = "http://localhost:8787";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Options;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
@@ -43,6 +45,26 @@ namespace NzbDrone.Common.Test
|
|||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.WriteAllText(configFile, It.IsAny<string>()))
|
.Setup(v => v.WriteAllText(configFile, It.IsAny<string>()))
|
||||||
.Callback<string, string>((p, t) => _configFileContents = t);
|
.Callback<string, string>((p, t) => _configFileContents = t);
|
||||||
|
|
||||||
|
Mocker.GetMock<IOptions<AuthOptions>>()
|
||||||
|
.Setup(v => v.Value)
|
||||||
|
.Returns(new AuthOptions());
|
||||||
|
|
||||||
|
Mocker.GetMock<IOptions<AppOptions>>()
|
||||||
|
.Setup(v => v.Value)
|
||||||
|
.Returns(new AppOptions());
|
||||||
|
|
||||||
|
Mocker.GetMock<IOptions<ServerOptions>>()
|
||||||
|
.Setup(v => v.Value)
|
||||||
|
.Returns(new ServerOptions());
|
||||||
|
|
||||||
|
Mocker.GetMock<IOptions<LogOptions>>()
|
||||||
|
.Setup(v => v.Value)
|
||||||
|
.Returns(new LogOptions());
|
||||||
|
|
||||||
|
Mocker.GetMock<IOptions<UpdateOptions>>()
|
||||||
|
.Setup(v => v.Value)
|
||||||
|
.Returns(new UpdateOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
|
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
|
||||||
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
|
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
|
||||||
|
|
||||||
|
// Telegram
|
||||||
|
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/sendmessage: chat_id=123456&parse_mode=HTML&text=<text>")]
|
||||||
|
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/")]
|
||||||
|
|
||||||
public void should_clean_message(string message)
|
public void should_clean_message(string message)
|
||||||
{
|
{
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using NUnit.Framework;
|
|||||||
using NzbDrone.Common.Composition.Extensions;
|
using NzbDrone.Common.Composition.Extensions;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
|
using NzbDrone.Common.Options;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Extensions;
|
using NzbDrone.Core.Datastore.Extensions;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
@@ -33,6 +34,11 @@ namespace NzbDrone.Common.Test
|
|||||||
|
|
||||||
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
||||||
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<AppOptions>>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<AuthOptions>>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<ServerOptions>>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<LogOptions>>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<UpdateOptions>>().Object);
|
||||||
|
|
||||||
var serviceProvider = container.GetServiceProvider();
|
var serviceProvider = container.GetServiceProvider();
|
||||||
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Discord
|
// Discord
|
||||||
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
|
// Telegram
|
||||||
|
new (@"api.telegram.org/bot(?<id>[\d]+):(?<secret>[\w-]+)/", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NzbDrone.Common.Options;
|
||||||
|
|
||||||
|
public class AppOptions
|
||||||
|
{
|
||||||
|
public string InstanceName { get; set; }
|
||||||
|
public string Theme { get; set; }
|
||||||
|
public bool? LaunchBrowser { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Common.Options;
|
||||||
|
|
||||||
|
public class AuthOptions
|
||||||
|
{
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
public bool? Enabled { get; set; }
|
||||||
|
public string Method { get; set; }
|
||||||
|
public string Required { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace NzbDrone.Common.Options;
|
||||||
|
|
||||||
|
public class LogOptions
|
||||||
|
{
|
||||||
|
public string Level { get; set; }
|
||||||
|
public bool? FilterSentryEvents { get; set; }
|
||||||
|
public int? Rotate { get; set; }
|
||||||
|
public bool? Sql { get; set; }
|
||||||
|
public string ConsoleLevel { get; set; }
|
||||||
|
public bool? AnalyticsEnabled { get; set; }
|
||||||
|
public string SyslogServer { get; set; }
|
||||||
|
public int? SyslogPort { get; set; }
|
||||||
|
public string SyslogLevel { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NzbDrone.Common.Options;
|
||||||
|
|
||||||
|
public class ServerOptions
|
||||||
|
{
|
||||||
|
public string UrlBase { get; set; }
|
||||||
|
public string BindAddress { get; set; }
|
||||||
|
public int? Port { get; set; }
|
||||||
|
public bool? EnableSsl { get; set; }
|
||||||
|
public int? SslPort { get; set; }
|
||||||
|
public string SslCertPath { get; set; }
|
||||||
|
public string SslCertPassword { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Common.Options;
|
||||||
|
|
||||||
|
public class UpdateOptions
|
||||||
|
{
|
||||||
|
public string Mechanism { get; set; }
|
||||||
|
public bool? Automatically { get; set; }
|
||||||
|
public string ScriptPath { get; set; }
|
||||||
|
public string Branch { get; set; }
|
||||||
|
}
|
||||||
+93
-4
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
Subject.Definition.Settings.As<QBittorrentSettings>().RecentTvPriority = (int)QBittorrentPriority.First;
|
Subject.Definition.Settings.As<QBittorrentSettings>().RecentTvPriority = (int)QBittorrentPriority.First;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause)
|
protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, int maxInactiveSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IQBittorrentProxy>()
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||||
@@ -118,7 +118,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
MaxRatio = maxRatio,
|
MaxRatio = maxRatio,
|
||||||
MaxRatioEnabled = maxRatio >= 0,
|
MaxRatioEnabled = maxRatio >= 0,
|
||||||
MaxSeedingTime = maxSeedingTime,
|
MaxSeedingTime = maxSeedingTime,
|
||||||
MaxSeedingTimeEnabled = maxSeedingTime >= 0
|
MaxSeedingTimeEnabled = maxSeedingTime >= 0,
|
||||||
|
MaxInactiveSeedingTime = maxInactiveSeedingTime,
|
||||||
|
MaxInactiveSeedingTimeEnabled = maxInactiveSeedingTime >= 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,6 +557,34 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
|
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_correct_category_output_path()
|
||||||
|
{
|
||||||
|
var config = new QBittorrentPreferences
|
||||||
|
{
|
||||||
|
SavePath = @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
|
.Setup(v => v.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||||
|
.Returns(config);
|
||||||
|
|
||||||
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
|
.Setup(v => v.GetApiVersion(It.IsAny<QBittorrentSettings>()))
|
||||||
|
.Returns(new Version(2, 0));
|
||||||
|
|
||||||
|
Mocker.GetMock<IQBittorrentProxy>()
|
||||||
|
.Setup(s => s.GetLabels(It.IsAny<QBittorrentSettings>()))
|
||||||
|
.Returns(new Dictionary<string, QBittorrentLabel>
|
||||||
|
{ { "music", new QBittorrentLabel { Name = "music", SavePath = "//server/store/downloads" } } });
|
||||||
|
|
||||||
|
var result = Subject.GetStatus();
|
||||||
|
|
||||||
|
result.IsLocalhost.Should().BeTrue();
|
||||||
|
result.OutputRootFolders.Should().NotBeNull();
|
||||||
|
result.OutputRootFolders.First().Should().Be(@"\\server\store\downloads");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Download_should_handle_http_redirect_to_magnet()
|
public async Task Download_should_handle_http_redirect_to_magnet()
|
||||||
{
|
{
|
||||||
@@ -610,7 +640,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
float ratio = 0.1f,
|
float ratio = 0.1f,
|
||||||
float ratioLimit = -2,
|
float ratioLimit = -2,
|
||||||
int seedingTime = 1,
|
int seedingTime = 1,
|
||||||
int seedingTimeLimit = -2)
|
int seedingTimeLimit = -2,
|
||||||
|
int inactiveSeedingTimeLimit = -2,
|
||||||
|
long lastActivity = -1)
|
||||||
{
|
{
|
||||||
var torrent = new QBittorrentTorrent
|
var torrent = new QBittorrentTorrent
|
||||||
{
|
{
|
||||||
@@ -624,7 +656,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
SavePath = "",
|
SavePath = "",
|
||||||
Ratio = ratio,
|
Ratio = ratio,
|
||||||
RatioLimit = ratioLimit,
|
RatioLimit = ratioLimit,
|
||||||
SeedingTimeLimit = seedingTimeLimit
|
SeedingTimeLimit = seedingTimeLimit,
|
||||||
|
InactiveSeedingTimeLimit = inactiveSeedingTimeLimit,
|
||||||
|
LastActivity = lastActivity == -1 ? DateTimeOffset.UtcNow.ToUnixTimeSeconds() : lastActivity
|
||||||
};
|
};
|
||||||
|
|
||||||
GivenTorrents(new List<QBittorrentTorrent>() { torrent });
|
GivenTorrents(new List<QBittorrentTorrent>() { torrent });
|
||||||
@@ -738,6 +772,50 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_be_removable_and_should_not_allow_move_files_if_max_inactive_seedingtime_reached_and_not_paused()
|
||||||
|
{
|
||||||
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||||
|
GivenCompletedTorrent("uploading", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
|
||||||
|
{
|
||||||
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||||
|
GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
|
||||||
|
{
|
||||||
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
|
||||||
|
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
|
||||||
|
{
|
||||||
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||||
|
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
|
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
|
||||||
{
|
{
|
||||||
@@ -749,6 +827,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
|
||||||
|
{
|
||||||
|
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
|
||||||
|
GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_fetch_details_twice()
|
public void should_not_fetch_details_twice()
|
||||||
{
|
{
|
||||||
|
|||||||
+5
-2
@@ -49,10 +49,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void magnet_download_should_not_return_the_item()
|
public void magnet_download_should_be_returned_as_queued()
|
||||||
{
|
{
|
||||||
PrepareClientToReturnMagnetItem();
|
PrepareClientToReturnMagnetItem();
|
||||||
Subject.GetItems().Count().Should().Be(0);
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
|
||||||
|
item.Status.Should().Be(DownloadItemStatus.Queued);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||||||
public void magnet_download_should_not_return_the_item()
|
public void magnet_download_should_not_return_the_item()
|
||||||
{
|
{
|
||||||
PrepareClientToReturnMagnetItem();
|
PrepareClientToReturnMagnetItem();
|
||||||
Subject.GetItems().Count().Should().Be(0);
|
|
||||||
|
var item = Subject.GetItems().Single();
|
||||||
|
|
||||||
|
item.Status.Should().Be(DownloadItemStatus.Queued);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using NzbDrone.Core.HealthCheck.Checks;
|
|||||||
using NzbDrone.Core.Localization;
|
using NzbDrone.Core.Localization;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Update;
|
using NzbDrone.Core.Update;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
@@ -21,28 +22,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
.Returns("Some Warning Message");
|
.Returns("Some Warning Message");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_error_when_app_folder_is_write_protected()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
Mocker.GetMock<IAppFolderInfo>()
|
|
||||||
.Setup(s => s.StartUpFolder)
|
|
||||||
.Returns(@"C:\NzbDrone");
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.FolderWritable(It.IsAny<string>()))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Subject.Check().ShouldBeError();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
|
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
|
||||||
{
|
{
|
||||||
PosixOnly();
|
var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
|
||||||
|
|
||||||
const string startupFolder = @"/opt/nzbdrone";
|
|
||||||
|
|
||||||
Mocker.GetMock<IConfigFileProvider>()
|
Mocker.GetMock<IConfigFileProvider>()
|
||||||
.Setup(s => s.UpdateAutomatically)
|
.Setup(s => s.UpdateAutomatically)
|
||||||
@@ -62,10 +45,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
|
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
|
||||||
{
|
{
|
||||||
PosixOnly();
|
var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
|
||||||
|
var uiFolder = @"C:\NzbDrone\UI".AsOsAgnostic();
|
||||||
const string startupFolder = @"/opt/nzbdrone";
|
|
||||||
const string uiFolder = @"/opt/nzbdrone/UI";
|
|
||||||
|
|
||||||
Mocker.GetMock<IConfigFileProvider>()
|
Mocker.GetMock<IConfigFileProvider>()
|
||||||
.Setup(s => s.UpdateAutomatically)
|
.Setup(s => s.UpdateAutomatically)
|
||||||
@@ -89,7 +70,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
|
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
|
||||||
{
|
{
|
||||||
PosixOnly();
|
var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
|
||||||
|
|
||||||
Mocker.GetMock<IConfigFileProvider>()
|
Mocker.GetMock<IConfigFileProvider>()
|
||||||
.Setup(s => s.UpdateAutomatically)
|
.Setup(s => s.UpdateAutomatically)
|
||||||
@@ -101,7 +82,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
|
|
||||||
Mocker.GetMock<IAppFolderInfo>()
|
Mocker.GetMock<IAppFolderInfo>()
|
||||||
.Setup(s => s.StartUpFolder)
|
.Setup(s => s.StartUpFolder)
|
||||||
.Returns(@"/opt/nzbdrone");
|
.Returns(startupFolder);
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());
|
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")]
|
[Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
|
||||||
public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
|
public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
|
||||||
{
|
{
|
||||||
private MetadataProfile _metadataProfile;
|
private MetadataProfile _metadataProfile;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
|
|||||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")]
|
[Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
|
||||||
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
|
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
|
||||||
{
|
{
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
|||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 61800696)]
|
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)]
|
||||||
[TestCase("B0192CTMYG", 61209488)]
|
[TestCase("B0192CTMYG", 61209488)]
|
||||||
[TestCase("9780439554930", 48517161)]
|
[TestCase("9780439554930", 48517161)]
|
||||||
public void successful_book_search(string title, int expected)
|
public void successful_book_search(string title, int expected)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests
|
|||||||
|
|
||||||
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
|
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
|
||||||
.All()
|
.All()
|
||||||
|
.With(v => v.IsTrackable = true)
|
||||||
.With(v => v.DownloadItem = downloadItem)
|
.With(v => v.DownloadItem = downloadItem)
|
||||||
.With(v => v.RemoteBook = remoteBook)
|
.With(v => v.RemoteBook = remoteBook)
|
||||||
.Build()
|
.Build()
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NzbDrone.Core.Authentication
|
||||||
|
{
|
||||||
|
public enum AuthenticationRequiredType
|
||||||
|
{
|
||||||
|
Enabled = 0,
|
||||||
|
DisabledForLocalAddresses = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace NzbDrone.Core.Authentication
|
namespace NzbDrone.Core.Authentication
|
||||||
{
|
{
|
||||||
public enum AuthenticationType
|
public enum AuthenticationType
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
Basic = 1,
|
Basic = 1,
|
||||||
Forms = 2
|
Forms = 2,
|
||||||
|
External = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ namespace NzbDrone.Core.Books
|
|||||||
public class BookCutoffService : IBookCutoffService
|
public class BookCutoffService : IBookCutoffService
|
||||||
{
|
{
|
||||||
private readonly IBookRepository _bookRepository;
|
private readonly IBookRepository _bookRepository;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IQualityProfileService _qualityProfileService;
|
||||||
|
|
||||||
public BookCutoffService(IBookRepository bookRepository, IProfileService profileService)
|
public BookCutoffService(IBookRepository bookRepository, IQualityProfileService qualityProfileService)
|
||||||
{
|
{
|
||||||
_bookRepository = bookRepository;
|
_bookRepository = bookRepository;
|
||||||
_profileService = profileService;
|
_qualityProfileService = qualityProfileService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
|
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
|
||||||
{
|
{
|
||||||
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
|
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
|
||||||
var profiles = _profileService.All();
|
var profiles = _qualityProfileService.All();
|
||||||
|
|
||||||
//Get all items less than the cutoff
|
//Get all items less than the cutoff
|
||||||
foreach (var profile in profiles)
|
foreach (var profile in profiles)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
using NzbDrone.Core.Books.Commands;
|
using NzbDrone.Core.Books.Commands;
|
||||||
using NzbDrone.Core.Books.Events;
|
using NzbDrone.Core.Books.Events;
|
||||||
@@ -55,6 +56,12 @@ namespace NzbDrone.Core.Books
|
|||||||
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", author.Name, sourcePath, destinationPath);
|
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", author.Name, sourcePath, destinationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourcePath.PathEquals(destinationPath))
|
||||||
|
{
|
||||||
|
_logger.ProgressInfo("{0} is already in the specified location '{1}'.", author, destinationPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_rootFolderWatchingService.ReportFileSystemChangeBeginning(sourcePath, destinationPath);
|
_rootFolderWatchingService.ReportFileSystemChangeBeginning(sourcePath, destinationPath);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
|
|||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Options;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration.Events;
|
using NzbDrone.Core.Configuration.Events;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
@@ -32,6 +33,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
bool EnableSsl { get; }
|
bool EnableSsl { get; }
|
||||||
bool LaunchBrowser { get; }
|
bool LaunchBrowser { get; }
|
||||||
AuthenticationType AuthenticationMethod { get; }
|
AuthenticationType AuthenticationMethod { get; }
|
||||||
|
AuthenticationRequiredType AuthenticationRequired { get; }
|
||||||
bool AnalyticsEnabled { get; }
|
bool AnalyticsEnabled { get; }
|
||||||
string LogLevel { get; }
|
string LogLevel { get; }
|
||||||
string ConsoleLogLevel { get; }
|
string ConsoleLogLevel { get; }
|
||||||
@@ -69,6 +71,11 @@ namespace NzbDrone.Core.Configuration
|
|||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICached<string> _cache;
|
private readonly ICached<string> _cache;
|
||||||
private readonly PostgresOptions _postgresOptions;
|
private readonly PostgresOptions _postgresOptions;
|
||||||
|
private readonly AuthOptions _authOptions;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
private readonly ServerOptions _serverOptions;
|
||||||
|
private readonly UpdateOptions _updateOptions;
|
||||||
|
private readonly LogOptions _logOptions;
|
||||||
|
|
||||||
private readonly string _configFile;
|
private readonly string _configFile;
|
||||||
|
|
||||||
@@ -78,13 +85,23 @@ namespace NzbDrone.Core.Configuration
|
|||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IOptions<PostgresOptions> postgresOptions)
|
IOptions<PostgresOptions> postgresOptions,
|
||||||
|
IOptions<AuthOptions> authOptions,
|
||||||
|
IOptions<AppOptions> appOptions,
|
||||||
|
IOptions<ServerOptions> serverOptions,
|
||||||
|
IOptions<UpdateOptions> updateOptions,
|
||||||
|
IOptions<LogOptions> logOptions)
|
||||||
{
|
{
|
||||||
_cache = cacheManager.GetCache<string>(GetType());
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configFile = appFolderInfo.GetConfigPath();
|
_configFile = appFolderInfo.GetConfigPath();
|
||||||
_postgresOptions = postgresOptions.Value;
|
_postgresOptions = postgresOptions.Value;
|
||||||
|
_authOptions = authOptions.Value;
|
||||||
|
_appOptions = appOptions.Value;
|
||||||
|
_serverOptions = serverOptions.Value;
|
||||||
|
_updateOptions = updateOptions.Value;
|
||||||
|
_logOptions = logOptions.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, object> GetConfigDictionary()
|
public Dictionary<string, object> GetConfigDictionary()
|
||||||
@@ -140,7 +157,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
{
|
{
|
||||||
const string defaultValue = "*";
|
const string defaultValue = "*";
|
||||||
|
|
||||||
var bindAddress = GetValue("BindAddress", defaultValue);
|
var bindAddress = _serverOptions.BindAddress ?? GetValue("BindAddress", defaultValue);
|
||||||
if (string.IsNullOrWhiteSpace(bindAddress))
|
if (string.IsNullOrWhiteSpace(bindAddress))
|
||||||
{
|
{
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -150,19 +167,19 @@ namespace NzbDrone.Core.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Port => GetValueInt("Port", 8787);
|
public int Port => _serverOptions.Port ?? GetValueInt("Port", 8787);
|
||||||
|
|
||||||
public int SslPort => GetValueInt("SslPort", 6868);
|
public int SslPort => _serverOptions.SslPort ?? GetValueInt("SslPort", 6868);
|
||||||
|
|
||||||
public bool EnableSsl => GetValueBoolean("EnableSsl", false);
|
public bool EnableSsl => _serverOptions.EnableSsl ?? GetValueBoolean("EnableSsl", false);
|
||||||
|
|
||||||
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true);
|
public bool LaunchBrowser => _appOptions.LaunchBrowser ?? GetValueBoolean("LaunchBrowser", true);
|
||||||
|
|
||||||
public string ApiKey
|
public string ApiKey
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var apiKey = GetValue("ApiKey", GenerateApiKey());
|
var apiKey = _authOptions.ApiKey ?? GetValue("ApiKey", GenerateApiKey());
|
||||||
|
|
||||||
if (apiKey.IsNullOrWhiteSpace())
|
if (apiKey.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
@@ -178,7 +195,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var enabled = GetValueBoolean("AuthenticationEnabled", false, false);
|
var enabled = _authOptions.Enabled ?? GetValueBoolean("AuthenticationEnabled", false, false);
|
||||||
|
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
@@ -186,17 +203,24 @@ namespace NzbDrone.Core.Configuration
|
|||||||
return AuthenticationType.Basic;
|
return AuthenticationType.Basic;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetValueEnum("AuthenticationMethod", AuthenticationType.None);
|
return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
|
||||||
|
? enumValue
|
||||||
|
: GetValueEnum("AuthenticationMethod", AuthenticationType.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
|
public AuthenticationRequiredType AuthenticationRequired =>
|
||||||
|
Enum.TryParse<AuthenticationRequiredType>(_authOptions.Required, out var enumValue)
|
||||||
|
? enumValue
|
||||||
|
: GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
|
||||||
|
|
||||||
|
public bool AnalyticsEnabled => _logOptions.AnalyticsEnabled ?? GetValueBoolean("AnalyticsEnabled", true, persist: false);
|
||||||
|
|
||||||
// TODO: Change back to "master" for the first stable release
|
// TODO: Change back to "master" for the first stable release
|
||||||
public string Branch => GetValue("Branch", "develop").ToLowerInvariant();
|
public string Branch => _updateOptions.Branch ?? GetValue("Branch", "develop").ToLowerInvariant();
|
||||||
|
|
||||||
public string LogLevel => GetValue("LogLevel", "info");
|
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").ToLowerInvariant();
|
||||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
public string ConsoleLogLevel => _logOptions.ConsoleLevel ?? GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||||
|
|
||||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||||
@@ -206,18 +230,18 @@ namespace NzbDrone.Core.Configuration
|
|||||||
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
|
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
|
||||||
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
||||||
|
|
||||||
public string Theme => GetValue("Theme", "auto", persist: false);
|
public string Theme => _appOptions.Theme ?? GetValue("Theme", "auto", persist: false);
|
||||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
public bool LogSql => _logOptions.Sql ?? GetValueBoolean("LogSql", false, persist: false);
|
||||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
public int LogRotate => _logOptions.Rotate ?? GetValueInt("LogRotate", 50, persist: false);
|
||||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
public bool FilterSentryEvents => _logOptions.FilterSentryEvents ?? GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||||
public string SslCertPath => GetValue("SslCertPath", "");
|
public string SslCertPath => _serverOptions.SslCertPath ?? GetValue("SslCertPath", "");
|
||||||
public string SslCertPassword => GetValue("SslCertPassword", "");
|
public string SslCertPassword => _serverOptions.SslCertPassword ?? GetValue("SslCertPassword", "");
|
||||||
|
|
||||||
public string UrlBase
|
public string UrlBase
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var urlBase = GetValue("UrlBase", "").Trim('/');
|
var urlBase = (_serverOptions.UrlBase ?? GetValue("UrlBase", "")).Trim('/');
|
||||||
|
|
||||||
if (urlBase.IsNullOrWhiteSpace())
|
if (urlBase.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
@@ -229,19 +253,22 @@ namespace NzbDrone.Core.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
||||||
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName);
|
public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
|
||||||
|
|
||||||
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
|
public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);
|
||||||
|
|
||||||
public UpdateMechanism UpdateMechanism => GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
|
public UpdateMechanism UpdateMechanism =>
|
||||||
|
Enum.TryParse<UpdateMechanism>(_updateOptions.Mechanism, out var enumValue)
|
||||||
|
? enumValue
|
||||||
|
: GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
|
||||||
|
|
||||||
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
|
public string UpdateScriptPath => _updateOptions.ScriptPath ?? GetValue("UpdateScriptPath", "", false);
|
||||||
|
|
||||||
public string SyslogServer => GetValue("SyslogServer", "", persist: false);
|
public string SyslogServer => _logOptions.SyslogServer ?? GetValue("SyslogServer", "", persist: false);
|
||||||
|
|
||||||
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false);
|
public int SyslogPort => _logOptions.SyslogPort ?? GetValueInt("SyslogPort", 514, persist: false);
|
||||||
|
|
||||||
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant();
|
public string SyslogLevel => _logOptions.SyslogLevel ?? GetValue("SyslogLevel", LogLevel, persist: false).ToLowerInvariant();
|
||||||
|
|
||||||
public int GetValueInt(string key, int defaultValue, bool persist = true)
|
public int GetValueInt(string key, int defaultValue, bool persist = true)
|
||||||
{
|
{
|
||||||
@@ -330,7 +357,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL
|
// If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL
|
||||||
if (EnableSsl && (GetValue("SslCertHash", null).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
|
if (EnableSsl && (GetValue("SslCertHash", string.Empty, false).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
|
||||||
{
|
{
|
||||||
SetValue("EnableSsl", false);
|
SetValue("EnableSsl", false);
|
||||||
}
|
}
|
||||||
@@ -377,13 +404,21 @@ namespace NzbDrone.Core.Configuration
|
|||||||
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it.");
|
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
|
var xDoc = XDocument.Parse(_diskProvider.ReadAllText(_configFile));
|
||||||
|
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).ToList();
|
||||||
|
|
||||||
|
if (config.Count != 1)
|
||||||
|
{
|
||||||
|
throw new InvalidConfigFileException($"{_configFile} is invalid. Please delete the config file and Readarr will recreate it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return xDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
var newXDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
||||||
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
|
newXDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
|
||||||
|
|
||||||
return xDoc;
|
return newXDoc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
||||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||||
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
||||||
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
builder.OrderBy($"\"{sortKey.Table ?? _table}\".\"{sortKey.Column}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||||
|
|
||||||
return queryFunc(builder).ToList();
|
return queryFunc(builder).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CorruptDatabaseException(string message, Exception innerException, params object[] args)
|
public CorruptDatabaseException(string message, Exception innerException, params object[] args)
|
||||||
: base(message, innerException, args)
|
: base(innerException, message, args)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public CorruptDatabaseException(string message, Exception innerException)
|
public CorruptDatabaseException(string message, Exception innerException)
|
||||||
: base(message, innerException)
|
: base(innerException, message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
|
|
||||||
protected virtual IList<TableDefinition> ReadTables()
|
protected virtual IList<TableDefinition> ReadTables()
|
||||||
{
|
{
|
||||||
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;";
|
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name;";
|
||||||
var dtTable = Read(sqlCommand).Tables[0];
|
var dtTable = Read(sqlCommand).Tables[0];
|
||||||
|
|
||||||
var tableDefinitionList = new List<TableDefinition>();
|
var tableDefinitionList = new List<TableDefinition>();
|
||||||
|
|||||||
@@ -91,33 +91,28 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSortKey(string sortKey)
|
public (string Table, string Column) GetSortKey(string sortKey)
|
||||||
{
|
{
|
||||||
string table = null;
|
string table = null;
|
||||||
|
|
||||||
if (sortKey.Contains('.'))
|
if (sortKey.Contains('.'))
|
||||||
{
|
{
|
||||||
var split = sortKey.Split('.');
|
var split = sortKey.Split('.');
|
||||||
if (split.Length != 2)
|
if (split.Length == 2)
|
||||||
{
|
{
|
||||||
return sortKey;
|
table = split[0];
|
||||||
|
sortKey = split[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
table = split[0];
|
|
||||||
sortKey = split[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase))
|
if (table != null)
|
||||||
{
|
{
|
||||||
return sortKey;
|
table = TableMap.Values.FirstOrDefault(x => x.Equals(table, StringComparison.OrdinalIgnoreCase)) ?? table;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_allowedOrderBy.Contains(sortKey))
|
sortKey = _allowedOrderBy.FirstOrDefault(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase)) ?? sortKey;
|
||||||
{
|
|
||||||
return sortKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _allowedOrderBy.First(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase));
|
return (table, sortKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,14 +122,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||||||
}
|
}
|
||||||
|
|
||||||
var items = new List<DownloadClientItem>();
|
var items = new List<DownloadClientItem>();
|
||||||
|
var ignoredCount = 0;
|
||||||
|
|
||||||
foreach (var torrent in torrents)
|
foreach (var torrent in torrents)
|
||||||
{
|
{
|
||||||
if (torrent.Hash == null)
|
// Silently ignore torrents with no hash
|
||||||
|
if (torrent.Hash.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
|
||||||
|
if (torrent.Name.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
ignoredCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var item = new DownloadClientItem();
|
var item = new DownloadClientItem();
|
||||||
item.DownloadId = torrent.Hash.ToUpper();
|
item.DownloadId = torrent.Hash.ToUpper();
|
||||||
item.Title = torrent.Name;
|
item.Title = torrent.Name;
|
||||||
@@ -187,6 +196,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||||||
items.Add(item);
|
items.Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ignoredCount > 0)
|
||||||
|
{
|
||||||
|
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "metaDL": // torrent magnet is being downloaded
|
case "metaDL": // torrent magnet is being downloaded
|
||||||
|
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
|
||||||
if (config.DhtEnabled)
|
if (config.DhtEnabled)
|
||||||
{
|
{
|
||||||
item.Status = DownloadItemStatus.Queued;
|
item.Status = DownloadItemStatus.Queued;
|
||||||
@@ -293,7 +294,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "forcedDL": // torrent is being downloaded, and was forced started
|
case "forcedDL": // torrent is being downloaded, and was forced started
|
||||||
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
|
|
||||||
case "moving": // torrent is being moved from a folder
|
case "moving": // torrent is being moved from a folder
|
||||||
case "downloading": // torrent is being downloaded and data is being transferred
|
case "downloading": // torrent is being downloaded and data is being transferred
|
||||||
item.Status = DownloadItemStatus.Downloading;
|
item.Status = DownloadItemStatus.Downloading;
|
||||||
@@ -375,7 +375,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
{
|
{
|
||||||
if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace())
|
if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
var labelDir = new OsPath(label.SavePath);
|
var savePath = label.SavePath;
|
||||||
|
|
||||||
|
if (savePath.StartsWith("//"))
|
||||||
|
{
|
||||||
|
_logger.Trace("Replacing double forward slashes in path '{0}'. If this is not meant to be a Windows UNC path fix the 'Save Path' in qBittorrent's {1} category", savePath, Settings.MusicCategory);
|
||||||
|
savePath = savePath.Replace('/', '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelDir = new OsPath(savePath);
|
||||||
|
|
||||||
if (labelDir.IsRooted)
|
if (labelDir.IsRooted)
|
||||||
{
|
{
|
||||||
@@ -623,7 +631,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasReachedSeedingTimeLimit(torrent, config))
|
if (HasReachedSeedingTimeLimit(torrent, config) || HasReachedInactiveSeedingTimeLimit(torrent, config))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -695,6 +703,26 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool HasReachedInactiveSeedingTimeLimit(QBittorrentTorrent torrent, QBittorrentPreferences config)
|
||||||
|
{
|
||||||
|
long inactiveSeedingTimeLimit;
|
||||||
|
|
||||||
|
if (torrent.InactiveSeedingTimeLimit >= 0)
|
||||||
|
{
|
||||||
|
inactiveSeedingTimeLimit = torrent.InactiveSeedingTimeLimit * 60;
|
||||||
|
}
|
||||||
|
else if (torrent.InactiveSeedingTimeLimit == -2 && config.MaxInactiveSeedingTimeEnabled)
|
||||||
|
{
|
||||||
|
inactiveSeedingTimeLimit = config.MaxInactiveSeedingTime * 60;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() - torrent.LastActivity > inactiveSeedingTimeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
protected void FetchTorrentDetails(QBittorrentTorrent torrent)
|
protected void FetchTorrentDetails(QBittorrentTorrent torrent)
|
||||||
{
|
{
|
||||||
var torrentProperties = Proxy.GetTorrentProperties(torrent.Hash, Settings);
|
var torrentProperties = Proxy.GetTorrentProperties(torrent.Hash, Settings);
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
[JsonProperty(PropertyName = "max_seeding_time")]
|
[JsonProperty(PropertyName = "max_seeding_time")]
|
||||||
public long MaxSeedingTime { get; set; } // Get the global share time limit in minutes
|
public long MaxSeedingTime { get; set; } // Get the global share time limit in minutes
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "max_inactive_seeding_time_enabled")]
|
||||||
|
public bool MaxInactiveSeedingTimeEnabled { get; set; } // True if share inactive time limit is enabled
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "max_inactive_seeding_time")]
|
||||||
|
public long MaxInactiveSeedingTime { get; set; } // Get the global share inactive time limit in minutes
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "max_ratio_act")]
|
[JsonProperty(PropertyName = "max_ratio_act")]
|
||||||
public QBittorrentMaxRatioAction MaxRatioAction { get; set; } // Action performed when a torrent reaches the maximum share ratio.
|
public QBittorrentMaxRatioAction MaxRatioAction { get; set; } // Action performed when a torrent reaches the maximum share ratio.
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
|
|
||||||
[JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited)
|
[JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited)
|
||||||
public long SeedingTimeLimit { get; set; } = -2;
|
public long SeedingTimeLimit { get; set; } = -2;
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "inactive_seeding_time_limit")] // Per torrent inactive seeding time limit (-2 = use global, -1 = unlimited)
|
||||||
|
public long InactiveSeedingTimeLimit { get; set; } = -2;
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "last_activity")] // Timestamp in unix seconds when a chunk was last downloaded/uploaded
|
||||||
|
public long LastActivity { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QBittorrentTorrentProperties
|
public class QBittorrentTorrentProperties
|
||||||
|
|||||||
@@ -41,12 +41,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||||||
|
|
||||||
foreach (var torrent in torrents)
|
foreach (var torrent in torrents)
|
||||||
{
|
{
|
||||||
// If totalsize == 0 the torrent is a magnet downloading metadata
|
|
||||||
if (torrent.TotalSize == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var outputPath = new OsPath(torrent.DownloadDir);
|
var outputPath = new OsPath(torrent.DownloadDir);
|
||||||
|
|
||||||
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
|
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
|
||||||
@@ -97,6 +91,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||||||
item.Status = DownloadItemStatus.Warning;
|
item.Status = DownloadItemStatus.Warning;
|
||||||
item.Message = torrent.ErrorString;
|
item.Message = torrent.ErrorString;
|
||||||
}
|
}
|
||||||
|
else if (torrent.TotalSize == 0)
|
||||||
|
{
|
||||||
|
item.Status = DownloadItemStatus.Queued;
|
||||||
|
}
|
||||||
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
|
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
|
||||||
torrent.Status == TransmissionTorrentStatus.Seeding ||
|
torrent.Status == TransmissionTorrentStatus.Seeding ||
|
||||||
torrent.Status == TransmissionTorrentStatus.SeedingWait))
|
torrent.Status == TransmissionTorrentStatus.SeedingWait))
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||||||
public bool IsFinished { get; set; }
|
public bool IsFinished { get; set; }
|
||||||
public long Eta { get; set; }
|
public long Eta { get; set; }
|
||||||
public TransmissionTorrentStatus Status { get; set; }
|
public TransmissionTorrentStatus Status { get; set; }
|
||||||
public int SecondsDownloading { get; set; }
|
public long SecondsDownloading { get; set; }
|
||||||
public int SecondsSeeding { get; set; }
|
public long SecondsSeeding { get; set; }
|
||||||
public string ErrorString { get; set; }
|
public string ErrorString { get; set; }
|
||||||
public long DownloadedEver { get; set; }
|
public long DownloadedEver { get; set; }
|
||||||
public long UploadedEver { get; set; }
|
public long UploadedEver { get; set; }
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace NzbDrone.Core.Download
|
|||||||
{
|
{
|
||||||
{ Result.HasHttpServerError: true } => PredicateResult.True(),
|
{ Result.HasHttpServerError: true } => PredicateResult.True(),
|
||||||
{ Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(),
|
{ Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(),
|
||||||
|
{ Exception: HttpException { Response.HasHttpServerError: true } } => PredicateResult.True(),
|
||||||
_ => PredicateResult.False()
|
_ => PredicateResult.False()
|
||||||
},
|
},
|
||||||
Delay = TimeSpan.FromSeconds(3),
|
Delay = TimeSpan.FromSeconds(3),
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||||||
var startupFolder = _appFolderInfo.StartUpFolder;
|
var startupFolder = _appFolderInfo.StartUpFolder;
|
||||||
var uiFolder = Path.Combine(startupFolder, "UI");
|
var uiFolder = Path.Combine(startupFolder, "UI");
|
||||||
|
|
||||||
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) &&
|
if (_configFileProvider.UpdateAutomatically &&
|
||||||
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
|
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
|
||||||
!_osInfo.IsDocker)
|
!_osInfo.IsDocker)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||||
{
|
{
|
||||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||||
|
|
||||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||||
{
|
{
|
||||||
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
|
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
|
||||||
{
|
{
|
||||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -268,26 +268,26 @@ namespace NzbDrone.Core.Indexers
|
|||||||
protected virtual RssEnclosure[] GetEnclosures(XElement item)
|
protected virtual RssEnclosure[] GetEnclosures(XElement item)
|
||||||
{
|
{
|
||||||
var enclosures = item.Elements("enclosure")
|
var enclosures = item.Elements("enclosure")
|
||||||
.Select(v =>
|
.Select(v =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new RssEnclosure
|
return new RssEnclosure
|
||||||
{
|
{
|
||||||
Url = v.Attribute("url")?.Value,
|
Url = v.Attribute("url")?.Value,
|
||||||
Type = v.Attribute("type")?.Value,
|
Type = v.Attribute("type")?.Value,
|
||||||
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title());
|
_logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.Where(v => v != null)
|
.Where(v => v != null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return enclosures;
|
return enclosures;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ namespace NzbDrone.Core.Indexers
|
|||||||
|
|
||||||
public class SeedCriteriaSettings
|
public class SeedCriteriaSettings
|
||||||
{
|
{
|
||||||
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")]
|
[FieldDefinition(0, Type = FieldType.Number, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
|
||||||
public double? SeedRatio { get; set; }
|
public double? SeedRatio { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]
|
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
|
||||||
public int? SeedTime { get; set; }
|
public int? SeedTime { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]
|
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]
|
||||||
|
|||||||
@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||||
{
|
{
|
||||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||||
|
|
||||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||||
{
|
{
|
||||||
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
|
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
|
||||||
{
|
{
|
||||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
"Year": "عام",
|
"Year": "عام",
|
||||||
"WeekColumnHeader": "رأس عمود الأسبوع",
|
"WeekColumnHeader": "رأس عمود الأسبوع",
|
||||||
"Version": "الإصدار",
|
"Version": "الإصدار",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
|
"BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "فرع لاستخدامه لتحديث Radarr",
|
"BranchUpdate": "فرع لاستخدامه لتحديث Radarr",
|
||||||
"Username": "اسم المستخدم",
|
"Username": "اسم المستخدم",
|
||||||
"UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet",
|
"UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet",
|
||||||
"UsenetDelay": "تأخير يوزنت",
|
"UsenetDelay": "تأخير يوزنت",
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
"ChangeFileDate": "تغيير تاريخ الملف",
|
"ChangeFileDate": "تغيير تاريخ الملف",
|
||||||
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
|
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
|
||||||
"CertificateValidation": "التحقق من صحة الشهادة",
|
"CertificateValidation": "التحقق من صحة الشهادة",
|
||||||
"CancelMessageText": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
|
"CancelPendingTask": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
|
||||||
"Cancel": "إلغاء",
|
"Cancel": "إلغاء",
|
||||||
"CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
|
"CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
|
||||||
"Calendar": "التقويم",
|
"Calendar": "التقويم",
|
||||||
@@ -332,7 +332,6 @@
|
|||||||
"AddingTag": "مضيفا العلامة",
|
"AddingTag": "مضيفا العلامة",
|
||||||
"AddListExclusion": "إضافة استبعاد قائمة",
|
"AddListExclusion": "إضافة استبعاد قائمة",
|
||||||
"About": "نبدة عن",
|
"About": "نبدة عن",
|
||||||
"APIKey": "مفتاح API",
|
|
||||||
"60MinutesSixty": "60 دقيقة: {0}",
|
"60MinutesSixty": "60 دقيقة: {0}",
|
||||||
"45MinutesFourtyFive": "90 دقيقة: {0}",
|
"45MinutesFourtyFive": "90 دقيقة: {0}",
|
||||||
"20MinutesTwenty": "120 دقيقة: {0}",
|
"20MinutesTwenty": "120 دقيقة: {0}",
|
||||||
@@ -430,7 +429,6 @@
|
|||||||
"HasPendingChangesNoChanges": "لا تغيرات",
|
"HasPendingChangesNoChanges": "لا تغيرات",
|
||||||
"Group": "مجموعة",
|
"Group": "مجموعة",
|
||||||
"GrabSelected": "انتزاع المحدد",
|
"GrabSelected": "انتزاع المحدد",
|
||||||
"ApiKeyHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
|
|
||||||
"DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
|
"DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
|
||||||
"LoadingBooksFailed": "فشل تحميل ملفات الفيلم",
|
"LoadingBooksFailed": "فشل تحميل ملفات الفيلم",
|
||||||
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
||||||
@@ -617,7 +615,7 @@
|
|||||||
"NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}",
|
"NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}",
|
||||||
"SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق",
|
"SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق",
|
||||||
"ConnectionLost": "انقطع الاتصال",
|
"ConnectionLost": "انقطع الاتصال",
|
||||||
"ConnectionLostReconnect": "سيحاول Radarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
|
"ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
|
||||||
"LastDuration": "المدة الماضية",
|
"LastDuration": "المدة الماضية",
|
||||||
"Large": "كبير",
|
"Large": "كبير",
|
||||||
"WhatsNew": "ما هو الجديد؟",
|
"WhatsNew": "ما هو الجديد؟",
|
||||||
@@ -636,5 +634,13 @@
|
|||||||
"ListsSettingsSummary": "القوائم",
|
"ListsSettingsSummary": "القوائم",
|
||||||
"SelectDropdown": "'تحديد...",
|
"SelectDropdown": "'تحديد...",
|
||||||
"SelectQuality": "حدد الجودة",
|
"SelectQuality": "حدد الجودة",
|
||||||
"CustomFilter": "مرشحات مخصصة"
|
"CustomFilter": "مرشحات مخصصة",
|
||||||
|
"IndexerFlags": "أعلام المفهرس",
|
||||||
|
"InteractiveSearchModalHeader": "بحث تفاعلي",
|
||||||
|
"ApiKey": "مفتاح API",
|
||||||
|
"AuthBasic": "أساسي (المتصفح المنبثق)",
|
||||||
|
"AuthForm": "النماذج (صفحة تسجيل الدخول)",
|
||||||
|
"Enabled": "ممكن",
|
||||||
|
"FailedLoadingSearchResults": "فشل تحميل نتائج البحث ، يرجى المحاولة مرة أخرى.",
|
||||||
|
"DisabledForLocalAddresses": "معطل بسبب العناوين المحلية"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ApiKeyHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
|
|
||||||
"Enable": "Активиране",
|
"Enable": "Активиране",
|
||||||
"MIA": "МВР",
|
"MIA": "МВР",
|
||||||
"Size": " Размер",
|
"Size": " Размер",
|
||||||
@@ -7,7 +6,6 @@
|
|||||||
"20MinutesTwenty": "120 минути: {0}",
|
"20MinutesTwenty": "120 минути: {0}",
|
||||||
"45MinutesFourtyFive": "90 минути: {0}",
|
"45MinutesFourtyFive": "90 минути: {0}",
|
||||||
"60MinutesSixty": "60 минути: {0}",
|
"60MinutesSixty": "60 минути: {0}",
|
||||||
"APIKey": "API ключ",
|
|
||||||
"About": "относно",
|
"About": "относно",
|
||||||
"AddListExclusion": "Добавяне на изключване от списъка",
|
"AddListExclusion": "Добавяне на изключване от списъка",
|
||||||
"AddingTag": "Добавяне на таг",
|
"AddingTag": "Добавяне на таг",
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
"Calendar": "Календар",
|
"Calendar": "Календар",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед",
|
"CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед",
|
||||||
"Cancel": "Отказ",
|
"Cancel": "Отказ",
|
||||||
"CancelMessageText": "Наистина ли искате да отмените тази чакаща задача?",
|
"CancelPendingTask": "Наистина ли искате да отмените тази чакаща задача?",
|
||||||
"CertificateValidation": "Валидиране на сертификат",
|
"CertificateValidation": "Валидиране на сертификат",
|
||||||
"CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране",
|
"CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране",
|
||||||
"ChangeFileDate": "Промяна на датата на файла",
|
"ChangeFileDate": "Промяна на датата на файла",
|
||||||
@@ -424,8 +422,8 @@
|
|||||||
"UsenetDelay": "Usenet Delay",
|
"UsenetDelay": "Usenet Delay",
|
||||||
"UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet",
|
"UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet",
|
||||||
"Username": "Потребителско име",
|
"Username": "Потребителско име",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Клон, който да се използва за актуализиране на Radarr",
|
"BranchUpdate": "Клон, който да се използва за актуализиране на Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
|
"BranchUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
|
||||||
"Version": "Версия",
|
"Version": "Версия",
|
||||||
"WeekColumnHeader": "Заглавка на колоната на седмицата",
|
"WeekColumnHeader": "Заглавка на колоната на седмицата",
|
||||||
"Year": "Година",
|
"Year": "Година",
|
||||||
@@ -636,5 +634,13 @@
|
|||||||
"SelectDropdown": "„Изберете ...",
|
"SelectDropdown": "„Изберете ...",
|
||||||
"SelectQuality": "Изберете Качество",
|
"SelectQuality": "Изберете Качество",
|
||||||
"CustomFilter": "Персонализирани филтри",
|
"CustomFilter": "Персонализирани филтри",
|
||||||
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?"
|
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?",
|
||||||
|
"IndexerFlags": "Индексиращи знамена",
|
||||||
|
"InteractiveSearchModalHeader": "Интерактивно търсене",
|
||||||
|
"FailedLoadingSearchResults": "Неуспешно зареждане на резултатите от търсенето, моля, опитайте отново.",
|
||||||
|
"ApiKey": "API ключ",
|
||||||
|
"AuthBasic": "Основно (изскачащ прозорец на браузъра)",
|
||||||
|
"AuthForm": "Формуляри (Страница за вход)",
|
||||||
|
"DisabledForLocalAddresses": "Забранено за местни адреси",
|
||||||
|
"Enabled": "Активирано"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@
|
|||||||
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
|
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
|
||||||
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr",
|
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr",
|
||||||
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Radarr ja no baixarà pel·lícules",
|
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Radarr ja no baixarà pel·lícules",
|
||||||
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?",
|
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
|
||||||
"PropersAndRepacks": "Propietats i Repacks",
|
"PropersAndRepacks": "Propietats i Repacks",
|
||||||
"RemotePathMappingCheckFolderPermissions": "Radarr pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.",
|
"RemotePathMappingCheckFolderPermissions": "Radarr pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.",
|
||||||
"RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització",
|
"RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització",
|
||||||
@@ -443,9 +443,9 @@
|
|||||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.",
|
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.",
|
||||||
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||||
"ApplicationURL": "URL de l'aplicació",
|
"ApplicationURL": "URL de l'aplicació",
|
||||||
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base",
|
"ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
|
||||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr",
|
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr",
|
||||||
"CancelMessageText": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
|
"CancelPendingTask": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
|
||||||
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Radarr.",
|
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Radarr.",
|
||||||
"ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats",
|
"ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats",
|
||||||
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
|
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
|
||||||
@@ -487,9 +487,9 @@
|
|||||||
"SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.",
|
"SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.",
|
||||||
"TimeLeft": "Temps restant",
|
"TimeLeft": "Temps restant",
|
||||||
"UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista",
|
"UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branca que s'utilitza per actualitzar Radarr",
|
"BranchUpdate": "Branca que s'utilitza per actualitzar Radarr",
|
||||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
|
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
|
"BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
|
||||||
"WriteTagsNo": "Mai",
|
"WriteTagsNo": "Mai",
|
||||||
"RestartReloadNote": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
|
"RestartReloadNote": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
|
||||||
"Series": "Sèries",
|
"Series": "Sèries",
|
||||||
@@ -505,8 +505,6 @@
|
|||||||
"GrabReleaseMessageText": "Lidarr no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que Lidarr no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?",
|
"GrabReleaseMessageText": "Lidarr no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que Lidarr no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?",
|
||||||
"IsCutoffCutoff": "Requisit",
|
"IsCutoffCutoff": "Requisit",
|
||||||
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
|
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
|
||||||
"APIKey": "Clau API",
|
|
||||||
"ApiKeyHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
|
||||||
"RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
|
"RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
|
||||||
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
|
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
|
||||||
"Size": " Mida",
|
"Size": " Mida",
|
||||||
@@ -564,13 +562,13 @@
|
|||||||
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
|
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
|
||||||
"UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
|
"UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
|
||||||
"RequiredPlaceHolder": "Afegeix una nova restricció",
|
"RequiredPlaceHolder": "Afegeix una nova restricció",
|
||||||
"20MinutesTwenty": "60 minuts: {0}",
|
"20MinutesTwenty": "20 minuts: {0}",
|
||||||
"AlternateTitles": "Títols alternatius",
|
"AlternateTitles": "Títols alternatius",
|
||||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Radarr. Això inclou informació sobre el vostre navegador, quines pàgines Radarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
|
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Radarr. Això inclou informació sobre el vostre navegador, quines pàgines Radarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
|
||||||
"AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
"AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||||
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
|
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
|
"CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
|
||||||
"45MinutesFourtyFive": "60 minuts: {0}",
|
"45MinutesFourtyFive": "45 minuts: {0}",
|
||||||
"60MinutesSixty": "60 minuts: {0}",
|
"60MinutesSixty": "60 minuts: {0}",
|
||||||
"BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
"BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||||
"BookIsDownloading": "La pel·lícula s'està baixant",
|
"BookIsDownloading": "La pel·lícula s'està baixant",
|
||||||
@@ -658,11 +656,11 @@
|
|||||||
"Activity": "Activitat",
|
"Activity": "Activitat",
|
||||||
"AddNew": "Afegeix nou",
|
"AddNew": "Afegeix nou",
|
||||||
"ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)",
|
"ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)",
|
||||||
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes",
|
"ApplyTagsHelpTextRemove": "Eliminació: elimina les etiquetes introduïdes",
|
||||||
"BlocklistReleases": "Llista de llançaments bloquejats",
|
"BlocklistReleases": "Llista de llançaments bloquejats",
|
||||||
"AutoAdd": "Afegeix automàticament",
|
"AutoAdd": "Afegeix automàticament",
|
||||||
"Backup": "Còpia de seguretat",
|
"Backup": "Còpia de seguretat",
|
||||||
"ApplyTagsHelpTextAdd": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent",
|
"ApplyTagsHelpTextAdd": "Afegiment: afegeix les etiquetes a la llista d'etiquetes existent",
|
||||||
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?",
|
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?",
|
||||||
"DeleteSelectedIndexers": "Suprimeix l'indexador(s)",
|
"DeleteSelectedIndexers": "Suprimeix l'indexador(s)",
|
||||||
"DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?",
|
"DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?",
|
||||||
@@ -712,5 +710,79 @@
|
|||||||
"DownloadClientDelugeSettingsDirectory": "Directori de baixada",
|
"DownloadClientDelugeSettingsDirectory": "Directori de baixada",
|
||||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
|
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
|
||||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
|
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
|
||||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge"
|
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
|
||||||
|
"WhatsNew": "Novetats",
|
||||||
|
"SelectDropdown": "Seleccioneu...",
|
||||||
|
"NoCutoffUnmetItems": "No hi ha elements de tall no assolits",
|
||||||
|
"ApplyTagsHelpTextHowToApplyAuthors": "Com aplicar etiquetes a les pel·lícules seleccionades",
|
||||||
|
"DeleteConditionMessageText": "Esteu segur que voleu suprimir la condició '{name}'?",
|
||||||
|
"NoChange": "Cap canvi",
|
||||||
|
"SetTags": "Estableix etiquetes",
|
||||||
|
"NoResultsFound": "Sense resultats",
|
||||||
|
"Author": "Autor",
|
||||||
|
"ResetQualityDefinitions": "Restableix les definicions de qualitat",
|
||||||
|
"Small": "Petita",
|
||||||
|
"TotalSpace": "Espai total",
|
||||||
|
"BlocklistReleaseHelpText": "Impedeix que {appName} torni a capturar aquesta versió automàticament",
|
||||||
|
"CatalogNumber": "número de catàleg",
|
||||||
|
"LastWriteTime": "La darrera hora d'escriptura",
|
||||||
|
"NextExecution": "Propera execució",
|
||||||
|
"RemoveCompleted": "S'ha eliminat",
|
||||||
|
"SelectReleaseGroup": "Seleccioneu grup de llançament",
|
||||||
|
"CountDownloadClientsSelected": "{count} client(s) de baixada seleccionat(s)",
|
||||||
|
"Authors": "Autor",
|
||||||
|
"FreeSpace": "Espai lliure",
|
||||||
|
"ExtraFileExtensionsHelpText": "Llista separada per comes de fitxers addicionals per importar (.nfo s'importarà com a .nfo-orig)",
|
||||||
|
"BypassIfAboveCustomFormatScore": "Ometre si està per sobre de la puntuació de format personalitzada",
|
||||||
|
"BypassIfAboveCustomFormatScoreHelpText": "Habiliteu l'omissió quan la versió tingui una puntuació superior a la puntuació mínima per al format personalitzat",
|
||||||
|
"RedownloadFailed": "Tornar a baixar les baixades fallades",
|
||||||
|
"ExistingTag": "Etiqueta existent",
|
||||||
|
"RemoveFailed": "Ha fallat l'eliminació",
|
||||||
|
"ImportLists": "llista d'importació",
|
||||||
|
"RemovingTag": "S'està eliminant l'etiqueta",
|
||||||
|
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||||
|
"ExtraFileExtensionsHelpTextsExamples": "Exemples: '.sub, .nfo' o 'sub,nfo'",
|
||||||
|
"SourceTitle": "Títol de la font",
|
||||||
|
"NoEventsFound": "No s'han trobat esdeveniments",
|
||||||
|
"InteractiveSearchModalHeader": "Cerca interactiva",
|
||||||
|
"NotificationStatusSingleClientHealthCheckMessage": "Llistes no disponibles a causa d'errors: {0}",
|
||||||
|
"Medium": "Suport",
|
||||||
|
"RecentChanges": "Canvis recents",
|
||||||
|
"Rejections": "Rebutjats",
|
||||||
|
"StatusEndedContinuing": "Continua",
|
||||||
|
"DeleteBookFileMessageText": "Esteu segur que voleu suprimir '{path}'?",
|
||||||
|
"DownloadClientTagHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.",
|
||||||
|
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El client de baixada {downloadClientName} està configurat per eliminar les baixades completades. Això pot provocar que les baixades s'eliminin del vostre client abans que {1} pugui importar-les.",
|
||||||
|
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Cerqueu i intenteu baixar automàticament una versió diferent quan es trobi una versió fallida a la cerca interactiva",
|
||||||
|
"FailedLoadingSearchResults": "No s'han pogut carregar els resultats de la cerca, torneu-ho a provar.",
|
||||||
|
"IndexerFlags": "Indicadors de l'indexador",
|
||||||
|
"Large": "Gran",
|
||||||
|
"LastDuration": "Darrera durada",
|
||||||
|
"LastExecution": "Darrere execució",
|
||||||
|
"Library": "Biblioteca",
|
||||||
|
"ListsSettingsSummary": "llista d'importació",
|
||||||
|
"Loading": "Carregant",
|
||||||
|
"MinimumCustomFormatScoreHelpText": "Puntuació mínima de format personalitzada necessaria per a evitar el retard del protocol preferit",
|
||||||
|
"ProfilesSettingsSummary": "Perfils de qualitat, idioma, retard i llançament",
|
||||||
|
"RemoveDownloadsAlert": "La configuració d'eliminació s'ha mogut a la configuració del client de baixada a la taula anterior.",
|
||||||
|
"RemoveQueueItemConfirmation": "Esteu segur que voleu eliminar '{sourceTitle}' de la cua?",
|
||||||
|
"RemoveSelectedItem": "Elimina l'element seleccionat",
|
||||||
|
"RemoveSelectedItems": "Elimina els elements seleccionats",
|
||||||
|
"RemoveSelectedItemsQueueMessageText": "Esteu segur que voleu eliminar {0} de la cua?",
|
||||||
|
"SelectQuality": "Seleccioneu Qualitat",
|
||||||
|
"SomeResultsAreHiddenByTheAppliedFilter": "Alguns resultats estan ocults pel filtre aplicat",
|
||||||
|
"AuthBasic": "Basic (finestra emergent del navegador)",
|
||||||
|
"AuthForm": "Formularis (pàgina d'inici de sessió)",
|
||||||
|
"AuthenticationMethod": "Mètode d'autenticació",
|
||||||
|
"AuthenticationMethodHelpTextWarning": "Seleccioneu un mètode d'autenticació vàlid",
|
||||||
|
"AuthenticationRequired": "Autenticació necessària",
|
||||||
|
"AuthenticationRequiredHelpText": "Canvia per a quines sol·licituds cal autenticar. No canvieu tret que entengueu els riscos.",
|
||||||
|
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmeu la nova contrasenya",
|
||||||
|
"AuthenticationRequiredPasswordHelpTextWarning": "Introduïu una contrasenya nova",
|
||||||
|
"AuthenticationRequiredUsernameHelpTextWarning": "Introduïu un nom d'usuari nou",
|
||||||
|
"AuthenticationRequiredWarning": "Per a evitar l'accés remot sense autenticació, ara {appName} requereix que l'autenticació estigui activada. Opcionalment, podeu desactivar l'autenticació des d'adreces locals.",
|
||||||
|
"DisabledForLocalAddresses": "Desactivat per a adreces locals",
|
||||||
|
"Enabled": "Habilitat",
|
||||||
|
"External": "Extern",
|
||||||
|
"ApiKey": "Clau API"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ApiKeyHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
|
||||||
"AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
"AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
||||||
"DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
"DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
||||||
"Group": "Skupina",
|
"Group": "Skupina",
|
||||||
@@ -12,7 +11,6 @@
|
|||||||
"20MinutesTwenty": "120 minut: {0}",
|
"20MinutesTwenty": "120 minut: {0}",
|
||||||
"45MinutesFourtyFive": "90 minut: {0}",
|
"45MinutesFourtyFive": "90 minut: {0}",
|
||||||
"60MinutesSixty": "60 minut: {0}",
|
"60MinutesSixty": "60 minut: {0}",
|
||||||
"APIKey": "Klíč API",
|
|
||||||
"About": "O aplikaci",
|
"About": "O aplikaci",
|
||||||
"AddListExclusion": "Přidat vyloučení seznamu",
|
"AddListExclusion": "Přidat vyloučení seznamu",
|
||||||
"AddingTag": "Přidání značky",
|
"AddingTag": "Přidání značky",
|
||||||
@@ -43,7 +41,7 @@
|
|||||||
"Calendar": "Kalendář",
|
"Calendar": "Kalendář",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
|
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
|
||||||
"Cancel": "Zrušit",
|
"Cancel": "Zrušit",
|
||||||
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?",
|
"CancelPendingTask": "Opravdu chcete zrušit tento nevyřízený úkol?",
|
||||||
"CertificateValidation": "Ověření certifikátu",
|
"CertificateValidation": "Ověření certifikátu",
|
||||||
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
|
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
|
||||||
"ChangeFileDate": "Změnit datum souboru",
|
"ChangeFileDate": "Změnit datum souboru",
|
||||||
@@ -68,7 +66,7 @@
|
|||||||
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
|
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
|
||||||
"CreateGroup": "Vytvořit skupinu",
|
"CreateGroup": "Vytvořit skupinu",
|
||||||
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy",
|
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy",
|
||||||
"CutoffUnmet": "Vynechat nevyhovující",
|
"CutoffUnmet": "Mezní hodnota nesplněna",
|
||||||
"DatabaseMigration": "Migrace databáze",
|
"DatabaseMigration": "Migrace databáze",
|
||||||
"Dates": "Termíny",
|
"Dates": "Termíny",
|
||||||
"DelayProfile": "Profil zpoždění",
|
"DelayProfile": "Profil zpoždění",
|
||||||
@@ -431,8 +429,8 @@
|
|||||||
"UsenetDelay": "Usenet Zpoždění",
|
"UsenetDelay": "Usenet Zpoždění",
|
||||||
"UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu",
|
"UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu",
|
||||||
"Username": "Uživatelské jméno",
|
"Username": "Uživatelské jméno",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Pobočka, která se má použít k aktualizaci Radarr",
|
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace",
|
"BranchUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace",
|
||||||
"Version": "Verze",
|
"Version": "Verze",
|
||||||
"WeekColumnHeader": "Záhlaví sloupce týdne",
|
"WeekColumnHeader": "Záhlaví sloupce týdne",
|
||||||
"Year": "Rok",
|
"Year": "Rok",
|
||||||
@@ -691,5 +689,29 @@
|
|||||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání",
|
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání",
|
||||||
"SelectDropdown": "'Vybrat...",
|
"SelectDropdown": "'Vybrat...",
|
||||||
"CustomFilter": "Vlastní filtry",
|
"CustomFilter": "Vlastní filtry",
|
||||||
"SelectQuality": "Vyberte kvalitu"
|
"SelectQuality": "Vyberte kvalitu",
|
||||||
|
"IndexerFlags": "Příznaky indexeru",
|
||||||
|
"InteractiveSearchModalHeader": "Interaktivní vyhledávání",
|
||||||
|
"FailedLoadingSearchResults": "Výsledky vyhledávání se nepodařilo načíst, zkuste to prosím znovu.",
|
||||||
|
"CustomFormatsSpecificationFlag": "Vlajka",
|
||||||
|
"CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena",
|
||||||
|
"BlocklistAndSearch": "Seznam blokovaných a vyhledávání",
|
||||||
|
"ChangeCategory": "Změnit kategorii",
|
||||||
|
"BlocklistMultipleOnlyHint": "Blokovat a nehledat náhradu",
|
||||||
|
"CustomFormatsSettingsTriggerInfo": "Vlastní formát se použije na vydání nebo soubor, pokud odpovídá alespoň jednomu z různých typů zvolených podmínek.",
|
||||||
|
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
|
||||||
|
"AuthBasic": "Základní (vyskakovací okno prohlížeče)",
|
||||||
|
"AuthenticationMethod": "Metoda ověřování",
|
||||||
|
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
|
||||||
|
"AuthenticationRequired": "Vyžadované ověření",
|
||||||
|
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.",
|
||||||
|
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
|
||||||
|
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
|
||||||
|
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
|
||||||
|
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.",
|
||||||
|
"BlocklistOnlyHint": "Blokovat a nehledat náhradu",
|
||||||
|
"Enabled": "Povoleno",
|
||||||
|
"ApiKey": "Klíč API",
|
||||||
|
"AuthForm": "Formuláře (přihlašovací stránka)",
|
||||||
|
"DisabledForLocalAddresses": "Zakázáno pro místní adresy"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"20MinutesTwenty": "20 minutter: {0}",
|
"20MinutesTwenty": "20 minutter: {0}",
|
||||||
"45MinutesFourtyFive": "45 minutter: {0}",
|
"45MinutesFourtyFive": "45 minutter: {0}",
|
||||||
"60MinutesSixty": "60 minutter: {0}",
|
"60MinutesSixty": "60 minutter: {0}",
|
||||||
"APIKey": "API-nøgle",
|
|
||||||
"About": "Om",
|
"About": "Om",
|
||||||
"AddListExclusion": "Tilføj ekskludering af liste",
|
"AddListExclusion": "Tilføj ekskludering af liste",
|
||||||
"AddingTag": "Tilføjer tag",
|
"AddingTag": "Tilføjer tag",
|
||||||
@@ -39,7 +38,7 @@
|
|||||||
"Calendar": "Kalender",
|
"Calendar": "Kalender",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Vist over hver kolonne, når ugen er den aktive visning",
|
"CalendarWeekColumnHeaderHelpText": "Vist over hver kolonne, når ugen er den aktive visning",
|
||||||
"Cancel": "Afbryd",
|
"Cancel": "Afbryd",
|
||||||
"CancelMessageText": "Er du sikker på, at du vil annullere denne afventende opgave?",
|
"CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
|
||||||
"CertificateValidation": "Validering af certifikat",
|
"CertificateValidation": "Validering af certifikat",
|
||||||
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
|
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
|
||||||
"ChangeFileDate": "Skift fildato",
|
"ChangeFileDate": "Skift fildato",
|
||||||
@@ -425,13 +424,12 @@
|
|||||||
"UsenetDelay": "Usenet-forsinkelse",
|
"UsenetDelay": "Usenet-forsinkelse",
|
||||||
"UsenetDelayHelpText": "Forsink i minutter, før du tager fat i en frigivelse fra Usenet",
|
"UsenetDelayHelpText": "Forsink i minutter, før du tager fat i en frigivelse fra Usenet",
|
||||||
"Username": "Brugernavn",
|
"Username": "Brugernavn",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Filial, der skal bruges til at opdatere Radarr",
|
"BranchUpdate": "Filial, der skal bruges til at opdatere Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gren brugt af ekstern opdateringsmekanisme",
|
"BranchUpdateMechanism": "Gren brugt af ekstern opdateringsmekanisme",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
"WeekColumnHeader": "Ugens kolonneoverskrift",
|
"WeekColumnHeader": "Ugens kolonneoverskrift",
|
||||||
"Year": "År",
|
"Year": "År",
|
||||||
"YesCancel": "Ja, Annuller",
|
"YesCancel": "Ja, Annuller",
|
||||||
"ApiKeyHelpTextWarning": "Kræver genstart for at træde i kraft",
|
|
||||||
"DeleteRootFolderMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
|
"DeleteRootFolderMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
|
||||||
"LoadingBooksFailed": "Indlæsning af filmfiler mislykkedes",
|
"LoadingBooksFailed": "Indlæsning af filmfiler mislykkedes",
|
||||||
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
||||||
@@ -644,5 +642,17 @@
|
|||||||
"CustomFilter": "Bruger Tilpassede Filtere",
|
"CustomFilter": "Bruger Tilpassede Filtere",
|
||||||
"SelectDropdown": "'Vælg...",
|
"SelectDropdown": "'Vælg...",
|
||||||
"SelectQuality": "Vælg Kvalitet",
|
"SelectQuality": "Vælg Kvalitet",
|
||||||
"ApplyChanges": "Anvend ændringer"
|
"ApplyChanges": "Anvend ændringer",
|
||||||
|
"Series": "Serie",
|
||||||
|
"IndexerFlags": "Indexer Flag",
|
||||||
|
"InteractiveSearchModalHeader": "Interaktiv søgning",
|
||||||
|
"Theme": "Tema",
|
||||||
|
"Publisher": "Udgiver",
|
||||||
|
"CatalogNumber": "katalognummer",
|
||||||
|
"FailedLoadingSearchResults": "Kunne ikke indlæse søgeresultater. Prøv igen.",
|
||||||
|
"AuthBasic": "Grundlæggende (pop op-browser)",
|
||||||
|
"AuthForm": "Formularer (login-side)",
|
||||||
|
"DisabledForLocalAddresses": "Deaktiveret for lokale adresser",
|
||||||
|
"Enabled": "Aktiveret",
|
||||||
|
"ApiKey": "API-nøgle"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
"20MinutesTwenty": "20 Minuten: {0}",
|
"20MinutesTwenty": "20 Minuten: {0}",
|
||||||
"45MinutesFourtyFive": "45 Minuten: {0}",
|
"45MinutesFourtyFive": "45 Minuten: {0}",
|
||||||
"60MinutesSixty": "60 Minuten: {0}",
|
"60MinutesSixty": "60 Minuten: {0}",
|
||||||
"APIKey": "API-Schlüssel",
|
|
||||||
"About": "Über",
|
"About": "Über",
|
||||||
"AddListExclusion": "Listenausschluss hinzufügen",
|
"AddListExclusion": "Listenausschluss hinzufügen",
|
||||||
"AddingTag": "Tag hinzufügen",
|
"AddingTag": "Tag hinzufügen",
|
||||||
"AgeWhenGrabbed": "Alter (zum Zeitpunkt der Entführung)",
|
"AgeWhenGrabbed": "Alter (bei Erfassung)",
|
||||||
"AlreadyInYourLibrary": "Bereits in Ihrer Bibliothek",
|
"AlreadyInYourLibrary": "Bereits in Ihrer Bibliothek",
|
||||||
"AlternateTitles": "Alternative Titel",
|
"AlternateTitles": "Alternative Titel",
|
||||||
"Analytics": "Analysen",
|
"Analytics": "Analysen",
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
"Calendar": "Kalender",
|
"Calendar": "Kalender",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Wird in der Wochenansicht über jeder Spalte angezeigt",
|
"CalendarWeekColumnHeaderHelpText": "Wird in der Wochenansicht über jeder Spalte angezeigt",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
"CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?",
|
"CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
|
||||||
"CertificateValidation": "Zertifikatsvalidierung",
|
"CertificateValidation": "Zertifikatsvalidierung",
|
||||||
"CertificateValidationHelpText": "Ändern Sie, wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nichts, es sei denn, Sie verstehen die Risiken.",
|
"CertificateValidationHelpText": "Ändern Sie, wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nichts, es sei denn, Sie verstehen die Risiken.",
|
||||||
"ChangeFileDate": "Ändern Sie das Dateidatum",
|
"ChangeFileDate": "Ändern Sie das Dateidatum",
|
||||||
@@ -61,7 +60,7 @@
|
|||||||
"CreateEmptyAuthorFoldersHelpText": "Leere Filmordner für fehlende Filme beim Scan erstellen",
|
"CreateEmptyAuthorFoldersHelpText": "Leere Filmordner für fehlende Filme beim Scan erstellen",
|
||||||
"CreateGroup": "Gruppe erstellen",
|
"CreateGroup": "Gruppe erstellen",
|
||||||
"CutoffHelpText": "Sobald diese Qualität erreicht wird, werden keine neuen Releases erfasst",
|
"CutoffHelpText": "Sobald diese Qualität erreicht wird, werden keine neuen Releases erfasst",
|
||||||
"CutoffUnmet": "› Schwelle nicht erreicht",
|
"CutoffUnmet": "Schwelle nicht erreicht",
|
||||||
"DatabaseMigration": "DB Migration",
|
"DatabaseMigration": "DB Migration",
|
||||||
"Dates": "Termine",
|
"Dates": "Termine",
|
||||||
"DelayProfile": "Verzögerungsprofil",
|
"DelayProfile": "Verzögerungsprofil",
|
||||||
@@ -69,24 +68,24 @@
|
|||||||
"DelayingDownloadUntilInterp": "Download verzögern bis {0} um {1}",
|
"DelayingDownloadUntilInterp": "Download verzögern bis {0} um {1}",
|
||||||
"Delete": "Löschen",
|
"Delete": "Löschen",
|
||||||
"DeleteBackup": "Sicherung löschen",
|
"DeleteBackup": "Sicherung löschen",
|
||||||
"DeleteBackupMessageText": "Sind Sie sicher, dass Sie die Sicherung „{name}“ löschen möchten?",
|
"DeleteBackupMessageText": "Soll das Backup '{name}' wirklich gelöscht werden?",
|
||||||
"DeleteDelayProfile": "Verzögerungsprofil löschen",
|
"DeleteDelayProfile": "Verzögerungsprofil löschen",
|
||||||
"DeleteDelayProfileMessageText": "Sind Sie sicher, dass Sie dieses Verzögerungsprofil löschen möchten?",
|
"DeleteDelayProfileMessageText": "Sind Sie sicher, dass Sie dieses Verzögerungsprofil löschen möchten?",
|
||||||
"DeleteDownloadClient": "Download-Client löschen",
|
"DeleteDownloadClient": "Download-Client löschen",
|
||||||
"DeleteDownloadClientMessageText": "Sind Sie sicher, dass Sie den Download-Client „{name}“ löschen möchten?",
|
"DeleteDownloadClientMessageText": "Bist du sicher, dass du den Download Client '{name}' wirklich löschen willst?",
|
||||||
"DeleteEmptyFolders": "Leere Ordner löschen",
|
"DeleteEmptyFolders": "Leere Ordner löschen",
|
||||||
"DeleteEmptyFoldersHelpText": "Lösche leere Autorordner während des Scans oder wenn Buchdateien gelöscht werden",
|
"DeleteEmptyFoldersHelpText": "Lösche leere Autorordner während des Scans oder wenn Buchdateien gelöscht werden",
|
||||||
"DeleteImportListMessageText": "Sind Sie sicher, dass Sie die Liste „{name}“ löschen möchten?",
|
"DeleteImportListMessageText": "Bist du sicher, dass du die Liste '{name}' wirklich löschen willst?",
|
||||||
"DeleteIndexer": "Indexer löschen",
|
"DeleteIndexer": "Indexer löschen",
|
||||||
"DeleteIndexerMessageText": "Sind Sie sicher, dass Sie den Indexer „{name}“ löschen möchten?",
|
"DeleteIndexerMessageText": "Bist du sicher, dass du den Indexer '{name}' wirklich löschen willst?",
|
||||||
"DeleteMetadataProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?",
|
"DeleteMetadataProfileMessageText": "Bist du sicher, dass du das Qualitätsprofil '{name}' wirklich löschen willst?",
|
||||||
"DeleteNotification": "Benachrichtigung löschen",
|
"DeleteNotification": "Benachrichtigung löschen",
|
||||||
"DeleteNotificationMessageText": "Sind Sie sicher, dass Sie die Benachrichtigung „{name}“ löschen möchten?",
|
"DeleteNotificationMessageText": "Bist du sicher, dass du die Benachrichtigung '{name}' wirklich löschen willst?",
|
||||||
"DeleteQualityProfile": "Qualitätsprofil löschen",
|
"DeleteQualityProfile": "Qualitätsprofil löschen",
|
||||||
"DeleteQualityProfileMessageText": "Sind Sie sicher, dass Sie das Qualitätsprofil „{name}“ löschen möchten?",
|
"DeleteQualityProfileMessageText": "Bist du sicher, dass du das Qualitätsprofil '{name}' wirklich löschen willst?",
|
||||||
"DeleteReleaseProfile": "Release-Profil löschen",
|
"DeleteReleaseProfile": "Release-Profil löschen",
|
||||||
"DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Release-Profil löschen willst?",
|
"DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Release-Profil löschen willst?",
|
||||||
"DeleteRootFolderMessageText": "Indexer '{0}' wirklich löschen?",
|
"DeleteRootFolderMessageText": "Bist du sicher, dass du den Root-Ordner '{name}' wirklich löschen willst?",
|
||||||
"DeleteSelectedBookFiles": "Ausgewählte Filmdateien löschen",
|
"DeleteSelectedBookFiles": "Ausgewählte Filmdateien löschen",
|
||||||
"DeleteSelectedBookFilesMessageText": "Ausgewählte Filme wirklich löschen?",
|
"DeleteSelectedBookFilesMessageText": "Ausgewählte Filme wirklich löschen?",
|
||||||
"DeleteTag": "Tag löschen",
|
"DeleteTag": "Tag löschen",
|
||||||
@@ -274,7 +273,7 @@
|
|||||||
"RemoveCompletedDownloadsHelpText": "Importierte Downloads aus dem Downloader Verlauf entfernen",
|
"RemoveCompletedDownloadsHelpText": "Importierte Downloads aus dem Downloader Verlauf entfernen",
|
||||||
"RemoveFailedDownloadsHelpText": "Fehlgeschlagene Downloads aus dem Downloader Verlauf entfernen",
|
"RemoveFailedDownloadsHelpText": "Fehlgeschlagene Downloads aus dem Downloader Verlauf entfernen",
|
||||||
"RemoveFilter": "Filter entfernen",
|
"RemoveFilter": "Filter entfernen",
|
||||||
"RemoveFromDownloadClient": "Vom Download-Client entfernen",
|
"RemoveFromDownloadClient": "Aus dem Download Client entfernen",
|
||||||
"RemoveFromQueue": "Aus der Warteschlage entfernen",
|
"RemoveFromQueue": "Aus der Warteschlage entfernen",
|
||||||
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
|
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
|
||||||
"RemoveSelected": "Auswahl entfernen",
|
"RemoveSelected": "Auswahl entfernen",
|
||||||
@@ -416,8 +415,8 @@
|
|||||||
"UsenetDelay": "Usenet-Verzögerung",
|
"UsenetDelay": "Usenet-Verzögerung",
|
||||||
"UsenetDelayHelpText": "Verzögerung in Minuten, bevor Sie eine Veröffentlichung aus dem Usenet erhalten",
|
"UsenetDelayHelpText": "Verzögerung in Minuten, bevor Sie eine Veröffentlichung aus dem Usenet erhalten",
|
||||||
"Username": "Nutzername",
|
"Username": "Nutzername",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branch zum updaten von Radarr",
|
"BranchUpdate": "Branch zum updaten von Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch für den externen Updateablauf",
|
"BranchUpdateMechanism": "Branch für den externen Updateablauf",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
"WeekColumnHeader": "Spaltenüberschrift „Woche“.",
|
"WeekColumnHeader": "Spaltenüberschrift „Woche“.",
|
||||||
"Year": "Jahr",
|
"Year": "Jahr",
|
||||||
@@ -427,7 +426,6 @@
|
|||||||
"ProxyUsernameHelpText": "Sie müssen nur einen Benutzernamen und ein Passwort eingeben, wenn dies erforderlich ist. Andernfalls lassen Sie sie leer.",
|
"ProxyUsernameHelpText": "Sie müssen nur einen Benutzernamen und ein Passwort eingeben, wenn dies erforderlich ist. Andernfalls lassen Sie sie leer.",
|
||||||
"SslPortHelpTextWarning": "Erfordert einen Neustart",
|
"SslPortHelpTextWarning": "Erfordert einen Neustart",
|
||||||
"UnableToLoadMetadataProfiles": "Verzögerungsprofile konnten nicht geladen werden",
|
"UnableToLoadMetadataProfiles": "Verzögerungsprofile konnten nicht geladen werden",
|
||||||
"ApiKeyHelpTextWarning": "Erfordert einen Neustart",
|
|
||||||
"Book": "Buch",
|
"Book": "Buch",
|
||||||
"Authors": "Autoren",
|
"Authors": "Autoren",
|
||||||
"AuthorFolderFormat": "Autor Orderformat",
|
"AuthorFolderFormat": "Autor Orderformat",
|
||||||
@@ -464,7 +462,7 @@
|
|||||||
"DownloadClientCheckDownloadingToRoot": "Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.",
|
"DownloadClientCheckDownloadingToRoot": "Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.",
|
||||||
"Series": "Serien",
|
"Series": "Serien",
|
||||||
"Publisher": "Herausgeber",
|
"Publisher": "Herausgeber",
|
||||||
"MaintenanceRelease": "Wartung: Fehlerbehebung und andere Verbesserungen. Siehe Github Commit History für weitere Details",
|
"MaintenanceRelease": "Maintenance Release: Fehlerbehebungen und andere Verbesserungen. Siehe Github Commit Verlauf für weitere Details",
|
||||||
"LogRotation": "Logrotation",
|
"LogRotation": "Logrotation",
|
||||||
"LogRotateHelpText": "Max. Anzahl der zu behaltenden Logdateien",
|
"LogRotateHelpText": "Max. Anzahl der zu behaltenden Logdateien",
|
||||||
"Label": "Label",
|
"Label": "Label",
|
||||||
@@ -606,7 +604,7 @@
|
|||||||
"OnImportFailureHelpText": "Bei fehlgeschlagenem Import",
|
"OnImportFailureHelpText": "Bei fehlgeschlagenem Import",
|
||||||
"OnReleaseImport": "Bei Veröffentlichungsimport",
|
"OnReleaseImport": "Bei Veröffentlichungsimport",
|
||||||
"OnReleaseImportHelpText": "Bei Veröffentlichungsimport",
|
"OnReleaseImportHelpText": "Bei Veröffentlichungsimport",
|
||||||
"OnRename": "Bei Umbenennen",
|
"OnRename": "Bei Umbenennung",
|
||||||
"PastDaysHelpText": "Tage für iCal-Feed, um in die Vergangenheit zu schauen",
|
"PastDaysHelpText": "Tage für iCal-Feed, um in die Vergangenheit zu schauen",
|
||||||
"PathHelpTextWarning": "Dies muss ein anderes Verzeichnis sein als das, in dem der Download Client die Dateien ablegt",
|
"PathHelpTextWarning": "Dies muss ein anderes Verzeichnis sein als das, in dem der Download Client die Dateien ablegt",
|
||||||
"ReleaseProfiles": "Veröffentlichungsprofile",
|
"ReleaseProfiles": "Veröffentlichungsprofile",
|
||||||
@@ -705,7 +703,7 @@
|
|||||||
"SelectedCountAuthorsSelectedInterp": "{0} Künstler ausgewählt",
|
"SelectedCountAuthorsSelectedInterp": "{0} Künstler ausgewählt",
|
||||||
"SizeLimit": "Grössenlimit",
|
"SizeLimit": "Grössenlimit",
|
||||||
"TheBooksFilesWillBeDeleted": "Die Dateien des Albums werden gelöscht.",
|
"TheBooksFilesWillBeDeleted": "Die Dateien des Albums werden gelöscht.",
|
||||||
"MediaManagement": "Medien",
|
"MediaManagement": "Medienverwaltung",
|
||||||
"Metadata": "Metadaten",
|
"Metadata": "Metadaten",
|
||||||
"MountCheckMessage": "Der Einhängepunkt, welcher einen Filmpfad enthält, ist schreibgeschützt eingehängt: ",
|
"MountCheckMessage": "Der Einhängepunkt, welcher einen Filmpfad enthält, ist schreibgeschützt eingehängt: ",
|
||||||
"OnBookFileDelete": "Bei Filmdatei löschen",
|
"OnBookFileDelete": "Bei Filmdatei löschen",
|
||||||
@@ -875,7 +873,7 @@
|
|||||||
"CustomFormatSettings": "Einstellungen für eigene Formate",
|
"CustomFormatSettings": "Einstellungen für eigene Formate",
|
||||||
"DataFutureBooks": "Überwachung von Alben die noch nicht veröffentlicht wurden",
|
"DataFutureBooks": "Überwachung von Alben die noch nicht veröffentlicht wurden",
|
||||||
"DeleteCustomFormat": "Benutzerdefiniertes Format löschen",
|
"DeleteCustomFormat": "Benutzerdefiniertes Format löschen",
|
||||||
"DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{0}' löschen willst?",
|
"DeleteCustomFormatMessageText": "Bist du sicher, dass du das benutzerdefinierte Format '{name}' wirklich löschen willst?",
|
||||||
"IncludeCustomFormatWhenRenamingHelpText": "In {Custom Formats} umbennenungs Format",
|
"IncludeCustomFormatWhenRenamingHelpText": "In {Custom Formats} umbennenungs Format",
|
||||||
"ResetTitles": "Titel zurücksetzen",
|
"ResetTitles": "Titel zurücksetzen",
|
||||||
"UnableToLoadCustomFormats": "Eigene Formate konnten nicht geladen werden",
|
"UnableToLoadCustomFormats": "Eigene Formate konnten nicht geladen werden",
|
||||||
@@ -931,13 +929,13 @@
|
|||||||
"RemoveFailed": "Fehlgeschlagene entfernen",
|
"RemoveFailed": "Fehlgeschlagene entfernen",
|
||||||
"RemoveCompleted": "Abgeschlossene entfernen",
|
"RemoveCompleted": "Abgeschlossene entfernen",
|
||||||
"ApplyChanges": "Änderungen anwenden",
|
"ApplyChanges": "Änderungen anwenden",
|
||||||
"ApplyTagsHelpTextAdd": "Hinzufügen: Fügen Sie die Tags der vorhandenen Tag-Liste hinzu",
|
"ApplyTagsHelpTextAdd": "Hinzufügen: Füge Tags zu den bestehenden Tags hinzu",
|
||||||
"ApplyTagsHelpTextHowToApplyImportLists": "So wenden Sie Tags auf die ausgewählten Importlisten an",
|
"ApplyTagsHelpTextHowToApplyImportLists": "Wie Tags den selektierten Importlisten hinzugefügt werden können",
|
||||||
"ApplyTagsHelpTextHowToApplyIndexers": "So wenden Sie Tags auf die ausgewählten Indexer an",
|
"ApplyTagsHelpTextHowToApplyIndexers": "Wie Tags zu den selektierten Indexern hinzugefügt werden können",
|
||||||
"AutomaticAdd": "Automatisch hinzufügen",
|
"AutomaticAdd": "Automatisch hinzufügen",
|
||||||
"ApplyTagsHelpTextRemove": "Entfernen: Die eingegebenen Tags entfernen",
|
"ApplyTagsHelpTextRemove": "Entfernen: Entferne die hinterlegten Tags",
|
||||||
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetzen Sie die Tags durch die eingegebenen Tags (geben Sie keine Tags ein, um alle Tags zu löschen).",
|
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetze die Tags mit den eingegebenen Tags (keine Tags eingeben um alle Tags zu löschen)",
|
||||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "So wenden Sie Tags auf die ausgewählten Download-Clients an",
|
"ApplyTagsHelpTextHowToApplyDownloadClients": "Wie Tags zu den selektierten Downloadclients hinzugefügt werden können",
|
||||||
"CountIndexersSelected": "{0} Indexer ausgewählt",
|
"CountIndexersSelected": "{0} Indexer ausgewählt",
|
||||||
"DeleteSelectedDownloadClients": "Lösche Download Client(s)",
|
"DeleteSelectedDownloadClients": "Lösche Download Client(s)",
|
||||||
"DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?",
|
"DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?",
|
||||||
@@ -988,7 +986,7 @@
|
|||||||
"Clone": "Klonen",
|
"Clone": "Klonen",
|
||||||
"AutomaticUpdatesDisabledDocker": "Automatische Updates werden bei Verwendung des Docker-Update-Mechanismus nicht direkt unterstützt. Sie müssen das Container-Image außerhalb von {appName} aktualisieren oder ein Skript verwenden",
|
"AutomaticUpdatesDisabledDocker": "Automatische Updates werden bei Verwendung des Docker-Update-Mechanismus nicht direkt unterstützt. Sie müssen das Container-Image außerhalb von {appName} aktualisieren oder ein Skript verwenden",
|
||||||
"AutoAdd": "Automatisch hinzufügen",
|
"AutoAdd": "Automatisch hinzufügen",
|
||||||
"WouldYouLikeToRestoreBackup": "Möchten Sie die Sicherung „{name}“ wiederherstellen?",
|
"WouldYouLikeToRestoreBackup": "Willst du das Backup '{name}' wiederherstellen?",
|
||||||
"Unmonitored": "Nicht beobachtet",
|
"Unmonitored": "Nicht beobachtet",
|
||||||
"Retention": "Aufbewahrung ( Retention )",
|
"Retention": "Aufbewahrung ( Retention )",
|
||||||
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
|
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
|
||||||
@@ -998,5 +996,72 @@
|
|||||||
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
|
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
|
||||||
"BlocklistOnly": "Nur der Sperrliste hinzufügen",
|
"BlocklistOnly": "Nur der Sperrliste hinzufügen",
|
||||||
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
|
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
|
||||||
"ChangeCategory": "Kategorie wechseln"
|
"ChangeCategory": "Kategorie wechseln",
|
||||||
|
"TagIsNotUsedAndCanBeDeleted": "Tag wird nicht benutzt und kann gelöscht werden",
|
||||||
|
"ExtraFileExtensionsHelpText": "Kommaseparierte Liste von Dateiendungen die als Extra Dateien importiert werden sollen ( .nfo wird in .nfo-orig umbenannt )",
|
||||||
|
"DeleteImportListExclusion": "Importlisten Ausschluss löschen",
|
||||||
|
"IndexerFlags": "Indexer-Flags",
|
||||||
|
"CountAuthorsSelected": "{selectedCount} Künstler ausgewählt",
|
||||||
|
"ExtraFileExtensionsHelpTextsExamples": "Vorschläge: sub, nfo, srt, jpg",
|
||||||
|
"DownloadClient": "Downloader",
|
||||||
|
"ShowMonitoredHelpText": "Beobachtungsstatus unter dem Plakat anzeigen",
|
||||||
|
"CountDownloadClientsSelected": "{count} Download-Client(s) ausgewählt",
|
||||||
|
"CountImportListsSelected": "{selectedCount} Künstler ausgewählt",
|
||||||
|
"CustomFormatsSpecificationRegularExpression": "Regulären Ausdruck",
|
||||||
|
"DeleteImportListExclusionMessageText": "Bist du sicher, dass du diesen Importlisten Ausschluss löschen willst?",
|
||||||
|
"CustomFilter": "Benutzerdefinierter Filter",
|
||||||
|
"AutoRedownloadFailed": "Erneuter Download fehlgeschlagen",
|
||||||
|
"SourceTitle": "Quellentitel",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"DownloadClientDelugeSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
|
||||||
|
"ErrorLoadingContent": "Beim Laden des Eintrags ist ein Fehler aufgetreten",
|
||||||
|
"BypassIfAboveCustomFormatScore": "Umgehen, wenn über dem Wert des benutzerdefinierten Formats liegt",
|
||||||
|
"BypassIfAboveCustomFormatScoreHelpText": "Aktivieren Sie die Umgehung, wenn die Veröffentlichung einen Wert hat, der höher ist als der konfigurierte Mindestwert für das benutzerdefinierte Format",
|
||||||
|
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Der Download-Client {downloadClientName} ist so eingestellt, dass abgeschlossene Downloads entfernt werden. Dies kann dazu führen, dass Downloads von Ihrem Client entfernt werden, bevor {appName} sie importieren kann.",
|
||||||
|
"AutoRedownloadFailedFromInteractiveSearch": "Erneuter Download aus Interaktiver Suche fehlgeschlagen",
|
||||||
|
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Suchen Sie automatisch nach einer anderen Version und versuchen Sie, sie herunterzuladen, wenn eine fehlerhafte Version aus der interaktiven Suche ausgewählt wurde",
|
||||||
|
"FailedLoadingSearchResults": "Die Suchergebnisse konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
|
||||||
|
"InteractiveSearchModalHeader": "Interaktive Suche",
|
||||||
|
"MinimumCustomFormatScoreHelpText": "Mindestwert für benutzerdefiniertes Format, der erforderlich ist, um Verzögerungen für das bevorzugte Protokoll zu umgehen",
|
||||||
|
"Rejections": "Ablehnungen",
|
||||||
|
"RemoveQueueItemConfirmation": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?",
|
||||||
|
"SelectDropdown": "Auswählen...",
|
||||||
|
"SelectQuality": "Qualität auswählen",
|
||||||
|
"SelectReleaseGroup": "Wähle Release-Gruppe",
|
||||||
|
"ShowMonitored": "Beobachtete anzeigen",
|
||||||
|
"DeleteSelected": "Markierte löschen",
|
||||||
|
"Bookshelf": "Bücherregal",
|
||||||
|
"BypassIfHighestQualityHelpText": "Umgehen der Verzögerung, wenn die Freigabe die höchste aktivierte Qualität im Qualitätsprofil hat",
|
||||||
|
"CustomFormatsSpecificationFlag": "Markierung",
|
||||||
|
"DeleteFormat": "Formatierung löschen",
|
||||||
|
"CustomFormatsSpecificationRegularExpressionHelpText": "Benutzerdefiniertes Format RegEx ist nicht groß-/kleinschreibungssensitiv",
|
||||||
|
"IndexerDownloadClientHealthCheckMessage": "Indexer mit ungültigen Downloader: {0}.",
|
||||||
|
"AuthorProgressBarText": "{availableBookCount} / {bookCount} (Gesamt: {totalBookCount}, Dateien: {bookFileCount})",
|
||||||
|
"BookProgressBarText": "{bookCount} / {totalBookCount} (Dateien: {bookFileCount})",
|
||||||
|
"ChangeCategoryHint": "Änderung des Downloads in die 'Post-Import-Kategorie' vom Download-Client",
|
||||||
|
"ChangeCategoryMultipleHint": "Änderung der Downloads in die 'Post-Import-Kategorie' vom Download-Client",
|
||||||
|
"DoNotBlocklist": "Nicht Sperren",
|
||||||
|
"DoNotBlocklistHint": "Entfernen ohne Sperren",
|
||||||
|
"CustomFormatsSettingsTriggerInfo": "Ein Eigenes Format wird auf eine Veröffentlichung oder Datei angewandt, wenn sie mindestens einer der verschiedenen ausgewählten Bedingungen entspricht.",
|
||||||
|
"ConnectionSettingsUrlBaseHelpText": "Fügt ein Präfix zur {connectionName} URL hinzu, z. B. {url}",
|
||||||
|
"DownloadClientDelugeSettingsDirectory": "Download Verzeichnis",
|
||||||
|
"DownloadClientDelugeSettingsDirectoryCompleted": "Verschieben, wenn Verzeichnis abgeschlossen",
|
||||||
|
"IgnoreDownloadHint": "Hält {appName} von der weiteren Verarbeitung dieses Downloads ab",
|
||||||
|
"IgnoreDownloads": "Downloads ignorieren",
|
||||||
|
"IgnoreDownload": "Download ignorieren",
|
||||||
|
"IgnoreDownloadsHint": "Hindert {appName}, diese Downloads weiter zu verarbeiten",
|
||||||
|
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
|
||||||
|
"ApiKey": "API-Schlüssel",
|
||||||
|
"AuthBasic": "Basis (Browser-Popup)",
|
||||||
|
"AuthForm": "Formulare (Anmeldeseite)",
|
||||||
|
"AuthenticationMethod": "Authentifizierungsmethode",
|
||||||
|
"AuthenticationMethodHelpTextWarning": "Bitte wähle eine gültige Authentifizierungsmethode aus",
|
||||||
|
"AuthenticationRequired": "Authentifizierung benötigt",
|
||||||
|
"AuthenticationRequiredHelpText": "Ändern, welche anfragen Authentifizierung benötigen. Ändere nichts wenn du dir nicht des Risikos bewusst bist.",
|
||||||
|
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Neues Passwort bestätigen",
|
||||||
|
"AuthenticationRequiredPasswordHelpTextWarning": "Gib ein neues Passwort ein",
|
||||||
|
"AuthenticationRequiredUsernameHelpTextWarning": "Gib einen neuen Benutzernamen ein",
|
||||||
|
"AuthenticationRequiredWarning": "Um unberechtigte Fernzugriffe zu vermeiden benötigt {appName} jetzt , dass Authentifizierung eingeschaltet ist. Du kannst Authentifizierung optional für lokale Adressen ausschalten.",
|
||||||
|
"DisabledForLocalAddresses": "Für lokale Adressen deaktiviert",
|
||||||
|
"Enabled": "Aktiviert"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
"20MinutesTwenty": "20 λεπτά: {0}",
|
"20MinutesTwenty": "20 λεπτά: {0}",
|
||||||
"45MinutesFourtyFive": "45 λεπτά: {0}",
|
"45MinutesFourtyFive": "45 λεπτά: {0}",
|
||||||
"60MinutesSixty": "60 λεπτά: {0}",
|
"60MinutesSixty": "60 λεπτά: {0}",
|
||||||
"APIKey": "Κλειδί API",
|
|
||||||
"About": "Σχετικά",
|
"About": "Σχετικά",
|
||||||
"AddListExclusion": "Προσθήκη εξαίρεσης λίστας",
|
"AddListExclusion": "Προσθήκη εξαίρεσης λίστας",
|
||||||
"AddingTag": "Προσθήκη ετικέτας",
|
"AddingTag": "Προσθήκη ετικέτας",
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
"Calendar": "Ημερολόγιο",
|
"Calendar": "Ημερολόγιο",
|
||||||
"CalendarWeekColumnHeaderHelpText": "Εμφανίζεται πάνω από κάθε στήλη όταν η εβδομάδα είναι η ενεργή προβολή",
|
"CalendarWeekColumnHeaderHelpText": "Εμφανίζεται πάνω από κάθε στήλη όταν η εβδομάδα είναι η ενεργή προβολή",
|
||||||
"Cancel": "Ακύρωση",
|
"Cancel": "Ακύρωση",
|
||||||
"CancelMessageText": "Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτήν την εργασία σε εκκρεμότητα;",
|
"CancelPendingTask": "Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτήν την εργασία σε εκκρεμότητα;",
|
||||||
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
||||||
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS.",
|
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS.",
|
||||||
"ChangeFileDate": "Αλλαγή ημερομηνίας αρχείου",
|
"ChangeFileDate": "Αλλαγή ημερομηνίας αρχείου",
|
||||||
@@ -425,13 +424,12 @@
|
|||||||
"UsenetDelay": "Καθυστέρηση Usenet",
|
"UsenetDelay": "Καθυστέρηση Usenet",
|
||||||
"UsenetDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πάρετε μια κυκλοφορία από το Usenet",
|
"UsenetDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πάρετε μια κυκλοφορία από το Usenet",
|
||||||
"Username": "Όνομα χρήστη",
|
"Username": "Όνομα χρήστη",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr",
|
"BranchUpdate": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Υποκατάστημα που χρησιμοποιείται από εξωτερικό μηχανισμό ενημέρωσης",
|
"BranchUpdateMechanism": "Υποκατάστημα που χρησιμοποιείται από εξωτερικό μηχανισμό ενημέρωσης",
|
||||||
"Version": "Εκδοχή",
|
"Version": "Εκδοχή",
|
||||||
"WeekColumnHeader": "Κεφαλίδα στήλης εβδομάδας",
|
"WeekColumnHeader": "Κεφαλίδα στήλης εβδομάδας",
|
||||||
"Year": "Ετος",
|
"Year": "Ετος",
|
||||||
"YesCancel": "Ναι, Ακύρωση",
|
"YesCancel": "Ναι, Ακύρωση",
|
||||||
"ApiKeyHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
|
|
||||||
"LoadingBooksFailed": "Η φόρτωση αρχείων ταινίας απέτυχε",
|
"LoadingBooksFailed": "Η φόρτωση αρχείων ταινίας απέτυχε",
|
||||||
"ProxyUsernameHelpText": "Πρέπει να εισαγάγετε ένα όνομα χρήστη και έναν κωδικό πρόσβασης μόνο εάν απαιτείται. Αφήστε τα κενά διαφορετικά.",
|
"ProxyUsernameHelpText": "Πρέπει να εισαγάγετε ένα όνομα χρήστη και έναν κωδικό πρόσβασης μόνο εάν απαιτείται. Αφήστε τα κενά διαφορετικά.",
|
||||||
"SslCertPathHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
|
"SslCertPathHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
|
||||||
@@ -993,5 +991,26 @@
|
|||||||
"AppUpdated": "{appName} Ενημερώθηκε",
|
"AppUpdated": "{appName} Ενημερώθηκε",
|
||||||
"AppUpdatedVersion": "ξαναφορτωθεί",
|
"AppUpdatedVersion": "ξαναφορτωθεί",
|
||||||
"AutoAdd": "Προσθήκη",
|
"AutoAdd": "Προσθήκη",
|
||||||
"ErrorLoadingContent": "Υπήρξε ένα σφάλμα κατά τη φόρτωση του αρχείου"
|
"ErrorLoadingContent": "Υπήρξε ένα σφάλμα κατά τη φόρτωση του αρχείου",
|
||||||
|
"ExtraFileExtensionsHelpTextsExamples": "Παραδείγματα: «.sub, .nfo» ή «sub, nfo»",
|
||||||
|
"IndexerFlags": "Σημαίες ευρετηρίου",
|
||||||
|
"InteractiveSearchModalHeader": "Διαδραστική αναζήτηση",
|
||||||
|
"ExtraFileExtensionsHelpText": "Λίστα πρόσθετων αρχείων που διαχωρίζονται με κόμμα για εισαγωγή (το .nfo θα εισαχθεί ως .nfo-orig)",
|
||||||
|
"CustomFilter": "Custom Φιλτρα",
|
||||||
|
"DeleteSelected": "Διαγραφή επιλεγμένων",
|
||||||
|
"SourceTitle": "Τίτλος πηγής",
|
||||||
|
"AutoRedownloadFailed": "Η λήψη απέτυχε",
|
||||||
|
"FailedLoadingSearchResults": "Αποτυχία φόρτωσης αποτελεσμάτων αναζήτησης, δοκιμάστε ξανά.",
|
||||||
|
"RemoveQueueItemConfirmation": "Είστε σίγουροι πως θέλετε να διαγράψετε {0} αντικείμενα από την ουρά;",
|
||||||
|
"SelectDropdown": "'Επιλέγω...",
|
||||||
|
"SelectQuality": "Επιλέξτε Ποιότητα",
|
||||||
|
"SelectReleaseGroup": "Επιλέξτε Ομάδα έκδοσης",
|
||||||
|
"AuthBasic": "Βασικό (Αναδυόμενο παράθυρο προγράμματος περιήγησης)",
|
||||||
|
"AuthForm": "Φόρμες (σελίδα σύνδεσης)",
|
||||||
|
"AuthenticationRequired": "Απαιτείται πιστοποίηση",
|
||||||
|
"AuthenticationRequiredHelpText": "Αλλαγή για τα οποία απαιτείται έλεγχος ταυτότητας. Μην αλλάζετε αν δεν κατανοήσετε τους κινδύνους.",
|
||||||
|
"AuthenticationRequiredWarning": "Για να αποτρέψει την απομακρυσμένη πρόσβαση χωρίς έλεγχο ταυτότητας, το {appName} απαιτεί τώρα να ενεργοποιηθεί ο έλεγχος ταυτότητας. Διαμορφώστε τη μέθοδο ελέγχου ταυτότητας και τα διαπιστευτήριά σας. Μπορείτε προαιρετικά να απενεργοποιήσετε τον έλεγχο ταυτότητας από τοπικές διευθύνσεις. Ανατρέξτε στις Συχνές Ερωτήσεις για πρόσθετες πληροφορίες.",
|
||||||
|
"Enabled": "Ενεργοποιήθηκε",
|
||||||
|
"ApiKey": "Κλειδί API",
|
||||||
|
"DisabledForLocalAddresses": "Απενεργοποιήθηκε για τοπικές διευθύνσεις"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"20MinutesTwenty": "20 Minutes: {0}",
|
"20MinutesTwenty": "20 Minutes: {0}",
|
||||||
"45MinutesFourtyFive": "45 Minutes: {0}",
|
"45MinutesFourtyFive": "45 Minutes: {0}",
|
||||||
"60MinutesSixty": "60 Minutes: {0}",
|
"60MinutesSixty": "60 Minutes: {0}",
|
||||||
"APIKey": "API Key",
|
|
||||||
"ASIN": "ASIN",
|
"ASIN": "ASIN",
|
||||||
"About": "About",
|
"About": "About",
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to Readarr's servers. This includes information on your browser, which Readarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
|
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to Readarr's servers. This includes information on your browser, which Readarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
|
||||||
"AnalyticsEnabledHelpTextWarning": "Requires restart to take effect",
|
"AnalyticsEnabledHelpTextWarning": "Requires restart to take effect",
|
||||||
"AnyEditionOkHelpText": "Readarr will automatically switch to the edition best matching downloaded files",
|
"AnyEditionOkHelpText": "Readarr will automatically switch to the edition best matching downloaded files",
|
||||||
"ApiKeyHelpTextWarning": "Requires restart to take effect",
|
"ApiKey": "API Key",
|
||||||
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
|
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
|
||||||
"AppDataDirectory": "AppData Directory",
|
"AppDataDirectory": "AppData Directory",
|
||||||
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
|
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
|
||||||
@@ -50,9 +49,20 @@
|
|||||||
"ApplyTagsHelpTextHowToApplyIndexers": "How to apply tags to the selected indexers",
|
"ApplyTagsHelpTextHowToApplyIndexers": "How to apply tags to the selected indexers",
|
||||||
"ApplyTagsHelpTextRemove": "Remove: Remove the entered tags",
|
"ApplyTagsHelpTextRemove": "Remove: Remove the entered tags",
|
||||||
"ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
|
"ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
|
||||||
|
"AptUpdater": "Use apt to install the update",
|
||||||
"AudioFileMetadata": "Write Metadata to Audio Files",
|
"AudioFileMetadata": "Write Metadata to Audio Files",
|
||||||
|
"AuthBasic": "Basic (Browser Popup)",
|
||||||
|
"AuthForm": "Forms (Login Page)",
|
||||||
"Authentication": "Authentication",
|
"Authentication": "Authentication",
|
||||||
"AuthenticationMethodHelpText": "Require Username and Password to access Readarr",
|
"AuthenticationMethod": "Authentication Method",
|
||||||
|
"AuthenticationMethodHelpText": "Require Username and Password to access {appName}",
|
||||||
|
"AuthenticationMethodHelpTextWarning": "Please select a valid authentication method",
|
||||||
|
"AuthenticationRequired": "Authentication Required",
|
||||||
|
"AuthenticationRequiredHelpText": "Change which requests authentication is required for. Do not change unless you understand the risks.",
|
||||||
|
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirm new password",
|
||||||
|
"AuthenticationRequiredPasswordHelpTextWarning": "Enter a new password",
|
||||||
|
"AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username",
|
||||||
|
"AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.",
|
||||||
"Author": "Author",
|
"Author": "Author",
|
||||||
"AuthorClickToChangeBook": "Click to change book",
|
"AuthorClickToChangeBook": "Click to change book",
|
||||||
"AuthorEditor": "Author Editor",
|
"AuthorEditor": "Author Editor",
|
||||||
@@ -107,6 +117,9 @@
|
|||||||
"BooksTotal": "Books ({0})",
|
"BooksTotal": "Books ({0})",
|
||||||
"Bookshelf": "Bookshelf",
|
"Bookshelf": "Bookshelf",
|
||||||
"Branch": "Branch",
|
"Branch": "Branch",
|
||||||
|
"BranchUpdate": "Branch to use to update {appName}",
|
||||||
|
"BranchUpdateMechanism": "Branch used by external update mechanism",
|
||||||
|
"BuiltIn": "Built-In",
|
||||||
"BypassIfAboveCustomFormatScore": "Bypass if Above Custom Format Score",
|
"BypassIfAboveCustomFormatScore": "Bypass if Above Custom Format Score",
|
||||||
"BypassIfAboveCustomFormatScoreHelpText": "Enable bypass when release has a score higher than the configured minimum custom format score",
|
"BypassIfAboveCustomFormatScoreHelpText": "Enable bypass when release has a score higher than the configured minimum custom format score",
|
||||||
"BypassIfHighestQuality": "Bypass if Highest Quality",
|
"BypassIfHighestQuality": "Bypass if Highest Quality",
|
||||||
@@ -128,7 +141,7 @@
|
|||||||
"CalibreUrlBase": "Calibre Url Base",
|
"CalibreUrlBase": "Calibre Url Base",
|
||||||
"CalibreUsername": "Calibre Username",
|
"CalibreUsername": "Calibre Username",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"CancelMessageText": "Are you sure you want to cancel this pending task?",
|
"CancelPendingTask": "Are you sure you want to cancel this pending task?",
|
||||||
"CatalogNumber": "Catalog Number",
|
"CatalogNumber": "Catalog Number",
|
||||||
"CertificateValidation": "Certificate Validation",
|
"CertificateValidation": "Certificate Validation",
|
||||||
"CertificateValidationHelpText": "Change how strict HTTPS certification validation is. Do not change unless you understand the risks.",
|
"CertificateValidationHelpText": "Change how strict HTTPS certification validation is. Do not change unless you understand the risks.",
|
||||||
@@ -188,6 +201,7 @@
|
|||||||
"CreateEmptyAuthorFolders": "Create empty author folders",
|
"CreateEmptyAuthorFolders": "Create empty author folders",
|
||||||
"CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan",
|
"CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan",
|
||||||
"CreateGroup": "Create group",
|
"CreateGroup": "Create group",
|
||||||
|
"CurrentlyInstalled": "Currently Installed",
|
||||||
"CustomFilter": "Custom Filter",
|
"CustomFilter": "Custom Filter",
|
||||||
"CustomFormat": "Custom Format",
|
"CustomFormat": "Custom Format",
|
||||||
"CustomFormatScore": "Custom Format Score",
|
"CustomFormatScore": "Custom Format Score",
|
||||||
@@ -277,12 +291,14 @@
|
|||||||
"DetailedProgressBarHelpText": "Show text on progress bar",
|
"DetailedProgressBarHelpText": "Show text on progress bar",
|
||||||
"Development": "Development",
|
"Development": "Development",
|
||||||
"Disabled": "Disabled",
|
"Disabled": "Disabled",
|
||||||
|
"DisabledForLocalAddresses": "Disabled for Local Addresses",
|
||||||
"DiscCount": "Disc Count",
|
"DiscCount": "Disc Count",
|
||||||
"DiscNumber": "Disc Number",
|
"DiscNumber": "Disc Number",
|
||||||
"DiskSpace": "Disk Space",
|
"DiskSpace": "Disk Space",
|
||||||
"DoNotBlocklist": "Do not Blocklist",
|
"DoNotBlocklist": "Do not Blocklist",
|
||||||
"DoNotBlocklistHint": "Remove without blocklisting",
|
"DoNotBlocklistHint": "Remove without blocklisting",
|
||||||
"Docker": "Docker",
|
"Docker": "Docker",
|
||||||
|
"DockerUpdater": "Update the docker container to receive the update",
|
||||||
"DownloadClient": "Download Client",
|
"DownloadClient": "Download Client",
|
||||||
"DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
|
"DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
|
||||||
"DownloadClientCheckNoneAvailableMessage": "No download client is available",
|
"DownloadClientCheckNoneAvailableMessage": "No download client is available",
|
||||||
@@ -331,6 +347,7 @@
|
|||||||
"EnableRssHelpText": "Will be used when Readarr periodically looks for releases via RSS Sync",
|
"EnableRssHelpText": "Will be used when Readarr periodically looks for releases via RSS Sync",
|
||||||
"EnableSSL": "Enable SSL",
|
"EnableSSL": "Enable SSL",
|
||||||
"EnableSslHelpText": " Requires restart running as administrator to take effect",
|
"EnableSslHelpText": " Requires restart running as administrator to take effect",
|
||||||
|
"Enabled": "Enabled",
|
||||||
"EnabledHelpText": "Check to enable release profile",
|
"EnabledHelpText": "Check to enable release profile",
|
||||||
"Ended": "Ended",
|
"Ended": "Ended",
|
||||||
"EndedAllBooksDownloaded": "Ended (All books downloaded)",
|
"EndedAllBooksDownloaded": "Ended (All books downloaded)",
|
||||||
@@ -345,9 +362,14 @@
|
|||||||
"ExistingTag": "Existing tag",
|
"ExistingTag": "Existing tag",
|
||||||
"ExistingTagsScrubbed": "Existing tags scrubbed",
|
"ExistingTagsScrubbed": "Existing tags scrubbed",
|
||||||
"ExportCustomFormat": "Export Custom Format",
|
"ExportCustomFormat": "Export Custom Format",
|
||||||
|
"External": "External",
|
||||||
|
"ExternalUpdater": "{appName} is configured to use an external update mechanism",
|
||||||
"ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
|
"ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
|
||||||
"ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'",
|
"ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'",
|
||||||
"FailedDownloadHandling": "Failed Download Handling",
|
"FailedDownloadHandling": "Failed Download Handling",
|
||||||
|
"FailedLoadingSearchResults": "Failed to load search results, please try again.",
|
||||||
|
"FailedToFetchSettings": "Failed to fetch settings",
|
||||||
|
"FailedToFetchUpdates": "Failed to fetch updates",
|
||||||
"FailedToLoadQueue": "Failed to load Queue",
|
"FailedToLoadQueue": "Failed to load Queue",
|
||||||
"FileDateHelpText": "Change file date on import/rescan",
|
"FileDateHelpText": "Change file date on import/rescan",
|
||||||
"FileDetails": "File Details",
|
"FileDetails": "File Details",
|
||||||
@@ -456,11 +478,20 @@
|
|||||||
"IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
|
"IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
|
||||||
"IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Readarr will not provide any interactive search results",
|
"IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Readarr will not provide any interactive search results",
|
||||||
"IndexerSettings": "Indexer Settings",
|
"IndexerSettings": "Indexer Settings",
|
||||||
|
"IndexerSettingsSeedRatio": "Seed Ratio",
|
||||||
|
"IndexerSettingsSeedRatioHelpText": "The ratio a torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules",
|
||||||
|
"IndexerSettingsSeedTime": "Seed Time",
|
||||||
|
"IndexerSettingsSeedTimeHelpText": "The time a torrent should be seeded before stopping, empty uses the download client's default",
|
||||||
"IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures",
|
"IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures",
|
||||||
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
|
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
|
||||||
"IndexerTagsHelpText": "Only use this indexer for authors with at least one matching tag. Leave blank to use with all authors.",
|
"IndexerTagsHelpText": "Only use this indexer for authors with at least one matching tag. Leave blank to use with all authors.",
|
||||||
"Indexers": "Indexers",
|
"Indexers": "Indexers",
|
||||||
"IndexersSettingsSummary": "Indexers and release restrictions",
|
"IndexersSettingsSummary": "Indexers and release restrictions",
|
||||||
|
"Install": "Install",
|
||||||
|
"InstallLatest": "Install Latest",
|
||||||
|
"InstallMajorVersionUpdate": "Install Update",
|
||||||
|
"InstallMajorVersionUpdateMessage": "This update will install a new major version and may not be compatible with your system. Are you sure you want to install this update?",
|
||||||
|
"InstallMajorVersionUpdateMessageLink": "Please check [{domain}]({url}) for more information.",
|
||||||
"InstanceName": "Instance Name",
|
"InstanceName": "Instance Name",
|
||||||
"InstanceNameHelpText": "Instance name in tab and for Syslog app name",
|
"InstanceNameHelpText": "Instance name in tab and for Syslog app name",
|
||||||
"InteractiveSearchModalHeader": "Interactive Search",
|
"InteractiveSearchModalHeader": "Interactive Search",
|
||||||
@@ -657,6 +688,7 @@
|
|||||||
"OnHealthIssueHelpText": "On Health Issue",
|
"OnHealthIssueHelpText": "On Health Issue",
|
||||||
"OnImportFailure": "On Import Failure",
|
"OnImportFailure": "On Import Failure",
|
||||||
"OnImportFailureHelpText": "On Import Failure",
|
"OnImportFailureHelpText": "On Import Failure",
|
||||||
|
"OnLatestVersion": "The latest version of {appName} is already installed",
|
||||||
"OnReleaseImport": "On Release Import",
|
"OnReleaseImport": "On Release Import",
|
||||||
"OnReleaseImportHelpText": "On Release Import",
|
"OnReleaseImportHelpText": "On Release Import",
|
||||||
"OnRename": "On Rename",
|
"OnRename": "On Rename",
|
||||||
@@ -674,6 +706,7 @@
|
|||||||
"PageSize": "Page Size",
|
"PageSize": "Page Size",
|
||||||
"PageSizeHelpText": "Number of items to show on each page",
|
"PageSizeHelpText": "Number of items to show on each page",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
|
"PasswordConfirmation": "Password Confirmation",
|
||||||
"PasswordHelpText": "Calibre content server password",
|
"PasswordHelpText": "Calibre content server password",
|
||||||
"PastDays": "Past Days",
|
"PastDays": "Past Days",
|
||||||
"PastDaysHelpText": "Days for iCal feed to look into the past",
|
"PastDaysHelpText": "Days for iCal feed to look into the past",
|
||||||
@@ -688,6 +721,7 @@
|
|||||||
"PosterSize": "Poster Size",
|
"PosterSize": "Poster Size",
|
||||||
"PreviewRename": "Preview Rename",
|
"PreviewRename": "Preview Rename",
|
||||||
"PreviewRetag": "Preview Retag",
|
"PreviewRetag": "Preview Retag",
|
||||||
|
"PreviouslyInstalled": "Previously Installed",
|
||||||
"Profiles": "Profiles",
|
"Profiles": "Profiles",
|
||||||
"ProfilesSettingsSummary": "Quality, Metadata, Delay, and Release profiles",
|
"ProfilesSettingsSummary": "Quality, Metadata, Delay, and Release profiles",
|
||||||
"Progress": "Progress",
|
"Progress": "Progress",
|
||||||
@@ -840,6 +874,7 @@
|
|||||||
"SSLPort": "SSL Port",
|
"SSLPort": "SSL Port",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Scheduled": "Scheduled",
|
"Scheduled": "Scheduled",
|
||||||
|
"Script": "Script",
|
||||||
"ScriptPath": "Script Path",
|
"ScriptPath": "Script Path",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"SearchAll": "Search All",
|
"SearchAll": "Search All",
|
||||||
@@ -1033,6 +1068,7 @@
|
|||||||
"UnmonitoredHelpText": "Include unmonitored books in the iCal feed",
|
"UnmonitoredHelpText": "Include unmonitored books in the iCal feed",
|
||||||
"UnselectAll": "Unselect All",
|
"UnselectAll": "Unselect All",
|
||||||
"UpdateAll": "Update all",
|
"UpdateAll": "Update all",
|
||||||
|
"UpdateAppDirectlyLoadError": "Unable to update {appName} directly,",
|
||||||
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
|
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
|
||||||
"UpdateAvailable": "New update is available",
|
"UpdateAvailable": "New update is available",
|
||||||
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.",
|
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.",
|
||||||
@@ -1061,14 +1097,13 @@
|
|||||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent provided by the app that called the API",
|
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent provided by the app that called the API",
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"UsernameHelpText": "Calibre content server username",
|
"UsernameHelpText": "Calibre content server username",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branch to use to update Readarr",
|
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch used by external update mechanism",
|
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
"Wanted": "Wanted",
|
"Wanted": "Wanted",
|
||||||
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
|
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
|
||||||
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
|
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
|
||||||
"WeekColumnHeader": "Week Column Header",
|
"WeekColumnHeader": "Week Column Header",
|
||||||
"WhatsNew": "What's New?",
|
"WhatsNew": "What's New?",
|
||||||
|
"WhySearchesCouldBeFailing": "Click here to find out why searches could be failing",
|
||||||
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
|
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
|
||||||
"WriteAudioTags": "Tag Audio Files with Metadata",
|
"WriteAudioTags": "Tag Audio Files with Metadata",
|
||||||
"WriteAudioTagsScrub": "Scrub Existing Tags",
|
"WriteAudioTagsScrub": "Scrub Existing Tags",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user